Compare commits

...

62 Commits

Author SHA1 Message Date
467d37fc4e Merge branch 'release/v1.0.0' into feat/application-command
All checks were successful
.NET Test Pipeline / build-and-test (pull_request) Successful in 1m25s
2024-01-26 20:49:41 +03:00
7a0a51f76a Configure ASP.NET for the API to work (#7)
Reviewed-on: #7
2024-01-26 20:41:00 +03:00
92504295f0 fix: add null ignoring
All checks were successful
.NET Test Pipeline / build-and-test (pull_request) Successful in 1m14s
2024-01-26 20:38:49 +03:00
c5c4a2a8da fix: add ignoring documentation warning 2024-01-26 20:38:13 +03:00
806c3eeb17 feat: add an initial DI container for working with Persistence
All checks were successful
.NET Test Pipeline / build-and-test (pull_request) Successful in 1m19s
2024-01-26 19:39:59 +03:00
3294325ad0 build: add the necessary packages 2024-01-26 19:39:01 +03:00
459d11dd6b feat: add a database creation class (initial) 2024-01-26 19:37:42 +03:00
2b02cfb616 feat: add and initialize work with the database 2024-01-26 19:36:18 +03:00
98ebe5ffdb feat: add configuration output to the console during debugging 2024-01-26 19:35:45 +03:00
7b8bff8e54 build: add dependencies to the project 2024-01-26 19:34:42 +03:00
f1ed45af96 feat: add API versioning 2024-01-26 19:33:25 +03:00
d5b3859e86 feat: add a CORS configuration 2024-01-26 19:32:10 +03:00
788103c8a5 fix: specify the current directory as a startup directory 2024-01-26 19:31:28 +03:00
e6cc9437d5 feat: add additional configuration from files 2024-01-26 19:30:41 +03:00
8f334ae5c2 feat: add reading from .env file 2024-01-26 19:29:08 +03:00
3c3bdc6155 feat: add the pre-needed server settings 2024-01-26 09:00:14 +03:00
e1c3165ad3 build: add a dependency on versioning
All checks were successful
.NET Test Pipeline / build-and-test (pull_request) Successful in 2m43s
2024-01-26 08:50:10 +03:00
e6c62eae09 feat: add swagger configuration
Some checks failed
.NET Test Pipeline / build-and-test (pull_request) Failing after 2m46s
2024-01-26 08:44:48 +03:00
cea8a14f8b refactor: remove unnecessary classes
All checks were successful
.NET Test Pipeline / build-and-test (pull_request) Successful in 2m47s
2024-01-26 07:58:05 +03:00
2c3e02b2de fix: delete a non-existent table
All checks were successful
.NET Test Pipeline / build-and-test (pull_request) Successful in 1m22s
2024-01-26 07:55:25 +03:00
84c6123caf Merge branch 'release/v1.0.0' into feat/application-command
Some checks failed
.NET Test Pipeline / build-and-test (pull_request) Failing after 1m15s
2024-01-26 07:48:27 +03:00
2d973eee3d Fix the sql schema (#6)
Reviewed-on: #6
2024-01-26 07:48:13 +03:00
66dc5e3e38 refactor: rewrite the configuration for new tables
All checks were successful
.NET Test Pipeline / build-and-test (pull_request) Successful in 1m21s
2024-01-26 07:46:23 +03:00
9493d4340f fix: do not delete lecture hall 2024-01-26 07:45:46 +03:00
194dd1b729 fix: data deletion 2024-01-26 07:44:25 +03:00
e7c05b4a68 refactor: fix database contexts 2024-01-26 07:43:27 +03:00
bd3a11486d refactor: correct new reference
Some checks failed
.NET Test Pipeline / build-and-test (pull_request) Failing after 1m12s
2024-01-26 07:39:47 +03:00
28e862c670 refactor: delete due to merging with lesson 2024-01-26 07:38:55 +03:00
9e4320f2d3 refactor: give the exact name 2024-01-26 07:38:26 +03:00
03ccec2119 refactor: create a discipline 2024-01-26 07:37:28 +03:00
96a120f017 refator: combine day and lesson 2024-01-26 07:36:13 +03:00
2bb7573ca0 feat: add a day command
All checks were successful
.NET Test Pipeline / build-and-test (pull_request) Successful in 1m24s
2024-01-25 17:53:22 +03:00
c82b29e90e feat: update exception 2024-01-25 17:53:00 +03:00
6fde0003e0 feat: add a campus command 2024-01-25 17:44:44 +03:00
4fe25005af feat: add a faculty command
All checks were successful
.NET Test Pipeline / build-and-test (pull_request) Successful in 1m19s
2024-01-25 17:39:07 +03:00
b9b34a29e8 feat: add a group command 2024-01-25 17:35:15 +03:00
31c214d955 refactor: move files 2024-01-21 23:55:47 +03:00
dbc8f7c68c feat: add a professor update
All checks were successful
.NET Test Pipeline / build-and-test (pull_request) Successful in 1m57s
2024-01-21 23:17:10 +03:00
654065f016 feat: add a professor create 2024-01-21 23:13:18 +03:00
9b0e5a3f15 feat: add a type of occupation update
All checks were successful
.NET Test Pipeline / build-and-test (pull_request) Successful in 2m41s
2024-01-21 22:53:52 +03:00
af7e370494 feat: add a new exception 2024-01-21 22:52:12 +03:00
83f85ebb7b style: update text exception 2024-01-21 22:51:20 +03:00
9aa74218aa style: rename classes 2024-01-21 22:31:10 +03:00
67e9b89379 feat: add a type of occupation create
All checks were successful
.NET Test Pipeline / build-and-test (pull_request) Successful in 2m35s
2024-01-16 12:52:35 +03:00
6f5a662e2f feat: add exception 2024-01-16 12:50:20 +03:00
def7111651 Add the required minimum for tests (#4)
Reviewed-on: #4
2024-01-08 23:30:15 +03:00
b4bbc413f2 ci: add test pipeline 2024-01-08 23:25:50 +03:00
6f5a84f645 Add a Persistence layer (#3)
Reviewed-on: #3
2024-01-08 23:22:35 +03:00
f9a04ee84a feat: add uber context for creating all context in one db 2024-01-08 23:21:04 +03:00
84214e38cc feat: add configuration files 2024-01-08 23:20:27 +03:00
40279ab2b8 feat: add a data context 2024-01-08 16:51:57 +03:00
cb55567519 feat: add project 2024-01-08 16:49:44 +03:00
8e628d17da Add an Application layer (#2)
Reviewed-on: #2
2024-01-08 16:13:09 +03:00
9e9d4e06fd feat: add mapping 2024-01-08 16:11:01 +03:00
cfbd847d9a feat: add DI 2024-01-08 15:04:34 +03:00
386272d493 feat: add validation behavior 2024-01-08 15:02:03 +03:00
924f97332f style: sort reference 2024-01-08 14:50:52 +03:00
a389eb0a70 feat: add an interface for working with db set 2024-01-08 14:50:21 +03:00
8028d40005 feat: add an interface for standard saving changes 2024-01-08 14:43:05 +03:00
f7998a1798 feat: add project 2024-01-08 14:42:52 +03:00
9f1c3cd648 Add basic schedule data models (#1)
Reviewed-on: #1
2024-01-07 02:06:45 +03:00
4eecc19f4f feat: add basic schedule data models 2024-01-07 02:00:00 +03:00
79 changed files with 1535 additions and 57 deletions

View File

@ -0,0 +1,26 @@
name: .NET Test Pipeline
on:
pull_request:
branches: [master, 'release/*']
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up .NET Core
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build the solution
run: dotnet build --configuration Release
- name: Run tests
run: dotnet test --configuration Release --no-build --no-restore --verbosity normal

View File

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<Company>Winsomnia</Company>
<Version>1.0.0-a0</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<AssemblyName>Mirea.Api.DataAccess.Application</AssemblyName>
<RootNamespace>$(AssemblyName)</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="12.0.1" />
<PackageReference Include="FluentValidation" Version="11.9.0" />
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.0" />
<PackageReference Include="MediatR" Version="12.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Domain\Domain.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,30 @@
using FluentValidation;
using MediatR;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Mirea.Api.DataAccess.Application.Common.Behaviors;
public class ValidationBehavior<TRequest, TResponse>(IEnumerable<IValidator<TRequest>> validators)
: IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>
{
public Task<TResponse> Handle(TRequest request,
RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
var context = new ValidationContext<TRequest>(request);
var failures = validators
.Select(v => v.Validate(context))
.SelectMany(result => result.Errors)
.Where(failure => failure != null)
.ToList();
if (failures.Count != 0)
{
throw new ValidationException(failures);
}
return next();
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Reflection;
namespace Mirea.Api.DataAccess.Application.Common.Exceptions;
public class NotFoundException : Exception
{
public NotFoundException(MemberInfo entity, string name, object id) : base($"The entity \"{entity.Name}\" property \"{name}\" was not found by id \"{id}\".") { }
public NotFoundException(MemberInfo entity, object id) : base($"The entity \"{entity.Name}\" was not found by id \"{id}\".") { }
}

View File

@ -0,0 +1,10 @@
using System;
using System.Reflection;
namespace Mirea.Api.DataAccess.Application.Common.Exceptions;
public class RecordExistException : Exception
{
public RecordExistException(MemberInfo entity, string name, object id) : base($"The entity \"{entity.Name}\" property \"{name}\" exists with id \"{id}\".") { }
public RecordExistException(MemberInfo entity, object id) : base($"The entity \"{entity.Name}\" exists with id \"{id}\".") { }
}

View File

@ -0,0 +1,28 @@
using AutoMapper;
using System;
using System.Linq;
using System.Reflection;
namespace Mirea.Api.DataAccess.Application.Common.Mappings;
public class AssemblyMappingProfile : Profile
{
public AssemblyMappingProfile(Assembly assembly) =>
ApplyMappingsFromAssembly(assembly);
private void ApplyMappingsFromAssembly(Assembly assembly)
{
var types = assembly.GetExportedTypes()
.Where(type => type.GetInterfaces()
.Any(i => i.IsGenericType &&
i.GetGenericTypeDefinition() == typeof(IMapWith<>)))
.ToList();
foreach (var type in types)
{
var instance = Activator.CreateInstance(type);
var methodInfo = type.GetMethod("Mapping");
methodInfo?.Invoke(instance, new[] { this });
}
}
}

View File

@ -0,0 +1,9 @@
using AutoMapper;
namespace Mirea.Api.DataAccess.Application.Common.Mappings;
public interface IMapWith<T>
{
void Mapping(Profile profile) =>
profile.CreateMap(typeof(T), GetType());
}

View File

@ -0,0 +1,10 @@
using MediatR;
namespace Mirea.Api.DataAccess.Application.Cqrs.Campus.Commands.CreateCampus;
public class CreateCampusCommand : IRequest<int>
{
public required string CodeName { get; set; }
public string? FullName { get; set; }
public string? Address { get; set; }
}

View File

@ -0,0 +1,31 @@
using MediatR;
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Application.Common.Exceptions;
using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
using System.Threading;
using System.Threading.Tasks;
namespace Mirea.Api.DataAccess.Application.Cqrs.Campus.Commands.CreateCampus;
public class CreateCampusCommandHandler(ICampusDbContext dbContext) : IRequestHandler<CreateCampusCommand, int>
{
public async Task<int> Handle(CreateCampusCommand request, CancellationToken cancellationToken)
{
var entity = await dbContext.Campuses.FirstOrDefaultAsync(c => c.CodeName == request.CodeName, cancellationToken: cancellationToken);
if (entity != null)
throw new RecordExistException(typeof(Domain.Schedule.Campus), nameof(Domain.Schedule.Campus.CodeName), entity.Id);
var campus = new Domain.Schedule.Campus()
{
CodeName = request.CodeName,
FullName = request.FullName,
Address = request.Address
};
var result = await dbContext.Campuses.AddAsync(campus, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
return result.Entity.Id;
}
}

View File

@ -0,0 +1,10 @@
using MediatR;
namespace Mirea.Api.DataAccess.Application.Cqrs.Campus.Commands.UpdateCampus;
public class UpdateCampusCommand : IRequest
{
public required int Id { get; set; }
public string? FullName { get; set; }
public string? Address { get; set; }
}

View File

@ -0,0 +1,23 @@
using MediatR;
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Application.Common.Exceptions;
using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
using System.Threading;
using System.Threading.Tasks;
namespace Mirea.Api.DataAccess.Application.Cqrs.Campus.Commands.UpdateCampus;
public class UpdateCampusCommandHandler(ICampusDbContext dbContext) : IRequestHandler<UpdateCampusCommand>
{
public async Task Handle(UpdateCampusCommand request, CancellationToken cancellationToken)
{
var entity = await dbContext.Campuses.FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken: cancellationToken) ?? throw new NotFoundException(typeof(Domain.Schedule.Campus), nameof(Domain.Schedule.Campus.Id), request.Id);
if (!string.IsNullOrEmpty(request.FullName))
entity.FullName = request.FullName;
if (!string.IsNullOrEmpty(request.Address))
entity.Address = request.Address;
await dbContext.SaveChangesAsync(cancellationToken);
}
}

View File

@ -0,0 +1,9 @@
using MediatR;
namespace Mirea.Api.DataAccess.Application.Cqrs.Faculty.Commands.CreateFaculty;
public class CreateFacultyCommand : IRequest<int>
{
public required string Name { get; set; }
public int? CampusId { get; set; }
}

View File

@ -0,0 +1,30 @@
using MediatR;
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Application.Common.Exceptions;
using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
using System.Threading;
using System.Threading.Tasks;
namespace Mirea.Api.DataAccess.Application.Cqrs.Faculty.Commands.CreateFaculty;
public class CreateFacultyCommandHandler(IFacultyDbContext dbContext) : IRequestHandler<CreateFacultyCommand, int>
{
public async Task<int> Handle(CreateFacultyCommand request, CancellationToken cancellationToken)
{
var entity = await dbContext.Faculties.FirstOrDefaultAsync(f => f.Name == request.Name, cancellationToken: cancellationToken);
if (entity != null)
throw new RecordExistException(typeof(Domain.Schedule.Faculty), nameof(Domain.Schedule.Faculty.Name), entity.Id);
var faculty = new Domain.Schedule.Faculty()
{
Name = request.Name,
CampusId = request.CampusId
};
var result = await dbContext.Faculties.AddAsync(faculty, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
return result.Entity.Id;
}
}

View File

@ -0,0 +1,9 @@
using MediatR;
namespace Mirea.Api.DataAccess.Application.Cqrs.Faculty.Commands.UpdateFaculty;
public class UpdateFacultyCommand : IRequest
{
public required int Id { get; set; }
public required int CampusId { get; set; }
}

View File

@ -0,0 +1,20 @@
using MediatR;
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Application.Common.Exceptions;
using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
using System.Threading;
using System.Threading.Tasks;
namespace Mirea.Api.DataAccess.Application.Cqrs.Faculty.Commands.UpdateFaculty;
public class UpdateFacultyCommandHandler(IFacultyDbContext dbContext) : IRequestHandler<UpdateFacultyCommand>
{
public async Task Handle(UpdateFacultyCommand request, CancellationToken cancellationToken)
{
var entity = await dbContext.Faculties.FirstOrDefaultAsync(f => f.Id == request.Id, cancellationToken: cancellationToken) ?? throw new NotFoundException(typeof(Domain.Schedule.Faculty), nameof(Domain.Schedule.Faculty.Id), request.Id);
entity.CampusId = request.CampusId;
await dbContext.SaveChangesAsync(cancellationToken);
}
}

View File

@ -0,0 +1,9 @@
using MediatR;
namespace Mirea.Api.DataAccess.Application.Cqrs.Group.Commands.CreateGroup;
public class CreateGroupCommand : IRequest<int>
{
public required string Name { get; set; }
public int? FacultyId { get; set; }
}

View File

@ -0,0 +1,30 @@
using MediatR;
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Application.Common.Exceptions;
using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
using System.Threading;
using System.Threading.Tasks;
namespace Mirea.Api.DataAccess.Application.Cqrs.Group.Commands.CreateGroup;
public class CreateGroupCommandHandler(IGroupDbContext dbContext) : IRequestHandler<CreateGroupCommand, int>
{
public async Task<int> Handle(CreateGroupCommand request, CancellationToken cancellationToken)
{
var entity = await dbContext.Groups.FirstOrDefaultAsync(g => g.Name == request.Name, cancellationToken: cancellationToken);
if (entity != null)
throw new RecordExistException(typeof(Domain.Schedule.Group), nameof(Domain.Schedule.Group.Name), entity.Id);
var group = new Domain.Schedule.Group()
{
Name = request.Name,
FacultyId = request.FacultyId
};
var result = await dbContext.Groups.AddAsync(group, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
return result.Entity.Id;
}
}

View File

@ -0,0 +1,9 @@
using MediatR;
namespace Mirea.Api.DataAccess.Application.Cqrs.Group.Commands.UpdateGroup;
public class UpdateGroupCommand : IRequest
{
public required int Id { get; set; }
public required int FacultyId { get; set; }
}

View File

@ -0,0 +1,20 @@
using MediatR;
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Application.Common.Exceptions;
using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
using System.Threading;
using System.Threading.Tasks;
namespace Mirea.Api.DataAccess.Application.Cqrs.Group.Commands.UpdateGroup;
public class UpdateGroupCommandHandler(IGroupDbContext dbContext) : IRequestHandler<UpdateGroupCommand>
{
public async Task Handle(UpdateGroupCommand request, CancellationToken cancellationToken)
{
var entity = await dbContext.Groups.FirstOrDefaultAsync(g => g.Id == request.Id, cancellationToken: cancellationToken) ?? throw new NotFoundException(typeof(Domain.Schedule.Group), nameof(Domain.Schedule.Group.Id), request.Id);
entity.FacultyId = request.FacultyId;
await dbContext.SaveChangesAsync(cancellationToken);
}
}

View File

@ -0,0 +1,9 @@
using MediatR;
namespace Mirea.Api.DataAccess.Application.Cqrs.Professor.Commands.CreateProfessor;
public class CreateProfessorCommand : IRequest<int>
{
public required string Name { get; set; }
public string? AltName { get; set; }
}

View File

@ -0,0 +1,30 @@
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Application.Common.Exceptions;
using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
namespace Mirea.Api.DataAccess.Application.Cqrs.Professor.Commands.CreateProfessor;
public class CreateProfessorCommandHandler(IProfessorDbContext dbContext) : IRequestHandler<CreateProfessorCommand, int>
{
public async Task<int> Handle(CreateProfessorCommand request, CancellationToken cancellationToken)
{
var entity = await dbContext.Professors.FirstOrDefaultAsync(p => p.Name == request.Name, cancellationToken: cancellationToken);
if (entity != null)
throw new RecordExistException(typeof(Domain.Schedule.Professor), nameof(Domain.Schedule.Professor.Name), entity.Id);
var professor = new Domain.Schedule.Professor()
{
Name = request.Name,
AltName = request.AltName
};
var result = await dbContext.Professors.AddAsync(professor, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
return result.Entity.Id;
}
}

View File

@ -0,0 +1,9 @@
using MediatR;
namespace Mirea.Api.DataAccess.Application.Cqrs.Professor.Commands.UpdateProfessor;
public class UpdateProfessorCommand : IRequest
{
public required int Id { get; set; }
public required string AltName { get; set; }
}

View File

@ -0,0 +1,20 @@
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Application.Common.Exceptions;
using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
namespace Mirea.Api.DataAccess.Application.Cqrs.Professor.Commands.UpdateProfessor;
public class UpdateProfessorCommandHandler(IProfessorDbContext dbContext) : IRequestHandler<UpdateProfessorCommand>
{
public async Task Handle(UpdateProfessorCommand request, CancellationToken cancellationToken)
{
var entity = await dbContext.Professors.FirstOrDefaultAsync(p => p.Id == request.Id, cancellationToken: cancellationToken) ?? throw new NotFoundException(typeof(Domain.Schedule.Professor), nameof(Domain.Schedule.Professor.Id), request.Id);
entity.AltName = request.AltName;
await dbContext.SaveChangesAsync(cancellationToken);
}
}

View File

@ -0,0 +1,9 @@
using MediatR;
namespace Mirea.Api.DataAccess.Application.Cqrs.TypeOfOccupation.Commands.CreateTypeOfOccupation;
public class CreateTypeOfOccupationCommand : IRequest<int>
{
public required string ShortName { get; set; }
public string? FullName { get; set; }
}

View File

@ -0,0 +1,30 @@
using MediatR;
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Application.Common.Exceptions;
using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
using System.Threading;
using System.Threading.Tasks;
namespace Mirea.Api.DataAccess.Application.Cqrs.TypeOfOccupation.Commands.CreateTypeOfOccupation;
public class CreateTypeOfOccupationCommandHandler(ITypeOfOccupationDbContext dbContext) : IRequestHandler<CreateTypeOfOccupationCommand, int>
{
public async Task<int> Handle(CreateTypeOfOccupationCommand request, CancellationToken cancellationToken)
{
var entity = await dbContext.TypeOfOccupations.FirstOrDefaultAsync(t => t.ShortName == request.ShortName, cancellationToken: cancellationToken);
if (entity != null)
throw new RecordExistException(typeof(Domain.Schedule.TypeOfOccupation), nameof(Domain.Schedule.TypeOfOccupation.ShortName), entity.Id);
var typeOfOccupation = new Domain.Schedule.TypeOfOccupation
{
ShortName = request.ShortName,
FullName = request.FullName
};
var result = await dbContext.TypeOfOccupations.AddAsync(typeOfOccupation, cancellationToken);
await dbContext.SaveChangesAsync(cancellationToken);
return result.Entity.Id;
}
}

View File

@ -0,0 +1,9 @@
using MediatR;
namespace Mirea.Api.DataAccess.Application.Cqrs.TypeOfOccupation.Commands.UpdateTypeOfOccupation;
public class UpdateTypeOfOccupationCommand : IRequest
{
public required int Id { get; set; }
public required string FullName { get; set; }
}

View File

@ -0,0 +1,20 @@
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Application.Common.Exceptions;
using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
namespace Mirea.Api.DataAccess.Application.Cqrs.TypeOfOccupation.Commands.UpdateTypeOfOccupation;
public class UpdateTypeOfOccupationCommandHandler(ITypeOfOccupationDbContext dbContext) : IRequestHandler<UpdateTypeOfOccupationCommand>
{
public async Task Handle(UpdateTypeOfOccupationCommand request, CancellationToken cancellationToken)
{
var entity = await dbContext.TypeOfOccupations.FirstOrDefaultAsync(t => t.Id == request.Id, cancellationToken: cancellationToken) ?? throw new NotFoundException(typeof(Domain.Schedule.TypeOfOccupation), nameof(Domain.Schedule.TypeOfOccupation.Id), request.Id);
entity.FullName = request.FullName;
await dbContext.SaveChangesAsync(cancellationToken);
}
}

View File

@ -0,0 +1,19 @@
using FluentValidation;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Mirea.Api.DataAccess.Application.Common.Behaviors;
using System.Reflection;
namespace Mirea.Api.DataAccess.Application;
public static class DependencyInjection
{
public static IServiceCollection AddApplication(this IServiceCollection services)
{
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
services.AddValidatorsFromAssemblies(new[] { Assembly.GetExecutingAssembly() });
services.AddTransient(typeof(IPipelineBehavior<,>),
typeof(ValidationBehavior<,>));
return services;
}
}

View File

@ -0,0 +1,9 @@
using System.Threading;
using System.Threading.Tasks;
namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts;
public interface IDbContextBase
{
Task<int> SaveChangesAsync(CancellationToken cancellationToken);
}

View File

@ -0,0 +1,9 @@
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Domain.Schedule;
namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
public interface ICampusDbContext : IDbContextBase
{
DbSet<Campus> Campuses { get; set; }
}

View File

@ -0,0 +1,9 @@
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Domain.Schedule;
namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
public interface IDisciplineDbContext : IDbContextBase
{
DbSet<Discipline> Disciplines { get; set; }
}

View File

@ -0,0 +1,9 @@
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Domain.Schedule;
namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
public interface IFacultyDbContext : IDbContextBase
{
DbSet<Faculty> Faculties { get; set; }
}

View File

@ -0,0 +1,9 @@
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Domain.Schedule;
namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
public interface IGroupDbContext : IDbContextBase
{
DbSet<Group> Groups { get; set; }
}

View File

@ -0,0 +1,9 @@
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Domain.Schedule;
namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
public interface ILectureHallDbContext : IDbContextBase
{
DbSet<LectureHall> LectureHalls { get; set; }
}

View File

@ -0,0 +1,9 @@
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Domain.Schedule;
namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
public interface ILessonAssociationDbContext : IDbContextBase
{
DbSet<LessonAssociation> LessonAssociations { get; set; }
}

View File

@ -0,0 +1,9 @@
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Domain.Schedule;
namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
public interface ILessonDbContext : IDbContextBase
{
DbSet<Lesson> Lessons { get; set; }
}

View File

@ -0,0 +1,9 @@
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Domain.Schedule;
namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
public interface IProfessorDbContext : IDbContextBase
{
DbSet<Professor> Professors { get; set; }
}

View File

@ -0,0 +1,9 @@
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Domain.Schedule;
namespace Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
public interface ITypeOfOccupationDbContext : IDbContextBase
{
DbSet<TypeOfOccupation> TypeOfOccupations { get; set; }
}

View File

@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34330.188
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Domain", "Domain\Domain.csproj", "{C27FB5CD-6A70-4FB2-847A-847B34806902}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Domain", "Domain\Domain.csproj", "{C27FB5CD-6A70-4FB2-847A-847B34806902}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Endpoint", "Endpoint\Endpoint.csproj", "{F3A1D12E-F5B2-4339-9966-DBF869E78357}"
EndProject
@ -17,6 +17,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Elements of the solution",
README.md = README.md
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application", "Application\Application.csproj", "{E7F0A4D4-B032-4BB9-9526-1AF688F341A4}"
ProjectSection(ProjectDependencies) = postProject
{C27FB5CD-6A70-4FB2-847A-847B34806902} = {C27FB5CD-6A70-4FB2-847A-847B34806902}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence", "Persistence\Persistence.csproj", "{4C1E558F-633F-438E-AC3A-61CDDED917C5}"
ProjectSection(ProjectDependencies) = postProject
{E7F0A4D4-B032-4BB9-9526-1AF688F341A4} = {E7F0A4D4-B032-4BB9-9526-1AF688F341A4}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -31,6 +41,14 @@ Global
{F3A1D12E-F5B2-4339-9966-DBF869E78357}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3A1D12E-F5B2-4339-9966-DBF869E78357}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3A1D12E-F5B2-4339-9966-DBF869E78357}.Release|Any CPU.Build.0 = Release|Any CPU
{E7F0A4D4-B032-4BB9-9526-1AF688F341A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E7F0A4D4-B032-4BB9-9526-1AF688F341A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E7F0A4D4-B032-4BB9-9526-1AF688F341A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E7F0A4D4-B032-4BB9-9526-1AF688F341A4}.Release|Any CPU.Build.0 = Release|Any CPU
{4C1E558F-633F-438E-AC3A-61CDDED917C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4C1E558F-633F-438E-AC3A-61CDDED917C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4C1E558F-633F-438E-AC3A-61CDDED917C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4C1E558F-633F-438E-AC3A-61CDDED917C5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

14
Domain/Schedule/Campus.cs Normal file
View File

@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace Mirea.Api.DataAccess.Domain.Schedule;
public class Campus
{
public int Id { get; set; }
public required string CodeName { get; set; }
public string? FullName { get; set; }
public string? Address { get; set; }
public List<Faculty>? Faculties { get; set; }
public List<LectureHall>? LectureHalls { get; set; }
}

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace Mirea.Api.DataAccess.Domain.Schedule;
public class Discipline
{
public int Id { get; set; }
public required string Name { get; set; }
public List<Lesson>? Lessons { get; set; }
}

View File

@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace Mirea.Api.DataAccess.Domain.Schedule;
public class Faculty
{
public int Id { get; set; }
public required string Name { get; set; }
public int? CampusId { get; set; }
public Campus? Campus { get; set; }
public List<Group>? Groups { get; set; }
}

14
Domain/Schedule/Group.cs Normal file
View File

@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace Mirea.Api.DataAccess.Domain.Schedule;
public class Group
{
public int Id { get; set; }
public required string Name { get; set; }
public int? FacultyId { get; set; }
public Faculty? Faculty { get; set; }
public List<Lesson>? Lessons { get; set; }
}

View File

@ -0,0 +1,14 @@
using System.Collections.Generic;
namespace Mirea.Api.DataAccess.Domain.Schedule;
public class LectureHall
{
public int Id { get; set; }
public required string Name { get; set; }
public int CampusId { get; set; }
public Campus? Campus { get; set; }
public List<LessonAssociation>? LessonAssociations { get; set; }
}

21
Domain/Schedule/Lesson.cs Normal file
View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
namespace Mirea.Api.DataAccess.Domain.Schedule;
public class Lesson
{
public int Id { get; set; }
public bool IsEven { get; set; }
public DayOfWeek DayOfWeek { get; set; }
public int PairNumber { get; set; }
public int GroupId { get; set; }
public Group? Group { get; set; }
public int TypeOfOccupationId { get; set; }
public TypeOfOccupation? TypeOfOccupation { get; set; }
public int DisciplineId { get; set; }
public Discipline? Discipline { get; set; }
public List<LessonAssociation>? LessonAssociations { get; set; }
}

View File

@ -0,0 +1,14 @@
namespace Mirea.Api.DataAccess.Domain.Schedule;
public class LessonAssociation
{
public int Id { get; set; }
public string? LinkToMeet { get; set; }
public int LessonId { get; set; }
public Lesson? Lesson { get; set; }
public int? ProfessorId { get; set; }
public Professor? Professor { get; set; }
public int? LectureHallId { get; set; }
public LectureHall? LectureHall { get; set; }
}

View File

@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace Mirea.Api.DataAccess.Domain.Schedule;
public class Professor
{
public int Id { get; set; }
public required string Name { get; set; }
public string? AltName { get; set; }
public List<LessonAssociation>? LessonAssociations { get; set; }
}

View File

@ -0,0 +1,12 @@
using System.Collections.Generic;
namespace Mirea.Api.DataAccess.Domain.Schedule;
public class TypeOfOccupation
{
public int Id { get; set; }
public required string ShortName { get; set; }
public string? FullName { get; set; }
public List<Lesson>? Lessons { get; set; }
}

View File

@ -0,0 +1,36 @@
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System;
namespace Mirea.Api.Endpoint;
public class ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) : IConfigureOptions<SwaggerGenOptions>
{
public void Configure(SwaggerGenOptions options)
{
foreach (var description in provider.ApiVersionDescriptions)
{
options.SwaggerDoc(description.GroupName, CreateInfoForApiVersion(description));
}
}
private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription description)
{
var info = new OpenApiInfo()
{
Title = "MIREA Schedule Web API",
Version = description.ApiVersion.ToString(),
Description = "This API provides a convenient interface for retrieving data stored in the database. Special attention was paid to the lightweight and easy transfer of all necessary data. Made by the Winsomnia team.",
Contact = new OpenApiContact { Name = "Author name", Email = "support@winsomnia.net" },
License = new OpenApiLicense { Name = "MIT", Url = new Uri("https://opensource.org/licenses/MIT") }
};
if (description.IsDeprecated)
info.Description += " This API version has been deprecated.";
return info;
}
}

View File

@ -1,36 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace Mirea.Api.Endpoint.Controllers;
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}

View File

@ -11,15 +11,25 @@
<AssemblyName>Mirea.Api.Endpoint</AssemblyName>
<RootNamespace>$(AssemblyName)</RootNamespace>
<OutputType>Exe</OutputType>
<InvariantGlobalization>true</InvariantGlobalization>
<InvariantGlobalization>false</InvariantGlobalization>
<UserSecretsId>65cea060-88bf-4e35-9cfb-18fc996a8f05</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>.</DockerfileContext>
<SignAssembly>False</SignAssembly>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<DocumentationFile>docs.xml</DocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Versioning" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Domain\Domain.csproj" />
<ProjectReference Include="..\Persistence\Persistence.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,24 @@
using System;
using System.IO;
namespace Mirea.Api.Endpoint;
internal static class EnvironmentManager
{
public static void LoadEnvironment(string filePath)
{
if (!File.Exists(filePath)) return;
foreach (var line in File.ReadAllLines(filePath))
{
var parts = line.Split(
'=',
StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2)
continue;
Environment.SetEnvironmentVariable(parts[0].Trim(), parts[1][..(parts[1].Contains('#') ? parts[1].IndexOf('#') : parts[1].Length)].Trim());
}
}
}

View File

@ -1,29 +1,114 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Mirea.Api.DataAccess.Application;
using Mirea.Api.DataAccess.Persistence;
using Mirea.Api.Endpoint.Properties;
using Swashbuckle.AspNetCore.SwaggerGen;
using System;
using System.Collections;
using System.IO;
using System.Linq;
namespace Mirea.Api.Endpoint;
public class Program
{
private static IConfigurationRoot ConfigureEnvironment()
{
EnvironmentManager.LoadEnvironment(".env");
var environmentVariables = Environment.GetEnvironmentVariables()
.OfType<DictionaryEntry>()
.ToDictionary(
entry => entry.Key.ToString() ?? string.Empty,
entry => entry.Value?.ToString() ?? string.Empty
);
var result = new ConfigurationBuilder().AddInMemoryCollection(environmentVariables!);
return result.Build();
}
public static void Main(string[] args)
{
Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddConfiguration(ConfigureEnvironment());
builder.Configuration.AddJsonFile(Settings.FilePath, optional: true, reloadOnChange: true);
// Add services to the container.
builder.Services.AddApplication();
builder.Services.AddPersistence(builder.Configuration);
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll", policy =>
{
policy.AllowAnyHeader();
policy.AllowAnyMethod();
policy.AllowAnyOrigin();
});
});
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = new UrlSegmentApiVersionReader();
});
builder.Services.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSwaggerGen(options =>
{
options.OperationFilter<SwaggerDefaultValues>();
var basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory);
var xmlPath = Path.Combine(basePath, "docs.xml");
options.IncludeXmlComments(xmlPath);
});
builder.Services.AddTransient<IConfigureOptions<SwaggerGenOptions>, ConfigureSwaggerOptions>();
var app = builder.Build();
#if DEBUG
// Write configurations
foreach (var item in app.Configuration.AsEnumerable())
Console.WriteLine($"{item.Key}:{item.Value}");
#endif
var uber = app.Services.CreateScope().ServiceProvider.GetService<UberDbContext>();
DbInitializer.Initialize(uber!);
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
app.UseSwaggerUI(options =>
{
var provider = app.Services.GetService<IApiVersionDescriptionProvider>();
foreach (var description in provider!.ApiVersionDescriptions)
{
var url = $"/swagger/{description.GroupName}/swagger.json";
var name = description.GroupName.ToUpperInvariant();
options.SwaggerEndpoint(url, name);
}
});
}
app.UseHttpsRedirection();

View File

@ -0,0 +1,36 @@
using Mirea.Api.DataAccess.Persistence.Properties;
namespace Mirea.Api.Endpoint.Properties;
public class EmailSettings
{
public string? Server { get; set; }
public string? User { get; set; }
public string? Password { get; set; }
public string? From { get; set; }
public int? Port { get; set; }
public bool? Ssl { get; set; }
}
public class LogSettings
{
public bool EnableLogToFile { get; set; }
public string? LogFilePath { get; set; }
public string? LogFileName { get; set; }
}
public class ScheduleSettings
{
// Every 6 hours
public string CronUpdateSchedule { get; set; } = "0 0 0/6 * * *";
}
public class Settings
{
public const string FilePath = "Settings.json";
public EmailSettings? EmailSettings { get; set; }
public LogSettings? LogSettings { get; set; }
public DbSettings? DbSettings { get; set; }
public ScheduleSettings? ScheduleSettings { get; set; }
}

View File

@ -0,0 +1,55 @@
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System;
using System.Linq;
using System.Text.Json;
namespace Mirea.Api.Endpoint;
public class SwaggerDefaultValues : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var apiDescription = context.ApiDescription;
operation.Deprecated |= apiDescription.IsDeprecated();
foreach (var responseType in context.ApiDescription.SupportedResponseTypes)
{
var responseKey = responseType.IsDefaultResponse ? "default" : responseType.StatusCode.ToString();
var response = operation.Responses[responseKey];
foreach (var contentType in response.Content.Keys)
{
if (responseType.ApiResponseFormats.All(x => x.MediaType != contentType))
{
response.Content.Remove(contentType);
}
}
}
if (operation.Parameters == null)
{
return;
}
foreach (var parameter in operation.Parameters)
{
var description = apiDescription.ParameterDescriptions.First(p => p.Name == parameter.Name);
parameter.Description ??= description.ModelMetadata?.Description;
if (parameter.Schema.Default == null &&
description.DefaultValue != null &&
description.DefaultValue is not DBNull &&
description.ModelMetadata is ModelMetadata modelMetadata)
{
var json = JsonSerializer.Serialize(description.DefaultValue, modelMetadata.ModelType);
parameter.Schema.Default = OpenApiAnyFactory.CreateFromJson(json);
}
parameter.Required |= description.IsRequired;
}
}
}

