From 42bcf47b394e92f16b4ce767d36e1a3af64b1b09 Mon Sep 17 00:00:00 2001
From: esantangelo <enzo020895@gmail.com>
Date: Sun, 18 Oct 2020 14:20:11 -0300
Subject: [PATCH] commit

---
 .../Tsi1.Api/Controllers/ForumController.cs   | 45 +++++++++++
 .../Tsi1.Api/Controllers/PostController.cs    | 71 +++++++++++++++++
 .../Controllers/PostMessageController.cs      | 71 +++++++++++++++++
 Tsi1.Api/Tsi1.Api/Startup.cs                  |  2 +
 .../Tsi1.BusinessLayer/Dtos/ForumCreateDto.cs |  2 +-
 .../Dtos/ForumPreviewDto.cs                   |  2 +-
 .../Tsi1.BusinessLayer/Dtos/PostCreateDto.cs  | 16 ++++
 .../Dtos/PostMessageCreateDto.cs              | 16 ++++
 .../Dtos/PostMessagePreviewDto.cs             | 20 +++++
 .../Tsi1.BusinessLayer/Dtos/PostPreviewDto.cs | 20 +++++
 .../Helpers/MappingProfile.cs                 |  9 +++
 .../Interfaces/IPostMessageService.cs         | 19 +++++
 .../Interfaces/IPostService.cs                | 11 ++-
 .../Services/PostMessageService.cs            | 79 +++++++++++++++++++
 .../Services/PostService.cs                   | 79 +++++++++++++++++++
 .../Tsi1.BusinessLayer.csproj                 |  5 ++
 16 files changed, 464 insertions(+), 3 deletions(-)
 create mode 100644 Tsi1.Api/Tsi1.Api/Controllers/PostController.cs
 create mode 100644 Tsi1.Api/Tsi1.Api/Controllers/PostMessageController.cs
 create mode 100644 Tsi1.Api/Tsi1.BusinessLayer/Dtos/PostCreateDto.cs
 create mode 100644 Tsi1.Api/Tsi1.BusinessLayer/Dtos/PostMessageCreateDto.cs
 create mode 100644 Tsi1.Api/Tsi1.BusinessLayer/Dtos/PostMessagePreviewDto.cs
 create mode 100644 Tsi1.Api/Tsi1.BusinessLayer/Dtos/PostPreviewDto.cs
 create mode 100644 Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IPostMessageService.cs
 create mode 100644 Tsi1.Api/Tsi1.BusinessLayer/Services/PostMessageService.cs
 create mode 100644 Tsi1.Api/Tsi1.BusinessLayer/Services/PostService.cs

diff --git a/Tsi1.Api/Tsi1.Api/Controllers/ForumController.cs b/Tsi1.Api/Tsi1.Api/Controllers/ForumController.cs
index 3e8575c..c0e99fb 100644
--- a/Tsi1.Api/Tsi1.Api/Controllers/ForumController.cs
+++ b/Tsi1.Api/Tsi1.Api/Controllers/ForumController.cs
@@ -2,8 +2,11 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
+using Tsi1.BusinessLayer.Dtos;
+using Tsi1.BusinessLayer.Helpers;
 using Tsi1.BusinessLayer.Interfaces;
 
 namespace Tsi1.Api.Controllers
@@ -18,5 +21,47 @@ namespace Tsi1.Api.Controllers
         {
             _forumService = forumService;
         }
+
+        [Authorize(Roles = UserTypes.Student + ", " + UserTypes.Professor)]
+        [HttpGet("GetForums/{courseId}")]
+        public async Task<IActionResult> GetForums(int courseId)
+        {
+            var result = await _forumService.GetForums(courseId);
+
+            if (result.HasError)
+            {
+                return BadRequest(result.Message);
+            }
+
+            return Ok(result.Data);
+        }
+
+        [Authorize(Roles = UserTypes.Student + ", " + UserTypes.Professor)]
+        [HttpPost("Create")]
+        public async Task<IActionResult> Create(ForumCreateDto newForum)
+        {
+            var result = await _forumService.Create(newForum);
+
+            if (result.HasError)
+            {
+                return BadRequest(result.Message);
+            }
+
+            return Ok();
+        }
+
+        [Authorize(Roles = UserTypes.Student + ", " + UserTypes.Professor)]
+        [HttpDelete("Delete/{forumId}")]
+        public async Task<IActionResult> Delete(int forumId)
+        {
+            var result = await _forumService.Delete(forumId);
+
+            if (result.HasError)
+            {
+                return BadRequest(result.Message);
+            }
+
+            return Ok();
+        }
     }
 }
