diff --git a/README.md b/README.md index e5bb59e..841bc0b 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,9 @@ - [πŸ“ Table of Contents](#-table-of-contents) - [🧐 About ](#-about-) +- [πŸ“‘ Documentation ](#-documentation-) - [🏁 Getting Started ](#-getting-started-) - [πŸ•ΈοΈ Prerequisites](#️-prerequisites) -- [🎈 Usage ](#-usage-) - [πŸš€ Deployment ](#-deployment-) - [⛏️ Built Using ](#️-built-using-) - [✍️ Authors ](#️-authors-) @@ -39,16 +39,21 @@ I really need to get my hands dirty from time to time, so I figured I'd make a g It includes many concepts that are very reasonable to tinker with as learning material, in a bite-sized project complexity that allows me to talk about it without losing the breadcrumb trail. +## πŸ“‘ Documentation + +[Comments and general documentation/musings on the project](docs/comments.md) + ## 🏁 Getting Started These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See [deployment](#deployment) for notes on how to deploy the project on a live system. ## πŸ•ΈοΈ Prerequisites +For running it locally: - .NET 8.0 - A running instance of MongoDB - A Cloudflare turnstile secret key for the captcha -- Not forget to set up environment variables +- Not forgetting to set up environment variables You will be able to see in `build/docker-compose.public.yml` that the application makes heavy usage of them. ``` @@ -65,19 +70,19 @@ You will be able to see in `build/docker-compose.public.yml` that the applicatio - LOG_LEVEL=Debug ``` -You will need to set them up either by hand or by using your IDE's capabilities. On Visual Studio, that can be done via the Debug Properties of Guestbooky.API. +> [!IMPORTANT] + You will need to set them up either by hand or by using your IDE's capabilities. On Visual Studio, that can be done via the Debug Properties of Guestbooky.API. -**CORS_ORIGINS**, **ACCESS_\*** -> variables related to JWT issuing and checking. In order to use the GET and DELETE endpoints for the messages, you need to use a bearer token. +|Env Variable Keys|Usage| +|----|----| +|**CORS_ORIGINS**, **ACCESS_\***|Variables related to JWT issuing and checking. In order to use the GET and DELETE endpoints for the messages, you need to use a bearer token.| +|**CLOUDFLARE_SECRET**|The turnstile secret, used in the server portion of the captcha check.| +|**MONGODB_\***|Related to the connection to MongoDB. Yeah.| +|**LOG_\***|Logging.| -**CLOUDFLARE_SECRET** -> The turnstile secret, used in the server portion of the captcha check. -**MONGODB_\*** -> Related to the connection to MongoDB. Yeah. - -**LOG_\*** -> Logging. - -## 🎈 Usage - -For local usage of the backend, you can use `docker-compose.local.yml` and edit the fields you need. +> [!TIP] +> For local usage of the backend, you can use `docker-compose.local.yml` and edit the fields you need. ## πŸš€ Deployment diff --git a/build/docker-compose.public.yml b/build/docker-compose.public.yml index 0c75725..225e0eb 100644 --- a/build/docker-compose.public.yml +++ b/build/docker-compose.public.yml @@ -4,6 +4,7 @@ services: build: context: ../ dockerfile: build/guestbooky-be/Dockerfile + container_name: guestbooky-be ports: - "8080:8080" environment: @@ -20,6 +21,7 @@ services: - LOG_LEVEL=Debug depends_on: - mongo + restart: unless-stopped mongo: image: mongo diff --git a/docs/comments.md b/docs/comments.md new file mode 100644 index 0000000..0f5e1b3 --- /dev/null +++ b/docs/comments.md @@ -0,0 +1,45 @@ + +# General Comments + +Oh, hello. In this document, I'll write down some impressions and musings from the development of this project. + +## Concept + +![Diagram](guestbooky.diagram.png) + +`Guestbooky` works by exposing just a port belonging to the backend. Optionally, `mongo-express` can be built and exposed as well in order to allow direct control of the data. + +Unlike most guestbook systems made since the 90s, `Guestbooky` allows sending messages but only the owner is capable of seeing them. Its credentials are set up via the `docker-compose` file itself, since it's meant to be run in a simple-ish way. + +In theory, this model is suitable for people receiving messages that pertains much more to them than the general public, such as engagements, weddings and the such. It can be a way to gather these and reuse them in another context, like a video during the ceremony and whatnot. + +Clearly, I built this thinking of my own circumstances at the moment. I'd love to see it being useful for someone else too, either as the application itself or learning material. + +___ + +## Architectural musings + +- I have attempted to keep this as close to something *domain-driven* would. The main issue though is that the domain itself here is rather anemic. There isn't much to do outside of **auth & CRUD**. It's not even CRUD - *no updates*! + +- It uses *Clean Architecture* - or at least my current best understanding of it at the moment. + - The domain layer turned out to contain only interfaces to things to be implemented in the Infrastructure Layer. + - Being an infrastructure-heavy API, the application layer didn't have much to do besides some Application-level validation. + - I decided to lean heavily on *DTOs* between layers for the requests and responses. They feel a bit too much for this at times, but it is still enjoyable to have very defined contracts. + +- It uses `Mediatr`, which simplifies the calls to the application layer immensely - once the necessary scaffolding is implemented. + +- Technically one can say Queries and Commands are not touching each other, but I'm not sure on the idea of it being *CQRS* if there isn't a clear use of it - such as actually different databases handling each further down the pipeline. + +- The more I worked on it, the more I noticed how *Vertical Slice architecture* would be a much better way to work in it. It seems to be very, very good at handling situations like APIs that performed well-defined, concise operations. The way I worked on this project was by building each functionality from one side to the other, filling the piping gaps inbetween. It would have been much easier with vertical slicing! Except handling the common structural code. It is a little bit of an *L-shaped architecture* specially at the beginning. + +- It uses a pretty sub-optimal way to handle *Refresh Tokens*. I have added it because I was curious, but it is not what one should go for in production code. + +- `GET /messages` is a pretty fun endpoint. I went digging for good ways to be *RESTful*, and abide to *RFCs* where I could. Turns out there isn't too much of a consensus in practice, even though there's a lot of nodding approving certain practices. + - It was pretty tricky to reconcile using a *DTO* abstraction as input with metadata in the request header, **AND** provide some kind of feedback that it is needed in *Swagger*, **AND** have decent model validation. It works, but I could work some more on this later. + - I think the general recommended mechanism (*the* `range` *header*) is neat, but it is rather cumbersome, and I wouldn't be much against not using it in custom internal projects. + +- The application layer ended up being a showcase for having some custom *Mediatr* behavior. After it was implemented, it made more sense, but I really think a class like my implementation of `ValidationBehavior` needs to be a default. Unless it is and I really didn't see it. + +- There isn't much exception handling, except in the API layer. This is on purpose. Another thing that this project could really use, but is left as an exercise, is using a `Maybe/Result/ErrorOr` type. + - Since there is so little that can go wrong with *low-stakes CRUDding*, it is a reasonable trade-off to let the API layer catch and send an internal server error. + diff --git a/docs/guestbooky.diagram.png b/docs/guestbooky.diagram.png new file mode 100644 index 0000000..87f7fdd Binary files /dev/null and b/docs/guestbooky.diagram.png differ diff --git a/src/Guestbooky/Guestbooky.API/DTOs/Messages/GetMessages.cs b/src/Guestbooky/Guestbooky.API/DTOs/Messages/GetMessages.cs index b04a072..a522a65 100644 --- a/src/Guestbooky/Guestbooky.API/DTOs/Messages/GetMessages.cs +++ b/src/Guestbooky/Guestbooky.API/DTOs/Messages/GetMessages.cs @@ -1,12 +1,10 @@ ο»Ώusing Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; -using System.ComponentModel.DataAnnotations; using System.Net.Http.Headers; -using System.Text.Json.Serialization; namespace Guestbooky.API.DTOs.Messages; -[SwaggerSchema("asd", Format = "string", Description = "Optional Range header (e.g., 'messages=1-50')")] +[SwaggerSchema("Range", Format = "string", Description = "Optional Range header (e.g., \"messages=1-50\")")] [SwaggerSubType(typeof(string))] public record GetMessagesRequestDto { diff --git a/tests/Guestbooky.UnitTests/Application/UseCases/AuthenticateUserCommandTests.cs b/tests/Guestbooky.UnitTests/Application/UseCases/AuthenticateUserCommandTests.cs index 2465a6c..cfc627e 100644 --- a/tests/Guestbooky.UnitTests/Application/UseCases/AuthenticateUserCommandTests.cs +++ b/tests/Guestbooky.UnitTests/Application/UseCases/AuthenticateUserCommandTests.cs @@ -3,11 +3,6 @@ using Guestbooky.Application.UseCases.AuthenticateUser; using Guestbooky.Domain.Abstractions.Infrastructure; using Guestbooky.Domain.Entities.User; using Moq; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Guestbooky.UnitTests.Application.UseCases;