https://github.com/anticlown322/events-web-application
Modsen Test Task | .NET Trainee | Feb 2025
https://github.com/anticlown322/events-web-application
docker dotnet entity-framework mssql test-task
Last synced: 8 months ago
JSON representation
Modsen Test Task | .NET Trainee | Feb 2025
- Host: GitHub
- URL: https://github.com/anticlown322/events-web-application
- Owner: anticlown322
- Created: 2025-02-07T09:20:32.000Z (about 1 year ago)
- Default Branch: develop
- Last Pushed: 2025-02-21T11:41:30.000Z (12 months ago)
- Last Synced: 2025-02-21T12:31:46.442Z (12 months ago)
- Topics: docker, dotnet, entity-framework, mssql, test-task
- Language: C#
- Homepage: https://www.modsen-software.com/
- Size: 116 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Table of contents
- [Overview](#eyes-overview)
- [Startup instructions](#startup-instructions)
- [First steps](#first-steps)
- [Fixes (edits by 17.02.2025)](#fixes-edits-by-17022025)
- [Fixes (edits by 24.02.2025)](#fixes-edits-by-24022025)
# :eyes: Overview
This repository contains a web app for working with events and registering participants for these events.
The application can be launched by following the instructions below.
## Startup instructions
> [!NOTE]
> This method uses Docker, so make sure you have Docker and Docker Compose installed on your machine.
1. Clone repository:
```bash
git clone https://github.com/anticlown322/Events-Web-Application
```
2. Go to the folder with docker file
```bash
cd Events-Web-Application/backend
```
3. Run docker compose for building and starting containers
```bash
docker-compose up -d
```
Option `-d` Allows you to run containers in the background.
4. Once the containers have been successfully launched, the application will be available at:
```bash
http://localhost:8080/swagger/index.html
```
## First steps
Create an admin using `api\authenticate` POST request in Swagger. Example credentials:
```
{
"firstName": "string",
"lastName": "string",
"userName": "string",
"password": "123456qwerty",
"email": "string@gmail.com",
"phoneNumber": "+375336216209",
"roles": [
"Administrator"
]
}
```

Once admin has been successfully created, you can login by credentials, provided in example data:
```
{
"userName": "string",
"password": "123456qwerty"
}
```

In server response section you will see two tokens:
- `accessToken` for authorization. It is necessary for making requests.
- `refreshToken` for refreshing your accessToken, if it is about to expire.
Access token expires after 30 minutes from the moment of its creation.
Use `refreshToken` via `api/authentication/refresh` endpoint.

Authorize with `accessToken` using Swagger `Authorize` button.


Try to send some requests(i.e. create an event or get list of all events).
Check if `Authorization header` is correct. Its value must consist of `Bearer` and value of your `accessToken`. Then check server response value.

If you need to stop the containers, run the command:
```bash
docker-compose down
```
## Fixes (edits by 17.02.2025)
### *1. эндпоинт регистрации возвращает ошибку {"StatusCode":500,"Message":"42P01: relation \u0022AspNetUsers\u0022 does not exist\n\nPOSITION: 368"}*
Ошибка была связана с неверным процессом миграции. В репозиторий файл миграции не включался из-за
`.gitignore`, а при запуске новая миграция не создавалась и следующий код в `Program.cs` не мог произвести миграцию:
```csharp
using (var scope = app.Services.CreateScope())
{
var dbContext = scope.ServiceProvider
.GetRequiredService();
dbContext.Database.Migrate();
}
```
Теперь миграция включена в репозиторий, ошибка исправлена,
эндпоинт корректно отвечает на запрос.
### *2. не использовать дата аннотации в домене, вместо этого использовать fluent API*
Исправлено, теперь в моделях из Domain нет дата аннотаций, описание моделей происходит в файлах из
`Infrastructure.Repository.Config`. Например, `EventsConfig.cs`:
```csharp
public class EventsConfig : IEntityTypeConfiguration
{
public void Configure(EntityTypeBuilder builder)
{
builder.HasKey(e => e.Id);
builder.Property(e => e.Id)
.HasColumnName("EventId")
.ValueGeneratedOnAdd();
//... and so on
}
}
```
### *3. ErrorDetails вынести из домена*
Исправлено, `ErrorDetails` перенесено в `Infrastructure`.
### *4. публичные методы репозитория не должны возвращать IQueryable*
Исправлено, теперь методы возвращают `Task>`. Контракт репозитория исправлен на следующий:
```csharp
public interface IRepositoryBase
{
Task> FindAllAsync
(bool trackChanges, CancellationToken cancellationToken);
Task> FindByConditionAsync(Expression>
expression, bool trackChanges, CancellationToken cancellationToken);
void Create(T entity);
void Update(T entity);
void Delete(T entity);
}
```
### *5. доменный слой и слой бизнес логики не должны зависеть от infrastructure*
Исправлено, теперь:
- `Domain` не имеет зависимостей от других слоев.
- `Application` зависит только от `Domain`.
- `Infrastructure` зависит от `Application`.
- `Presentation` зависит от `Infrastructure`.
- `Tests` зависит от `Infrastructure`.
### *6. добавить cancellation token на всех уровнях*
Исправлено, теперь в repositories, use cases, controllers и services используются `Cancellation token`.
Контракт репозитория описан выше.
### *7. BadRequest, Unauthorized возвращать через middleware, контроллеры должны возвращать только 2\*\* статус коды*
Исправлено, теперь контроллеры возвращают только:
- `OK(200)`,
- `NoContent(204)`,
- `File(200)`,
- `CreatedAtRoute(201)`.
### *8. для чего разделение слоев архитектуры на несколько отдельных проектов?*
Сложно и неприятно исполнять правки, поставленные в виде вопроса и не имеющие четких указаний, но я попытался.
Теперь в проекте есть одно решение, которое состоит из 5 сборок:
- `Domain`
- `Application`
- `Infrastructure`
- `Presentation`
- `Tests`
### *9. разнести профили маппера по разным файлам*
Исправлено, теперь в `Application.DTO.MappingProfiles` 3 папки с набором профилей:
- `Event` (3 файла)
- `Participant` (2 файла)
- `User` (1 файл)
### *10. работа с изображением должна быть вынесена в отдельный сервис на слое infrastructure, бизнес логика получения изображения для события должна быть реализована в соответствующем юзкейсе*
Исправлено, теперь:
- `ImageService` находится в `Infrastructure`,
- для получения изображения создан `GetImageUseCase`,
- в контроллере `ImageController` используется `GetImageUseCase`.
### *11. генерация и валидация токенов должна проводиться в отдельном сервисе на слое infrastructure, бизнес логика связанная с токенами должна быть реализована в соответствующем юзкейсе*
Исправлено, теперь:
- генерация и валидация токенов происходит в `AuthenticationManager`,
- `AuthenticationManager` находится в `Infrastructure`,
- для токенов созданы `CreateTokenForAuthUseCase`, `RefreshTokenForAuthUseCase`, `RegisterUserUseCase`.
- В контроллерах `AuthenticationController` и `TokenController` используются use cases.
### *12. при создании пользователя нет проверки на уникальность username*
Исправлено, `RegisterUserUseCase` исправлен на следующий:
```csharp
public async Task ExecuteAsync(UserForRegistrationDto userForRegistration)
{
var existingUser = await userManager
.FindByNameAsync(userForRegistration.UserName);
if (existingUser != null)
{
throw new UserAlreadyExistsException(userForRegistration.UserName);
}
var user = mapper.Map(userForRegistration);
var result = await userManager.CreateAsync(user, userForRegistration.Password);
if (result.Succeeded)
await userManager.AddToRolesAsync(user, userForRegistration.Roles);
return result;
}
```
Теперь перед регистраций происходит поиск существующего пользователя с переданным `UserName`.
## Fixes (edits by 24.02.2025)
### *1. эндпоинт регистрации на событие возвращает ошибку {"StatusCode":500,"Message":"Object reference not set to an instance of an object."}*
Исправлено, теперь при `POST`-запросе приходит ответ с кодом `200`:

### *2. сервис работы с изображением должен отвечать только за запись/чтение изображения, в нем не должно быть обращений к репозиторию для получения события*
Исправлено, обращение к БД перенесено `ImageService` из в `GetImageUseCase`.
### *3. сервис работы с токеном не должен обращаться к базе данных, он должен отвечать только за генерацию и валидацию токенов*
Исправлено, обращение к БД перенесено из `AuthenticationManager` в `RefreshTokenForAuthUseCase`.
### *4. интерфейсы репозиториев должны находиться в домене*
Исправлено, `RepositoryContracts` перенесены в сборку `Domain`.
### *5. в методах репозитория не должно быть присваивания полей сущности (CreateParticipant)*
Исправлено, теперь в `ParticipantsRepository` не присваиваются значения полям:
```csharp
public void CreateParticipant(Guid eventId, Participant participant) => Create(participant);
```
Присвоение перенесено в `CreateParticipantUseCase`:
```csharp
participantEntity.EventId = eventId;
participantEntity.RegistrationTime = DateTime.UtcNow;
```
### *6. cancellation token не передается в методы ImageService*
Исправлено, теперь ImageService не принимает cancellationToken ни в одном из методов:
```csharp
public interface IImageService
{
Task<(byte[] fileBytes, string contentType, string filename)> GetImageAsync(string imageFileName);
Task WriteFileAsync(IFormFile image);
void DeleteFile(string fileName);
}
```
### *7. при регистрации на событие нет проверки, что пользователь с таким email еще не зарегистрирован на это событие*
Исправлено, теперь в `CreateParticipantUseCase` происходит следующая проверка перед регистрацией:
```csharp
var isUniqueEmail = await repository.Participant
.IsUniqueEmailAsync(participantForCreation.Email, cancellationToken);
if (!isUniqueEmail)
throw new EmailAlreadyRegisteredException(
eventId, participantForCreation.Email);
```
Метод реализован `IsUniqueEmailAsync` в `ParticipantsRepository` следующий образом:
```csharp
public async Task IsUniqueEmailAsync(string email, CancellationToken cancellationToken) =>
!await RepositoryContext.Participants
.AnyAsync(e => e.Email == email, cancellationToken);
```
### *8. миграции лучше перенести на слой Infrastructure*
Исправлено, теперь миграции находятся в сборке `Infrastructure`,
для процесса миграции был создан класс `RepositoryContextFactory`.