diff --git a/Tsi1.Api/Tsi1.Api/Controllers/PostController.cs b/Tsi1.Api/Tsi1.Api/Controllers/PostController.cs
new file mode 100644
index 0000000..eb1c19e
--- /dev/null
+++ b/Tsi1.Api/Tsi1.Api/Controllers/PostController.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Tsi1.BusinessLayer.Dtos;
+using Tsi1.BusinessLayer.Helpers;
+using Tsi1.BusinessLayer.Interfaces;
+
+namespace Tsi1.Api.Controllers
+{
+    [Route("api/[controller]")]
+    [ApiController]
+    public class PostController : ControllerBase
+    {
+        private readonly IPostService _postService;
+
+        public PostController(IPostService postService)
+        {
+            _postService = postService;
+        }
+
+        [Authorize(Roles = UserTypes.Student + ", " + UserTypes.Professor)]
+        [HttpGet("GetPosts/{forumId}")]
+        public async Task<IActionResult> GetPosts(int forumId)
+        {
+            var result = await _postService.GetPosts(forumId);
+
+            if (result.HasError)
+            {
+                return BadRequest(result.Message);
+            }
+
+            return Ok(result.Data);
+        }
+
+        [Authorize(Roles = UserTypes.Student + ", " + UserTypes.Professor)]
+        [HttpPost("Create")]
+        public async Task<IActionResult> Create(PostCreateDto newPost)
+        {
+            var userId = int.Parse(HttpContext.User.Claims.FirstOrDefault(x => x.Type == "Id").Value);
+
+            newPost.UserId = userId;
+
+            var result = await _postService.Create(newPost);
+
+            if (result.HasError)
+            {
+                return BadRequest(result.Message);
+            }
+
+            return Ok();
+        }
+
+        [Authorize(Roles = UserTypes.Student + ", " + UserTypes.Professor)]
+        [HttpDelete("Delete/{postId}")]
+        public async Task<IActionResult> Delete(int postId)
+        {
+            var result = await _postService.Delete(postId);
+
+            if (result.HasError)
+            {
+                return BadRequest(result.Message);
+            }
+
+            return Ok();
+        }
+    }
+}
diff --git a/Tsi1.Api/Tsi1.Api/Controllers/PostMessageController.cs b/Tsi1.Api/Tsi1.Api/Controllers/PostMessageController.cs
new file mode 100644
index 0000000..0a92456
--- /dev/null
+++ b/Tsi1.Api/Tsi1.Api/Controllers/PostMessageController.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using Tsi1.BusinessLayer.Dtos;
+using Tsi1.BusinessLayer.Helpers;
+using Tsi1.BusinessLayer.Interfaces;
+
+namespace Tsi1.Api.Controllers
+{
+    [Route("api/[controller]")]
+    [ApiController]
+    public class PostMessageController : ControllerBase
+    {
+        private readonly IPostMessageService _postMessageService;
+
+        public PostMessageController(IPostMessageService postMessageService)
+        {
+            _postMessageService = postMessageService;
+        }
+
+        [Authorize(Roles = UserTypes.Student + ", " + UserTypes.Professor)]
+        [HttpGet("GetPostMessages/{postId}")]
+        public async Task<IActionResult> GetGetPostMessagesPosts(int postId)
+        {
+            var result = await _postMessageService.GetPostMessages(postId);
+
+            if (result.HasError)
+            {
+                return BadRequest(result.Message);
+            }
+
+            return Ok(result.Data);
+        }
+
+        [Authorize(Roles = UserTypes.Student + ", " + UserTypes.Professor)]
+        [HttpPost("Create")]
+        public async Task<IActionResult> Create(PostMessageCreateDto newPostMessage)
+        {
+            var userId = int.Parse(HttpContext.User.Claims.FirstOrDefault(x => x.Type == "Id").Value);
+
+            newPostMessage.UserId = userId;
+
+            var result = await _postMessageService.Create(newPostMessage);
+
+            if (result.HasError)
+            {
+                return BadRequest(result.Message);
+            }
+
+            return Ok();
+        }
+
+        [Authorize(Roles = UserTypes.Student + ", " + UserTypes.Professor)]
+        [HttpDelete("Delete/{postMessageId}")]
+        public async Task<IActionResult> Delete(int postMessageId)
+        {
+            var result = await _postMessageService.Delete(postMessageId);
+
+            if (result.HasError)
+            {
+                return BadRequest(result.Message);
+            }
+
+            return Ok();
+        }
+    }
+}
diff --git a/Tsi1.Api/Tsi1.Api/Startup.cs b/Tsi1.Api/Tsi1.Api/Startup.cs
index 75f0c2e..0c5c215 100644
--- a/Tsi1.Api/Tsi1.Api/Startup.cs
+++ b/Tsi1.Api/Tsi1.Api/Startup.cs
@@ -43,6 +43,8 @@ namespace Tsi1.Api
             services.AddScoped<IUserTypeService, UserTypeService>();
             services.AddScoped<ICourseService, CourseService>();
             services.AddScoped<IForumService, ForumService>();