View File

@ -1,14 +0,0 @@
using System;
namespace Mirea.Api.Endpoint;
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
public string? Summary { get; set; }
}

View File

@ -0,0 +1,17 @@
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
using Mirea.Api.DataAccess.Domain.Schedule;
using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule;
namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule;
public class CampusDbContext(DbContextOptions<CampusDbContext> options) : DbContext(options), ICampusDbContext
{
public DbSet<Campus> Campuses { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new CampusConfiguration());
base.OnModelCreating(modelBuilder);
}
}

View File

@ -0,0 +1,17 @@
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
using Mirea.Api.DataAccess.Domain.Schedule;
using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule;
namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule;
public class DisciplineDbContext(DbContextOptions<DisciplineDbContext> options) : DbContext(options), IDisciplineDbContext
{
public DbSet<Discipline> Disciplines { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new DisciplineConfiguration());
base.OnModelCreating(modelBuilder);
}
}

View File

@ -0,0 +1,17 @@
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
using Mirea.Api.DataAccess.Domain.Schedule;
using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule;
namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule;
public class FacultyDbContext(DbContextOptions<FacultyDbContext> options) : DbContext(options), IFacultyDbContext
{
public DbSet<Faculty> Faculties { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new FacultyConfiguration());
base.OnModelCreating(modelBuilder);
}
}

