diff --git a/.gitignore b/.gitignore index 3a868930..7b4c3434 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ riderModule.iml /_ReSharper.Caches/ .fake .vscode +.vs/ \ No newline at end of file diff --git a/.vs/MyFirstBlog/DesignTimeBuild/.dtbcache.v2 b/.vs/MyFirstBlog/DesignTimeBuild/.dtbcache.v2 index e3a42a61..676b2cb5 100644 Binary files a/.vs/MyFirstBlog/DesignTimeBuild/.dtbcache.v2 and b/.vs/MyFirstBlog/DesignTimeBuild/.dtbcache.v2 differ diff --git a/.vs/MyFirstBlog/FileContentIndex/1451f00c-8a3c-4b67-9988-24ae06d88ff9.vsidx b/.vs/MyFirstBlog/FileContentIndex/1451f00c-8a3c-4b67-9988-24ae06d88ff9.vsidx deleted file mode 100644 index ae1b509b..00000000 Binary files a/.vs/MyFirstBlog/FileContentIndex/1451f00c-8a3c-4b67-9988-24ae06d88ff9.vsidx and /dev/null differ diff --git a/.vs/MyFirstBlog/FileContentIndex/3671b2ea-91c1-4fa3-8590-3e73598d97ea.vsidx b/.vs/MyFirstBlog/FileContentIndex/3671b2ea-91c1-4fa3-8590-3e73598d97ea.vsidx deleted file mode 100644 index 78b55c80..00000000 Binary files a/.vs/MyFirstBlog/FileContentIndex/3671b2ea-91c1-4fa3-8590-3e73598d97ea.vsidx and /dev/null differ diff --git a/.vs/MyFirstBlog/v17/.futdcache.v2 b/.vs/MyFirstBlog/v17/.futdcache.v2 index 128982d8..8e10da6b 100644 Binary files a/.vs/MyFirstBlog/v17/.futdcache.v2 and b/.vs/MyFirstBlog/v17/.futdcache.v2 differ diff --git a/.vs/MyFirstBlog/v17/.suo b/.vs/MyFirstBlog/v17/.suo index 74e1a08b..86988d0e 100644 Binary files a/.vs/MyFirstBlog/v17/.suo and b/.vs/MyFirstBlog/v17/.suo differ diff --git a/.vs/MyFirstBlog/v17/DocumentLayout.json b/.vs/MyFirstBlog/v17/DocumentLayout.json index 653eb6b4..39c1feae 100644 --- a/.vs/MyFirstBlog/v17/DocumentLayout.json +++ b/.vs/MyFirstBlog/v17/DocumentLayout.json @@ -1,38 +1,10 @@ { "Version": 1, - "WorkspaceRootPath": "C:\\00code\\FullStack\\MyFirstBlogBackEnd\\", + "WorkspaceRootPath": "C:\\Users\\Diana Paola\\source\\repos\\MyFirstBlogBackEnd\\", "Documents": [ { - "AbsoluteMoniker": "D:0:0:{BB64A02C-C5D9-4D64-A357-7322C54B1419}|MyFirstBlog\\MyFirstBlog.csproj|c:\\00code\\fullstack\\myfirstblogbackend\\myfirstblog\\properties\\launchsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}", - "RelativeMoniker": "D:0:0:{BB64A02C-C5D9-4D64-A357-7322C54B1419}|MyFirstBlog\\MyFirstBlog.csproj|solutionrelative:myfirstblog\\properties\\launchsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}" - }, - { - "AbsoluteMoniker": "D:0:0:{BB64A02C-C5D9-4D64-A357-7322C54B1419}|MyFirstBlog\\MyFirstBlog.csproj|c:\\00code\\fullstack\\myfirstblogbackend\\myfirstblog\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{BB64A02C-C5D9-4D64-A357-7322C54B1419}|MyFirstBlog\\MyFirstBlog.csproj|solutionrelative:myfirstblog\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{BB64A02C-C5D9-4D64-A357-7322C54B1419}|MyFirstBlog\\MyFirstBlog.csproj|c:\\00code\\fullstack\\myfirstblogbackend\\myfirstblog\\entities\\post.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{BB64A02C-C5D9-4D64-A357-7322C54B1419}|MyFirstBlog\\MyFirstBlog.csproj|solutionrelative:myfirstblog\\entities\\post.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{BB64A02C-C5D9-4D64-A357-7322C54B1419}|MyFirstBlog\\MyFirstBlog.csproj|c:\\00code\\fullstack\\myfirstblogbackend\\myfirstblog\\services\\postservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{BB64A02C-C5D9-4D64-A357-7322C54B1419}|MyFirstBlog\\MyFirstBlog.csproj|solutionrelative:myfirstblog\\services\\postservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{BB64A02C-C5D9-4D64-A357-7322C54B1419}|MyFirstBlog\\MyFirstBlog.csproj|c:\\00code\\fullstack\\myfirstblogbackend\\myfirstblog\\appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}", - "RelativeMoniker": "D:0:0:{BB64A02C-C5D9-4D64-A357-7322C54B1419}|MyFirstBlog\\MyFirstBlog.csproj|solutionrelative:myfirstblog\\appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}" - }, - { - "AbsoluteMoniker": "D:0:0:{BB64A02C-C5D9-4D64-A357-7322C54B1419}|MyFirstBlog\\MyFirstBlog.csproj|c:\\00code\\fullstack\\myfirstblogbackend\\myfirstblog\\extensions.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{BB64A02C-C5D9-4D64-A357-7322C54B1419}|MyFirstBlog\\MyFirstBlog.csproj|solutionrelative:myfirstblog\\extensions.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{BB64A02C-C5D9-4D64-A357-7322C54B1419}|MyFirstBlog\\MyFirstBlog.csproj|c:\\00code\\fullstack\\myfirstblogbackend\\myfirstblog\\dtos\\postdto.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{BB64A02C-C5D9-4D64-A357-7322C54B1419}|MyFirstBlog\\MyFirstBlog.csproj|solutionrelative:myfirstblog\\dtos\\postdto.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" - }, - { - "AbsoluteMoniker": "D:0:0:{BB64A02C-C5D9-4D64-A357-7322C54B1419}|MyFirstBlog\\MyFirstBlog.csproj|c:\\00code\\fullstack\\myfirstblogbackend\\myfirstblog\\controllers\\postscontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", - "RelativeMoniker": "D:0:0:{BB64A02C-C5D9-4D64-A357-7322C54B1419}|MyFirstBlog\\MyFirstBlog.csproj|solutionrelative:myfirstblog\\controllers\\postscontroller.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + "AbsoluteMoniker": "D:0:0:{9B011752-04F1-43B9-91DC-AD6788400F60}|Solution Items{9B011752-04F1-43B9-91DC-AD6788400F60}|C:\\Users\\Diana Paola\\source\\repos\\MyFirstBlogBackEnd\\.gitignore||{3B902123-F8A7-4915-9F01-361F908088D0}", + "RelativeMoniker": "D:0:0:{9B011752-04F1-43B9-91DC-AD6788400F60}|Solution Items{9B011752-04F1-43B9-91DC-AD6788400F60}|solutionrelative:.gitignore||{3B902123-F8A7-4915-9F01-361F908088D0}" } ], "DocumentGroupContainers": [ @@ -41,105 +13,43 @@ "VerticalTabListWidth": 256, "DocumentGroups": [ { - "DockedWidth": 200, - "SelectedChildIndex": 6, + "DockedWidth": 48, + "SelectedChildIndex": 1, "Children": [ { - "$type": "Document", - "DocumentIndex": 1, - "Title": "Program.cs", - "DocumentMoniker": "C:\\00code\\FullStack\\MyFirstBlogBackEnd\\MyFirstBlog\\Program.cs", - "RelativeDocumentMoniker": "MyFirstBlog\\Program.cs", - "ToolTip": "C:\\00code\\FullStack\\MyFirstBlogBackEnd\\MyFirstBlog\\Program.cs", - "RelativeToolTip": "MyFirstBlog\\Program.cs", - "ViewState": "AQIAAAAAAAAAAAAAAAAAAA4AAAAjAAAA", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2024-05-15T14:44:39.864Z" + "$type": "Bookmark", + "Name": "ST:128:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" }, { "$type": "Document", - "DocumentIndex": 2, - "Title": "Post.cs", - "DocumentMoniker": "C:\\00code\\FullStack\\MyFirstBlogBackEnd\\MyFirstBlog\\Entities\\Post.cs", - "RelativeDocumentMoniker": "MyFirstBlog\\Entities\\Post.cs", - "ToolTip": "C:\\00code\\FullStack\\MyFirstBlogBackEnd\\MyFirstBlog\\Entities\\Post.cs", - "RelativeToolTip": "MyFirstBlog\\Entities\\Post.cs", - "ViewState": "AQIAAAAAAAAAAAAAAAAAAAEAAAAAAAAA", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2024-05-15T14:43:03.248Z" - }, - { - "$type": "Document", - "DocumentIndex": 6, - "Title": "PostDto.cs", - "DocumentMoniker": "C:\\00code\\FullStack\\MyFirstBlogBackEnd\\MyFirstBlog\\Dtos\\PostDto.cs", - "RelativeDocumentMoniker": "MyFirstBlog\\Dtos\\PostDto.cs", - "ToolTip": "C:\\00code\\FullStack\\MyFirstBlogBackEnd\\MyFirstBlog\\Dtos\\PostDto.cs", - "RelativeToolTip": "MyFirstBlog\\Dtos\\PostDto.cs", - "ViewState": "AQIAAAAAAAAAAAAAAAAAAAgAAAABAAAA", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2024-05-15T14:42:47.081Z" - }, - { - "$type": "Document", - "DocumentIndex": 4, - "Title": "appsettings.json", - "DocumentMoniker": "C:\\00code\\FullStack\\MyFirstBlogBackEnd\\MyFirstBlog\\appsettings.json", - "RelativeDocumentMoniker": "MyFirstBlog\\appsettings.json", - "ToolTip": "C:\\00code\\FullStack\\MyFirstBlogBackEnd\\MyFirstBlog\\appsettings.json", - "RelativeToolTip": "MyFirstBlog\\appsettings.json", - "ViewState": "AQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|", - "WhenOpened": "2024-05-15T14:49:58.169Z" - }, - { - "$type": "Document", - "DocumentIndex": 3, - "Title": "PostService.cs", - "DocumentMoniker": "C:\\00code\\FullStack\\MyFirstBlogBackEnd\\MyFirstBlog\\Services\\PostService.cs", - "RelativeDocumentMoniker": "MyFirstBlog\\Services\\PostService.cs", - "ToolTip": "C:\\00code\\FullStack\\MyFirstBlogBackEnd\\MyFirstBlog\\Services\\PostService.cs", - "RelativeToolTip": "MyFirstBlog\\Services\\PostService.cs", - "ViewState": "AQIAAAAAAAAAAAAAAAAAAAkAAAAkAAAA", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2024-05-15T14:45:00.15Z" - }, + "DocumentIndex": 0, + "Title": ".gitignore", + "DocumentMoniker": "C:\\Users\\Diana Paola\\source\\repos\\MyFirstBlogBackEnd\\.gitignore", + "RelativeDocumentMoniker": ".gitignore", + "ToolTip": "C:\\Users\\Diana Paola\\source\\repos\\MyFirstBlogBackEnd\\.gitignore", + "RelativeToolTip": ".gitignore", + "ViewState": "AQIAAAAAAAAAAAAAAAAAAAcAAAAEAAAA", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001001|", + "WhenOpened": "2024-08-11T07:34:07.004Z", + "EditorCaption": "" + } + ] + }, + { + "DockedWidth": 148, + "SelectedChildIndex": -1, + "Children": [ { - "$type": "Document", - "DocumentIndex": 5, - "Title": "Extensions.cs", - "DocumentMoniker": "C:\\00code\\FullStack\\MyFirstBlogBackEnd\\MyFirstBlog\\Extensions.cs", - "RelativeDocumentMoniker": "MyFirstBlog\\Extensions.cs", - "ToolTip": "C:\\00code\\FullStack\\MyFirstBlogBackEnd\\MyFirstBlog\\Extensions.cs", - "RelativeToolTip": "MyFirstBlog\\Extensions.cs", - "ViewState": "AQIAAAAAAAAAAAAAAAAAAAwAAAAuAAAA", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2024-05-15T14:44:53.417Z" + "$type": "Bookmark", + "Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}" }, { - "$type": "Document", - "DocumentIndex": 0, - "Title": "launchSettings.json", - "DocumentMoniker": "C:\\00code\\FullStack\\MyFirstBlogBackEnd\\MyFirstBlog\\Properties\\launchSettings.json", - "RelativeDocumentMoniker": "MyFirstBlog\\Properties\\launchSettings.json", - "ToolTip": "C:\\00code\\FullStack\\MyFirstBlogBackEnd\\MyFirstBlog\\Properties\\launchSettings.json", - "RelativeToolTip": "MyFirstBlog\\Properties\\launchSettings.json", - "ViewState": "AQIAAA8AAAAAAAAAAAAAABkAAAAfAAAA", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|", - "WhenOpened": "2024-05-15T14:50:05.724Z", - "EditorCaption": "" + "$type": "Bookmark", + "Name": "ST:2:0:{e8034f19-ab72-4f06-83fd-f6832b41aa63}" }, { - "$type": "Document", - "DocumentIndex": 7, - "Title": "PostsController.cs", - "DocumentMoniker": "C:\\00code\\FullStack\\MyFirstBlogBackEnd\\MyFirstBlog\\Controllers\\PostsController.cs", - "RelativeDocumentMoniker": "MyFirstBlog\\Controllers\\PostsController.cs", - "ToolTip": "C:\\00code\\FullStack\\MyFirstBlogBackEnd\\MyFirstBlog\\Controllers\\PostsController.cs", - "RelativeToolTip": "MyFirstBlog\\Controllers\\PostsController.cs", - "ViewState": "AQIAAAYAAAAAAAAAAAAAAAsAAAAAAAAA", - "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", - "WhenOpened": "2024-05-15T14:42:54.111Z" + "$type": "Bookmark", + "Name": "ST:0:0:{3ae79031-e1bc-11d0-8f78-00a0c9110057}" } ] } diff --git a/.vs/ProjectEvaluation/myfirstblog.metadata.v7.bin b/.vs/ProjectEvaluation/myfirstblog.metadata.v7.bin index a3a97897..253300d6 100644 Binary files a/.vs/ProjectEvaluation/myfirstblog.metadata.v7.bin and b/.vs/ProjectEvaluation/myfirstblog.metadata.v7.bin differ diff --git a/.vs/ProjectEvaluation/myfirstblog.projects.v7.bin b/.vs/ProjectEvaluation/myfirstblog.projects.v7.bin index cfc3e6a4..3259dd53 100644 Binary files a/.vs/ProjectEvaluation/myfirstblog.projects.v7.bin and b/.vs/ProjectEvaluation/myfirstblog.projects.v7.bin differ diff --git a/MyFirstBlog.sln b/MyFirstBlog.sln index e2ffab4c..74b032c4 100644 --- a/MyFirstBlog.sln +++ b/MyFirstBlog.sln @@ -1,8 +1,16 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyFirstBlog", "MyFirstBlog\MyFirstBlog.csproj", "{BB64A02C-C5D9-4D64-A357-7322C54B1419}" +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34723.18 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyFirstBlog", "MyFirstBlog\MyFirstBlog.csproj", "{BB64A02C-C5D9-4D64-A357-7322C54B1419}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyFirstBlogTests", "MyFirstBlogTests\MyFirstBlogTests.csproj", "{84D99732-A7E9-4E69-B5C7-7DFAFE9815B7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyFirstBlogTests", "MyFirstBlogTests\MyFirstBlogTests.csproj", "{84D99732-A7E9-4E69-B5C7-7DFAFE9815B7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9B011752-04F1-43B9-91DC-AD6788400F60}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -19,4 +27,7 @@ Global {84D99732-A7E9-4E69-B5C7-7DFAFE9815B7}.Release|Any CPU.ActiveCfg = Release|Any CPU {84D99732-A7E9-4E69-B5C7-7DFAFE9815B7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection EndGlobal diff --git a/MyFirstBlog/Controllers/PostsController.cs b/MyFirstBlog/Controllers/PostsController.cs index 8fa6bf2c..e9d9a58c 100644 --- a/MyFirstBlog/Controllers/PostsController.cs +++ b/MyFirstBlog/Controllers/PostsController.cs @@ -1,34 +1,92 @@ -namespace MyFirstBlog.Controllers; +namespace MyFirstBlog.Controllers +{ + using Microsoft.AspNetCore.Mvc; + using MyFirstBlog.Dtos; + using MyFirstBlog.Services; + using MyFirstBlog.Entities; + using System.Collections.Generic; + using System.Threading.Tasks; + using System.Text.RegularExpressions; -using Microsoft.AspNetCore.Mvc; -using MyFirstBlog.Dtos; -using MyFirstBlog.Services; + [ApiController] + [Route("posts")] + public class PostsController : ControllerBase + { + private readonly IPostService _postService; -[ApiController] -[Route("posts")] + public PostsController(IPostService postService) + { + _postService = postService; + } -public class PostsController : ControllerBase { - private IPostService _postService; + // GET /posts + [HttpGet] + public IEnumerable GetPosts() + { + return _postService.GetPosts(); + } - public PostsController(IPostService postService) { - _postService = postService; - } + // GET /posts/{slug} + [HttpGet("{slug}")] + public ActionResult GetPost(string slug) + { + var post = _postService.GetPost(slug); - // Get /posts - [HttpGet] - public IEnumerable GetPosts() { - return _postService.GetPosts(); - } + if (post is null) + { + return NotFound(); + } + + return post; + } + + // POST /posts + [HttpPost] + public async Task CreatePost([FromBody] PostDto newPost) + { + // Validate the incoming data + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + // Generate a unique slug based on the title (assuming slug should be unique and URL-friendly) + var slug = GenerateSlug(newPost.Title); - // Get /posts/:slug - [HttpGet("{slug}")] - public ActionResult GetPost(string slug) { - var post = _postService.GetPost(slug); + // Create a new Post entity from the DTO + var post = new Post + { + Id = Guid.NewGuid(), // Generate a new Guid for the post + Title = newPost.Title, + Slug = slug, // Generate a slug for the post + Body = newPost.Body, // Assuming 'Description' in PostDto corresponds to 'Body' in Post entity + CreatedDate = DateTime.UtcNow // Set the current date and time as the creation date + }; - if (post is null) { - return NotFound(); + // Use the service to save the new post + var createdPost = await _postService.CreatePostAsync(post); + + // Return the result with a 201 Created status code + return CreatedAtAction(nameof(GetPost), new { slug = createdPost.Slug }, createdPost); + } + + private string GenerateSlug(string title) + { + // Convert to lower case + string slug = title.ToLower(); + + // Replace spaces with hyphens + slug = slug.Replace(" ", "-"); + + // Remove invalid characters + slug = Regex.Replace(slug, @"[^a-z0-9\-]", ""); + + // Trim hyphens from the ends + slug = slug.Trim('-'); + + return slug; } - return post; + } } diff --git a/MyFirstBlog/Helpers/ConnectionHelper.cs b/MyFirstBlog/Helpers/ConnectionHelper.cs index 5e6671a6..7f239626 100644 --- a/MyFirstBlog/Helpers/ConnectionHelper.cs +++ b/MyFirstBlog/Helpers/ConnectionHelper.cs @@ -1,31 +1,12 @@ -namespace MyFirstBlog.Helpers; +using System.Data.SqlClient; -using Npgsql; - -public static class ConnectionHelper +namespace MyFirstBlog.Helpers { - public static string GetConnectionString(IConfiguration configuration) + public static class ConnectionHelper { - var connectionString = configuration.GetConnectionString("DefaultConnection"); - var databaseUrl = Environment.GetEnvironmentVariable("DATABASE_URL"); - return string.IsNullOrEmpty(databaseUrl) ? connectionString : BuildConnectionString(databaseUrl); - } - - //build the connection string from the environment. i.e. Heroku or Railway - private static string BuildConnectionString(string databaseUrl) - { - var databaseUri = new Uri(databaseUrl); - var userInfo = databaseUri.UserInfo.Split(':'); - var builder = new NpgsqlConnectionStringBuilder + public static string GetConnectionString(IConfiguration configuration) { - Host = databaseUri.Host, - Port = databaseUri.Port, - Username = userInfo[0], - Password = userInfo[1], - Database = databaseUri.LocalPath.TrimStart('/'), - SslMode = SslMode.Require, - TrustServerCertificate = true - }; - return builder.ToString(); - } -} \ No newline at end of file + return configuration.GetConnectionString("DefaultConnection"); + } + } +} diff --git a/MyFirstBlog/Helpers/DataContext.cs b/MyFirstBlog/Helpers/DataContext.cs index 60ad2bfc..7e4b29c8 100644 --- a/MyFirstBlog/Helpers/DataContext.cs +++ b/MyFirstBlog/Helpers/DataContext.cs @@ -1,22 +1,22 @@ -namespace MyFirstBlog.Helpers; - using Microsoft.EntityFrameworkCore; using MyFirstBlog.Entities; - -public class DataContext : DbContext +namespace MyFirstBlog.Helpers { - protected readonly IConfiguration Configuration; - - public DataContext(IConfiguration configuration) + public class DataContext : DbContext { - Configuration = configuration; - } + protected readonly IConfiguration Configuration; - protected override void OnConfiguring(DbContextOptionsBuilder options) - { - options.UseNpgsql(ConnectionHelper.GetConnectionString(Configuration)); - } + public DataContext(IConfiguration configuration) + { + Configuration = configuration; + } - public DbSet Posts { get; set; } -} \ No newline at end of file + protected override void OnConfiguring(DbContextOptionsBuilder options) + { + options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")); + } + + public DbSet Posts { get; set; } + } +} diff --git a/MyFirstBlog/Migrations/20221128224120_InitialCreate.Designer.cs b/MyFirstBlog/Migrations/20240811053028_InitialCreate.Designer.cs similarity index 60% rename from MyFirstBlog/Migrations/20221128224120_InitialCreate.Designer.cs rename to MyFirstBlog/Migrations/20240811053028_InitialCreate.Designer.cs index a8d15762..d0c1f224 100644 --- a/MyFirstBlog/Migrations/20221128224120_InitialCreate.Designer.cs +++ b/MyFirstBlog/Migrations/20240811053028_InitialCreate.Designer.cs @@ -1,17 +1,18 @@ // +using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using MyFirstBlog.Helpers; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable namespace MyFirstBlog.Migrations { [DbContext(typeof(DataContext))] - [Migration("20221128224120_InitialCreate")] + [Migration("20240811053028_InitialCreate")] partial class InitialCreate { /// @@ -19,28 +20,28 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "7.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 63); + .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("Relational:MaxIdentifierLength", 128); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - modelBuilder.Entity("MyFirstBlog.Models.Post", b => + modelBuilder.Entity("MyFirstBlog.Entities.Post", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("uuid"); + .HasColumnType("uniqueidentifier"); b.Property("Body") - .HasColumnType("text"); + .HasColumnType("nvarchar(max)"); - b.Property("CreatedDate") - .HasColumnType("timestamp with time zone"); + b.Property("CreatedDate") + .HasColumnType("datetime2"); b.Property("Slug") - .HasColumnType("text"); + .HasColumnType("nvarchar(max)"); b.Property("Title") - .HasColumnType("text"); + .HasColumnType("nvarchar(max)"); b.HasKey("Id"); diff --git a/MyFirstBlog/Migrations/20221128224120_InitialCreate.cs b/MyFirstBlog/Migrations/20240811053028_InitialCreate.cs similarity index 59% rename from MyFirstBlog/Migrations/20221128224120_InitialCreate.cs rename to MyFirstBlog/Migrations/20240811053028_InitialCreate.cs index 87caf0f2..134ab0bc 100644 --- a/MyFirstBlog/Migrations/20221128224120_InitialCreate.cs +++ b/MyFirstBlog/Migrations/20240811053028_InitialCreate.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore.Migrations; +using System; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable @@ -14,11 +15,11 @@ protected override void Up(MigrationBuilder migrationBuilder) name: "Posts", columns: table => new { - Id = table.Column(type: "uuid", nullable: false), - Title = table.Column(type: "text", nullable: true), - Slug = table.Column(type: "text", nullable: true), - Body = table.Column(type: "text", nullable: true), - CreatedDate = table.Column(type: "timestamp with time zone", nullable: false) + Id = table.Column(type: "uniqueidentifier", nullable: false), + Title = table.Column(type: "nvarchar(max)", nullable: true), + Slug = table.Column(type: "nvarchar(max)", nullable: true), + Body = table.Column(type: "nvarchar(max)", nullable: true), + CreatedDate = table.Column(type: "datetime2", nullable: false) }, constraints: table => { diff --git a/MyFirstBlog/Migrations/DataContextModelSnapshot.cs b/MyFirstBlog/Migrations/DataContextModelSnapshot.cs index 2965cd9a..8f3f81f7 100644 --- a/MyFirstBlog/Migrations/DataContextModelSnapshot.cs +++ b/MyFirstBlog/Migrations/DataContextModelSnapshot.cs @@ -1,9 +1,10 @@ // +using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using MyFirstBlog.Helpers; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; #nullable disable @@ -16,28 +17,28 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "7.0.0") - .HasAnnotation("Relational:MaxIdentifierLength", 63); + .HasAnnotation("ProductVersion", "7.0.20") + .HasAnnotation("Relational:MaxIdentifierLength", 128); - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - modelBuilder.Entity("MyFirstBlog.Models.Post", b => + modelBuilder.Entity("MyFirstBlog.Entities.Post", b => { b.Property("Id") .ValueGeneratedOnAdd() - .HasColumnType("uuid"); + .HasColumnType("uniqueidentifier"); b.Property("Body") - .HasColumnType("text"); + .HasColumnType("nvarchar(max)"); - b.Property("CreatedDate") - .HasColumnType("timestamp with time zone"); + b.Property("CreatedDate") + .HasColumnType("datetime2"); b.Property("Slug") - .HasColumnType("text"); + .HasColumnType("nvarchar(max)"); b.Property("Title") - .HasColumnType("text"); + .HasColumnType("nvarchar(max)"); b.HasKey("Id"); diff --git a/MyFirstBlog/MyFirstBlog.csproj b/MyFirstBlog/MyFirstBlog.csproj index 04ccc9c1..be82821d 100644 --- a/MyFirstBlog/MyFirstBlog.csproj +++ b/MyFirstBlog/MyFirstBlog.csproj @@ -14,8 +14,17 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/MyFirstBlog/Program.cs b/MyFirstBlog/Program.cs index 4042b610..4e614bd7 100644 --- a/MyFirstBlog/Program.cs +++ b/MyFirstBlog/Program.cs @@ -1,5 +1,6 @@ using MyFirstBlog.Helpers; using MyFirstBlog.Services; +using Microsoft.EntityFrameworkCore; var MyAllowLocalhostOrigins = "_myAllowLocalhostOrigins"; @@ -10,7 +11,10 @@ // Add services to the container. -services.AddDbContext(); +services.AddDbContext(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); + + services.AddCors(policyBuilder => { policyBuilder.AddPolicy( MyAllowLocalhostOrigins, diff --git a/MyFirstBlog/Services/PostService.cs b/MyFirstBlog/Services/PostService.cs index 6bac099f..84761687 100644 --- a/MyFirstBlog/Services/PostService.cs +++ b/MyFirstBlog/Services/PostService.cs @@ -2,18 +2,21 @@ namespace MyFirstBlog.Services; using MyFirstBlog.Helpers; using MyFirstBlog.Entities; -using System.Text.RegularExpressions; using MyFirstBlog.Dtos; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; public interface IPostService { IEnumerable GetPosts(); - PostDto GetPost(String slug); + PostDto GetPost(string slug); + Task CreatePostAsync(Post post); // Add this method } public class PostService : IPostService { - private DataContext _context; + private readonly DataContext _context; public PostService(DataContext context) { @@ -27,11 +30,23 @@ public IEnumerable GetPosts() public PostDto GetPost(string slug) { - return getPost(slug).AsDto(); + return getPost(slug)?.AsDto(); + } + + public async Task CreatePostAsync(Post post) + { + // Add the post to the database context + _context.Posts.Add(post); + + // Save changes asynchronously + await _context.SaveChangesAsync(); + + // Return the created post as a DTO + return post.AsDto(); } private Post getPost(string slug) { - return _context.Posts.Where(a=>a.Slug==slug.ToString()).SingleOrDefault(); + return _context.Posts.SingleOrDefault(a => a.Slug == slug); } } diff --git a/MyFirstBlog/appsettings.Development.json b/MyFirstBlog/appsettings.Development.json index 68bd7dc3..2999a268 100644 --- a/MyFirstBlog/appsettings.Development.json +++ b/MyFirstBlog/appsettings.Development.json @@ -1,11 +1,12 @@ { "ConnectionStrings": { - "DefaultConnection": "Host=localhost; Database=bvc-blog; Username=postgres; Password=postgres" + "DefaultConnection": "Server=LAPTOP-F429VMFA\\SQLEXPRESS;Database=YourDatabaseName;Trusted_Connection=True;TrustServerCertificate=True;" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } - } + }, + "AllowedHosts": "*" } diff --git a/MyFirstBlog/appsettings.json b/MyFirstBlog/appsettings.json index 10f68b8c..2999a268 100644 --- a/MyFirstBlog/appsettings.json +++ b/MyFirstBlog/appsettings.json @@ -1,4 +1,7 @@ { + "ConnectionStrings": { + "DefaultConnection": "Server=LAPTOP-F429VMFA\\SQLEXPRESS;Database=YourDatabaseName;Trusted_Connection=True;TrustServerCertificate=True;" + }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/MyFirstBlogTests/MyFirstBlogTests.csproj b/MyFirstBlogTests/MyFirstBlogTests.csproj index 79ed3371..aecee306 100644 --- a/MyFirstBlogTests/MyFirstBlogTests.csproj +++ b/MyFirstBlogTests/MyFirstBlogTests.csproj @@ -1,16 +1,26 @@ - net5.0 + net7.0 false - - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/MyFirstBlogTests/UnitTest1.cs b/MyFirstBlogTests/UnitTest1.cs index 921a6668..3a0ccf3a 100644 --- a/MyFirstBlogTests/UnitTest1.cs +++ b/MyFirstBlogTests/UnitTest1.cs @@ -1,18 +1,76 @@ using NUnit.Framework; +using Moq; +using MyFirstBlog.Controllers; +using MyFirstBlog.Dtos; +using MyFirstBlog.Services; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using MyFirstBlog.Entities; -namespace MyFirstBlogTests +[TestFixture] +public class PostsControllerTests { - public class Tests + private Mock mockPostService; + private PostsController controller; + + [SetUp] + public void Setup() + { + // Initialize the mock service + mockPostService = new Mock(); + + // Setup the mock to return a PostDto when CreatePostAsync is called + mockPostService.Setup(service => service.CreatePostAsync(It.IsAny())) + .ReturnsAsync((Post post) => new PostDto + { + Id = post.Id, + Title = post.Title, + Slug = post.Slug, + Body = post.Body, + CreatedDate = post.CreatedDate + }); + + // Pass the mock object to the controller + controller = new PostsController(mockPostService.Object); + } + + [Test] + public async Task CreatePost_Returns201Created_WhenPostIsValid() + { + // Arrange + var newPostDto = new PostDto { Title = "Valid Title", Body = "Some content" }; + + // Act + var result = await controller.CreatePost(newPostDto); + + // Assert + Assert.IsInstanceOf(result); + var createdAtActionResult = (CreatedAtActionResult)result; + Assert.IsInstanceOf(createdAtActionResult.Value); + var returnValue = (PostDto)createdAtActionResult.Value; + Assert.AreEqual("Valid Title", returnValue.Title); + } + + [Test] + public async Task CreatePost_Returns400BadRequest_WhenTitleIsBlank() { - [SetUp] - public void Setup() - { - } - - [Test] - public void Test1() - { - Assert.Pass(); - } + // Arrange + var newPostDto = new PostDto { Title = "", Body = "Some content" }; + controller.ModelState.AddModelError("Title", "Title cannot be blank"); + + // Act + var result = await controller.CreatePost(newPostDto); + + // Assert + Assert.IsInstanceOf(result); + var badRequestResult = (BadRequestObjectResult)result; + var serializableError = badRequestResult.Value as SerializableError; + + // Get the error messages for the "Title" key + var titleErrors = serializableError["Title"] as string[]; + + // Assert that the first error message contains the expected text + Assert.That(titleErrors[0], Does.Contain("Title cannot be blank")); } + }