From f7464c4c11a91f3f1e10e6639d40c9d0324736ae Mon Sep 17 00:00:00 2001 From: Tafdav <46773345+Tafdav@users.noreply.github.com> Date: Tue, 22 Jan 2019 16:12:58 +0200 Subject: [PATCH 1/4] Can create my database and my user account read and write normally, still working on chache --- data/createdatabase.sql | 76 +++++++++---------- data/createuser.sql | 12 +-- ...0190122084316_initailisecreate.Designer.cs | 57 ++++++++++++++ .../20190122084316_initailisecreate.cs | 40 ++++++++++ .../TimesheetContextModelSnapshot.cs | 55 ++++++++++++++ src/MyTimesheet/MyTimesheet/Startup.cs | 4 +- 6 files changed, 199 insertions(+), 45 deletions(-) create mode 100644 src/MyTimesheet/MyTimesheet/Migrations/20190122084316_initailisecreate.Designer.cs create mode 100644 src/MyTimesheet/MyTimesheet/Migrations/20190122084316_initailisecreate.cs create mode 100644 src/MyTimesheet/MyTimesheet/Migrations/TimesheetContextModelSnapshot.cs diff --git a/data/createdatabase.sql b/data/createdatabase.sql index 41b5a17..430f69a 100644 --- a/data/createdatabase.sql +++ b/data/createdatabase.sql @@ -1,115 +1,115 @@ USE [master] GO -/****** Object: Database [sql101.firstname.lastname] Script Date: 1/21/2019 9:11:50 PM ******/ -CREATE DATABASE [sql101.firstname.lastname] +/****** Object: Database [sql101.tafara.vurayayi] Script Date: 1/21/2019 9:11:50 PM ******/ +CREATE DATABASE [sql101.tafara.vurayayi] CONTAINMENT = NONE ON PRIMARY -( NAME = N'sql101.firstname.lastname', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\sql101.firstname.lastname.mdf' , SIZE = 8192KB , MAXSIZE = UNLIMITED, FILEGROWTH = 65536KB ) +( NAME = N'sql101.tafara.vurayayi', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\sql101.tafara.vurayayi.mdf' , SIZE = 8192KB , MAXSIZE = UNLIMITED, FILEGROWTH = 65536KB ) LOG ON -( NAME = N'sql101.firstname.lastname_log', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\sql101.firstname.lastname_log.ldf' , SIZE = 8192KB , MAXSIZE = 2048GB , FILEGROWTH = 65536KB ) +( NAME = N'sql101.tafara.vurayayi_log', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\DATA\sql101.tafara.vurayayi_log.ldf' , SIZE = 8192KB , MAXSIZE = 2048GB , FILEGROWTH = 65536KB ) GO -ALTER DATABASE [sql101.firstname.lastname] SET COMPATIBILITY_LEVEL = 130 +ALTER DATABASE [sql101.tafara.vurayayi] SET COMPATIBILITY_LEVEL = 130 GO IF (1 = FULLTEXTSERVICEPROPERTY('IsFullTextInstalled')) begin -EXEC [sql101.firstname.lastname].[dbo].[sp_fulltext_database] @action = 'enable' +EXEC [sql101.tafara.vurayayi].[dbo].[sp_fulltext_database] @action = 'enable' end GO -ALTER DATABASE [sql101.firstname.lastname] SET ANSI_NULL_DEFAULT OFF +ALTER DATABASE [sql101.tafara.vurayayi] SET ANSI_NULL_DEFAULT OFF GO -ALTER DATABASE [sql101.firstname.lastname] SET ANSI_NULLS OFF +ALTER DATABASE [sql101.tafara.vurayayi] SET ANSI_NULLS OFF GO -ALTER DATABASE [sql101.firstname.lastname] SET ANSI_PADDING OFF +ALTER DATABASE [sql101.tafara.vurayayi] SET ANSI_PADDING OFF GO -ALTER DATABASE [sql101.firstname.lastname] SET ANSI_WARNINGS OFF +ALTER DATABASE [sql101.tafara.vurayayi] SET ANSI_WARNINGS OFF GO -ALTER DATABASE [sql101.firstname.lastname] SET ARITHABORT OFF +ALTER DATABASE [sql101.tafara.vurayayi] SET ARITHABORT OFF GO -ALTER DATABASE [sql101.firstname.lastname] SET AUTO_CLOSE OFF +ALTER DATABASE [sql101.tafara.vurayayi] SET AUTO_CLOSE OFF GO -ALTER DATABASE [sql101.firstname.lastname] SET AUTO_SHRINK OFF +ALTER DATABASE [sql101.tafara.vurayayi] SET AUTO_SHRINK OFF GO -ALTER DATABASE [sql101.firstname.lastname] SET AUTO_UPDATE_STATISTICS ON +ALTER DATABASE [sql101.tafara.vurayayi] SET AUTO_UPDATE_STATISTICS ON GO -ALTER DATABASE [sql101.firstname.lastname] SET CURSOR_CLOSE_ON_COMMIT OFF +ALTER DATABASE [sql101.tafara.vurayayi] SET CURSOR_CLOSE_ON_COMMIT OFF GO -ALTER DATABASE [sql101.firstname.lastname] SET CURSOR_DEFAULT GLOBAL +ALTER DATABASE [sql101.tafara.vurayayi] SET CURSOR_DEFAULT GLOBAL GO -ALTER DATABASE [sql101.firstname.lastname] SET CONCAT_NULL_YIELDS_NULL OFF +ALTER DATABASE [sql101.tafara.vurayayi] SET CONCAT_NULL_YIELDS_NULL OFF GO -ALTER DATABASE [sql101.firstname.lastname] SET NUMERIC_ROUNDABORT OFF +ALTER DATABASE [sql101.tafara.vurayayi] SET NUMERIC_ROUNDABORT OFF GO -ALTER DATABASE [sql101.firstname.lastname] SET QUOTED_IDENTIFIER OFF +ALTER DATABASE [sql101.tafara.vurayayi] SET QUOTED_IDENTIFIER OFF GO -ALTER DATABASE [sql101.firstname.lastname] SET RECURSIVE_TRIGGERS OFF +ALTER DATABASE [sql101.tafara.vurayayi] SET RECURSIVE_TRIGGERS OFF GO -ALTER DATABASE [sql101.firstname.lastname] SET DISABLE_BROKER +ALTER DATABASE [sql101.tafara.vurayayi] SET DISABLE_BROKER GO -ALTER DATABASE [sql101.firstname.lastname] SET AUTO_UPDATE_STATISTICS_ASYNC OFF +ALTER DATABASE [sql101.tafara.vurayayi] SET AUTO_UPDATE_STATISTICS_ASYNC OFF GO -ALTER DATABASE [sql101.firstname.lastname] SET DATE_CORRELATION_OPTIMIZATION OFF +ALTER DATABASE [sql101.tafara.vurayayi] SET DATE_CORRELATION_OPTIMIZATION OFF GO -ALTER DATABASE [sql101.firstname.lastname] SET TRUSTWORTHY OFF +ALTER DATABASE [sql101.tafara.vurayayi] SET TRUSTWORTHY OFF GO -ALTER DATABASE [sql101.firstname.lastname] SET ALLOW_SNAPSHOT_ISOLATION OFF +ALTER DATABASE [sql101.tafara.vurayayi] SET ALLOW_SNAPSHOT_ISOLATION OFF GO -ALTER DATABASE [sql101.firstname.lastname] SET PARAMETERIZATION SIMPLE +ALTER DATABASE [sql101.tafara.vurayayi] SET PARAMETERIZATION SIMPLE GO -ALTER DATABASE [sql101.firstname.lastname] SET READ_COMMITTED_SNAPSHOT OFF +ALTER DATABASE [sql101.tafara.vurayayi] SET READ_COMMITTED_SNAPSHOT OFF GO -ALTER DATABASE [sql101.firstname.lastname] SET HONOR_BROKER_PRIORITY OFF +ALTER DATABASE [sql101.tafara.vurayayi] SET HONOR_BROKER_PRIORITY OFF GO -ALTER DATABASE [sql101.firstname.lastname] SET RECOVERY FULL +ALTER DATABASE [sql101.tafara.vurayayi] SET RECOVERY FULL GO -ALTER DATABASE [sql101.firstname.lastname] SET MULTI_USER +ALTER DATABASE [sql101.tafara.vurayayi] SET MULTI_USER GO -ALTER DATABASE [sql101.firstname.lastname] SET PAGE_VERIFY CHECKSUM +ALTER DATABASE [sql101.tafara.vurayayi] SET PAGE_VERIFY CHECKSUM GO -ALTER DATABASE [sql101.firstname.lastname] SET DB_CHAINING OFF +ALTER DATABASE [sql101.tafara.vurayayi] SET DB_CHAINING OFF GO -ALTER DATABASE [sql101.firstname.lastname] SET FILESTREAM( NON_TRANSACTED_ACCESS = OFF ) +ALTER DATABASE [sql101.tafara.vurayayi] SET FILESTREAM( NON_TRANSACTED_ACCESS = OFF ) GO -ALTER DATABASE [sql101.firstname.lastname] SET TARGET_RECOVERY_TIME = 60 SECONDS +ALTER DATABASE [sql101.tafara.vurayayi] SET TARGET_RECOVERY_TIME = 60 SECONDS GO -ALTER DATABASE [sql101.firstname.lastname] SET DELAYED_DURABILITY = DISABLED +ALTER DATABASE [sql101.tafara.vurayayi] SET DELAYED_DURABILITY = DISABLED GO -ALTER DATABASE [sql101.firstname.lastname] SET QUERY_STORE = OFF +ALTER DATABASE [sql101.tafara.vurayayi] SET QUERY_STORE = OFF GO -USE [sql101.firstname.lastname] +USE [sql101.tafara.vurayayi] GO ALTER DATABASE SCOPED CONFIGURATION SET LEGACY_CARDINALITY_ESTIMATION = OFF; @@ -124,7 +124,7 @@ GO ALTER DATABASE SCOPED CONFIGURATION SET QUERY_OPTIMIZER_HOTFIXES = OFF; GO -ALTER DATABASE [sql101.firstname.lastname] SET READ_WRITE +ALTER DATABASE [sql101.tafara.vurayayi] SET READ_WRITE GO diff --git a/data/createuser.sql b/data/createuser.sql index bcf3038..c6a0f66 100644 --- a/data/createuser.sql +++ b/data/createuser.sql @@ -1,18 +1,18 @@ USE [master] GO -CREATE LOGIN [firstnamelastname] WITH PASSWORD=N'rabbit123!@#' MUST_CHANGE, DEFAULT_DATABASE=[sql101.firstname.lastname], CHECK_EXPIRATION=ON, CHECK_POLICY=ON +CREATE LOGIN [tafaravurayayi] WITH PASSWORD=N'rabbit123!@#' MUST_CHANGE, DEFAULT_DATABASE=[sql101.tafara.vurayayi], CHECK_EXPIRATION=ON, CHECK_POLICY=ON GO -use [sql101.firstname.lastname] +use [sql101.tafara.vurayayi] GO use [master] GO -USE [sql101.firstname.lastname] +USE [sql101.tafara.vurayayi] GO -CREATE USER [firstnamelastname] FOR LOGIN [firstnamelastname] +CREATE USER [tafaravurayayi] FOR LOGIN [tafaravurayayi] GO -USE [sql101.firstname.lastname] +USE [sql101.tafara.vurayayi] GO -ALTER ROLE [db_owner] ADD MEMBER [firstnamelastname] +ALTER ROLE [db_owner] ADD MEMBER [tafaravurayayi] GO diff --git a/src/MyTimesheet/MyTimesheet/Migrations/20190122084316_initailisecreate.Designer.cs b/src/MyTimesheet/MyTimesheet/Migrations/20190122084316_initailisecreate.Designer.cs new file mode 100644 index 0000000..3f406f4 --- /dev/null +++ b/src/MyTimesheet/MyTimesheet/Migrations/20190122084316_initailisecreate.Designer.cs @@ -0,0 +1,57 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MyTimesheet.Models; + +namespace MyTimesheet.Migrations +{ + [DbContext(typeof(TimesheetContext))] + [Migration("20190122084316_initailisecreate")] + partial class initailisecreate + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.1-servicing-10028") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("MyTimesheet.Models.TimesheetEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Billable"); + + b.Property("Client"); + + b.Property("Date"); + + b.Property("Description"); + + b.Property("Duration"); + + b.Property("Name"); + + b.Property("Project"); + + b.Property("Surname"); + + b.Property("TimeEnd"); + + b.Property("TimeStart"); + + b.HasKey("Id"); + + b.ToTable("Entries"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/MyTimesheet/MyTimesheet/Migrations/20190122084316_initailisecreate.cs b/src/MyTimesheet/MyTimesheet/Migrations/20190122084316_initailisecreate.cs new file mode 100644 index 0000000..b78defc --- /dev/null +++ b/src/MyTimesheet/MyTimesheet/Migrations/20190122084316_initailisecreate.cs @@ -0,0 +1,40 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace MyTimesheet.Migrations +{ + public partial class initailisecreate : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Entries", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: true), + Surname = table.Column(nullable: true), + Client = table.Column(nullable: true), + Project = table.Column(nullable: true), + Date = table.Column(nullable: false), + TimeStart = table.Column(nullable: false), + TimeEnd = table.Column(nullable: false), + Duration = table.Column(nullable: false), + Description = table.Column(nullable: true), + Billable = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Entries", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Entries"); + } + } +} diff --git a/src/MyTimesheet/MyTimesheet/Migrations/TimesheetContextModelSnapshot.cs b/src/MyTimesheet/MyTimesheet/Migrations/TimesheetContextModelSnapshot.cs new file mode 100644 index 0000000..6404921 --- /dev/null +++ b/src/MyTimesheet/MyTimesheet/Migrations/TimesheetContextModelSnapshot.cs @@ -0,0 +1,55 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MyTimesheet.Models; + +namespace MyTimesheet.Migrations +{ + [DbContext(typeof(TimesheetContext))] + partial class TimesheetContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.1-servicing-10028") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("MyTimesheet.Models.TimesheetEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Billable"); + + b.Property("Client"); + + b.Property("Date"); + + b.Property("Description"); + + b.Property("Duration"); + + b.Property("Name"); + + b.Property("Project"); + + b.Property("Surname"); + + b.Property("TimeEnd"); + + b.Property("TimeStart"); + + b.HasKey("Id"); + + b.ToTable("Entries"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/MyTimesheet/MyTimesheet/Startup.cs b/src/MyTimesheet/MyTimesheet/Startup.cs index e81072d..9774ecf 100644 --- a/src/MyTimesheet/MyTimesheet/Startup.cs +++ b/src/MyTimesheet/MyTimesheet/Startup.cs @@ -35,9 +35,11 @@ public void ConfigureServices(IServiceCollection services) c.SwaggerDoc("v1", new Info { Title = "My Timesheet API", Version = "v1" }); }); - var connection = @"Server=sql101labs1793591179000.westeurope.cloudapp.azure.com;Database=sql101.#NAME.SURNAME;User Id=myUsername;Password=myPassword;"; + var connection = @"Server=sql101labs1793591179000.westeurope.cloudapp.azure.com;Database=sql101.tafara.vurayayi;User Id=tafaravurayayi;Password=retro123!#@;"; + services.AddSingleton(Configuration); services.AddDbContext (options => options.UseSqlServer(connection)); + } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. From 7402c088fa58f5371a7a493af1a42713dde3a3ac Mon Sep 17 00:00:00 2001 From: Tafdav <46773345+Tafdav@users.noreply.github.com> Date: Sat, 26 Jan 2019 18:13:31 +0200 Subject: [PATCH 2/4] Made seperate normalized tables, now to get them to read and write --- .vscode/launch.json | 46 +++++ .vscode/tasks.json | 15 ++ .../MyTimesheet/CacheSecrets.config | 4 + .../Controllers/TimesheetController.cs | 29 ++- .../20190126160349_normalized.Designer.cs | 120 ++++++++++++ .../Migrations/20190126160349_normalized.cs | 171 ++++++++++++++++++ .../TimesheetContextModelSnapshot.cs | 75 +++++++- src/MyTimesheet/MyTimesheet/Models/Client.cs | 15 ++ .../MyTimesheet/Models/Employee.cs | 16 ++ src/MyTimesheet/MyTimesheet/Models/Project.cs | 16 ++ .../MyTimesheet/Models/TimesheetContext.cs | 3 + .../MyTimesheet/Models/TimesheetEntry.cs | 53 +++++- src/MyTimesheet/MyTimesheet/appsettings.json | 13 +- 13 files changed, 556 insertions(+), 20 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 src/MyTimesheet/MyTimesheet/CacheSecrets.config create mode 100644 src/MyTimesheet/MyTimesheet/Migrations/20190126160349_normalized.Designer.cs create mode 100644 src/MyTimesheet/MyTimesheet/Migrations/20190126160349_normalized.cs create mode 100644 src/MyTimesheet/MyTimesheet/Models/Client.cs create mode 100644 src/MyTimesheet/MyTimesheet/Models/Employee.cs create mode 100644 src/MyTimesheet/MyTimesheet/Models/Project.cs diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..90342a4 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,46 @@ +{ + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/src/MyTimesheet/MyTimesheet/bin/Debug/netcoreapp2.2/MyTimesheet.dll", + "args": [], + "cwd": "${workspaceFolder}/src/MyTimesheet/MyTimesheet", + "stopAtEntry": false, + "internalConsoleOptions": "openOnSessionStart", + "launchBrowser": { + "enabled": true, + "args": "${auto-detect-url}", + "windows": { + "command": "cmd.exe", + "args": "/C start ${auto-detect-url}" + }, + "osx": { + "command": "open" + }, + "linux": { + "command": "xdg-open" + } + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ,] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..0a18066 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,15 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/src/MyTimesheet/MyTimesheet/MyTimesheet.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/src/MyTimesheet/MyTimesheet/CacheSecrets.config b/src/MyTimesheet/MyTimesheet/CacheSecrets.config new file mode 100644 index 0000000..fbacd61 --- /dev/null +++ b/src/MyTimesheet/MyTimesheet/CacheSecrets.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/MyTimesheet/MyTimesheet/Controllers/TimesheetController.cs b/src/MyTimesheet/MyTimesheet/Controllers/TimesheetController.cs index b2f5a7c..1f33bc6 100644 --- a/src/MyTimesheet/MyTimesheet/Controllers/TimesheetController.cs +++ b/src/MyTimesheet/MyTimesheet/Controllers/TimesheetController.cs @@ -5,6 +5,9 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using System.Configuration; +using StackExchange.Redis; +using Microsoft.Extensions.Configuration; namespace MyTimesheet.Controllers { @@ -13,9 +16,11 @@ namespace MyTimesheet.Controllers public class TimesheetController : ControllerBase { private readonly TimesheetContext _db; - public TimesheetController(TimesheetContext context) + readonly IConfiguration _config; + public TimesheetController(TimesheetContext context, IConfiguration config) { _db = context; + _config = config; } // GET api/values @@ -34,10 +39,28 @@ public async Task> Get(int id) // POST api/values [HttpPost] - public async Task Post([FromBody] TimesheetEntry value) + public async Task Post([FromBody] TimesheetEntry value) { await _db.Entries.AddAsync(value); await _db.SaveChangesAsync(); + + + var lazyConnection = new Lazy(() => + { + string cacheConnection = _config.GetValue("CacheConnection").ToString(); + return ConnectionMultiplexer.Connect(cacheConnection); + }); + + //await cache.StringSetAsync("key", "4"); + //var cacheItem = await cache.StringGetAsync($"{value.Name}--{value.Surname}"); + + IDatabase cache = lazyConnection.Value.GetDatabase(); + await cache.StringSetAsync($"{value.Project.Name}--{value.Employee.Name}", value.ToString()); + var cacheItem = await cache.StringGetAsync($"{value.Project.Name}--{value.Employee.Name}"); + + lazyConnection.Value.Dispose(); + + return cacheItem; } // PUT api/values/5 @@ -57,5 +80,7 @@ public async Task Delete(int id) _db.Entries.Remove(entry); await _db.SaveChangesAsync(); } + + } } diff --git a/src/MyTimesheet/MyTimesheet/Migrations/20190126160349_normalized.Designer.cs b/src/MyTimesheet/MyTimesheet/Migrations/20190126160349_normalized.Designer.cs new file mode 100644 index 0000000..9449f33 --- /dev/null +++ b/src/MyTimesheet/MyTimesheet/Migrations/20190126160349_normalized.Designer.cs @@ -0,0 +1,120 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MyTimesheet.Models; + +namespace MyTimesheet.Migrations +{ + [DbContext(typeof(TimesheetContext))] + [Migration("20190126160349_normalized")] + partial class normalized + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.1-servicing-10028") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("MyTimesheet.Models.Client", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("Clients"); + }); + + modelBuilder.Entity("MyTimesheet.Models.Employee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name"); + + b.Property("Surname"); + + b.HasKey("Id"); + + b.ToTable("Employees"); + }); + + modelBuilder.Entity("MyTimesheet.Models.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClientId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("Projects"); + }); + + modelBuilder.Entity("MyTimesheet.Models.TimesheetEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Billable"); + + b.Property("Date"); + + b.Property("Description"); + + b.Property("Duration"); + + b.Property("EmployeeId"); + + b.Property("ProjectId"); + + b.Property("TimeEnd"); + + b.Property("TimeStart"); + + b.HasKey("Id"); + + b.HasIndex("EmployeeId"); + + b.HasIndex("ProjectId"); + + b.ToTable("Entries"); + }); + + modelBuilder.Entity("MyTimesheet.Models.Project", b => + { + b.HasOne("MyTimesheet.Models.Client", "Client") + .WithMany() + .HasForeignKey("ClientId"); + }); + + modelBuilder.Entity("MyTimesheet.Models.TimesheetEntry", b => + { + b.HasOne("MyTimesheet.Models.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId"); + + b.HasOne("MyTimesheet.Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/MyTimesheet/MyTimesheet/Migrations/20190126160349_normalized.cs b/src/MyTimesheet/MyTimesheet/Migrations/20190126160349_normalized.cs new file mode 100644 index 0000000..816efc7 --- /dev/null +++ b/src/MyTimesheet/MyTimesheet/Migrations/20190126160349_normalized.cs @@ -0,0 +1,171 @@ +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace MyTimesheet.Migrations +{ + public partial class normalized : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Client", + table: "Entries"); + + migrationBuilder.DropColumn( + name: "Name", + table: "Entries"); + + migrationBuilder.DropColumn( + name: "Project", + table: "Entries"); + + migrationBuilder.DropColumn( + name: "Surname", + table: "Entries"); + + migrationBuilder.AddColumn( + name: "EmployeeId", + table: "Entries", + nullable: true); + + migrationBuilder.AddColumn( + name: "ProjectId", + table: "Entries", + nullable: true); + + migrationBuilder.CreateTable( + name: "Clients", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Clients", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Employees", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: true), + Surname = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Employees", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Projects", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: true), + ClientId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Projects", x => x.Id); + table.ForeignKey( + name: "FK_Projects_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_Entries_EmployeeId", + table: "Entries", + column: "EmployeeId"); + + migrationBuilder.CreateIndex( + name: "IX_Entries_ProjectId", + table: "Entries", + column: "ProjectId"); + + migrationBuilder.CreateIndex( + name: "IX_Projects_ClientId", + table: "Projects", + column: "ClientId"); + + migrationBuilder.AddForeignKey( + name: "FK_Entries_Employees_EmployeeId", + table: "Entries", + column: "EmployeeId", + principalTable: "Employees", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_Entries_Projects_ProjectId", + table: "Entries", + column: "ProjectId", + principalTable: "Projects", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Entries_Employees_EmployeeId", + table: "Entries"); + + migrationBuilder.DropForeignKey( + name: "FK_Entries_Projects_ProjectId", + table: "Entries"); + + migrationBuilder.DropTable( + name: "Employees"); + + migrationBuilder.DropTable( + name: "Projects"); + + migrationBuilder.DropTable( + name: "Clients"); + + migrationBuilder.DropIndex( + name: "IX_Entries_EmployeeId", + table: "Entries"); + + migrationBuilder.DropIndex( + name: "IX_Entries_ProjectId", + table: "Entries"); + + migrationBuilder.DropColumn( + name: "EmployeeId", + table: "Entries"); + + migrationBuilder.DropColumn( + name: "ProjectId", + table: "Entries"); + + migrationBuilder.AddColumn( + name: "Client", + table: "Entries", + nullable: true); + + migrationBuilder.AddColumn( + name: "Name", + table: "Entries", + nullable: true); + + migrationBuilder.AddColumn( + name: "Project", + table: "Entries", + nullable: true); + + migrationBuilder.AddColumn( + name: "Surname", + table: "Entries", + nullable: true); + } + } +} diff --git a/src/MyTimesheet/MyTimesheet/Migrations/TimesheetContextModelSnapshot.cs b/src/MyTimesheet/MyTimesheet/Migrations/TimesheetContextModelSnapshot.cs index 6404921..2f95044 100644 --- a/src/MyTimesheet/MyTimesheet/Migrations/TimesheetContextModelSnapshot.cs +++ b/src/MyTimesheet/MyTimesheet/Migrations/TimesheetContextModelSnapshot.cs @@ -19,6 +19,51 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("MyTimesheet.Models.Client", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name"); + + b.HasKey("Id"); + + b.ToTable("Clients"); + }); + + modelBuilder.Entity("MyTimesheet.Models.Employee", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name"); + + b.Property("Surname"); + + b.HasKey("Id"); + + b.ToTable("Employees"); + }); + + modelBuilder.Entity("MyTimesheet.Models.Project", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ClientId"); + + b.Property("Name"); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("Projects"); + }); + modelBuilder.Entity("MyTimesheet.Models.TimesheetEntry", b => { b.Property("Id") @@ -27,19 +72,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Billable"); - b.Property("Client"); - b.Property("Date"); b.Property("Description"); b.Property("Duration"); - b.Property("Name"); + b.Property("EmployeeId"); - b.Property("Project"); - - b.Property("Surname"); + b.Property("ProjectId"); b.Property("TimeEnd"); @@ -47,8 +88,30 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasKey("Id"); + b.HasIndex("EmployeeId"); + + b.HasIndex("ProjectId"); + b.ToTable("Entries"); }); + + modelBuilder.Entity("MyTimesheet.Models.Project", b => + { + b.HasOne("MyTimesheet.Models.Client", "Client") + .WithMany() + .HasForeignKey("ClientId"); + }); + + modelBuilder.Entity("MyTimesheet.Models.TimesheetEntry", b => + { + b.HasOne("MyTimesheet.Models.Employee", "Employee") + .WithMany() + .HasForeignKey("EmployeeId"); + + b.HasOne("MyTimesheet.Models.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId"); + }); #pragma warning restore 612, 618 } } diff --git a/src/MyTimesheet/MyTimesheet/Models/Client.cs b/src/MyTimesheet/MyTimesheet/Models/Client.cs new file mode 100644 index 0000000..a4373b6 --- /dev/null +++ b/src/MyTimesheet/MyTimesheet/Models/Client.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace MyTimesheet.Models +{ + public class Client + { + [Key] + public int Id { get; set; } + public string Name { get; set; } + } +} diff --git a/src/MyTimesheet/MyTimesheet/Models/Employee.cs b/src/MyTimesheet/MyTimesheet/Models/Employee.cs new file mode 100644 index 0000000..70ba248 --- /dev/null +++ b/src/MyTimesheet/MyTimesheet/Models/Employee.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace MyTimesheet.Models +{ + public class Employee + { + [Key] + public int Id { get; set; } + public string Name { get; set; } + public string Surname { get; set; } + } +} diff --git a/src/MyTimesheet/MyTimesheet/Models/Project.cs b/src/MyTimesheet/MyTimesheet/Models/Project.cs new file mode 100644 index 0000000..b6bd2bd --- /dev/null +++ b/src/MyTimesheet/MyTimesheet/Models/Project.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace MyTimesheet.Models +{ + public class Project + { + [Key] + public int Id { get; set; } + public string Name { get; set; } + public Client Client { get; set; } + } +} diff --git a/src/MyTimesheet/MyTimesheet/Models/TimesheetContext.cs b/src/MyTimesheet/MyTimesheet/Models/TimesheetContext.cs index 5bf7e22..6e08caf 100644 --- a/src/MyTimesheet/MyTimesheet/Models/TimesheetContext.cs +++ b/src/MyTimesheet/MyTimesheet/Models/TimesheetContext.cs @@ -13,6 +13,9 @@ public TimesheetContext(DbContextOptions options) { } public DbSet Entries { get; set; } + public DbSet Employees { get; set; } + public DbSet Clients { get; set; } + public DbSet Projects { get; set; } } } diff --git a/src/MyTimesheet/MyTimesheet/Models/TimesheetEntry.cs b/src/MyTimesheet/MyTimesheet/Models/TimesheetEntry.cs index 8c65667..12c5142 100644 --- a/src/MyTimesheet/MyTimesheet/Models/TimesheetEntry.cs +++ b/src/MyTimesheet/MyTimesheet/Models/TimesheetEntry.cs @@ -1,18 +1,20 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; namespace MyTimesheet.Models { + /* public class TimesheetEntry { - /* - Name | Surname | Client | Project | Date | Time Started | Time ended | Duration | Description | Billable - --- | --- | --- | --- | --- | --- | --- | --- | --- | --- - John | Doe | Client X | Website | 2019-01-22 | 09:00 | 11:00 | 120 | I was rocking HTML5 | YES - John | Doe | Client X | API | 2019-01-22 | 13:00 | 17:00 | 240 | Grafting on golang api | YES - */ + + Name | Surname | Client | Project | Date | Time Started | Time ended | Duration | Description | Billable + --- | --- | --- | --- | --- | -- | --- | --- | --- | --- + John | Doe | Client X | Website | 2019-01-22 | 09:00 | 11:00 | 120 |I was rocking HTML5 | YES + John | Doe | Client X | API | 2019-01-22 | 13:00 | 17:00 | 240 | Grafting on golang api | YES + public int Id { get; set; } public string Name { get; set; } @@ -26,4 +28,43 @@ public class TimesheetEntry public string Description { get; set; } public bool Billable { get; set; } } + + public class Employee + { + [Key] + public int Id { get; set; } + public string Name { get; set; } + public string Surname { get; set; } + } + public class Client + { + [Key] + public int Id { get; set; } + public string Name { get; set; } + } + public class Project + { + [Key] + public int Id { get; set; } + public string Name { get; set; } + } + + */ + + public class TimesheetEntry + { + [Key] + public int Id { get; set; } + public Employee Employee { get; set; }// public int EmpId { get; set;} + public Project Project { get; set; }//public int CId { get; set; } + //public string Project { get; set; } + public DateTime Date { get; set; } + public DateTime TimeStart { get; set; } + public DateTime TimeEnd { get; set; } + public int Duration { get; set; } + public string Description { get; set; } + public bool Billable { get; set; } + } + + } diff --git a/src/MyTimesheet/MyTimesheet/appsettings.json b/src/MyTimesheet/MyTimesheet/appsettings.json index def9159..438f796 100644 --- a/src/MyTimesheet/MyTimesheet/appsettings.json +++ b/src/MyTimesheet/MyTimesheet/appsettings.json @@ -1,8 +1,9 @@ { - "Logging": { - "LogLevel": { - "Default": "Warning" - } - }, - "AllowedHosts": "*" + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*", + "CacheConnection": "101.redis.cache.windows.net,abortConnect=false,ssl=true,password=85BYRVqUHV6aW7Jz8zIIxRnaYMgbBd2kwo4bpDiz4vw=" } From 5685c14cf7452d6621a36336250ed75af43d9204 Mon Sep 17 00:00:00 2001 From: Tafdav <46773345+Tafdav@users.noreply.github.com> Date: Sat, 26 Jan 2019 20:48:53 +0200 Subject: [PATCH 3/4] Pretty sure post, put and get work now --- .../Controllers/TimesheetController.cs | 50 +++++++++++++++---- .../MyTimesheet/Models/TimesheetEntry.cs | 29 +++++++++++ 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/MyTimesheet/MyTimesheet/Controllers/TimesheetController.cs b/src/MyTimesheet/MyTimesheet/Controllers/TimesheetController.cs index 1f33bc6..3a9d118 100644 --- a/src/MyTimesheet/MyTimesheet/Controllers/TimesheetController.cs +++ b/src/MyTimesheet/MyTimesheet/Controllers/TimesheetController.cs @@ -32,9 +32,26 @@ public async Task>> Get() // GET api/values/5 [HttpGet("{id}")] - public async Task> Get(int id) + public async Task*/> Get(int id) { - return await _db.Entries.FindAsync(id); + var lazyConnection = new Lazy(() => + { + string cacheConnection = _config.GetValue("CacheConnection").ToString(); + return ConnectionMultiplexer.Connect(cacheConnection); + }); + + IDatabase cache = lazyConnection.Value.GetDatabase(); + var cacheItem = await cache.StringGetAsync($"{id}"); + if(cacheItem.HasValue) + { + return cacheItem; + } + else + { + var val = await _db.Entries.FindAsync(id); + return val.ToString(); + } + } // POST api/values @@ -44,19 +61,16 @@ public async Task Post([FromBody] TimesheetEntry value) await _db.Entries.AddAsync(value); await _db.SaveChangesAsync(); - var lazyConnection = new Lazy(() => { string cacheConnection = _config.GetValue("CacheConnection").ToString(); return ConnectionMultiplexer.Connect(cacheConnection); }); - //await cache.StringSetAsync("key", "4"); - //var cacheItem = await cache.StringGetAsync($"{value.Name}--{value.Surname}"); - IDatabase cache = lazyConnection.Value.GetDatabase(); - await cache.StringSetAsync($"{value.Project.Name}--{value.Employee.Name}", value.ToString()); - var cacheItem = await cache.StringGetAsync($"{value.Project.Name}--{value.Employee.Name}"); + + await cache.StringSetAsync($"{value.Id}", value.ToString()); + var cacheItem = await cache.StringGetAsync($"{value.Id}"); lazyConnection.Value.Dispose(); @@ -65,11 +79,29 @@ public async Task Post([FromBody] TimesheetEntry value) // PUT api/values/5 [HttpPut("{id}")] - public async Task Put(int id, [FromBody] TimesheetEntry value) + public async Task Put(int id, [FromBody] TimesheetEntry value) { var entry = await _db.Entries.FindAsync(id); entry = value; await _db.SaveChangesAsync(); + + var lazyConnection = new Lazy(() => + { + string cacheConnection = _config.GetValue("CacheConnection").ToString(); + return ConnectionMultiplexer.Connect(cacheConnection); + }); + + //await cache.StringSetAsync("key", "4"); + //var cacheItem = await cache.StringGetAsync($"{value.Name}--{value.Surname}"); + + IDatabase cache = lazyConnection.Value.GetDatabase(); + + await cache.StringSetAsync($"{entry.Id}", entry.ToString()); + var cacheItem = await cache.StringGetAsync($"{entry.Id}"); + + lazyConnection.Value.Dispose(); + + return cacheItem; } // DELETE api/values/5 diff --git a/src/MyTimesheet/MyTimesheet/Models/TimesheetEntry.cs b/src/MyTimesheet/MyTimesheet/Models/TimesheetEntry.cs index 12c5142..2e175c5 100644 --- a/src/MyTimesheet/MyTimesheet/Models/TimesheetEntry.cs +++ b/src/MyTimesheet/MyTimesheet/Models/TimesheetEntry.cs @@ -64,6 +64,35 @@ public class TimesheetEntry public int Duration { get; set; } public string Description { get; set; } public bool Billable { get; set; } + public override string ToString() + { + string val = ""; + val = $"{{" + + $"\"id\": {Id},\n" + + $"\"employee\":{{\n" + + $"\"id\": {Employee.Id},\n" + + $"\"name\": \"{Employee.Name}\",\n" + + $"\"surname\":\"{Employee.Surname}\"\n" + + $"}},\n" + + $"\"project\":{{\n" + + $"\"id\": {Project.Id},\n" + + $"\"name\": \"{Project.Name}\",\n" + + $"\"client\": {{\n" + + $"\"id\": {Project.Client.Id},\n" + + $"\"name\": \"{Project.Client.Name}\"\n" + + $"}}\n" + + $"}},\n" + + $"\"date\": \"{Date.ToString()}\"\n" + + $"\"timeStart\": \"{TimeStart.ToString()}\",\n" + + $"\"timeEnd\": \"{TimeEnd.ToString()}\",\n" + + $"\"duration\": {Duration},\n" + + $"\"description\": \"{Description}\",\n" + + $"\"billable\": {Billable}\n" + + $"}}"; + + + return val; + } } From aa47275ffd46dce8277e1cd3734c88abc675a24d Mon Sep 17 00:00:00 2001 From: Tafdav <46773345+Tafdav@users.noreply.github.com> Date: Sat, 26 Jan 2019 21:08:02 +0200 Subject: [PATCH 4/4] final commit --- .../Controllers/TimesheetController.cs | 12 +++++++++ .../MyTimesheet/Models/TimesheetEntry.cs | 26 +++++++++---------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/MyTimesheet/MyTimesheet/Controllers/TimesheetController.cs b/src/MyTimesheet/MyTimesheet/Controllers/TimesheetController.cs index 3a9d118..e615e4f 100644 --- a/src/MyTimesheet/MyTimesheet/Controllers/TimesheetController.cs +++ b/src/MyTimesheet/MyTimesheet/Controllers/TimesheetController.cs @@ -24,6 +24,18 @@ public TimesheetController(TimesheetContext context, IConfiguration config) } // GET api/values + /*[HttpGet] + public async Task>> Get() + { + IEnumerable var = await _db.Entries.ToListAsync(); + string toReturn = "[\n"; + foreach (var item in var) + { + toReturn += item.ToString()+"\n"; + } + toReturn += "]"; + return toReturn; + }*/ [HttpGet] public async Task>> Get() { diff --git a/src/MyTimesheet/MyTimesheet/Models/TimesheetEntry.cs b/src/MyTimesheet/MyTimesheet/Models/TimesheetEntry.cs index 2e175c5..0c7fded 100644 --- a/src/MyTimesheet/MyTimesheet/Models/TimesheetEntry.cs +++ b/src/MyTimesheet/MyTimesheet/Models/TimesheetEntry.cs @@ -67,24 +67,24 @@ public class TimesheetEntry public override string ToString() { string val = ""; - val = $"{{" + + val = $"{{\n" + $"\"id\": {Id},\n" + $"\"employee\":{{\n" + - $"\"id\": {Employee.Id},\n" + - $"\"name\": \"{Employee.Name}\",\n" + - $"\"surname\":\"{Employee.Surname}\"\n" + + $"\t\"id\": {Employee.Id},\n" + + $"\t\"name\": \"{Employee.Name}\",\n" + + $"\t\"surname\":\"{Employee.Surname}\"\n" + $"}},\n" + $"\"project\":{{\n" + - $"\"id\": {Project.Id},\n" + - $"\"name\": \"{Project.Name}\",\n" + - $"\"client\": {{\n" + - $"\"id\": {Project.Client.Id},\n" + - $"\"name\": \"{Project.Client.Name}\"\n" + - $"}}\n" + + $"\t\"id\": {Project.Id},\n" + + $"\t\"name\": \"{Project.Name}\",\n" + + $"\t\"client\": {{\n" + + $"\t\t\"id\": {Project.Client.Id},\n" + + $"\t\t\"name\": \"{Project.Client.Name}\"\n" + + $"\t}}\n" + $"}},\n" + - $"\"date\": \"{Date.ToString()}\"\n" + - $"\"timeStart\": \"{TimeStart.ToString()}\",\n" + - $"\"timeEnd\": \"{TimeEnd.ToString()}\",\n" + + $"\"date\": \"{Date}\"\n" + + $"\"timeStart\": \"{TimeStart}\",\n" + + $"\"timeEnd\": \"{TimeEnd}\",\n" + $"\"duration\": {Duration},\n" + $"\"description\": \"{Description}\",\n" + $"\"billable\": {Billable}\n" +