diff --git a/Backend/Backend/Controllers/AnnotationController.cs b/Backend/Backend/Controllers/AnnotationController.cs index 09fe8922a9477b4cd17e0f6ce41629486833ac4e..553dfb58b39025bb5aa38dc31cda9d99ce6e4573 100644 --- a/Backend/Backend/Controllers/AnnotationController.cs +++ b/Backend/Backend/Controllers/AnnotationController.cs @@ -44,5 +44,34 @@ namespace RestAPI.Controllers return Ok(); } + + [HttpGet("/annotation")] + [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(AnnotationInfo))] + [ProducesResponseType((int)HttpStatusCode.Forbidden)] + public ActionResult<AnnotationInfo> GetAnnotation([FromServices] ClientInfo clientInfo, [FromBody] AnnotationGetRequest request) + { + if (clientInfo.LoggedUser == null) + { + logger.Warning("ClientInfo has null LoggerUser in [Authorized] controller /annotations"); + return Problem(); + } + + // Take care of - non-admin user requesting not-assigned annotation + // non-existent annotation + try + { + var res = annotationService.GetAnnotation(request.AnnotationId, clientInfo.LoggedUser.Id, clientInfo.LoggedUser.Role); + return Ok(res); + } + catch (InvalidOperationException e) + { + throw new BadRequestException("Could not find specified annotation"); + } + catch (UnauthorizedAccessException) + { + return Forbid(); + } + + } } } diff --git a/Backend/Backend/Controllers/UserController.cs b/Backend/Backend/Controllers/UserController.cs index a619ed3f4203de7f9b3df1e8ff7e9b542ba7dbfa..4b196e226e49e06bbcf393971f5553ca34bae0ec 100644 --- a/Backend/Backend/Controllers/UserController.cs +++ b/Backend/Backend/Controllers/UserController.cs @@ -1,5 +1,7 @@ using Core.Services; +using Core.Services.AnnotationService; using Microsoft.AspNetCore.Mvc; +using Models.Annotations; using Models.Users; using RestAPI.Authentication; using RestAPI.Controllers.Common; @@ -13,11 +15,13 @@ namespace RestAPI.Controllers { private readonly Serilog.ILogger logger; private readonly IUserService userService; + private readonly IAnnotationService annotationService; - public UserController(Serilog.ILogger logger, IUserService userService) + public UserController(Serilog.ILogger logger, IUserService userService, IAnnotationService annotationService) { this.logger = logger; this.userService = userService; + this.annotationService = annotationService; } [HttpGet("/users")] @@ -42,7 +46,22 @@ namespace RestAPI.Controllers throw new InternalErrorException(e.Message); } - + + } + + [HttpGet("/user/annotations")] + [ProducesResponseType((int)HttpStatusCode.OK, Type = typeof(AnnotationListResponse))] + [ProducesResponseType((int)HttpStatusCode.Forbidden)] + public ActionResult<AnnotationListResponse> GetUserAnnotations([FromServices] ClientInfo clientInfo) + { + if (clientInfo.LoggedUser == null) + { + logger.Warning("ClientInfo has null LoggerUser in [Authorized] controller /annotations"); + return Problem(); + } + + var res = annotationService.GetUserAnnotations(clientInfo.LoggedUser.Id); + return Ok(res); } } } diff --git a/Backend/Core/MapperProfiles/TagProfileEF.cs b/Backend/Core/MapperProfiles/TagProfileEF.cs index 89b40942c65e51bb94e4e0c3c0dbc9fbca0ff9c9..dcc4e0e1759d6dc2cd8304576e59e84da90b5644 100644 --- a/Backend/Core/MapperProfiles/TagProfileEF.cs +++ b/Backend/Core/MapperProfiles/TagProfileEF.cs @@ -16,6 +16,7 @@ namespace Core.MapperProfiles CreateMap<TagCategory, TagCategoryInfo>(); CreateMap<Tag, TagInfo>(); CreateMap<SubTag, SubTagInfo>(); + CreateMap<AnnotationTag, TagInstanceInfo>(); } } } diff --git a/Backend/Core/Seeding/Seeder.cs b/Backend/Core/Seeding/Seeder.cs index 1b1e95427c42ebb27478a627b89df87b3fa920c9..3e1a7df0ff7efab2b0b8c7928ac40472b07b2b02 100644 --- a/Backend/Core/Seeding/Seeder.cs +++ b/Backend/Core/Seeding/Seeder.cs @@ -20,9 +20,99 @@ namespace Core.Seeding var classes = AddClasses(context); DummyTags.AddDummyTags(context); context.SaveChanges(); + + AddTagInstance(context); + context.SaveChanges(); } } + private static void AddTagInstance(DatabaseContext context) + { + AddDocumentAndAnnotation(context); + + var tag = context.Tags.Where(t => context.SubTags.Any(st => st.Tag == t)).First(); + var subtag = context.SubTags.Where(st => st.Tag == tag).First(); + var annotation = context.Annotations.First(); + AnnotationTag at = new AnnotationTag() + { + Annotation = annotation, + Instance = 1, + Length = 10, + Position = 0, + SubTag = subtag, + Note = "asdasdasd", + Tag = tag + }; + context.AnnotationTags.Add(at); + } + + private static void AddDocumentAndAnnotation(DatabaseContext context) + { + var adminUser = context.Users.Where(u => u.Role == ERole.ADMINISTRATOR).First(); + var normalUser = context.Users.Where(u => u.Role == ERole.ANNOTATOR).First(); + + // Add documents + DocumentContent dctext = new DocumentContent() + { + Content = "sample document content of TEXT file" + }; + context.DocumentContents.Add(dctext); + + Document dtext = new() + { + Content = dctext, + Name = "Sample TEXT document", + DateAdded = DateTime.Now, + UserAdded = adminUser, + Length = dctext.Content.Length, + RequiredAnnotations = 3 + }; + context.Documents.Add(dtext); + + DocumentContent dchtml = new DocumentContent() + { + Content = "<!DOCTYPE html><html>sample document content of HTML file</html>" + }; + context.DocumentContents.Add(dchtml); + + Document dhtml = new() + { + Content = dchtml, + Name = "Sample TEXT document", + DateAdded = DateTime.Now, + UserAdded = adminUser, + Length = dchtml.Content.Length, + RequiredAnnotations = 3 + }; + context.Documents.Add(dhtml); + + // Create user and admin annotation + Annotation annotationAdmin = new Annotation() + { + State = EState.NEW, + DateAssigned = DateTime.Now, + DateLastChanged = DateTime.Now, + Document = dtext, + Note = "sample note", + User = adminUser, + UserAssigned = adminUser, + }; + Annotation annotationUser = new Annotation() + { + State = EState.NEW, + DateAssigned = DateTime.Now, + DateLastChanged = DateTime.Now, + Document = dhtml, + Note = "sample note", + User = normalUser, + UserAssigned = adminUser, + }; + context.Annotations.Add(annotationUser); + context.Annotations.Add(annotationAdmin); + + context.SaveChanges(); + } + public static void SeedProduction(DatabaseContext context) { if (!context.Users.Any(u => u.Role == ERole.ADMINISTRATOR)) diff --git a/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs b/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs index 89e4803da1dd4bc4e56b2dcd5a92d8dc28f5e5cb..8ec16814dc5ed30e72c0b056c1c8b7dad6256154 100644 --- a/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs +++ b/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs @@ -8,6 +8,9 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using AutoMapper; +using Models.Tags; namespace Core.Services.AnnotationService { @@ -15,11 +18,13 @@ namespace Core.Services.AnnotationService { private readonly DatabaseContext context; private readonly ILogger logger; + private readonly IMapper mapper; - public AnnotationServiceEF(DatabaseContext context, ILogger logger) + public AnnotationServiceEF(DatabaseContext context, ILogger logger, IMapper mapper) { this.context = context; this.logger = logger; + this.mapper = mapper; } public void CreateDocumentAnnotations(AnnotationsAddRequest request, Guid clientUserId) @@ -34,7 +39,7 @@ namespace Core.Services.AnnotationService throw new InvalidOperationException($"{request.DocumentIdList.Count - documents.Count()} of the received documents do not exist"); } - var users = context.Users.Where(u => request.UserIdList.Contains(u.Id)).ToList(); + var users = context.Users.Where(u => request.UserIdList.Contains(u.Id)).ToList(); foreach (var user in users) { var userAnnotatedDocuments = context.Annotations.Where(a => a.User == user).Select(a => a.Document).ToList(); @@ -61,5 +66,76 @@ namespace Core.Services.AnnotationService context.SaveChanges(); } + + public AnnotationListResponse GetUserAnnotations(Guid userId) + { + var annotations = context.Annotations.Where(a => a.User.Id == userId).Include(a => a.Document).ToList(); + var documentIds = annotations.Select(a => a.Document.Id).ToList(); + var documents = context.Documents.Where(d => documentIds.Contains(d.Id)); + var infos = new List<AnnotationListInfo>(); + + var annotationsDocuments = annotations.Zip(documents, (a, d) => new { Annotation = a, Document = d }); + foreach (var ad in annotationsDocuments) + { + infos.Add(new AnnotationListInfo() + { + AnnotationId = ad.Annotation.Id, + DocumentName = ad.Document.Name, + State = ad.Annotation.State + }); + } + + return new AnnotationListResponse() + { + Annotations = infos + }; + } + + public AnnotationInfo GetAnnotation(Guid annotationId, Guid userId, ERole userRole) + { + var annotation = context.Annotations + .Where(a => a.Id == annotationId) + .Include(a => a.User) + .Include(a => a.Document).ThenInclude(d => d.Content) + .First(); + + if (userRole < ERole.ADMINISTRATOR) + { + if (annotation.User.Id != userId) + { + throw new UnauthorizedAccessException($"User {userId} does not have assigned annotation {annotationId}"); + } + } + + var documentContent = context.Documents.Where(d => d.Id == annotation.Document.Id).Select(d => d.Content).First(); + + // We probably cannot use AutoMapper since we are dealing with too many different entities + AnnotationInfo annotationInfo = new() + { + DocumentText = documentContent.Content, + Note = annotation.Note, + State = annotation.State, + Type = IsHtml(documentContent.Content) ? EDocumentType.HTML : EDocumentType.TEXT + }; + + var tags = context.AnnotationTags.Where(at => at.Annotation.Id == annotationId) + .Include(at => at.Tag).ThenInclude(t => t.Category) + .Include(at => at.SubTag) + .ToList(); + + foreach (var tag in tags) + { + var tagInstance = mapper.Map<TagInstanceInfo>(tag); + annotationInfo.TagInstances.Add(tagInstance); + } + + return annotationInfo; + } + + // TODO temporary + private bool IsHtml(string text) + { + return text.Contains("<!DOCTYPE html>"); + } } } diff --git a/Backend/Core/Services/AnnotationService/IAnnotationService.cs b/Backend/Core/Services/AnnotationService/IAnnotationService.cs index 2cc5f700c52fb05cf6a14e8aa63b55b4da50c483..afb584da04cd4cbd55dac6abe542337f4d8f8a8c 100644 --- a/Backend/Core/Services/AnnotationService/IAnnotationService.cs +++ b/Backend/Core/Services/AnnotationService/IAnnotationService.cs @@ -1,4 +1,5 @@ using Models.Annotations; +using Models.Enums; using System; using System.Collections.Generic; using System.Linq; @@ -10,5 +11,7 @@ namespace Core.Services.AnnotationService public interface IAnnotationService { public void CreateDocumentAnnotations(AnnotationsAddRequest request, Guid userId); + public AnnotationListResponse GetUserAnnotations(Guid userId); + public AnnotationInfo GetAnnotation(Guid annotationId, Guid userId, ERole userRole); } } diff --git a/Backend/Models/Annotations/AnnotationGetRequest.cs b/Backend/Models/Annotations/AnnotationGetRequest.cs new file mode 100644 index 0000000000000000000000000000000000000000..53dd0ebf5032469c55aafdc13e2c5550f5f760e2 --- /dev/null +++ b/Backend/Models/Annotations/AnnotationGetRequest.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Models.Annotations +{ + public class AnnotationGetRequest + { + public Guid AnnotationId { get; set; } + } +} diff --git a/Backend/Models/Annotations/AnnotationInfo.cs b/Backend/Models/Annotations/AnnotationInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..ee6a1afc9f2d73497e44af626768e57ecce1f8b8 --- /dev/null +++ b/Backend/Models/Annotations/AnnotationInfo.cs @@ -0,0 +1,19 @@ +using Models.Enums; +using Models.Tags; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Models.Annotations +{ + public class AnnotationInfo + { + public string DocumentText { get; set; } + public EState State { get; set; } + public EDocumentType Type { get; set; } + public string Note { get; set; } + public List<TagInstanceInfo> TagInstances { get; set; } = new(); + } +} diff --git a/Backend/Models/Annotations/AnnotationListInfo.cs b/Backend/Models/Annotations/AnnotationListInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..cfdc28dc63e68d244e44ee46bcc57ac805fc3730 --- /dev/null +++ b/Backend/Models/Annotations/AnnotationListInfo.cs @@ -0,0 +1,16 @@ +using Models.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Models.Annotations +{ + public class AnnotationListInfo + { + public string DocumentName { get; set; } + public EState State { get; set; } + public Guid AnnotationId { get; set; } + } +} diff --git a/Backend/Models/Annotations/AnnotationListResponse.cs b/Backend/Models/Annotations/AnnotationListResponse.cs new file mode 100644 index 0000000000000000000000000000000000000000..4fb0d17b08c200e3f2fb0d1067e167ec35e052a7 --- /dev/null +++ b/Backend/Models/Annotations/AnnotationListResponse.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Models.Annotations +{ + public class AnnotationListResponse + { + public List<AnnotationListInfo> Annotations { get; set; } = new(); + } +} diff --git a/Backend/Models/Enums/EDocumentType.cs b/Backend/Models/Enums/EDocumentType.cs new file mode 100644 index 0000000000000000000000000000000000000000..3a56e85e7c8a03c090f4cf62f02be71a9354cfa5 --- /dev/null +++ b/Backend/Models/Enums/EDocumentType.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Models.Enums +{ + public enum EDocumentType + { + HTML, + TEXT + } +} diff --git a/Backend/Models/Tags/TagInstanceInfo.cs b/Backend/Models/Tags/TagInstanceInfo.cs new file mode 100644 index 0000000000000000000000000000000000000000..204a69083f03da49e62719224316d3e98ae41d6f --- /dev/null +++ b/Backend/Models/Tags/TagInstanceInfo.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Models.Tags +{ + public class TagInstanceInfo + { + public string TagName { get; set; } + public Guid TagId { get; set; } + public string TagCategoryName { get; set; } + public Guid TagCategoryId { get; set; } + public string? SubTagName { get; set; } + public Guid? SubTagId { get; set; } + + public int Instance { get; set; } + public int Position { get; set; } + public int Length { get; set; } + + public string Note { get; set; } + + } +}