diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..93af010
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,18 @@
+
+bin/
+obj/
+
+
+*.user
+*.suo
+*.userosscache
+*.sln.docstates
+
+
+*.vs/
+*.vscode/
+
+*.bak
+*.tmp
+
+*.log
\ No newline at end of file
diff --git a/ThirdCourseTask.Data/Entities/TaskEntity.cs b/ThirdCourseTask.Data/Entities/TaskEntity.cs
new file mode 100644
index 0000000..a810271
--- /dev/null
+++ b/ThirdCourseTask.Data/Entities/TaskEntity.cs
@@ -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; }
+ }
+}
diff --git a/ThirdCourseTask.Data/ThirdCourseTask.Data.csproj b/ThirdCourseTask.Data/ThirdCourseTask.Data.csproj
new file mode 100644
index 0000000..6bc3899
--- /dev/null
+++ b/ThirdCourseTask.Data/ThirdCourseTask.Data.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/ThirdCourseTask.Infrastructure/Database/DbConnectionFactory.cs b/ThirdCourseTask.Infrastructure/Database/DbConnectionFactory.cs
new file mode 100644
index 0000000..916d181
--- /dev/null
+++ b/ThirdCourseTask.Infrastructure/Database/DbConnectionFactory.cs
@@ -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);
+ }
+}
diff --git a/ThirdCourseTask.Infrastructure/Database/DbProvider.cs b/ThirdCourseTask.Infrastructure/Database/DbProvider.cs
new file mode 100644
index 0000000..94ee7d1
--- /dev/null
+++ b/ThirdCourseTask.Infrastructure/Database/DbProvider.cs
@@ -0,0 +1,6 @@
+namespace ThirdCourseTask.Infrastructure.Database;
+public enum DbProvider
+{
+ SqlServer,
+ Postgres
+}
diff --git a/ThirdCourseTask.Infrastructure/Database/IDbConnectionFactory.cs b/ThirdCourseTask.Infrastructure/Database/IDbConnectionFactory.cs
new file mode 100644
index 0000000..78ffe38
--- /dev/null
+++ b/ThirdCourseTask.Infrastructure/Database/IDbConnectionFactory.cs
@@ -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();
+ }
+}
diff --git a/ThirdCourseTask.Infrastructure/Repositories/Abstractions/ITaskRepository.cs b/ThirdCourseTask.Infrastructure/Repositories/Abstractions/ITaskRepository.cs
new file mode 100644
index 0000000..3df27b4
--- /dev/null
+++ b/ThirdCourseTask.Infrastructure/Repositories/Abstractions/ITaskRepository.cs
@@ -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 CreateAsync(TaskEntity entity);
+ Task> GetAllAsync();
+ Task SetCompletedAsync(int taskId, bool isCompleted);
+ Task DeleteAsync(int taskId);
+ }
+}
diff --git a/ThirdCourseTask.Infrastructure/Repositories/Implementations/TaskRepository.cs b/ThirdCourseTask.Infrastructure/Repositories/Implementations/TaskRepository.cs
new file mode 100644
index 0000000..ff4817e
--- /dev/null
+++ b/ThirdCourseTask.Infrastructure/Repositories/Implementations/TaskRepository.cs
@@ -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 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(sql, entity);
+ return createdTaskId;
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException("Failed to create a new task.", ex);
+ }
+ }
+
+ public async Task> 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(sql);
+ return tasks.ToList();
+ }
+ catch (Exception ex)
+ {
+ throw new InvalidOperationException("Failed to retrieve tasks.", ex);
+ }
+ }
+
+ public async Task 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 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);
+ }
+ }
+ }
+}
diff --git a/ThirdCourseTask.Infrastructure/Services/Abstractions/ITaskService.cs b/ThirdCourseTask.Infrastructure/Services/Abstractions/ITaskService.cs
new file mode 100644
index 0000000..e266b84
--- /dev/null
+++ b/ThirdCourseTask.Infrastructure/Services/Abstractions/ITaskService.cs
@@ -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 AddAsync(string title, string? description);
+ Task> GetAllAsync();
+ Task CompleteAsync(int id, bool isCompleted);
+ Task RemoveAsync(int id);
+ }
+}
diff --git a/ThirdCourseTask.Infrastructure/Services/Implementations/TaskService.cs b/ThirdCourseTask.Infrastructure/Services/Implementations/TaskService.cs
new file mode 100644
index 0000000..311ee7d
--- /dev/null
+++ b/ThirdCourseTask.Infrastructure/Services/Implementations/TaskService.cs
@@ -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 AddAsync(string title, string? description)
+ {
+ return await _taskRepository.CreateAsync(new TaskEntity
+ {
+ Title = title,
+ Description = description,
+ IsCompleted = false,
+ });
+ }
+
+ public async Task> GetAllAsync()
+ {
+ return await _taskRepository.GetAllAsync();
+ }
+
+
+ public async Task CompleteAsync(int id, bool isCompleted)
+ {
+ return await _taskRepository.SetCompletedAsync(id, isCompleted);
+ }
+
+
+ public async Task RemoveAsync(int id)
+ {
+ return await _taskRepository.DeleteAsync(id);
+ }
+
+ }
+}
diff --git a/ThirdCourseTask.Infrastructure/SqlScripts/DataBaseInit.sql b/ThirdCourseTask.Infrastructure/SqlScripts/DataBaseInit.sql
new file mode 100644
index 0000000..bedcf26
--- /dev/null
+++ b/ThirdCourseTask.Infrastructure/SqlScripts/DataBaseInit.sql
@@ -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
diff --git a/ThirdCourseTask.Infrastructure/ThirdCourseTask.Infrastructure.csproj b/ThirdCourseTask.Infrastructure/ThirdCourseTask.Infrastructure.csproj
new file mode 100644
index 0000000..4153347
--- /dev/null
+++ b/ThirdCourseTask.Infrastructure/ThirdCourseTask.Infrastructure.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ThirdCourseTask.sln b/ThirdCourseTask.sln
new file mode 100644
index 0000000..6fc3ecd
--- /dev/null
+++ b/ThirdCourseTask.sln
@@ -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
diff --git a/ThirdCourseTask/Program.cs b/ThirdCourseTask/Program.cs
new file mode 100644
index 0000000..303a58e
--- /dev/null
+++ b/ThirdCourseTask/Program.cs
@@ -0,0 +1,70 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using ThirdCourseTask.Infrastructure.Database;
+using ThirdCourseTask.Infrastructure.Database.Repositories.Abstractions;
+using ThirdCourseTask.Infrastructure.Database.Repositories.Implementations;
+using ThirdCourseTask.Infrastructure.Services.Abstractions;
+using ThirdCourseTask.Infrastructure.Services.Implementations;
+using ThirdCourseTask.UI;
+
+Console.WriteLine("Select a database:");
+Console.WriteLine("1) SQL Server");
+Console.WriteLine("2) PostgreSQL");
+
+string? choice = null;
+
+while (choice != "1" && choice != "2")
+{
+ Console.Write("Enter 1 or 2: ");
+ choice = Console.ReadLine();
+}
+
+if (choice == "2")
+{
+ Environment.SetEnvironmentVariable("DB_PROVIDER", "Postgres");
+}
+else
+{
+ Environment.SetEnvironmentVariable("DB_PROVIDER", "SqlServer");
+}
+
+using var host = Host.CreateDefaultBuilder(args)
+ .ConfigureAppConfiguration(cfg =>
+ {
+ cfg.Sources.Clear();
+ cfg.SetBasePath(AppContext.BaseDirectory);
+ cfg.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
+ cfg.AddEnvironmentVariables();
+ })
+ .ConfigureServices(services =>
+ {
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+ })
+ .Build();
+
+
+var inputService = host.Services.GetRequiredService();
+
+while (true)
+{
+ try
+ {
+ await inputService.RunAsync();
+ }
+ catch (ApplicationException ex)
+ {
+ Console.WriteLine("Application error: " + ex.Message);
+ }
+ catch (InvalidOperationException ex)
+ {
+ Console.WriteLine("Operation error: " + ex.Message);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Unexpected error: " + ex.Message);
+ }
+}
diff --git a/ThirdCourseTask/ThirdCourseTask.csproj b/ThirdCourseTask/ThirdCourseTask.csproj
new file mode 100644
index 0000000..5d71abb
--- /dev/null
+++ b/ThirdCourseTask/ThirdCourseTask.csproj
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+
+
+
+
+
+ PreserveNewest
+
+
+
+
diff --git a/ThirdCourseTask/UI/ConsoleInputService.cs b/ThirdCourseTask/UI/ConsoleInputService.cs
new file mode 100644
index 0000000..f85123b
--- /dev/null
+++ b/ThirdCourseTask/UI/ConsoleInputService.cs
@@ -0,0 +1,199 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using ThirdCourseTask.Infrastructure.Services.Abstractions;
+
+namespace ThirdCourseTask.UI
+{
+ public class ConsoleInputService : IInputService
+ {
+ private ITaskService _taskService;
+
+ public ConsoleInputService(ITaskService taskService)
+ {
+ _taskService = taskService;
+ }
+
+ public async Task RunAsync()
+ {
+ var key = Console.ReadLine();
+ Menu();
+ switch (key)
+ {
+ case "1":
+ {
+ await AddTaskAsync();
+ break;
+ }
+
+
+ case "2":
+ {
+ await ListTasksAsync();
+ break;
+ }
+
+
+ case "3":
+ {
+ await ChangeTaskStatus();
+ break;
+ }
+
+ case "4":
+ {
+ await RemoveTaskByIdAsync();
+ break;
+ }
+
+ case "0":
+ return;
+
+ default:
+ Console.WriteLine("The operation you selected does not exist. Please try again.");
+ break;
+ }
+ }
+
+ private void Menu()
+ {
+ Console.WriteLine("\n=== Task Manager ===");
+ Console.WriteLine("1) Add Task");
+ Console.WriteLine("2) Show All Tasks");
+ Console.WriteLine("3) Change Task status");
+ Console.WriteLine("4) Remove Task By Id");
+ }
+
+ public async Task AddTaskAsync()
+ {
+ try
+ {
+ Console.Write("Title: ");
+ string? title = Console.ReadLine();
+
+ if (string.IsNullOrWhiteSpace(title))
+ {
+ Console.WriteLine("Title cannot be empty.");
+ return;
+ }
+
+ Console.Write("Description: ");
+ string? description = Console.ReadLine();
+
+ int createdTaskId = await _taskService.AddAsync(title, description);
+ Console.WriteLine("Created. Id=" + createdTaskId);
+ }
+
+ catch (Exception ex)
+ {
+ Console.WriteLine("Unexpected error: " + ex.Message);
+ }
+ }
+
+ private async Task ListTasksAsync()
+ {
+ try
+ {
+ var tasks = await _taskService.GetAllAsync();
+
+ if (tasks.Count == 0)
+ {
+ Console.WriteLine("Empty.");
+ return;
+ }
+
+ foreach (var task in tasks)
+ {
+ Console.WriteLine("Id: " + task.Id + ", Title: " + task.Title + ", Completed: " + task.IsCompleted + ", CreatedAt: " + task.CreatedAt);
+ }
+ }
+
+ catch (Exception ex)
+ {
+ Console.WriteLine("Unexpected error: " + ex.Message);
+ }
+ }
+
+ private async Task ChangeTaskStatus()
+ {
+ try
+ {
+ Console.Write("Id: ");
+ string? input = Console.ReadLine();
+
+ int taskId;
+ bool parsed = int.TryParse(input, out taskId);
+
+ if (!parsed)
+ {
+ Console.WriteLine("Invalid Id.");
+ return;
+ }
+
+ Console.Write("Completed? (y/n): ");
+ string? ans = Console.ReadLine();
+
+ bool isCompleted = false;
+
+ if (ans == "y" || ans == "1")
+ {
+ isCompleted = true;
+ }
+
+ bool updated = await _taskService.CompleteAsync(taskId, isCompleted);
+
+ if (updated)
+ {
+ Console.WriteLine("Updated.");
+ }
+ else
+ {
+ Console.WriteLine("Not found.");
+ }
+ }
+
+ catch (Exception ex)
+ {
+ Console.WriteLine("Unexpected error: " + ex.Message);
+ }
+
+ }
+
+ private async Task RemoveTaskByIdAsync()
+ {
+ try
+ {
+ Console.Write("Id: ");
+ string? input = Console.ReadLine();
+
+ int taskId;
+ bool parsed = int.TryParse(input, out taskId);
+
+ if (!parsed)
+ {
+ Console.WriteLine("Invalid Id.");
+ return;
+ }
+
+ bool deleted = await _taskService.RemoveAsync(taskId);
+
+ if (deleted)
+ {
+ Console.WriteLine("Deleted.");
+ }
+ else
+ {
+ Console.WriteLine("Not found.");
+ }
+ }
+
+ catch (Exception ex)
+ {
+ Console.WriteLine("Unexpected error: " + ex.Message);
+ }
+
+ }
+ }
+}
diff --git a/ThirdCourseTask/UI/IConsoleInputService.cs b/ThirdCourseTask/UI/IConsoleInputService.cs
new file mode 100644
index 0000000..0cb3cd2
--- /dev/null
+++ b/ThirdCourseTask/UI/IConsoleInputService.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ThirdCourseTask.UI
+{
+ public interface IInputService
+ {
+ Task RunAsync();
+ }
+}
diff --git a/ThirdCourseTask/appsettings.json b/ThirdCourseTask/appsettings.json
new file mode 100644
index 0000000..0c7b3a0
--- /dev/null
+++ b/ThirdCourseTask/appsettings.json
@@ -0,0 +1,6 @@
+{
+ "ConnectionStrings": {
+ "SqlServer": "Server=DESKTOP-RMIFAFV\\SQLSERVER2019;Database=ThirdCourseTaskDb;Integrated Security=True",
+ "Postgres": "Host=localhost;Port=5432;Username=postgres;Password=postgres;Database=thirdcoursetaskdb"
+ }
+}