View File

@ -0,0 +1,17 @@
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
using Mirea.Api.DataAccess.Domain.Schedule;
using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule;
namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule;
public class GroupDbContext(DbContextOptions<GroupDbContext> options) : DbContext(options), IGroupDbContext
{
public DbSet<Group> Groups { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new GroupConfiguration());
base.OnModelCreating(modelBuilder);
}
}

View File

@ -0,0 +1,17 @@
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
using Mirea.Api.DataAccess.Domain.Schedule;
using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule;
namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule;
public class LectureHallDbContext(DbContextOptions<LectureHallDbContext> options) : DbContext(options), ILectureHallDbContext
{
public DbSet<LectureHall> LectureHalls { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new LectureHallConfiguration());
base.OnModelCreating(modelBuilder);
}
}

View File

@ -0,0 +1,17 @@
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
using Mirea.Api.DataAccess.Domain.Schedule;
using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule;
namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule;
public class LessonAssociationDbContext(DbContextOptions<LessonAssociationDbContext> options) : DbContext(options), ILessonAssociationDbContext
{
public DbSet<LessonAssociation> LessonAssociations { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new LessonAssociationConfiguration());
base.OnModelCreating(modelBuilder);
}
}

View File

@ -0,0 +1,17 @@
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
using Mirea.Api.DataAccess.Domain.Schedule;
using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule;
namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule;
public class LessonDbContext(DbContextOptions<LessonDbContext> options) : DbContext(options), ILessonDbContext
{
public DbSet<Lesson> Lessons { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new LessonConfiguration());
base.OnModelCreating(modelBuilder);
}
}