+            services.AddScoped<IPostService, PostService>();
+            services.AddScoped<IPostMessageService, PostMessageService>();
 
             services.AddCors();
 
diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/ForumCreateDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/ForumCreateDto.cs
index f9506dd..2756562 100644
--- a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/ForumCreateDto.cs
+++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/ForumCreateDto.cs
@@ -4,7 +4,7 @@ using System.Text;
 
 namespace Tsi1.BusinessLayer.Dtos
 {
-    class ForumCreateDto
+    public class ForumCreateDto
     {
         public string Name { get; set; }
 
diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/ForumPreviewDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/ForumPreviewDto.cs
index ae1ca4b..efe56b6 100644
--- a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/ForumPreviewDto.cs
+++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/ForumPreviewDto.cs
@@ -4,7 +4,7 @@ using System.Text;
 
 namespace Tsi1.BusinessLayer.Dtos
 {
-    class ForumPreviewDto
+    public class ForumPreviewDto
     {
         public int Id { get; set; }
 
diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/PostCreateDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/PostCreateDto.cs
new file mode 100644
index 0000000..b3bbe80
--- /dev/null
+++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/PostCreateDto.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Tsi1.BusinessLayer.Dtos
+{
+    public class PostCreateDto
+    {
+        public string Title { get; set; }
+
+        public int ForumId { get; set; }
+
+        public int UserId { get; set; }
+
+    }
+}
diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/PostMessageCreateDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/PostMessageCreateDto.cs
new file mode 100644
index 0000000..b42cf31
--- /dev/null
+++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/PostMessageCreateDto.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Tsi1.BusinessLayer.Dtos
+{
+    public class PostMessageCreateDto
+    {
+        public string Content { get; set; }
+
+        public int UserId { get; set; }
+
+        public int PostId { get; set; }
+
+    }
+}
diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/PostMessagePreviewDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/PostMessagePreviewDto.cs
new file mode 100644
index 0000000..cad57d7
--- /dev/null
+++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/PostMessagePreviewDto.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Tsi1.BusinessLayer.Dtos
+{
+    public class PostMessagePreviewDto
+    {
+        public int Id { get; set; }
+
+        public string Content { get; set; }
+
+        public DateTime Date { get; set; }
+
+        public int UserId { get; set; }
+
+        public int PostId { get; set; }
+
+    }
+}
diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Dtos/PostPreviewDto.cs b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/PostPreviewDto.cs
new file mode 100644
index 0000000..526ca19
--- /dev/null
+++ b/Tsi1.Api/Tsi1.BusinessLayer/Dtos/PostPreviewDto.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Tsi1.BusinessLayer.Dtos
+{
+    public class PostPreviewDto
+    {
+        public int Id { get; set; }
+
+        public string Title { get; set; }
+
+        public DateTime Date { get; set; }
+
+        public int ForumId { get; set; }
+
+        public int UserId { get; set; }
+
+    }
+}
diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Helpers/MappingProfile.cs b/Tsi1.Api/Tsi1.BusinessLayer/Helpers/MappingProfile.cs
index 1343433..95ed2d6 100644
--- a/Tsi1.Api/Tsi1.BusinessLayer/Helpers/MappingProfile.cs
+++ b/Tsi1.Api/Tsi1.BusinessLayer/Helpers/MappingProfile.cs
@@ -1,6 +1,7 @@
 using AutoMapper;
 using System;
 using System.Collections.Generic;
+using System.Security.Cryptography.X509Certificates;
 using System.Text;
 using Tsi1.BusinessLayer.Dtos;
 using Tsi1.DataLayer.Entities;
@@ -13,9 +14,17 @@ namespace Tsi1.BusinessLayer.Helpers
         {
             CreateMap<Forum, ForumCreateDto>();
             CreateMap<Forum, ForumPreviewDto>();
+            CreateMap<Post, PostCreateDto>();
+            CreateMap<Post, PostPreviewDto>();
+            CreateMap<PostMessage, PostMessageCreateDto>();
+            CreateMap<PostMessage, PostMessagePreviewDto>();
 
             CreateMap<ForumCreateDto, Forum>();
             CreateMap<ForumPreviewDto, Forum>();
+            CreateMap<PostCreateDto, Post>();
+            CreateMap<PostPreviewDto, Post>();
+            CreateMap<PostMessageCreateDto, PostMessage>();
+            CreateMap<PostMessagePreviewDto, PostMessage>();
         }
     }
 }
diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IPostMessageService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IPostMessageService.cs
new file mode 100644
index 0000000..a1b34f4
--- /dev/null
+++ b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IPostMessageService.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using Tsi1.BusinessLayer.Dtos;
+using Tsi1.BusinessLayer.Helpers;
+using Tsi1.DataLayer.Entities;
+
+namespace Tsi1.BusinessLayer.Interfaces
+{
+    public interface IPostMessageService
+    {
+        Task<ServiceResult<List<PostMessagePreviewDto>>> GetPostMessages(int postId);
+
+        Task<ServiceResult<PostMessage>> Create(PostMessageCreateDto newPostMessage);
+
+        Task<ServiceResult<PostMessage>> Delete(int postMessageId);
+    }
+}
diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IPostService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IPostService.cs
index 6c31e43..40dbc56 100644
--- a/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IPostService.cs
+++ b/Tsi1.Api/Tsi1.BusinessLayer/Interfaces/IPostService.cs
@@ -1,10 +1,19 @@
 using System;
 using System.Collections.Generic;
 using System.Text;
+using System.Threading.Tasks;
+using Tsi1.BusinessLayer.Dtos;
+using Tsi1.BusinessLayer.Helpers;
+using Tsi1.DataLayer.Entities;
 
 namespace Tsi1.BusinessLayer.Interfaces
 {
-    interface IPostService
+    public interface IPostService
     {
+        Task<ServiceResult<List<PostPreviewDto>>> GetPosts(int forumId);
+
+        Task<ServiceResult<Post>> Create(PostCreateDto newPost);
+
+        Task<ServiceResult<Post>> Delete(int postId);
     }
 }
diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Services/PostMessageService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Services/PostMessageService.cs
new file mode 100644
index 0000000..067a331
--- /dev/null
+++ b/Tsi1.Api/Tsi1.BusinessLayer/Services/PostMessageService.cs
@@ -0,0 +1,79 @@
+using AutoMapper;
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Tsi1.BusinessLayer.Dtos;
+using Tsi1.BusinessLayer.Helpers;
+using Tsi1.BusinessLayer.Interfaces;
+using Tsi1.DataLayer;
+using Tsi1.DataLayer.Entities;
+
+namespace Tsi1.BusinessLayer.Services
+{
+    public class PostMessageService : IPostMessageService
+    {
+        private readonly Tsi1Context _context;
+
+        private readonly IMapper _mapper;
+
+        public PostMessageService(Tsi1Context context, IMapper mapper)
+        {
+            _context = context;
+            _mapper = mapper;
+        }
+
+        public async Task<ServiceResult<PostMessage>> Create(PostMessageCreateDto newPostMessage)
+        {
+            var result = new ServiceResult<PostMessage>();
+
+            var postMessage = _mapper.Map<PostMessage>(newPostMessage);
+
+            postMessage.Date = DateTime.Now;
+
+            _context.PostMessages.Add(postMessage);
+            await _context.SaveChangesAsync();
+
+            result.Data = postMessage;
+
+            return result;
+        }
+
+        public async Task<ServiceResult<PostMessage>> Delete(int postMessageId)
+        {
+            var result = new ServiceResult<PostMessage>();
+
+            var postMessage = await _context.PostMessages.FirstOrDefaultAsync(x => x.Id == postMessageId);
+
+            if (postMessage == null)
+            {
+                result.HasError = true;
+                result.Message = string.Format(ErrorMessages.PostMessageDoesNotExist, postMessageId);
+                return result;
+            }
+
+            _context.PostMessages.Remove(postMessage);
+
+            await _context.SaveChangesAsync();
+
+            return result;
+        }
+
+        public async Task<ServiceResult<List<PostMessagePreviewDto>>> GetPostMessages(int postId)
+        {
+            var result = new ServiceResult<List<PostMessagePreviewDto>>();
+
+            var postMessages = await _context.PostMessages
+                .Where(x => x.PostId == postId)
+                .ToListAsync();
+
+            var postMessageDtos = _mapper.Map<List<PostMessagePreviewDto>>(postMessages);
+
+            result.Data = postMessageDtos;
+
+            return result;
+        }
+    }
+}
diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Services/PostService.cs b/Tsi1.Api/Tsi1.BusinessLayer/Services/PostService.cs
new file mode 100644
index 0000000..0d625f4
--- /dev/null
+++ b/Tsi1.Api/Tsi1.BusinessLayer/Services/PostService.cs
@@ -0,0 +1,79 @@
+using AutoMapper;
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Tsi1.BusinessLayer.Dtos;
+using Tsi1.BusinessLayer.Helpers;
+using Tsi1.BusinessLayer.Interfaces;
+using Tsi1.DataLayer;
+using Tsi1.DataLayer.Entities;
+
+namespace Tsi1.BusinessLayer.Services
+{
+    public class PostService : IPostService
+    {
+        private readonly Tsi1Context _context;
+
+        private readonly IMapper _mapper;
+
+        public PostService(Tsi1Context context, IMapper mapper)
+        {
+            _context = context;
+            _mapper = mapper;
+        }
+
+        public async Task<ServiceResult<Post>> Create(PostCreateDto newPost)
+        {
+            var result = new ServiceResult<Post>();
+
+            var post = _mapper.Map<Post>(newPost);
+
+            post.Date = DateTime.Now;
+
+            _context.Posts.Add(post);
+            await _context.SaveChangesAsync();
+
+            result.Data = post;
+
+            return result;
+        }
+
+        public async Task<ServiceResult<Post>> Delete(int postId)
+        {
+            var result = new ServiceResult<Post>();
+
+            var post = await _context.Posts.FirstOrDefaultAsync(x => x.Id == postId);
+
+            if (post == null)
+            {
+                result.HasError = true;
+                result.Message = string.Format(ErrorMessages.PostDoesNotExist, postId);
+                return result;
+            }
+
+            _context.Posts.Remove(post);
+
+            await _context.SaveChangesAsync();
+
+            return result;
+        }
+
+        public async Task<ServiceResult<List<PostPreviewDto>>> GetPosts(int forumId)
+        {
+            var result = new ServiceResult<List<PostPreviewDto>>();
+
+            var posts = await _context.Posts
+                .Where(x => x.ForumId == forumId)
+                .ToListAsync();
+
+            var postDtos = _mapper.Map<List<PostPreviewDto>>(posts);
+
+            result.Data = postDtos;
+
+            return result;
+        }
+    }
+}
diff --git a/Tsi1.Api/Tsi1.BusinessLayer/Tsi1.BusinessLayer.csproj b/Tsi1.Api/Tsi1.BusinessLayer/Tsi1.BusinessLayer.csproj
index 71bca38..bf03c35 100644
--- a/Tsi1.Api/Tsi1.BusinessLayer/Tsi1.BusinessLayer.csproj
+++ b/Tsi1.Api/Tsi1.BusinessLayer/Tsi1.BusinessLayer.csproj
@@ -4,6 +4,11 @@
     <TargetFramework>netcoreapp3.1</TargetFramework>
   </PropertyGroup>
 
+  <ItemGroup>
+    <PackageReference Include="AutoMapper" Version="10.1.1" />
+    <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.0" />
+  </ItemGroup>
+
   <ItemGroup>
     <ProjectReference Include="..\Tsi1.DataLayer\Tsi1.DataLayer.csproj" />
   </ItemGroup>
-- 
GitLab