private async Task SetLimitForUserAndGroup(string userId, string group, long limit)
        {
            var key   = CommonUtils.GetKey(userId);
            var model = await GetModelByUserId(userId);

            if (model == null)
            {
                model = new StoreModel();
                var          keyOfGroup = CommonUtils.GetKey(group);
                StoreEntries entry      = new StoreEntries();
                entry.LimitSetByAdmin = limit;
                model.GroupEntries.Add(keyOfGroup, entry);
            }
            else
            {
                if (model.GroupEntries == null)
                {
                    model.GroupEntries = new Dictionary <string, StoreEntries>();
                }

                var keyOfGroup = CommonUtils.GetKey(group);
                model.GroupEntries.TryGetValue(keyOfGroup, out StoreEntries entry);
                if (entry == null)
                {
                    entry = new StoreEntries();
                }
                entry.LimitSetByAdmin = limit;
                model.GroupEntries.Remove(keyOfGroup);
                model.GroupEntries.Add(keyOfGroup, entry);
            }
            await _provider.SetAsync(key, model);
        }
        public void Deserialize(GenericReader reader)
        {
            int version = reader.ReadInt();

            switch (version)
            {
            case 3:
            {
                LockWithdrawalAmount = reader.ReadBool();
                goto case 2;
            }

            case 2:
            {
                Insured = reader.ReadBool();
                goto case 1;
            }

            //case 1: added reference for loottype of parent object, as well as columns to display on gump
            case 1:
            {
                LootType       = (LootType)reader.ReadInt();
                DisplayColumns = reader.ReadInt();
                goto case 0;
            }

            case 0:
            default:
            {
                Label      = reader.ReadString();
                Dynamic    = reader.ReadBool();
                OfferDeeds = reader.ReadBool();

                WithdrawAmount    = reader.ReadInt();
                MinWithdrawAmount = reader.ReadInt();

                int entrycount = reader.ReadInt();

                //read in the active items
                if (entrycount > 0)
                {
                    for (int i = 0; i < entrycount; i++)
                    {
                        //dynamically create store entries, ore derived ones, based on the type name stored in the serial data, then add it to the serial data reader

                        //WARNING... this is very delicate!!  if an improper type name was saved to the serial data (eg. if a tool or resource used to belong to a tool, and was removed from the shard) then an exception will be thrown here.
                        //be sure to remove any and all tool types from keys, and cycle a world load/save before taking that class out

                        StoreEntry entry = (StoreEntry)Activator.CreateInstance(ScriptCompiler.FindTypeByName(reader.ReadString()), new object[] { reader });

                        //register this store with the entry for refresh purposes
                        entry.Store = this;
                        StoreEntries.Add(entry);
                    }
                }

                //read in the expelled items

                entrycount = reader.ReadInt();

                if (entrycount > 0)
                {
                    for (int i = 0; i < entrycount; i++)
                    {
                        StoreEntry entry = (StoreEntry)Activator.CreateInstance(ScriptCompiler.FindTypeByName(reader.ReadString()), new object[] { reader });
                        //register this store with the entry for refresh purposes
                        entry.Store = this;

                        ExpelStoreEntries.Add(entry);
                    }
                }
                break;
            }
            }
        }        //deserialize
        public async Task <Tuple <bool, APIRateLimiterUserIdServiceResponse> > Validate(TimeSpan span, long limit, string groupKey)
        {
            string clientId = CommonUtils.GetUserId(_settings, _httpContext);

            if (string.IsNullOrWhiteSpace(clientId))
            {
                return(new Tuple <bool, APIRateLimiterUserIdServiceResponse>(true, null));
            }


            if (_settings.ExcludeList != null)
            {
                foreach (string exculedIps in _settings.ExcludeList)
                {
                    if (exculedIps == clientId)
                    {
                        return(new Tuple <bool, APIRateLimiterUserIdServiceResponse>(true, null));
                    }
                }
            }

            DateTime now     = DateTime.UtcNow;
            string   path    = CommonUtils.GetPath(_httpContext);
            string   key     = string.Empty;
            bool     isGroup = !string.IsNullOrWhiteSpace(groupKey);

            key = CommonUtils.GetKey(clientId);
            StoreModel model = await _provider.GetAsync <StoreModel>(key);

            if (model == null)
            {
                model = new StoreModel
                {
                    PathEntries  = new Dictionary <string, StoreEntries>(),
                    GroupEntries = new Dictionary <string, StoreEntries>(),
                    ClientId     = clientId
                };
            }

            var keyOfPathOrGroup = CommonUtils.GetKey(isGroup ? groupKey : path);

            long         spanedOutNow = now.Ticks - span.Ticks;
            StoreEntries storeentry   = new StoreEntries();

            if (isGroup)
            {
                model.GroupEntries.TryGetValue(keyOfPathOrGroup, out storeentry);
            }
            else
            {
                model.PathEntries.TryGetValue(keyOfPathOrGroup, out storeentry);
            }
            if (storeentry == null)
            {
                storeentry = new StoreEntries();
            }

            await Task.Run(() => { storeentry.Entries.RemoveWhere(x => x < spanedOutNow); });

            DateTime firstDate = now.Add(span);

            if (storeentry.Entries.Any())
            {
                firstDate = new DateTime(storeentry.Entries.First());
                firstDate = firstDate.Add(span);
            }

            limit = storeentry.LimitSetByAdmin ?? limit;
            if (storeentry.Entries.Count >= limit)
            {
                return(new Tuple <bool, APIRateLimiterUserIdServiceResponse>(false, new APIRateLimiterUserIdServiceResponse {
                    ResetIn = firstDate, MaxLimit = limit, Period = span.TotalSeconds
                }));
            }
            storeentry.Entries.Add(now.Ticks);
            if (isGroup)
            {
                model.GroupEntries.Remove(keyOfPathOrGroup);
                model.GroupEntries.Add(keyOfPathOrGroup, storeentry);
            }
            else
            {
                model.PathEntries.Remove(keyOfPathOrGroup);
                model.PathEntries.Add(keyOfPathOrGroup, storeentry);
            }

            await _provider.SetAsync(key, model);

            return(new Tuple <bool, APIRateLimiterUserIdServiceResponse>(true, new APIRateLimiterUserIdServiceResponse {
                AvaliableLimit = limit - storeentry.Entries.Count, ResetIn = firstDate, Period = span.TotalSeconds
            }));
        }