View File

@ -0,0 +1,17 @@
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
using Mirea.Api.DataAccess.Domain.Schedule;
using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule;
namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule;
public class ProfessorDbContext(DbContextOptions<ProfessorDbContext> options) : DbContext(options), IProfessorDbContext
{
public DbSet<Professor> Professors { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new ProfessorConfiguration());
base.OnModelCreating(modelBuilder);
}
}

View File

@ -0,0 +1,17 @@
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
using Mirea.Api.DataAccess.Domain.Schedule;
using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule;
namespace Mirea.Api.DataAccess.Persistence.Contexts.Schedule;
public class TypeOfOccupationDbContext(DbContextOptions<TypeOfOccupationDbContext> options) : DbContext(options), ITypeOfOccupationDbContext
{
public DbSet<TypeOfOccupation> TypeOfOccupations { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new TypeOfOccupationConfiguration());
base.OnModelCreating(modelBuilder);
}
}

View File

@ -0,0 +1,11 @@
using Microsoft.EntityFrameworkCore;
namespace Mirea.Api.DataAccess.Persistence;
public static class DbInitializer
{
public static void Initialize(DbContext dbContext)
{
dbContext.Database.EnsureCreated();
}
}

View File

@ -0,0 +1,64 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Mirea.Api.DataAccess.Application.Interfaces.DbContexts.Schedule;
using Mirea.Api.DataAccess.Persistence.Contexts.Schedule;
using Mirea.Api.DataAccess.Persistence.Properties;
using System;
using System.Collections.Generic;
namespace Mirea.Api.DataAccess.Persistence;
public static class DependencyInjection
{
public static IServiceCollection AddPersistence(this IServiceCollection services, IConfiguration configuration)
{
var settings = configuration.GetSection(nameof(DbSettings)).Get<DbSettings>();
var connection = settings?.ConnectionStringSql;
Dictionary<DatabaseEnum, Action<DbContextOptionsBuilder>> dbConfigurations = new()
{
{
DatabaseEnum.Mysql,
options => options.UseMySql(connection, ServerVersion.AutoDetect(connection))
},
{
DatabaseEnum.Sqlite,
options => options.UseSqlite(connection)
},
{
DatabaseEnum.PostgresSql,
options => options.UseNpgsql(connection)
}
};
if (dbConfigurations.TryGetValue((DatabaseEnum)settings?.TypeDatabase!, out var dbConfig))
{
services.AddDbContext<CampusDbContext>(dbConfig);
services.AddDbContext<DisciplineDbContext>(dbConfig);
services.AddDbContext<FacultyDbContext>(dbConfig);
services.AddDbContext<GroupDbContext>(dbConfig);
services.AddDbContext<LectureHallDbContext>(dbConfig);
services.AddDbContext<LessonAssociationDbContext>(dbConfig);
services.AddDbContext<ProfessorDbContext>(dbConfig);
services.AddDbContext<LessonDbContext>(dbConfig);
services.AddDbContext<TypeOfOccupationDbContext>(dbConfig);
services.AddDbContext<UberDbContext>(dbConfig);
}
else
throw new NotSupportedException("Unsupported database type");
services.AddScoped<ICampusDbContext>(provider => provider.GetService<CampusDbContext>()!);
services.AddScoped<IDisciplineDbContext>(provider => provider.GetService<DisciplineDbContext>()!);
services.AddScoped<IFacultyDbContext>(provider => provider.GetService<FacultyDbContext>()!);
services.AddScoped<IGroupDbContext>(provider => provider.GetService<GroupDbContext>()!);
services.AddScoped<ILectureHallDbContext>(provider => provider.GetService<LectureHallDbContext>()!);
services.AddScoped<ILessonAssociationDbContext>(provider => provider.GetService<LessonAssociationDbContext>()!);
services.AddScoped<IProfessorDbContext>(provider => provider.GetService<ProfessorDbContext>()!);
services.AddScoped<ILessonDbContext>(provider => provider.GetService<LessonDbContext>()!);
services.AddScoped<ITypeOfOccupationDbContext>(provider => provider.GetService<TypeOfOccupationDbContext>()!);
return services;
}
}

