Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

bin/
obj/


*.user
*.suo
*.userosscache
*.sln.docstates


*.vs/
*.vscode/

*.bak
*.tmp

*.log
17 changes: 17 additions & 0 deletions ThirdCourseTask.Data/Entities/TaskEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ThirdCourseTask.Data.Entities
{
public class TaskEntity
{
public int Id { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public bool IsCompleted { get; set; }
public DateTime CreatedAt { get; set; }
}
}
13 changes: 13 additions & 0 deletions ThirdCourseTask.Data/ThirdCourseTask.Data.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Dapper" Version="2.1.66" />
</ItemGroup>

</Project>
48 changes: 48 additions & 0 deletions ThirdCourseTask.Infrastructure/Database/DbConnectionFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System.Data;
using Microsoft.Data.SqlClient;
using Npgsql;
using Microsoft.Extensions.Configuration;

namespace ThirdCourseTask.Infrastructure.Database;

public sealed class DbConnectionFactory : IDbConnectionFactory
{
private readonly string _sqlConnectionString;
private readonly string _pgConnectionString;
private readonly DbProvider _provider;

public DbConnectionFactory(IConfiguration configuration)
{
_sqlConnectionString = configuration.GetConnectionString("SqlServer")
?? throw new InvalidOperationException("Missing 'SqlServer' connection string.");

_pgConnectionString = configuration.GetConnectionString("Postgres")
?? throw new InvalidOperationException("Missing 'Postgres' connection string.");

string? providerFromEnv = Environment.GetEnvironmentVariable("DB_PROVIDER");

if (providerFromEnv != null && providerFromEnv.ToLowerInvariant() == "postgres")
{
_provider = DbProvider.Postgres;
}
else
{
_provider = DbProvider.SqlServer;
}
}

public IDbConnection Create()
{
if (_provider == DbProvider.Postgres)
{
return new NpgsqlConnection(_pgConnectionString);
}

if (_provider == DbProvider.SqlServer)
{
return new SqlConnection(_sqlConnectionString);
}

throw new InvalidOperationException("Unsupported provider: " + _provider);
}
}
6 changes: 6 additions & 0 deletions ThirdCourseTask.Infrastructure/Database/DbProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace ThirdCourseTask.Infrastructure.Database;
public enum DbProvider
{
SqlServer,
Postgres
}
14 changes: 14 additions & 0 deletions ThirdCourseTask.Infrastructure/Database/IDbConnectionFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ThirdCourseTask.Infrastructure.Database
{
public interface IDbConnectionFactory
{
IDbConnection Create();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ThirdCourseTask.Data.Entities;

namespace ThirdCourseTask.Infrastructure.Database.Repositories.Abstractions
{
public interface ITaskRepository
{
Task<int> CreateAsync(TaskEntity entity);
Task<IReadOnlyList<TaskEntity>> GetAllAsync();
Task<bool> SetCompletedAsync(int taskId, bool isCompleted);
Task<bool> DeleteAsync(int taskId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System.Data;
using Dapper;
using ThirdCourseTask.Data.Entities;
using ThirdCourseTask.Infrastructure.Database.Repositories.Abstractions;

namespace ThirdCourseTask.Infrastructure.Database.Repositories.Implementations
{
public class TaskRepository : ITaskRepository
{
private readonly IDbConnectionFactory _factory;

public TaskRepository(IDbConnectionFactory connectionFactory)
{
_factory = connectionFactory;
}

public async Task<int> CreateAsync(TaskEntity entity)
{
const string sql = @"
INSERT INTO dbo.Tasks(Title, [Description], IsCompleted, CreatedAt)
VALUES (@Title, @Description, @IsCompleted, SYSUTCDATETIME());
SELECT CAST(SCOPE_IDENTITY() AS INT);";

try
{
using var dbConnection = _factory.Create();
int createdTaskId = await dbConnection.ExecuteScalarAsync<int>(sql, entity);
return createdTaskId;
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to create a new task.", ex);
}
}

public async Task<IReadOnlyList<TaskEntity>> GetAllAsync()
{
const string sql = @"SELECT Id, Title, [Description], IsCompleted, CreatedAt
FROM dbo.Tasks ORDER BY CreatedAt DESC;";

try
{
using var dbConnection = _factory.Create();
var tasks = await dbConnection.QueryAsync<TaskEntity>(sql);
return tasks.ToList();
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to retrieve tasks.", ex);
}
}

public async Task<bool> SetCompletedAsync(int id, bool isCompleted)
{
const string sql = @"UPDATE dbo.Tasks SET IsCompleted = @isCompleted WHERE Id = @id;";

try
{
using var dbConnection = _factory.Create();
int affectedRows = await dbConnection.ExecuteAsync(sql, new { id, isCompleted });
return affectedRows > 0;
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to update completion status for task Id=" + id + ".", ex);
}
}

public async Task<bool> DeleteAsync(int id)
{
const string sql = @"DELETE FROM dbo.Tasks WHERE Id = @id;";

try
{
using var dbConnection = _factory.Create();
int affectedRows = await dbConnection.ExecuteAsync(sql, new { id });
return affectedRows > 0;
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to delete task Id=" + id + ".", ex);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ThirdCourseTask.Data.Entities;

namespace ThirdCourseTask.Infrastructure.Services.Abstractions
{
public interface ITaskService
{
Task<int> AddAsync(string title, string? description);
Task<IReadOnlyList<TaskEntity>> GetAllAsync();
Task<bool> CompleteAsync(int id, bool isCompleted);
Task<bool> RemoveAsync(int id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ThirdCourseTask.Data.Entities;
using ThirdCourseTask.Infrastructure.Database.Repositories.Abstractions;
using ThirdCourseTask.Infrastructure.Services.Abstractions;

namespace ThirdCourseTask.Infrastructure.Services.Implementations
{
public class TaskService : ITaskService
{
private readonly ITaskRepository _taskRepository;

public TaskService(ITaskRepository taskRepository)
{
_taskRepository = taskRepository;
}

public async Task<int> AddAsync(string title, string? description)
{
return await _taskRepository.CreateAsync(new TaskEntity
{
Title = title,
Description = description,
IsCompleted = false,
});
}

public async Task<IReadOnlyList<TaskEntity>> GetAllAsync()
{
return await _taskRepository.GetAllAsync();
}


public async Task<bool> CompleteAsync(int id, bool isCompleted)
{
return await _taskRepository.SetCompletedAsync(id, isCompleted);
}


public async Task<bool> RemoveAsync(int id)
{
return await _taskRepository.DeleteAsync(id);
}

}
}
10 changes: 10 additions & 0 deletions ThirdCourseTask.Infrastructure/SqlScripts/DataBaseInit.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'Tasks')
BEGIN
CREATE TABLE dbo.Tasks (
Id INT IDENTITY(1,1) PRIMARY KEY,
Title NVARCHAR(200) NOT NULL,
Description NVARCHAR(MAX) NULL,
IsCompleted BIT NOT NULL DEFAULT(0),
CreatedAt DATETIME2(0) NOT NULL DEFAULT (SYSUTCDATETIME())
);
END
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<Folder Include="SqlScripts\" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.9" />
<PackageReference Include="Npgsql" Version="9.0.4" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ThirdCourseTask.Data\ThirdCourseTask.Data.csproj" />
</ItemGroup>

</Project>
67 changes: 67 additions & 0 deletions ThirdCourseTask.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Элементы решения", "Элементы решения", "{754FC069-D67B-A9D7-50A1-8D1CA196D8F1}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThirdCourseTask.Infrastructure", "ThirdCourseTask.Infrastructure\ThirdCourseTask.Infrastructure.csproj", "{396FDC09-A11B-4031-9425-5A785CDD357E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThirdCourseTask", "ThirdCourseTask\ThirdCourseTask.csproj", "{4667C4D6-91DD-4431-8A08-6500E1F79C82}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThirdCourseTask.Data", "ThirdCourseTask.Data\ThirdCourseTask.Data.csproj", "{A508F38B-0BC5-4E68-9067-932100DA97B5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{396FDC09-A11B-4031-9425-5A785CDD357E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{396FDC09-A11B-4031-9425-5A785CDD357E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{396FDC09-A11B-4031-9425-5A785CDD357E}.Debug|x64.ActiveCfg = Debug|Any CPU
{396FDC09-A11B-4031-9425-5A785CDD357E}.Debug|x64.Build.0 = Debug|Any CPU
{396FDC09-A11B-4031-9425-5A785CDD357E}.Debug|x86.ActiveCfg = Debug|Any CPU
{396FDC09-A11B-4031-9425-5A785CDD357E}.Debug|x86.Build.0 = Debug|Any CPU
{396FDC09-A11B-4031-9425-5A785CDD357E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{396FDC09-A11B-4031-9425-5A785CDD357E}.Release|Any CPU.Build.0 = Release|Any CPU
{396FDC09-A11B-4031-9425-5A785CDD357E}.Release|x64.ActiveCfg = Release|Any CPU
{396FDC09-A11B-4031-9425-5A785CDD357E}.Release|x64.Build.0 = Release|Any CPU
{396FDC09-A11B-4031-9425-5A785CDD357E}.Release|x86.ActiveCfg = Release|Any CPU
{396FDC09-A11B-4031-9425-5A785CDD357E}.Release|x86.Build.0 = Release|Any CPU
{4667C4D6-91DD-4431-8A08-6500E1F79C82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4667C4D6-91DD-4431-8A08-6500E1F79C82}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4667C4D6-91DD-4431-8A08-6500E1F79C82}.Debug|x64.ActiveCfg = Debug|Any CPU
{4667C4D6-91DD-4431-8A08-6500E1F79C82}.Debug|x64.Build.0 = Debug|Any CPU
{4667C4D6-91DD-4431-8A08-6500E1F79C82}.Debug|x86.ActiveCfg = Debug|Any CPU
{4667C4D6-91DD-4431-8A08-6500E1F79C82}.Debug|x86.Build.0 = Debug|Any CPU
{4667C4D6-91DD-4431-8A08-6500E1F79C82}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4667C4D6-91DD-4431-8A08-6500E1F79C82}.Release|Any CPU.Build.0 = Release|Any CPU
{4667C4D6-91DD-4431-8A08-6500E1F79C82}.Release|x64.ActiveCfg = Release|Any CPU
{4667C4D6-91DD-4431-8A08-6500E1F79C82}.Release|x64.Build.0 = Release|Any CPU
{4667C4D6-91DD-4431-8A08-6500E1F79C82}.Release|x86.ActiveCfg = Release|Any CPU
{4667C4D6-91DD-4431-8A08-6500E1F79C82}.Release|x86.Build.0 = Release|Any CPU
{A508F38B-0BC5-4E68-9067-932100DA97B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A508F38B-0BC5-4E68-9067-932100DA97B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A508F38B-0BC5-4E68-9067-932100DA97B5}.Debug|x64.ActiveCfg = Debug|Any CPU
{A508F38B-0BC5-4E68-9067-932100DA97B5}.Debug|x64.Build.0 = Debug|Any CPU
{A508F38B-0BC5-4E68-9067-932100DA97B5}.Debug|x86.ActiveCfg = Debug|Any CPU
{A508F38B-0BC5-4E68-9067-932100DA97B5}.Debug|x86.Build.0 = Debug|Any CPU
{A508F38B-0BC5-4E68-9067-932100DA97B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A508F38B-0BC5-4E68-9067-932100DA97B5}.Release|Any CPU.Build.0 = Release|Any CPU
{A508F38B-0BC5-4E68-9067-932100DA97B5}.Release|x64.ActiveCfg = Release|Any CPU
{A508F38B-0BC5-4E68-9067-932100DA97B5}.Release|x64.Build.0 = Release|Any CPU
{A508F38B-0BC5-4E68-9067-932100DA97B5}.Release|x86.ActiveCfg = Release|Any CPU
{A508F38B-0BC5-4E68-9067-932100DA97B5}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
Loading