diff options
| author | ivarlovlie <git@ivarlovlie.no> | 2020-08-01 20:14:34 +0200 |
|---|---|---|
| committer | ivarlovlie <git@ivarlovlie.no> | 2020-08-01 20:14:34 +0200 |
| commit | a800b3b9f18ae3e8ab030c30c5d7b6504f2a5ebb (patch) | |
| tree | 68ffcdb7a2b42418ff1c4818d0b2cd5af41d5fa2 /src/server | |
| download | dough-a800b3b9f18ae3e8ab030c30c5d7b6504f2a5ebb.tar.xz dough-a800b3b9f18ae3e8ab030c30c5d7b6504f2a5ebb.zip | |
Initial commit
Diffstat (limited to 'src/server')
23 files changed, 999 insertions, 0 deletions
diff --git a/src/server/Controllers/AccountController.cs b/src/server/Controllers/AccountController.cs new file mode 100644 index 0000000..58bb7b6 --- /dev/null +++ b/src/server/Controllers/AccountController.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Dough.Models; +using Dough.Models.Database; +using Dough.Models.Results; +using Dough.Utilities; + +namespace Dough.Controllers +{ + public class AccountController : BaseController + { + private readonly MainDbContext _context; + + public AccountController(MainDbContext context) + { + _context = context; + } + + [HttpPost("login")] + public async Task<ActionResult> Login(string username, string password) + { + var user = _context.Users.SingleByNameOrDefault(username); + if (user == default) + return BadRequest(new ErrorResult("Ugyldig brukernavn eller passord", + "Verifiser at passord og brukernavn er riktig og prøv igjen")); + + if (!user.VerifyPassword(password)) + return BadRequest(new ErrorResult("Ugyldig brukernavn eller passord", + "Verifiser at passord og brukernavn er riktig")); + + var claims = new List<Claim> + { + new Claim(ClaimTypes.Name, user.Username), + new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()) + }; + + var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); + var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); + + var authenticationProperties = new AuthenticationProperties + { + IsPersistent = false, + IssuedUtc = DateTime.UtcNow, + AllowRefresh = true, + ExpiresUtc = DateTime.UtcNow.AddDays(7), + }; + + await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, + claimsPrincipal, + authenticationProperties); + + return Ok(); + } + + [HttpGet("logout")] + public async Task<ActionResult> Logout(string continueTo = default) + { + await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + if (continueTo.IsPresent() && continueTo.IsValidUrl()) return Redirect(continueTo); + return Ok(); + } + + [Authorize] + [HttpGet("me")] + public ActionResult GetClaimsForUser() + { + return Ok(LoggedInUser); + } + } +} diff --git a/src/server/Controllers/BaseController.cs b/src/server/Controllers/BaseController.cs new file mode 100644 index 0000000..33f1e4b --- /dev/null +++ b/src/server/Controllers/BaseController.cs @@ -0,0 +1,27 @@ +using System;
+using System.Security.Claims;
+using Microsoft.AspNetCore.Mvc;
+using Dough.Models.Database;
+using Dough.Utilities;
+
+namespace Dough.Controllers
+{
+ [ApiController]
+ [Route("api/[controller]")]
+ public class BaseController : ControllerBase
+ {
+ public LoggedInUserModel LoggedInUser => new LoggedInUserModel
+ {
+ Id = User.GetClaimValueOrDefault(ClaimTypes.NameIdentifier)?.ToGuidOrDefault() ?? default,
+ Username = User.GetClaimValueOrDefault(ClaimTypes.Name),
+ SessionStart = User.GetClaimValueOrDefault(ClaimTypes.AuthenticationInstant).ToDateTimeOrDefault()
+ };
+
+ public class LoggedInUserModel
+ {
+ public Guid Id { get; set; }
+ public string Username { get; set; }
+ public DateTime SessionStart { get; set; }
+ }
+ }
+}
diff --git a/src/server/Controllers/TransactionsController.cs b/src/server/Controllers/TransactionsController.cs new file mode 100644 index 0000000..8441beb --- /dev/null +++ b/src/server/Controllers/TransactionsController.cs @@ -0,0 +1,78 @@ +using System; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Dough.Models.Database; +using Dough.Models; +using Dough.Models.Exceptions; +using Dough.Models.Results; + +namespace Dough.Controllers +{ + public class TransactionsController : BaseController + { + private readonly MainDbContext _context; + + public TransactionsController(MainDbContext context) + { + _context = context; + } + + [HttpGet] + public ActionResult GetTransactions() + { + var transactions = _context.Transactions.Where(c => !c.Hidden); + return Ok(transactions.ToList()); + } + + [HttpPost("add")] + public ActionResult AddTransaction(Transaction data) + { + data.SetBaseProperties(); + try + { + data.Validate(); + } + catch (Exception e) + { + Console.WriteLine(e); + if (e is ModelValidationException mve) + return BadRequest(mve.ErrorResult); + return BadRequest(new ErrorResult()); + } + + _context.Transactions.Add(data); + _context.SaveChanges(); + return Ok(); + } + + [HttpPost("update")] + public ActionResult UpdateTransaction(Transaction data) + { + try + { + data.Validate(); + } + catch (Exception e) + { + Console.WriteLine(e); + if (e is ModelValidationException mve) + return BadRequest(mve.ErrorResult); + return BadRequest(new ErrorResult()); + } + + var transaction = _context.Transactions.SingleOrDefault(data.Id); + transaction.Update(data); + _context.SaveChanges(); + return Ok(data); + } + + [HttpDelete("delete")] + public ActionResult DeleteTransaction(Transaction data) + { + if (data.Id == default) return BadRequest(new ErrorResult()); + _context.Transactions.Remove(data); + _context.SaveChanges(); + return Ok(); + } + } +} diff --git a/src/server/Controllers/UsersController.cs b/src/server/Controllers/UsersController.cs new file mode 100644 index 0000000..bef8cc2 --- /dev/null +++ b/src/server/Controllers/UsersController.cs @@ -0,0 +1,6 @@ +namespace Dough.Controllers +{ + public class UsersController : BaseController + { + } +} diff --git a/src/server/Dough.csproj b/src/server/Dough.csproj new file mode 100644 index 0000000..ae3d56a --- /dev/null +++ b/src/server/Dough.csproj @@ -0,0 +1,16 @@ +<Project Sdk="Microsoft.NET.Sdk.Web">
+
+ <PropertyGroup>
+ <TargetFramework>netcoreapp3.1</TargetFramework>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="BCrypt.Net-Core" Version="1.6.0" />
+ <PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.6" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.6" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.6" />
+ </ItemGroup>
+
+
+</Project>
diff --git a/src/server/Migrations/20200729090558_Initial.Designer.cs b/src/server/Migrations/20200729090558_Initial.Designer.cs new file mode 100644 index 0000000..9657f66 --- /dev/null +++ b/src/server/Migrations/20200729090558_Initial.Designer.cs @@ -0,0 +1,143 @@ +// <auto-generated /> +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Dough.Models.Database; + +namespace Dough.Migrations +{ + [DbContext(typeof(MainDbContext))] + [Migration("20200729090558_Initial")] + partial class Initial + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.6"); + + modelBuilder.Entity("MoneyManager.Models.Database.Category", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property<DateTime>("Created") + .HasColumnType("TEXT"); + + b.Property<Guid?>("CreatedBy") + .HasColumnType("TEXT"); + + b.Property<bool>("Hidden") + .HasColumnType("INTEGER"); + + b.Property<string>("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("MoneyManager.Models.Database.Payee", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property<DateTime>("Created") + .HasColumnType("TEXT"); + + b.Property<Guid?>("CreatedBy") + .HasColumnType("TEXT"); + + b.Property<bool>("Hidden") + .HasColumnType("INTEGER"); + + b.Property<string>("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Payees"); + }); + + modelBuilder.Entity("MoneyManager.Models.Database.Transaction", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property<double>("Amount") + .HasColumnType("REAL"); + + b.Property<Guid>("CategoryId") + .HasColumnType("TEXT"); + + b.Property<DateTime>("Created") + .HasColumnType("TEXT"); + + b.Property<Guid?>("CreatedBy") + .HasColumnType("TEXT"); + + b.Property<DateTime>("Date") + .HasColumnType("TEXT"); + + b.Property<bool>("Hidden") + .HasColumnType("INTEGER"); + + b.Property<string>("Note") + .HasColumnType("TEXT"); + + b.Property<Guid>("PayeeId") + .HasColumnType("TEXT"); + + b.Property<string>("Tags") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Transactions"); + }); + + modelBuilder.Entity("MoneyManager.Models.Database.User", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property<DateTime>("Created") + .HasColumnType("TEXT"); + + b.Property<Guid?>("CreatedBy") + .HasColumnType("TEXT"); + + b.Property<bool>("Hidden") + .HasColumnType("INTEGER"); + + b.Property<string>("Password") + .HasColumnType("TEXT"); + + b.Property<string>("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + + b.HasData( + new + { + Id = new Guid("193053d0-4292-4dc5-baae-59a920b64891"), + Created = new DateTime(2020, 7, 29, 9, 5, 57, 914, DateTimeKind.Utc).AddTicks(3427), + Hidden = false, + Password = "$2b$10$RFdcYLeqporq94pUIOoJGOPnhUbpV7R4e.2Iz8ot02N2PqeCpDCA6", + Username = "ivar" + }); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/server/Migrations/20200729090558_Initial.cs b/src/server/Migrations/20200729090558_Initial.cs new file mode 100644 index 0000000..5dc0b40 --- /dev/null +++ b/src/server/Migrations/20200729090558_Initial.cs @@ -0,0 +1,97 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Dough.Migrations +{ + public partial class Initial : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Categories", + columns: table => new + { + Id = table.Column<Guid>(nullable: false), + Created = table.Column<DateTime>(nullable: false), + CreatedBy = table.Column<Guid>(nullable: true), + Hidden = table.Column<bool>(nullable: false), + Name = table.Column<string>(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Categories", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Payees", + columns: table => new + { + Id = table.Column<Guid>(nullable: false), + Created = table.Column<DateTime>(nullable: false), + CreatedBy = table.Column<Guid>(nullable: true), + Hidden = table.Column<bool>(nullable: false), + Name = table.Column<string>(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Payees", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Transactions", + columns: table => new + { + Id = table.Column<Guid>(nullable: false), + Created = table.Column<DateTime>(nullable: false), + CreatedBy = table.Column<Guid>(nullable: true), + Hidden = table.Column<bool>(nullable: false), + Tags = table.Column<string>(nullable: true), + Note = table.Column<string>(nullable: true), + Date = table.Column<DateTime>(nullable: false), + Amount = table.Column<double>(nullable: false), + PayeeId = table.Column<Guid>(nullable: false), + CategoryId = table.Column<Guid>(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Transactions", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column<Guid>(nullable: false), + Created = table.Column<DateTime>(nullable: false), + CreatedBy = table.Column<Guid>(nullable: true), + Hidden = table.Column<bool>(nullable: false), + Password = table.Column<string>(nullable: true), + Username = table.Column<string>(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.InsertData( + table: "Users", + columns: new[] { "Id", "Created", "CreatedBy", "Hidden", "Password", "Username" }, + values: new object[] { new Guid("193053d0-4292-4dc5-baae-59a920b64891"), new DateTime(2020, 7, 29, 9, 5, 57, 914, DateTimeKind.Utc).AddTicks(3427), null, false, "$2b$10$RFdcYLeqporq94pUIOoJGOPnhUbpV7R4e.2Iz8ot02N2PqeCpDCA6", "ivar" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Categories"); + + migrationBuilder.DropTable( + name: "Payees"); + + migrationBuilder.DropTable( + name: "Transactions"); + + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/src/server/Migrations/MainDbContextModelSnapshot.cs b/src/server/Migrations/MainDbContextModelSnapshot.cs new file mode 100644 index 0000000..ad883c5 --- /dev/null +++ b/src/server/Migrations/MainDbContextModelSnapshot.cs @@ -0,0 +1,141 @@ +// <auto-generated /> +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Dough.Models.Database; + +namespace Dough.Migrations +{ + [DbContext(typeof(MainDbContext))] + partial class MainDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.6"); + + modelBuilder.Entity("MoneyManager.Models.Database.Category", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property<DateTime>("Created") + .HasColumnType("TEXT"); + + b.Property<Guid?>("CreatedBy") + .HasColumnType("TEXT"); + + b.Property<bool>("Hidden") + .HasColumnType("INTEGER"); + + b.Property<string>("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("MoneyManager.Models.Database.Payee", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property<DateTime>("Created") + .HasColumnType("TEXT"); + + b.Property<Guid?>("CreatedBy") + .HasColumnType("TEXT"); + + b.Property<bool>("Hidden") + .HasColumnType("INTEGER"); + + b.Property<string>("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Payees"); + }); + + modelBuilder.Entity("MoneyManager.Models.Database.Transaction", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property<double>("Amount") + .HasColumnType("REAL"); + + b.Property<Guid>("CategoryId") + .HasColumnType("TEXT"); + + b.Property<DateTime>("Created") + .HasColumnType("TEXT"); + + b.Property<Guid?>("CreatedBy") + .HasColumnType("TEXT"); + + b.Property<DateTime>("Date") + .HasColumnType("TEXT"); + + b.Property<bool>("Hidden") + .HasColumnType("INTEGER"); + + b.Property<string>("Note") + .HasColumnType("TEXT"); + + b.Property<Guid>("PayeeId") + .HasColumnType("TEXT"); + + b.Property<string>("Tags") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Transactions"); + }); + + modelBuilder.Entity("MoneyManager.Models.Database.User", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property<DateTime>("Created") + .HasColumnType("TEXT"); + + b.Property<Guid?>("CreatedBy") + .HasColumnType("TEXT"); + + b.Property<bool>("Hidden") + .HasColumnType("INTEGER"); + + b.Property<string>("Password") + .HasColumnType("TEXT"); + + b.Property<string>("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + + b.HasData( + new + { + Id = new Guid("193053d0-4292-4dc5-baae-59a920b64891"), + Created = new DateTime(2020, 7, 29, 9, 5, 57, 914, DateTimeKind.Utc).AddTicks(3427), + Hidden = false, + Password = "$2b$10$RFdcYLeqporq94pUIOoJGOPnhUbpV7R4e.2Iz8ot02N2PqeCpDCA6", + Username = "ivar" + }); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/server/Models/Database/BaseModel.cs b/src/server/Models/Database/BaseModel.cs new file mode 100644 index 0000000..32c2f8f --- /dev/null +++ b/src/server/Models/Database/BaseModel.cs @@ -0,0 +1,25 @@ +using System; + +namespace Dough.Models.Database +{ + public class BaseModel + { + public Guid Id { get; set; } + public DateTime Created { get; set; } + public Guid? CreatedBy { get; set; } + public bool Hidden { get; set; } + + public void SetBaseProperties(User actor = default) + { + Id = Guid.NewGuid(); + Created = DateTime.UtcNow; + CreatedBy = actor?.Id; + } + + protected void Update(BaseModel data) + { + Hidden = data.Hidden; + CreatedBy = data.CreatedBy; + } + } +} diff --git a/src/server/Models/Database/Category.cs b/src/server/Models/Database/Category.cs new file mode 100644 index 0000000..10ef226 --- /dev/null +++ b/src/server/Models/Database/Category.cs @@ -0,0 +1,7 @@ +namespace Dough.Models.Database +{ + public class Category : BaseModel + { + public string Name { get; set; } + } +} diff --git a/src/server/Models/Database/MainDbContext.cs b/src/server/Models/Database/MainDbContext.cs new file mode 100644 index 0000000..88b1585 --- /dev/null +++ b/src/server/Models/Database/MainDbContext.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; + +namespace Dough.Models.Database +{ + public class MainDbContext : DbContext + { + + public MainDbContext(DbContextOptions<MainDbContext> options) : base(options) { + + } + + public DbSet<Transaction> Transactions { get; set; } + public DbSet<User> Users { get; set; } + public DbSet<Category> Categories { get; set; } + public DbSet<Payee> Payees { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity<Transaction>().ToTable("Transactions"); + var initUser = new User("ivar"); + initUser.SetBaseProperties(); + initUser.HashAndSetPassword("ivar123"); + modelBuilder.Entity<User>().HasData(new List<User> + { + initUser + }); + modelBuilder.Entity<User>().ToTable("Users"); + modelBuilder.Entity<Category>().ToTable("Categories"); + modelBuilder.Entity<Payee>().ToTable("Payees"); + base.OnModelCreating(modelBuilder); + } + } +} diff --git a/src/server/Models/Database/Payee.cs b/src/server/Models/Database/Payee.cs new file mode 100644 index 0000000..b801d5f --- /dev/null +++ b/src/server/Models/Database/Payee.cs @@ -0,0 +1,7 @@ +namespace Dough.Models.Database +{ + public class Payee : BaseModel + { + public string Name { get; set; } + } +} diff --git a/src/server/Models/Database/Transaction.cs b/src/server/Models/Database/Transaction.cs new file mode 100644 index 0000000..a81626b --- /dev/null +++ b/src/server/Models/Database/Transaction.cs @@ -0,0 +1,43 @@ +using System; +using Dough.Models.Exceptions; + +namespace Dough.Models.Database +{ + public class Transaction : BaseModel + { + public string Tags { get; set; } + public string Note { get; set; } + public DateTime Date { get; set; } + public double Amount { get; set; } + public Guid PayeeId { get; set; } + public Guid CategoryId { get; set; } + + public void Validate() + { + if (PayeeId == default) + { + var validationException = new ModelValidationException("PayeeId is invalid"); + validationException.ErrorResult.Title = "Mottaker er ugyldig"; + throw validationException; + } + + if (CategoryId == default) + { + var validationException = new ModelValidationException("CategoryId is invalid"); + validationException.ErrorResult.Title = "Kategori er ugyldig"; + throw validationException; + } + } + + public void Update(Transaction data) + { + Amount = data.Amount; + Date = data.Date; + Note = data.Note; + Tags = data.Tags; + CategoryId = data.CategoryId; + PayeeId = data.PayeeId; + base.Update(data); + } + } +} diff --git a/src/server/Models/Database/User.cs b/src/server/Models/Database/User.cs new file mode 100644 index 0000000..479c15c --- /dev/null +++ b/src/server/Models/Database/User.cs @@ -0,0 +1,31 @@ +using System; + +namespace Dough.Models.Database +{ + public class User : BaseModel + { + + public User(string username = default) + { + Username = username; + } + public string Password { get; set; } + public string Username { get; set; } + + public void Update(User data) + { + Username = data.Username; + base.Update(data); + } + + public void HashAndSetPassword(string password) + { + Password = BCrypt.Net.BCrypt.HashPassword(password); + } + + public bool VerifyPassword(string password) + { + return BCrypt.Net.BCrypt.Verify(password, Password); + } + } +} diff --git a/src/server/Models/DbSetOverrides.cs b/src/server/Models/DbSetOverrides.cs new file mode 100644 index 0000000..24622bc --- /dev/null +++ b/src/server/Models/DbSetOverrides.cs @@ -0,0 +1,25 @@ +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Dough.Models.Database; + +namespace Dough.Models +{ + public static class DbSetOverrides + { + public static T SingleOrDefault<T>(this DbSet<T> entity, Guid id, + bool includeHidden = false) where T : BaseModel + { + if (includeHidden) + return entity.SingleOrDefault(c => c.Id == id); + return entity.SingleOrDefault(c => c.Id == id && !c.Hidden); + } + + public static User SingleByNameOrDefault(this DbSet<User> users, string username, bool includeHidden = false) + { + if (includeHidden) + return users.SingleOrDefault(c => c.Username == username); + return users.SingleOrDefault(c => c.Username == username && !c.Hidden); + } + } +} diff --git a/src/server/Models/Exceptions/ModelValidationException.cs b/src/server/Models/Exceptions/ModelValidationException.cs new file mode 100644 index 0000000..3156229 --- /dev/null +++ b/src/server/Models/Exceptions/ModelValidationException.cs @@ -0,0 +1,31 @@ +using System; +using System.Runtime.Serialization; +using Dough.Models.Results; + +namespace Dough.Models.Exceptions +{ + [Serializable] + public class ModelValidationException : Exception + { + public ErrorResult ErrorResult { get; set; } + + public ModelValidationException() + { + ErrorResult = new ErrorResult(); + } + + public ModelValidationException(string message) : base(message) + { + } + + public ModelValidationException(string message, Exception inner) : base(message, inner) + { + } + + protected ModelValidationException( + SerializationInfo info, + StreamingContext context) : base(info, context) + { + } + } +} diff --git a/src/server/Models/Results/ErrorResult.cs b/src/server/Models/Results/ErrorResult.cs new file mode 100644 index 0000000..8d4504f --- /dev/null +++ b/src/server/Models/Results/ErrorResult.cs @@ -0,0 +1,14 @@ +namespace Dough.Models.Results +{ + public class ErrorResult + { + public ErrorResult(string title = default, string message = default) + { + Title = title; + Message = message; + } + + public string Title { get; set; } = "En feil oppstod"; + public string Message { get; set; } = "Vennligst prøv igjen snart"; + } +} diff --git a/src/server/Program.cs b/src/server/Program.cs new file mode 100644 index 0000000..04a9447 --- /dev/null +++ b/src/server/Program.cs @@ -0,0 +1,55 @@ +using System;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Hosting;
+using Serilog;
+using Serilog.Events;
+using Serilog.Sinks.SystemConsole.Themes;
+
+namespace Dough
+{
+ public class Program
+ {
+ public static int Main(string[] args)
+ {
+ Log.Logger = new LoggerConfiguration()
+ .MinimumLevel.Debug()
+ .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
+ .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information)
+ .MinimumLevel.Override("System", LogEventLevel.Warning)
+ .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information)
+ .Enrich.FromLogContext()
+ .WriteTo.File(
+ @"/var/log/money-manager.txt",
+ fileSizeLimitBytes: 4_000_000,
+ rollOnFileSizeLimit: true,
+ shared: true,
+ flushToDiskInterval: TimeSpan.FromSeconds(1))
+ .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Code)
+ .CreateLogger();
+
+ try
+ {
+ Log.Information("Starting host...");
+ CreateHostBuilder(args).Build().Run();
+ return 0;
+ }
+ catch (Exception ex)
+ {
+ Log.Fatal(ex, "Host terminated unexpectedly.");
+ return 1;
+ }
+ finally
+ {
+ Log.CloseAndFlush();
+ }
+ }
+
+ public static IHostBuilder CreateHostBuilder(string[] args) =>
+ Host.CreateDefaultBuilder(args)
+ .UseSerilog()
+ .ConfigureWebHostDefaults(webBuilder =>
+ {
+ webBuilder.UseStartup<Startup>();
+ });
+ }
+}
diff --git a/src/server/Properties/launchSettings.json b/src/server/Properties/launchSettings.json new file mode 100644 index 0000000..2b071f3 --- /dev/null +++ b/src/server/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "MoneyManager": {
+ "commandName": "Project",
+ "applicationUrl": "https://localhost:5002;http://localhost:5001",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/src/server/Startup.cs b/src/server/Startup.cs new file mode 100644 index 0000000..ad98370 --- /dev/null +++ b/src/server/Startup.cs @@ -0,0 +1,67 @@ +using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Dough.Models;
+using Dough.Utilities;
+using Dough.Models.Database;
+
+namespace Dough
+{
+ public class Startup
+ {
+ public Startup(IConfiguration configuration)
+ {
+ Configuration = configuration;
+ }
+
+ public IConfiguration Configuration { get; }
+ private const string MainCorsPolicy = "MainCorsPolicy";
+
+ public void ConfigureServices(IServiceCollection services)
+ {
+
+ services.AddCors(options =>
+ {
+ options.AddPolicy(MainCorsPolicy, builder =>
+ {
+ builder
+ .AllowAnyHeader()
+ .AllowAnyMethod()
+ .AllowCredentials()
+ .WithOrigins("http://localhost:8080");
+ });
+ });
+
+ services.AddDbContext<MainDbContext>(options => {
+ options.UseSqlite("Data Source=database.sqlite");
+ });
+
+ services.AddControllers();
+
+ services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
+ .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
+ {
+ options.LoginPath = "/api/account/login";
+ options.SlidingExpiration = true;
+ options.LogoutPath = "/api/account/logout";
+ });
+ }
+
+ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+ {
+ if (env.IsDevelopment())
+ app.UseDeveloperExceptionPage();
+
+ app.UseCors(MainCorsPolicy);
+ app.UseRouting();
+ app.UseStatusCodePages();
+ app.UseAuthentication();
+ app.UseAuthorization();
+ app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
+ }
+ }
+}
diff --git a/src/server/Utilities/ExtensionMethods.cs b/src/server/Utilities/ExtensionMethods.cs new file mode 100644 index 0000000..d6bce94 --- /dev/null +++ b/src/server/Utilities/ExtensionMethods.cs @@ -0,0 +1,54 @@ +using System; +using System.Linq; +using System.Security.Claims; +using System.Security.Principal; + +namespace Dough.Utilities +{ + public static class ExtentionMethods + { + public static Guid ToGuidOrDefault(this string value) + { + if (value.IsMissing()) return default; + try + { + return new Guid(value); + } + catch (Exception e) + { + Console.WriteLine(e); + return default; + } + } + + public static DateTime ToDateTimeOrDefault(this string value) + { + if (value.IsMissing()) return default; + try + { + return DateTime.Parse(value); + } + catch (Exception e) + { + Console.WriteLine(e); + return default; + } + } + + public static bool IsMissing(this string value) => string.IsNullOrWhiteSpace(value); + public static bool IsPresent(this string value) => !value.IsMissing(); + + public static bool IsValidUrl(this string value) + { + return Uri.TryCreate(value, UriKind.Absolute, out var uriResult) + && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps); + } + + public static string GetClaimValueOrDefault(this IPrincipal currentPrincipal, string claimName) + { + var identity = currentPrincipal.Identity as ClaimsIdentity; + var claim = identity?.Claims.SingleOrDefault(c => c.Type == claimName); + return claim?.Value; + } + } +} diff --git a/src/server/appsettings.json b/src/server/appsettings.json new file mode 100644 index 0000000..81ff877 --- /dev/null +++ b/src/server/appsettings.json @@ -0,0 +1,10 @@ +{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/src/server/database.sqlite b/src/server/database.sqlite Binary files differnew file mode 100644 index 0000000..f86d443 --- /dev/null +++ b/src/server/database.sqlite |