View File

@ -0,0 +1,20 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Mirea.Api.DataAccess.Domain.Schedule;
namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule;
public class CampusConfiguration : IEntityTypeConfiguration<Campus>
{
public void Configure(EntityTypeBuilder<Campus> builder)
{
builder.ToTable(nameof(Campus));
builder.HasKey(c => c.Id);
builder.HasIndex(c => c.Id).IsUnique();
builder.Property(c => c.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd();
builder.Property(c => c.Address).HasColumnType("TEXT").HasMaxLength(512);
builder.Property(c => c.CodeName).HasColumnType("TEXT").IsRequired().HasMaxLength(16);
builder.Property(c => c.FullName).HasColumnType("TEXT").HasMaxLength(256);
}
}

View File

@ -0,0 +1,18 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Mirea.Api.DataAccess.Domain.Schedule;
namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule;
public class DisciplineConfiguration : IEntityTypeConfiguration<Discipline>
{
public void Configure(EntityTypeBuilder<Discipline> builder)
{
builder.ToTable(nameof(Discipline));
builder.HasKey(d => d.Id);
builder.HasIndex(d => d.Id).IsUnique();
builder.Property(d => d.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd();
builder.Property(d => d.Name).HasColumnType("TEXT").HasMaxLength(256).IsRequired();
}
}

View File

@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Mirea.Api.DataAccess.Domain.Schedule;
namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule;
public class FacultyConfiguration : IEntityTypeConfiguration<Faculty>
{
public void Configure(EntityTypeBuilder<Faculty> builder)
{
builder.ToTable(nameof(Faculty));
builder.HasKey(f => f.Id);
builder.HasIndex(f => f.Id).IsUnique();
builder.Property(f => f.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd();
builder.Property(f => f.Name).HasColumnType("TEXT").IsRequired().HasMaxLength(256);
builder.Property(f => f.CampusId).HasColumnType("INTEGER");
builder
.HasOne(f => f.Campus)
.WithMany(c => c.Faculties)
.HasForeignKey(c => c.CampusId)
.OnDelete(DeleteBehavior.SetNull);
}
}

View File

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Mirea.Api.DataAccess.Domain.Schedule;
namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule;
public class GroupConfiguration : IEntityTypeConfiguration<Group>
{
public void Configure(EntityTypeBuilder<Group> builder)
{
builder.ToTable(nameof(Group));
builder.HasKey(g => g.Id);
builder.HasIndex(g => g.Id).IsUnique();
builder.Property(g => g.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd();
builder.Property(g => g.FacultyId).HasColumnType("INTEGER");
builder.Property(g => g.Name).HasColumnType("TEXT").IsRequired().HasMaxLength(64);
builder
.HasOne(g => g.Faculty)
.WithMany(u => u.Groups)
.HasForeignKey(d => d.FacultyId)
.OnDelete(DeleteBehavior.SetNull);
}
}

View File

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Mirea.Api.DataAccess.Domain.Schedule;
namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule;
public class LectureHallConfiguration : IEntityTypeConfiguration<LectureHall>
{
public void Configure(EntityTypeBuilder<LectureHall> builder)
{
builder.ToTable(nameof(LectureHall));
builder.HasKey(l => l.Id);
builder.HasIndex(l => l.Id).IsUnique();
builder.Property(l => l.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd();
builder.Property(l => l.CampusId).HasColumnType("INTEGER").IsRequired();
builder.Property(l => l.Name).HasColumnType("TEXT").IsRequired().HasMaxLength(64);
builder
.HasOne(l => l.Campus)
.WithMany(c => c.LectureHalls)
.HasForeignKey(d => d.CampusId)
.OnDelete(DeleteBehavior.Restrict);
}
}

View File

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Mirea.Api.DataAccess.Domain.Schedule;
namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule;
public class LessonAssociationConfiguration : IEntityTypeConfiguration<LessonAssociation>
{
public void Configure(EntityTypeBuilder<LessonAssociation> builder)
{
builder.ToTable(nameof(LessonAssociation));
builder.HasKey(l => l.Id);
builder.HasIndex(l => l.Id).IsUnique();
builder.Property(l => l.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd();
builder.Property(l => l.LinkToMeet).HasColumnType("TEXT").HasMaxLength(512);
builder.Property(l => l.LessonId).HasColumnType("INTEGER").IsRequired();
builder.Property(l => l.ProfessorId).HasColumnType("INTEGER");
builder.Property(l => l.LectureHallId).HasColumnType("INTEGER");
builder
.HasOne(l => l.Lesson)
.WithMany(d => d.LessonAssociations)
.HasForeignKey(l => l.LessonId)
.OnDelete(DeleteBehavior.Cascade);
builder
.HasOne(l => l.Professor)
.WithMany(p => p.LessonAssociations)
.HasForeignKey(l => l.ProfessorId)
.OnDelete(DeleteBehavior.SetNull);
builder
.HasOne(l => l.LectureHall)
.WithMany(l => l.LessonAssociations)
.HasForeignKey(l => l.LectureHallId)
.OnDelete(DeleteBehavior.SetNull);
}
}

View File

@ -0,0 +1,43 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Mirea.Api.DataAccess.Domain.Schedule;
namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule;
public class LessonConfiguration : IEntityTypeConfiguration<Lesson>
{
public void Configure(EntityTypeBuilder<Lesson> builder)
{
builder.ToTable(nameof(Lesson));
builder.HasKey(l => l.Id);
builder.HasIndex(l => l.Id).IsUnique();
builder.Property(l => l.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd();
builder.Property(l => l.IsEven).HasColumnType("BIT").IsRequired();
builder.Property(l => l.DayOfWeek).HasColumnType("INTEGER").IsRequired();
builder.Property(l => l.PairNumber).HasColumnType("INTEGER").IsRequired();
builder.Property(l => l.GroupId).HasColumnType("INTEGER").IsRequired();
builder.Property(l => l.TypeOfOccupationId).HasColumnType("INTEGER").IsRequired();
builder.Property(l => l.DisciplineId).HasColumnType("INTEGER").IsRequired();
builder
.HasOne(l => l.Group)
.WithMany(g => g.Lessons)
.HasForeignKey(d => d.GroupId)
.OnDelete(DeleteBehavior.Cascade);
builder
.HasOne(l => l.TypeOfOccupation)
.WithMany(t => t.Lessons)
.HasForeignKey(d => d.TypeOfOccupationId)
.OnDelete(DeleteBehavior.Cascade);
builder
.HasOne(l => l.Discipline)
.WithMany(d => d.Lessons)
.HasForeignKey(l => l.DisciplineId)
.OnDelete(DeleteBehavior.Cascade);
}
}

View File

@ -0,0 +1,19 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Mirea.Api.DataAccess.Domain.Schedule;
namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule;
public class ProfessorConfiguration : IEntityTypeConfiguration<Professor>
{
public void Configure(EntityTypeBuilder<Professor> builder)
{
builder.ToTable(nameof(Professor));
builder.HasKey(p => p.Id);
builder.HasIndex(p => p.Id).IsUnique();
builder.Property(p => p.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd();
builder.Property(p => p.Name).HasColumnType("TEXT").IsRequired();
builder.Property(p => p.AltName).HasColumnType("TEXT");
}
}

View File

@ -0,0 +1,19 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Mirea.Api.DataAccess.Domain.Schedule;
namespace Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule;
public class TypeOfOccupationConfiguration : IEntityTypeConfiguration<TypeOfOccupation>
{
public void Configure(EntityTypeBuilder<TypeOfOccupation> builder)
{
builder.ToTable(nameof(TypeOfOccupation));
builder.HasKey(t => t.Id);
builder.HasIndex(t => t.Id).IsUnique();
builder.Property(t => t.Id).HasColumnType("INTEGER").IsRequired().ValueGeneratedOnAdd();
builder.Property(t => t.ShortName).HasColumnType("TEXT").IsRequired().HasMaxLength(16);
builder.Property(t => t.FullName).HasColumnType("TEXT").HasMaxLength(64);
}
}

View File

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<Company>Winsomnia</Company>
<Version>1.0.0-a0</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<AssemblyName>Mirea.Api.DataAccess.Persistence</AssemblyName>
<RootNamespace>$(AssemblyName)</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.0-beta.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Application\Application.csproj" />
<ProjectReference Include="..\Domain\Domain.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Migration\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,15 @@
namespace Mirea.Api.DataAccess.Persistence.Properties;
public enum DatabaseEnum
{
Mysql,
Sqlite,
PostgresSql
}
public class DbSettings
{
public bool IsDoneConfiguration { get; set; }
public DatabaseEnum TypeDatabase { get; set; }
public required string ConnectionStringSql { get; set; }
public DatabaseEnum? MigrateTo { get; set; }
}

View File

@ -0,0 +1,33 @@
using Microsoft.EntityFrameworkCore;
using Mirea.Api.DataAccess.Domain.Schedule;
using Mirea.Api.DataAccess.Persistence.EntityTypeConfigurations.Schedule;
namespace Mirea.Api.DataAccess.Persistence;
public class UberDbContext(DbContextOptions<UberDbContext> options) : DbContext(options)
{
public DbSet<Campus> Campuses { get; set; } = null!;
public DbSet<Discipline> Disciplines { get; set; } = null!;
public DbSet<Faculty> Faculties { get; set; } = null!;
public DbSet<Group> Groups { get; set; } = null!;
public DbSet<LectureHall> LectureHalls { get; set; } = null!;
public DbSet<LessonAssociation> LessonAssociations { get; set; } = null!;
public DbSet<Lesson> Lessons { get; set; } = null!;
public DbSet<Professor> Professors { get; set; } = null!;
public DbSet<TypeOfOccupation> TypeOfOccupations { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new CampusConfiguration());
modelBuilder.ApplyConfiguration(new DisciplineConfiguration());
modelBuilder.ApplyConfiguration(new FacultyConfiguration());
modelBuilder.ApplyConfiguration(new GroupConfiguration());
modelBuilder.ApplyConfiguration(new LectureHallConfiguration());
modelBuilder.ApplyConfiguration(new LessonConfiguration());
modelBuilder.ApplyConfiguration(new ProfessorConfiguration());
modelBuilder.ApplyConfiguration(new LessonAssociationConfiguration());
modelBuilder.ApplyConfiguration(new TypeOfOccupationConfiguration());
base.OnModelCreating(modelBuilder);
}
}