public async Task <ActionResult> FindByFullName(string name) { return(Json(JsonQuery.Create(true).Result( data: await DbContext.Lecturers.Where(l => Regex.IsMatch(l.FullName, name, RegexOptions.IgnoreCase | RegexOptions.Multiline)) .ToArrayAsync()))); }
public async Task <ActionResult> GetLecturer(string id) { var res = new JsonQuery(true); if (!await DbContext.Lecturers.AnyAsync(l => l.Id == id)) { res.AddError(null, "query", "Лектор не найден"); } return(Json(res.Result(data: await DbContext.Lecturers.FirstOrDefaultAsync(l => l.Id == id)))); }
public async Task <IActionResult> GetLecturers() { var lecturers = await DbContext.Lecturers.ToListAsync(); // for (int i = 0; i < 1000; i++) { // lecturers.Add (new Lecturer () { // Id = Guid.NewGuid ().ToString (), // FirstName = $"Lecturer {i}", // }); // } return(Json(JsonQuery.Create(true).Result(data: lecturers))); }
public ActionResult Index() { var owner = Deserialize.FromJson(JsonData); var model = new HomeModel() { CatsOfMales = JsonQuery.GetAnimalNamesByGender(owner, "Male", "Cat"), CatsOfFemales = JsonQuery.GetAnimalNamesByGender(owner, "Female", "Cat") }; return(View(model)); }
public QueryInsert(Execute.Executer executer, JObject query) : base(executer, query) { // data 절 JToken dataToken = JsonQuery.GetProperty("data").Value; if (dataToken is JValue) { string data = (string)dataToken; if (data.Length > 0 && data[0] == '@') { List <DataObject> resultSet; if (ParentExecuter.ResultSets.TryGetValue(data, out resultSet) == false) { throw new AegisException(RoseResult.InvalidArgument, $"{data} is not exist name."); } foreach (var item in resultSet) { Data.Add(JObject.Parse(item.Data)); } } else { throw new AegisException(RoseResult.InvalidArgument, $"{data} is not valid name."); } } else if (dataToken is JObject) { Data.Add(dataToken); } else if (dataToken is JArray) { foreach (var item in dataToken) { Data.Add(item); } } UniqueFor = (string)JsonQuery.GetProperty("uniqueFor", false)?.Value ?? null; OnDuplicate = (string)JsonQuery.GetProperty("onDuplicate", false)?.Value ?? null; if (OnDuplicate == null) { OnDuplicate = "ignore"; } if (OnDuplicate != "ignore" && OnDuplicate != "update") { throw new AegisException(RoseResult.InvalidArgument, "onDuplicate value must be 'ignore' or 'update'."); } }
public string ToQuery(bool supportsSearch) { var queries = new List <string>(); if (Skip.HasValue) { queries.Add($"$skip={Skip.Value}"); } if (Top.HasValue) { queries.Add($"$top={Top.Value}"); } if (!string.IsNullOrWhiteSpace(OrderBy)) { queries.Add($"$orderby={OrderBy}"); } if (JsonQuery != null) { queries.Add($"q={JsonQuery.ToJson()}"); } if (Ids != null && Ids.Count > 0) { queries.Add($"ids={string.Join(",", Ids)}"); } if (!string.IsNullOrWhiteSpace(Search)) { if (!supportsSearch) { throw new NotSupportedException("Full text search is not supported."); } queries.Add($"$search=\"{Search}\""); } else if (!string.IsNullOrWhiteSpace(Filter)) { queries.Add($"$filter={Filter}"); } var queryString = string.Join("&", queries); if (!string.IsNullOrWhiteSpace(queryString)) { queryString = "?" + queryString; } return(queryString); }
public async Task <ActionResult> Delete(string id) { var res = new JsonQuery(true); if (!await DbContext.Lecturers.AnyAsync(l => l.Id == id)) { res.AddError(null, "query", "Лектор не найден"); } DbContext.Lecturers.Remove(DbContext.Lecturers.FirstOrDefault(l => l.Id == id)); await DbContext.SaveChangesAsync(); return(Json(res.Result())); }
public QueryUpdate(Execute.Executer executer, JObject query) : base(executer, query) { // data 절 JToken dataToken = JsonQuery.GetProperty("data").Value; if (dataToken is JValue) { string data = (string)dataToken; if (data.Length > 0 && data[0] == '@') { List <DataObject> resultSet; if (ParentExecuter.ResultSets.TryGetValue(data, out resultSet) == false) { throw new AegisException(RoseResult.InvalidArgument, $"{data} is not exist name."); } if (resultSet.Count() > 1) { JArray arr = new JArray(); foreach (var item in resultSet) { arr.Add(JObject.Parse(item.Data)); } Data = arr; } else if (resultSet.Count() == 1) { Data = JObject.Parse(resultSet.First().Data); } else { Data = JArray.Parse("[]"); } } else { throw new AegisException(RoseResult.InvalidArgument, $"{data} is not valid name."); } } else if (dataToken is JObject) { Data = dataToken; } else if (dataToken is JArray) { Data = dataToken.DeepClone(); } }
public async Task <ActionResult> AddOrEdit(Lecturer model) { var res = new JsonQuery(true); res.Parse(ModelState); if (ModelState.IsValid) { Lecturer current; if (!string.IsNullOrWhiteSpace(model.Id)) { if (!await DbContext.Lecturers.AnyAsync(l => l.Id == model.Id)) { res.AddError(null, "FirstName", "Лектор не найден"); goto res; } DbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; current = await DbContext.Lecturers.FirstOrDefaultAsync(l => l.Id == model.Id); var find = await DbContext.Lecturers.FirstOrDefaultAsync(l => l.FirstName == model.FirstName && l.SecondName == model.SecondName && l.LastName == model.LastName); if (find != null && find.Id != current.Id) { res.AddError(null, "FirstName", "Лектор с таким именем фамилией и отчеством уже есть."); goto res; } DbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.TrackAll; DbContext.Update(model); } else { if (await DbContext.Lecturers.AnyAsync(l => l.FirstName == model.FirstName && l.SecondName == model.SecondName && l.LastName == model.LastName)) { res.AddError(null, "FirstName", "Лектор с таким именем фамилией и отчеством уже есть."); goto res; } await DbContext.Lecturers.AddAsync(model); } await DbContext.SaveChangesAsync(); } res: return(Json(res.Result(data: model))); }
public Q ToQuery() { var result = Q.Empty; if (Ids != null) { result = result.WithIds(Ids); } if (OData != null) { result = result.WithODataQuery(OData); } if (JsonQuery != null) { result = result.WithJsonQuery(JsonQuery.ToString()); } return(result); }
/// <summary> /// Initializes a new instance of the <see cref="ProtectedUsersAndAuthentication"/> class. /// </summary> /// <param name="accessRepo">Supplied through DI.</param> /// <param name="signUpRepo">Supplied through DI.</param> /// <param name="personRepo">Supplied through DI.</param> /// <param name="env">Supplied through DI.</param> /// <param name="date">Supplied through DI.</param> public ProtectedUsersAndAuthentication( IRepository <AccessTokenSchema> accessRepo, IRepository <SignUpTokenSchema> signUpRepo, IRepository <PersonSchema> personRepo, IAppEnvironment env, IDateExtra date) : base("/") { this.Before += PreSecurity.CheckAccess(accessRepo, date); this.Get <GetUser>("users/{id}/", async(req, res) => { string userID = req.RouteValues.As <string>("id"); var person = await personRepo.FindById(userID); if (person == null || !person.IsExisting) { res.StatusCode = Status404NotFound; return; } // Has to exit due to pre-security check string token = req.Cookies["ExperienceCapture-Access-Token"]; var accessToken = await accessRepo.FindOne( Builders <AccessTokenSchema> .Filter .Where(a => a.Hash == PasswordHasher.Hash(token))); if (person.InternalId != accessToken.User && accessToken.Role != RoleOptions.Admin) { res.StatusCode = Status401Unauthorized; return; } string json = JsonQuery.FulfilEncoding(req.Query, person); if (json != null) { await res.FromJson(json); return; } person.InternalId = null; await res.FromBson(person); }); this.Delete <DeleteUser>("users/{id}/", async(req, res) => { string userID = req.RouteValues.As <string>("id"); var person = await personRepo.FindById(userID); if (person == null) { res.StatusCode = Status404NotFound; return; } // Has to exit due to pre-security check string token = req.Cookies["ExperienceCapture-Access-Token"]; var accessFilter = Builders <AccessTokenSchema> .Filter .Where(a => a.Hash == PasswordHasher.Hash(token)); var accessToken = await accessRepo.FindOne(accessFilter); // Check if the user being requested is the same // As the one requesting, unless they have the admin role if (person.InternalId != accessToken.User && accessToken.Role != RoleOptions.Admin) { res.StatusCode = Status401Unauthorized; return; } var filter = Builders <PersonSchema> .Filter .Where(p => p.Id == userID); var update = Builders <PersonSchema> .Update .Set(p => p.IsExisting, false); await personRepo.Update(filter, update); await res.FromString(); }); this.Post <PostSignUp>("authentication/signUps", async(req, res) => { string newToken = Generate.GetRandomToken(); var tokenDoc = new SignUpTokenSchema { InternalId = ObjectId.GenerateNewId(), Hash = PasswordHasher.Hash(newToken), CreatedAt = new BsonDateTime(date.UtcNow), }; await signUpRepo.Add(tokenDoc); var responce = new SignUpTokenResponce { SignUpToken = newToken, Expiration = TimerExtra.ProjectSeconds(date, tokenDoc.ExpirationSeconds), }; var responceDoc = responce.ToBsonDocument(); string json = JsonQuery.FulfilEncoding(req.Query, responceDoc); if (json != null) { await res.FromJson(json); return; } await res.FromBson(responceDoc); }); }
/// <summary> /// 打印装车单信息查询 /// </summary> /// <param name="vhTrainNo"></param> /// <returns></returns> public List <OrderSortDetailPrintEntity> Query(string vhTrainNo) { List <OrderSortDetailPrintEntity> list = new List <OrderSortDetailPrintEntity>(); try { #region 请求数据 System.Text.StringBuilder loStr = new System.Text.StringBuilder(); loStr.Append("vhTrainNo=").Append(vhTrainNo).Append("&"); loStr.Append("warehouseType=").Append(EUtilStroreType.WarehouseTypeToInt(GlobeSettings.LoginedUser.WarehouseType)); string jsonQuery = WebWork.SendRequest(loStr.ToString(), WebWork.URL_Query); if (string.IsNullOrEmpty(jsonQuery)) { MsgBox.Warn(WebWork.RESULT_NULL); //LogHelper.InfoLog(WebWork.RESULT_NULL); return(list); } #endregion #region 正常错误处理 JsonQuery bill = JsonConvert.DeserializeObject <JsonQuery>(jsonQuery); if (bill == null) { MsgBox.Warn(WebWork.JSON_DATA_NULL); return(list); } if (bill.flag != 0) { MsgBox.Warn(bill.error); return(list); } #endregion #region 赋值数据 foreach (JsonQueryResult jbr in bill.result) { OrderSortDetailPrintEntity asnEntity = new OrderSortDetailPrintEntity(); asnEntity.BillID = Convert.ToInt32(jbr.billId); asnEntity.BillNo = jbr.billNo; asnEntity.BillRemark = jbr.wmsRemark; asnEntity.CustomerAddress = jbr.address; asnEntity.CustomerName = jbr.cName; asnEntity.FullCount = Convert.ToInt32(jbr.fullCount); asnEntity.OrderSort = Convert.ToInt32(jbr.inVhSort); asnEntity.Warehouse = jbr.whName; //try //{ // if (!string.IsNullOrEmpty(jbr.closeDate)) // asnEntity.CloseDate = Convert.ToDateTime(jbr.closeDate); // if (!string.IsNullOrEmpty(jbr.printedTime)) // asnEntity.PrintedTime = Convert.ToDateTime(jbr.printedTime); // if (!string.IsNullOrEmpty(jbr.createDate)) // asnEntity.CreateDate = Convert.ToDateTime(jbr.createDate); //} //catch (Exception msg) //{ // LogHelper.errorLog("FrmVehicle+QueryNotRelatedBills", msg); //} list.Add(asnEntity); } return(list); #endregion } catch (Exception ex) { MsgBox.Err(ex.Message); } return(list); }
/// <summary> /// Initializes a new instance of the <see cref="UsersAndAuthentication"/> class. /// </summary> /// <param name="accessRepo">Supplied through DI.</param> /// <param name="signUpRepo">Supplied through DI.</param> /// <param name="claimRepo">Supplied through DI.</param> /// <param name="personRepo">Supplied through DI.</param> /// <param name="env">Supplied through DI.</param> /// <param name="date">Supplied through DI.</param> public UsersAndAuthentication( IRepository <AccessTokenSchema> accessRepo, IRepository <SignUpTokenSchema> signUpRepo, IRepository <ClaimTokenSchema> claimRepo, IRepository <PersonSchema> personRepo, IAppEnvironment env, IDateExtra date) : base("/") { this.Post <PostUsers>("users", async(req, res) => { var newPerson = await req.BindAndValidate <PersonRequest>(); if (!newPerson.ValidationResult.IsValid) { res.StatusCode = Status400BadRequest; return; } var person = await GoogleApi.ValidateUser(newPerson.Data.idToken, env); if (person == null) { res.StatusCode = Status401Unauthorized; return; } var filter = Builders <SignUpTokenSchema> .Filter .Where(t => t.Hash == PasswordHasher.Hash(newPerson.Data.signUpToken)); var signUpDoc = await signUpRepo.FindOne(filter); if (signUpDoc == null || signUpDoc.CreatedAt.IsAfter(date, signUpDoc.ExpirationSeconds)) { res.StatusCode = Status401Unauthorized; return; } if (!signUpDoc.IsExisting) { res.StatusCode = Status404NotFound; return; } var update = Builders <SignUpTokenSchema> .Update .Set(s => s.IsExisting, false); var existingPerson = await personRepo.FindById(person.Subject); // Check if the person already exists if (existingPerson != null) { // Recreate them if they where deleted if (!existingPerson.IsExisting) { var filterPerson = Builders <PersonSchema> .Filter .Where(p => p.Id == person.Subject); var updatePerson = Builders <PersonSchema> .Update .Set(p => p.IsExisting, true); await personRepo.Update(filterPerson, updatePerson); await signUpRepo.Update(filter, update); await res.FromString(); } else { // Return error is they really exist res.StatusCode = Status409Conflict; } return; } var personObject = new PersonSchema { InternalId = ObjectId.GenerateNewId(), Id = person.Subject, Fullname = person.Name, Firstname = person.GivenName, Lastname = person.FamilyName, Email = person.Email, CreatedAt = new BsonDateTime(date.UtcNow), Role = signUpDoc.Role, }; await personRepo.Add(personObject); await signUpRepo.Update(filter, update); await res.FromString(); }); // Due to a bug with adding multiple routes of the same path // Path in different modules, we manual auth here this.Get <GetUsers>("users", async(req, res) => { var isAllowed = await PreSecurity.CheckAccessDirectly(req, res, accessRepo, date, RoleOptions.Admin); if (!isAllowed) { return; } var filter = Builders <PersonSchema> .Filter .Where(p => p.IsExisting == true); var sorter = Builders <PersonSchema> .Sort .Descending(p => p.Fullname); var persons = await personRepo.FindAll(filter, sorter); var personsWithoutId = persons.Select((p) => { p.InternalId = null; return(p); }); var responceBody = new PersonsResponce { ContentList = new List <PersonSchema>(personsWithoutId), }; string json = JsonQuery.FulfilEncoding(req.Query, responceBody); if (json != null) { await res.FromJson(json); return; } await res.FromBson(responceBody); }); this.Post <PostAccessToken>("users/{id}/tokens/", async(req, res) => { string userID = req.RouteValues.As <string>("id"); var userDoc = await personRepo.FindById(userID); if (userDoc == null || !userDoc.IsExisting) { res.StatusCode = Status404NotFound; return; } var newAccessRequest = await req.BindAndValidate <AccessTokenRequest>(); if (!newAccessRequest.ValidationResult.IsValid) { res.StatusCode = Status400BadRequest; return; } var person = await GoogleApi.ValidateUser(newAccessRequest.Data.idToken, env); if (person == null) { res.StatusCode = Status401Unauthorized; return; } if (person.Subject != userID) { res.StatusCode = Status409Conflict; return; } string newToken = Generate.GetRandomToken(); string newHash = PasswordHasher.Hash(newToken); var tokenObject = new AccessTokenSchema { InternalId = ObjectId.GenerateNewId(), Hash = newHash, User = userDoc.InternalId, CreatedAt = new BsonDateTime(date.UtcNow), Role = userDoc.Role, }; await accessRepo.Add(tokenObject); if (newAccessRequest.Data.claimToken != null) { var filterClaims = Builders <ClaimTokenSchema> .Filter .Where(c => c.Hash == PasswordHasher.Hash(newAccessRequest.Data.claimToken)); var claimDoc = await claimRepo.FindOne(filterClaims); // 401 returned twice, which may be hard for the client to interpret if (claimDoc == null || claimDoc.CreatedAt.IsAfter(date, claimDoc.ExpirationSeconds)) { res.StatusCode = Status401Unauthorized; return; } // Don't allow overwriting an access token if (claimDoc.Access == null && claimDoc.IsExisting) { var update = Builders <ClaimTokenSchema> .Update .Set(c => c.Access, tokenObject.InternalId) #pragma warning disable SA1515 // Unfortunately claim access tokens have to be saved to the database // So that state can be communicated between clients #pragma warning restore SA1515 .Set(c => c.AccessToken, newToken); await claimRepo.Update(filterClaims, update); } await res.FromString(); } else { var responce = new AccessTokenResponce { AccessToken = newToken, Expiration = TimerExtra.ProjectSeconds(date, tokenObject.ExpirationSeconds), }; string json = JsonQuery.FulfilEncoding(req.Query, responce); if (json != null) { await res.FromJson(json); return; } await res.FromBson(responce.ToBsonDocument()); } }); this.Post <PostClaims>("authentication/claims/", async(req, res) => { string newToken = Generate.GetRandomToken(); string newHash = PasswordHasher.Hash(newToken); var tokenDoc = new ClaimTokenSchema { InternalId = ObjectId.GenerateNewId(), Hash = newHash, CreatedAt = new BsonDateTime(date.UtcNow), }; await claimRepo.Add(tokenDoc); var responce = new ClaimTokenResponce { ClaimToken = newToken, Expiration = TimerExtra.ProjectSeconds(date, tokenDoc.ExpirationSeconds), }; string json = JsonQuery.FulfilEncoding(req.Query, responce); if (json != null) { await res.FromJson(json); return; } await res.FromBson(responce.ToBsonDocument()); }); this.Get <GetClaims>("authentication/claims/", async(req, res) => { string claimToken = req.Cookies["ExperienceCapture-Claim-Token"]; if (claimToken == null) { res.StatusCode = Status400BadRequest; return; } var filter = Builders <ClaimTokenSchema> .Filter .Where(c => c.Hash == PasswordHasher.Hash(claimToken)); var claimDoc = await claimRepo.FindOne(filter); if (claimDoc == null) { res.StatusCode = Status404NotFound; return; } if (!claimDoc.IsExisting || claimDoc.CreatedAt.IsAfter(date, claimDoc.ExpirationSeconds)) { res.StatusCode = Status404NotFound; return; } if (claimDoc.Access == null) { res.StatusCode = Status202Accepted; await res.FromString("PENDING"); return; } var update = Builders <ClaimTokenSchema> .Update .Set(c => c.IsExisting, false) #pragma warning disable SA1515 // Removes the access token from the database // Important to increase security #pragma warning restore SA1515 .Set(c => c.AccessToken, null); await claimRepo.Update(filter, update); var responce = new AccessTokenResponce { AccessToken = claimDoc.AccessToken, Expiration = TimerExtra.ProjectSeconds(date, claimDoc.ExpirationSeconds), }; string json = JsonQuery.FulfilEncoding(req.Query, responce); if (json != null) { await res.FromString(json); return; } await res.FromBson(responce.ToBsonDocument()); }); this.Post <PostAdmin>("authentication/admins/", async(req, res) => { var newAdmin = await req.BindAndValidate <AdminPasswordRequest>(); if (!newAdmin.ValidationResult.IsValid) { res.StatusCode = Status400BadRequest; return; } if (!PasswordHasher.Check(newAdmin.Data.password, env.PasswordHash) && env.SkipValidation != "true") { res.StatusCode = Status401Unauthorized; return; } string newToken = Generate.GetRandomToken(); var tokenDoc = new SignUpTokenSchema { InternalId = ObjectId.GenerateNewId(), Hash = PasswordHasher.Hash(newToken), CreatedAt = new BsonDateTime(date.UtcNow), Role = RoleOptions.Admin, }; await signUpRepo.Add(tokenDoc); var responce = new SignUpTokenResponce { SignUpToken = newToken, Expiration = TimerExtra.ProjectSeconds(date, tokenDoc.ExpirationSeconds), }; string json = JsonQuery.FulfilEncoding(req.Query, responce); if (json != null) { await res.FromJson(json); return; } await res.FromBson(responce.ToBsonDocument()); }); }
public QuerySelect(Execute.Executer executer, JObject query) : base(executer, query) { // sortKey 절 var sortKey = JsonQuery.GetProperty("sortKey", false)?.Value; if (sortKey != null) { if (sortKey is JValue) { var item = ((string)sortKey).Split(' '); if (item.Count() == 0 || item.Count() > 2) { throw new AegisException(RoseResult.InvalidArgument, "'sortKey' is must have one or two arguments."); } string orderBy = (item.Count() >= 2 ? item[1] : "asc"); if (orderBy != "asc" && orderBy != "desc") { throw new AegisException(RoseResult.InvalidArgument, $"'orderBy' is must be 'asc' or 'desc'."); } SortKeys = new SortKey[1]; SortKeys[0] = new SortKey() { Key = item[0], OrderBy = orderBy }; } else if (sortKey is JArray) { int index = 0; SortKeys = new SortKey[sortKey.Count()]; foreach (var key in sortKey) { var item = ((string)key).Split(' '); if (item.Count() == 0 || item.Count() > 2) { throw new AegisException(RoseResult.InvalidArgument, "'sortKey' is must have one or two arguments."); } string orderBy = (item.Count() >= 2 ? item[1] : "asc"); if (orderBy != "asc" && orderBy != "desc") { throw new AegisException(RoseResult.InvalidArgument, $"'orderBy' is must be 'asc' or 'desc'."); } SortKeys[index++] = new SortKey() { Key = item[0], OrderBy = orderBy }; } } } // range 절 var range = JsonQuery.GetProperty("range", false)?.Value as JArray; if (range != null) { if (range.Count() != 2) { throw new AegisException(RoseResult.InvalidArgument, $"'range' is must have two arguments."); } RangeStart = range.ElementAt(0).ToString().ToInt32(); RangeCount = range.ElementAt(1).ToString().ToInt32(); if (RangeStart < 0 || RangeCount < 0) { throw new AegisException(RoseResult.InvalidArgument, $"Argument of 'range' is not valid."); } } else { RangeStart = -1; RangeCount = -1; } }
protected void btnCreateTree_Click(object sender, EventArgs e) { var root = new JsonQuery().GetJsonTree(txtJson.Text); literalOutput.Text = JsonConvert.SerializeObject(root, Formatting.Indented); }
protected void btnGetData_Click(object sender, EventArgs e) { var data = new JsonQuery().GetDataFromPath(txtJson.Text, txtPath.Text); literalOutput.Text = JsonConvert.SerializeObject(data); }
/// <summary> /// Initializes a new instance of the <see cref="Sessions"/> class. /// </summary> /// <param name="accessRepo">Supplied through DI.</param> /// <param name="sessionRepo">Supplied through DI.</param> /// <param name="personRepo">Supplied through DI.</param> /// <param name="captureRepo">Supplied through DI.</param> /// <param name="logger">Supplied through DI.</param> /// <param name="date">Supplied through DI.</param> public Sessions( IRepository <AccessTokenSchema> accessRepo, IRepository <SessionSchema> sessionRepo, IRepository <PersonSchema> personRepo, IRepository <BsonDocument> captureRepo, ILogger logger, IDateExtra date) : base("/sessions") { this.Before += PreSecurity.CheckAccess(accessRepo, date); this.Post <PostSessions>("/", async(req, res) => { // Has to exist due to PreSecurity Check string token = req.Cookies["ExperienceCapture-Access-Token"]; var accessTokenDoc = await accessRepo.FindOne( Builders <AccessTokenSchema> .Filter .Where(a => a.Hash == PasswordHasher.Hash(token))); var filterUser = Builders <PersonSchema> .Filter.Where(p => p.InternalId == accessTokenDoc.User); var user = await personRepo .FindOne(filterUser); string shortID = Generate.GetRandomId(4); var sessionDoc = new SessionSchema { InternalId = ObjectId.GenerateNewId(), Id = shortID, User = user, // Copying user data instead of referencing so it can never change in the session CreatedAt = new BsonDateTime(date.UtcNow), Tags = new List <string>(), }; // Retry generating a short id until it is unique bool isUnique = true; do { try { sessionDoc.Id = shortID; await sessionRepo.Add(sessionDoc); isUnique = true; } catch (MongoWriteException e) { // Re-throw any other type of exception except non-unique keys if (e.WriteError.Code != 11000) { throw e; } shortID = Generate.GetRandomId(4); isUnique = false; } }while (!isUnique); // isOngoing is a proxy variable and will always start out as true sessionDoc.IsOngoing = true; sessionDoc.InternalId = null; sessionDoc.User.InternalId = null; captureRepo.Configure($"sessions.{shortID}"); // Secondary index or else Mongo will fail on large queries // It has a limit for max number of documents on properties // Without an index, see https://docs.mongodb.com/manual/reference/limits/#Sort-Operations var index = Builders <BsonDocument> .IndexKeys; var key = index.Ascending("frameInfo.realtimeSinceStartup"); await captureRepo.Index(key); string json = JsonQuery.FulfilEncoding(req.Query, sessionDoc); if (json != null) { await res.FromJson(json); return; } await res.FromBson(sessionDoc); }); this.Get <GetSessions>("/", async(req, res) => { var builder = Builders <SessionSchema> .Filter; // Note: only use `&=` for adding to the filter, // Or else the filter cannot handle multiple query string options FilterDefinition <SessionSchema> filter = builder.Empty; var startMin = new BsonDateTime(date.UtcNow.AddSeconds(-300)); // 5 minutes var closeMin = new BsonDateTime(date.UtcNow.AddSeconds(-5)); // 5 seconds var hasTags = req.Query.AsMultiple <string>("hasTags").ToList(); if (hasTags.Count > 0) { foreach (var tag in hasTags) { filter &= builder.Where(s => s.Tags.Contains(tag)); } } var lacksTags = req.Query.AsMultiple <string>("lacksTags").ToList(); if (lacksTags.Count > 0) { foreach (var tag in lacksTags) { filter &= builder.Where(s => !s.Tags.Contains(tag)); } } // Three potential options: null, true, or false if (req.Query.As <bool?>("isOngoing") != null) { bool isOngoing = req.Query.As <bool>("isOngoing"); if (isOngoing) { filter &= builder.Where(s => s.IsOpen == true) & ((builder.Eq(s => s.LastCaptureAt, BsonNull.Value) & builder.Where(s => s.CreatedAt > startMin)) | (builder.Eq(s => s.LastCaptureAt, BsonNull.Value) & builder.Where(s => s.LastCaptureAt > closeMin))); } else { filter &= builder.Where(s => s.IsOpen == false) | ((builder.Eq(s => s.LastCaptureAt, BsonNull.Value) & builder.Where(s => s.CreatedAt < startMin)) | (builder.Eq(s => s.LastCaptureAt, BsonNull.Value) & builder.Where(s => s.LastCaptureAt < closeMin))); } } var page = req.Query.As <int?>("page") ?? 1; if (page < 1) { // Page query needs to be possible res.StatusCode = Status400BadRequest; return; } var direction = req.Query.As <string>("sort"); SortDefinition <SessionSchema> sorter; if (direction == null) { sorter = Builders <SessionSchema> .Sort.Descending(s => s.CreatedAt); } else { if (Enum.TryParse(typeof(SortOptions), direction, true, out object options)) { sorter = ((SortOptions)options).ToDefinition(); } else { res.StatusCode = Status400BadRequest; return; } } var sessionDocs = await sessionRepo .FindAll(filter, sorter, page); var sessionsDocsWithOngoing = sessionDocs.Select((s) => { bool isStarted = false; if (s.LastCaptureAt != BsonNull.Value) { isStarted = true; } bool isOngoing; if (s.IsOpen) { isOngoing = (!isStarted && startMin < s.CreatedAt) || (isStarted && closeMin < s.LastCaptureAt); } else { isOngoing = false; } s.IsOngoing = isOngoing; s.InternalId = null; s.User.InternalId = null; return(s); }); var count = await sessionRepo.FindThenCount(filter); var clientValues = new SessionsResponce { // Bson documents can't start with an array like Json, so a wrapping object is used instead ContentList = sessionsDocsWithOngoing.ToList(), PageTotal = (long)Math.Ceiling((double)count / 10d), }; string json = JsonQuery.FulfilEncoding(req.Query, clientValues); if (json != null) { await res.FromJson(json); return; } await res.FromBson(clientValues); }); this.Post <PostSession>("/{id}", async(req, res) => { string shortID = req.RouteValues.As <string>("id"); var sessionDoc = await sessionRepo .FindById(shortID); if (sessionDoc == null) { res.StatusCode = Status404NotFound; return; } if (!sessionDoc.IsOpen) { res.StatusCode = Status400BadRequest; return; } BsonDocument document; if (JsonQuery.CheckDecoding(req.Query)) { using (var ms = new MemoryStream()) { await req.Body.CopyToAsync(ms); ms.Position = 0; try { document = BsonSerializer.Deserialize <BsonDocument>(ms); } catch (Exception err) { logger.LogError(err.Message); res.StatusCode = Status400BadRequest; return; } } } else { string json = await req.Body.AsStringAsync(); try { document = BsonDocument.Parse(json); } catch (Exception err) { logger.LogError(err.Message); res.StatusCode = Status400BadRequest; return; } } // Manual validation, because Fluent Validation would remove extra properties if (!document.Contains("frameInfo") || document["frameInfo"].BsonType != BsonType.Document || !document["frameInfo"].AsBsonDocument.Contains("realtimeSinceStartup") || document["frameInfo"]["realtimeSinceStartup"].BsonType != BsonType.Double) { res.StatusCode = Status400BadRequest; return; } captureRepo.Configure($"sessions.{shortID}"); await captureRepo.Add(document); var filter = Builders <SessionSchema> .Filter.Where(s => s.Id == shortID); // This lastCaptureAt is undefined on the session document until the first call of this endpoint // Export flags are reset so the session can be re-exported var update = Builders <SessionSchema> .Update .Set(s => s.LastCaptureAt, new BsonDateTime(date.UtcNow)) .Set(s => s.ExportState, ExportOptions.NotStarted); await sessionRepo.Update(filter, update); await res.FromString(); }); this.Get <GetSession>("/{id}", async(req, res) => { string shortID = req.RouteValues.As <string>("id"); var sessionDoc = await sessionRepo .FindById(shortID); if (sessionDoc == null) { res.StatusCode = Status404NotFound; return; } var startRange = new BsonDateTime(date.UtcNow.AddSeconds(-300)); // 5 minutes var closeRange = new BsonDateTime(date.UtcNow.AddSeconds(-5)); // 5 seconds bool isStarted = false; // Check if key exists if (sessionDoc.LastCaptureAt != BsonNull.Value) { isStarted = true; } bool isOngoing; if (sessionDoc.IsOpen) { isOngoing = (!isStarted && startRange.CompareTo(sessionDoc.CreatedAt) < 0) || (isStarted && closeRange.CompareTo(sessionDoc.LastCaptureAt) < 0); } else { isOngoing = false; } sessionDoc.IsOngoing = isOngoing; sessionDoc.InternalId = null; sessionDoc.User.InternalId = null; string json = JsonQuery.FulfilEncoding(req.Query, sessionDoc); if (json != null) { await res.FromJson(json); return; } await res.FromBson(sessionDoc); }); this.Delete <DeleteSession>("/{id}", async(req, res) => { string shortID = req.RouteValues.As <string>("id"); var filter = Builders <SessionSchema> .Filter.Where(s => s.Id == shortID); var sessionDoc = await sessionRepo .FindById(shortID); if (sessionDoc == null) { res.StatusCode = Status404NotFound; return; } var update = Builders <SessionSchema> .Update .Set(s => s.IsOpen, false); await sessionRepo.Update(filter, update); await res.FromString(); }); }