diff --git a/.gitignore b/.gitignore index 3a868930..5cf459c5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ riderModule.iml /_ReSharper.Caches/ .fake .vscode +.vs/ diff --git a/.vs/MyFirstBlog/DesignTimeBuild/.dtbcache.v2 b/.vs/MyFirstBlog/DesignTimeBuild/.dtbcache.v2 deleted file mode 100644 index e3a42a61..00000000 Binary files a/.vs/MyFirstBlog/DesignTimeBuild/.dtbcache.v2 and /dev/null 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/config/applicationhost.config b/.vs/MyFirstBlog/config/applicationhost.config deleted file mode 100644 index 0d88f0db..00000000 --- a/.vs/MyFirstBlog/config/applicationhost.config +++ /dev/null @@ -1,1016 +0,0 @@ - - - - - - - -
-
-
-
-
-
-
-
- - - -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
- -
-
-
-
-
-
- -
-
-
-
-
- -
-
-
- -
-
- -
-
- -
-
-
- - -
-
-
-
-
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.vs/MyFirstBlog/v17/.futdcache.v2 b/.vs/MyFirstBlog/v17/.futdcache.v2 deleted file mode 100644 index 128982d8..00000000 Binary files a/.vs/MyFirstBlog/v17/.futdcache.v2 and /dev/null differ diff --git a/.vs/MyFirstBlog/v17/.suo b/.vs/MyFirstBlog/v17/.suo deleted file mode 100644 index 74e1a08b..00000000 Binary files a/.vs/MyFirstBlog/v17/.suo and /dev/null differ diff --git a/.vs/MyFirstBlog/v17/DocumentLayout.json b/.vs/MyFirstBlog/v17/DocumentLayout.json deleted file mode 100644 index 653eb6b4..00000000 --- a/.vs/MyFirstBlog/v17/DocumentLayout.json +++ /dev/null @@ -1,149 +0,0 @@ -{ - "Version": 1, - "WorkspaceRootPath": "C:\\00code\\FullStack\\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}" - } - ], - "DocumentGroupContainers": [ - { - "Orientation": 0, - "VerticalTabListWidth": 256, - "DocumentGroups": [ - { - "DockedWidth": 200, - "SelectedChildIndex": 6, - "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": "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" - }, - { - "$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": "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": "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" - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/.vs/ProjectEvaluation/myfirstblog.metadata.v7.bin b/.vs/ProjectEvaluation/myfirstblog.metadata.v7.bin deleted file mode 100644 index a3a97897..00000000 Binary files a/.vs/ProjectEvaluation/myfirstblog.metadata.v7.bin and /dev/null differ diff --git a/.vs/ProjectEvaluation/myfirstblog.projects.v7.bin b/.vs/ProjectEvaluation/myfirstblog.projects.v7.bin deleted file mode 100644 index cfc3e6a4..00000000 Binary files a/.vs/ProjectEvaluation/myfirstblog.projects.v7.bin and /dev/null differ diff --git a/MyFirstBlog/Controllers/PostsController.cs b/MyFirstBlog/Controllers/PostsController.cs index 8fa6bf2c..59412de5 100644 --- a/MyFirstBlog/Controllers/PostsController.cs +++ b/MyFirstBlog/Controllers/PostsController.cs @@ -1,34 +1,66 @@ -namespace MyFirstBlog.Controllers; - using Microsoft.AspNetCore.Mvc; using MyFirstBlog.Dtos; using MyFirstBlog.Services; +using System.Collections.Generic; -[ApiController] -[Route("posts")] +namespace MyFirstBlog.Controllers +{ + [ApiController] + [Route("posts")] + public class PostsController : ControllerBase + { + private readonly IPostService _postService; -public class PostsController : ControllerBase { - private IPostService _postService; + public PostsController(IPostService postService) + { + _postService = postService; + } - public PostsController(IPostService postService) { - _postService = postService; - } + // GET /posts + [HttpGet] + public IEnumerable GetPosts() + { + return _postService.GetPosts(); + } - // Get /posts - [HttpGet] - public IEnumerable GetPosts() { - return _postService.GetPosts(); - } + // GET /posts/{slug} + + [HttpGet("{slug}")] + public ActionResult GetPost(string slug) + { + var post = _postService.GetPost(slug); + if (post == null) + { + return NotFound(); + } + return Ok(post); + } + + + + [HttpPost] + public IActionResult CreatePost([FromBody] PostCreateRequest post) + { + var errors = new List(); + + if (string.IsNullOrWhiteSpace(post.Title)) + errors.Add("Title cannot be blank"); + + if (string.IsNullOrWhiteSpace(post.Description)) + errors.Add("You cannot send a message with empty description"); + + if (errors.Any()) + return BadRequest(new { errors }); - // Get /posts/:slug - [HttpGet("{slug}")] - public ActionResult GetPost(string slug) { - var post = _postService.GetPost(slug); + var newPost = _postService.CreatePost(post); - if (post is null) { - return NotFound(); + return CreatedAtAction(nameof(GetPost), new { slug = newPost.Slug }, new + { + title = newPost.Title, + description = newPost.Description + }); + //return CreatedAtAction(nameof(GetPost), new { slug = newPost.Slug }, newPost); } - return post; } } diff --git a/MyFirstBlog/Dtos/PostCreateRequest.cs b/MyFirstBlog/Dtos/PostCreateRequest.cs new file mode 100644 index 00000000..e3a3b8b5 --- /dev/null +++ b/MyFirstBlog/Dtos/PostCreateRequest.cs @@ -0,0 +1,8 @@ +namespace MyFirstBlog.Dtos +{ + public class PostCreateRequest + { + public string Title { get; set; } + public string Description { get; set; } + } +} diff --git a/MyFirstBlog/Dtos/PostDto.cs b/MyFirstBlog/Dtos/PostDto.cs index 12ca4818..a3ea024f 100644 --- a/MyFirstBlog/Dtos/PostDto.cs +++ b/MyFirstBlog/Dtos/PostDto.cs @@ -5,5 +5,6 @@ public record PostDto { public string Title { get; init; } = default!; public string Slug { get; init; } = default!; public string Body { get; init; } = default!; + public string Description { get; set; } = default!; public DateTime CreatedDate { get; init; } } diff --git a/MyFirstBlog/Entities/Post.cs b/MyFirstBlog/Entities/Post.cs index 9347e859..3e1dca5a 100644 --- a/MyFirstBlog/Entities/Post.cs +++ b/MyFirstBlog/Entities/Post.cs @@ -4,5 +4,6 @@ public record Post { public string Title { get; init; } = default!; public string Slug { get; init; } = default!; public string Body { get; init; } = default!; + public string Description { get; set; } public DateTime CreatedDate { get; init; } } diff --git a/MyFirstBlog/Extensions.cs b/MyFirstBlog/Extensions.cs index a5326018..bd1d11da 100644 --- a/MyFirstBlog/Extensions.cs +++ b/MyFirstBlog/Extensions.cs @@ -10,6 +10,7 @@ public static PostDto AsDto(this Post post) { Title = post.Title, Slug = post.Slug, Body = post.Body, + Description = post.Description, CreatedDate = post.CreatedDate }; diff --git a/MyFirstBlog/Migrations/20250807001933_AddDescriptionToPost.Designer.cs b/MyFirstBlog/Migrations/20250807001933_AddDescriptionToPost.Designer.cs new file mode 100644 index 00000000..8bedcef9 --- /dev/null +++ b/MyFirstBlog/Migrations/20250807001933_AddDescriptionToPost.Designer.cs @@ -0,0 +1,56 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +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("20250807001933_AddDescriptionToPost")] + partial class AddDescriptionToPost + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("MyFirstBlog.Entities.Post", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Body") + .HasColumnType("text"); + + b.Property("CreatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Slug") + .HasColumnType("text"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Posts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/MyFirstBlog/Migrations/20250807001933_AddDescriptionToPost.cs b/MyFirstBlog/Migrations/20250807001933_AddDescriptionToPost.cs new file mode 100644 index 00000000..becd03e2 --- /dev/null +++ b/MyFirstBlog/Migrations/20250807001933_AddDescriptionToPost.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MyFirstBlog.Migrations +{ + /// + public partial class AddDescriptionToPost : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Description", + table: "Posts", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Description", + table: "Posts"); + } + } +} diff --git a/MyFirstBlog/Migrations/20250807042432_AddDescriptionToPosts.Designer.cs b/MyFirstBlog/Migrations/20250807042432_AddDescriptionToPosts.Designer.cs new file mode 100644 index 00000000..9bd2300a --- /dev/null +++ b/MyFirstBlog/Migrations/20250807042432_AddDescriptionToPosts.Designer.cs @@ -0,0 +1,56 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +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("20250807042432_AddDescriptionToPosts")] + partial class AddDescriptionToPosts + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("MyFirstBlog.Entities.Post", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Body") + .HasColumnType("text"); + + b.Property("CreatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Slug") + .HasColumnType("text"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Posts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/MyFirstBlog/Migrations/20250807042432_AddDescriptionToPosts.cs b/MyFirstBlog/Migrations/20250807042432_AddDescriptionToPosts.cs new file mode 100644 index 00000000..a2fb9035 --- /dev/null +++ b/MyFirstBlog/Migrations/20250807042432_AddDescriptionToPosts.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MyFirstBlog.Migrations +{ + /// + public partial class AddDescriptionToPosts : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/MyFirstBlog/Migrations/20250807050512_UpdatePostSchema.Designer.cs b/MyFirstBlog/Migrations/20250807050512_UpdatePostSchema.Designer.cs new file mode 100644 index 00000000..6b5b62e1 --- /dev/null +++ b/MyFirstBlog/Migrations/20250807050512_UpdatePostSchema.Designer.cs @@ -0,0 +1,56 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +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("20250807050512_UpdatePostSchema")] + partial class UpdatePostSchema + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("MyFirstBlog.Entities.Post", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Body") + .HasColumnType("text"); + + b.Property("CreatedDate") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasColumnType("text"); + + b.Property("Slug") + .HasColumnType("text"); + + b.Property("Title") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Posts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/MyFirstBlog/Migrations/20250807050512_UpdatePostSchema.cs b/MyFirstBlog/Migrations/20250807050512_UpdatePostSchema.cs new file mode 100644 index 00000000..4437f6d4 --- /dev/null +++ b/MyFirstBlog/Migrations/20250807050512_UpdatePostSchema.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace MyFirstBlog.Migrations +{ + /// + public partial class UpdatePostSchema : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/MyFirstBlog/Migrations/DataContextModelSnapshot.cs b/MyFirstBlog/Migrations/DataContextModelSnapshot.cs index 2965cd9a..679ec616 100644 --- a/MyFirstBlog/Migrations/DataContextModelSnapshot.cs +++ b/MyFirstBlog/Migrations/DataContextModelSnapshot.cs @@ -1,4 +1,5 @@ // +using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -21,7 +22,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - modelBuilder.Entity("MyFirstBlog.Models.Post", b => + modelBuilder.Entity("MyFirstBlog.Entities.Post", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -30,9 +31,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Body") .HasColumnType("text"); - b.Property("CreatedDate") + b.Property("CreatedDate") .HasColumnType("timestamp with time zone"); + b.Property("Description") + .HasColumnType("text"); + b.Property("Slug") .HasColumnType("text"); diff --git a/MyFirstBlog/Services/PostService.cs b/MyFirstBlog/Services/PostService.cs index 6bac099f..5d540adf 100644 --- a/MyFirstBlog/Services/PostService.cs +++ b/MyFirstBlog/Services/PostService.cs @@ -1,37 +1,53 @@ -namespace MyFirstBlog.Services; - -using MyFirstBlog.Helpers; +using MyFirstBlog.Dtos; using MyFirstBlog.Entities; +using MyFirstBlog.Helpers; +using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; -using MyFirstBlog.Dtos; public interface IPostService { IEnumerable GetPosts(); - PostDto GetPost(String slug); + PostDto GetPost(string slug); + PostDto CreatePost(PostCreateRequest post); } -public class PostService : IPostService -{ - private DataContext _context; - public PostService(DataContext context) +namespace MyFirstBlog.Services +{ + public class PostService : IPostService { - _context = context; - } + private readonly DataContext _context; - public IEnumerable GetPosts() - { - return _context.Posts.Select(post => post.AsDto()); - } + public PostService(DataContext context) + { + _context = context; + } - public PostDto GetPost(string slug) - { - return getPost(slug).AsDto(); - } + public IEnumerable GetPosts() + { + return _context.Posts.Select(post => post.AsDto()); + } - private Post getPost(string slug) - { - return _context.Posts.Where(a=>a.Slug==slug.ToString()).SingleOrDefault(); + public PostDto GetPost(string slug) + { + return _context.Posts.FirstOrDefault(p => p.Slug == slug)?.AsDto(); + } + + public PostDto CreatePost(PostCreateRequest post) + { + var entity = new Post + { + Title = post.Title, + Description = post.Description, + Slug = Regex.Replace(post.Title.ToLower(), @"\s+", "-"), + CreatedDate = DateTime.UtcNow + }; + + _context.Posts.Add(entity); + _context.SaveChanges(); // ✅ Save to DB + + return entity.AsDto(); + } } } diff --git a/MyFirstBlog/appsettings.Development.json b/MyFirstBlog/appsettings.Development.json index 68bd7dc3..5bf58494 100644 --- a/MyFirstBlog/appsettings.Development.json +++ b/MyFirstBlog/appsettings.Development.json @@ -1,6 +1,6 @@ { "ConnectionStrings": { - "DefaultConnection": "Host=localhost; Database=bvc-blog; Username=postgres; Password=postgres" + "DefaultConnection": "Host=localhost; Database=bvc-blog; Username=postgres; Password=admin123" }, "Logging": { "LogLevel": {