summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/.idea/.idea.WhatApi/.idea/data_source_mapping.xml6
-rw-r--r--api/.idea/.idea.WhatApi/.idea/indexLayout.xml4
-rw-r--r--api/.idea/.idea.WhatApi/.idea/sqldialects.xml6
-rw-r--r--api/WhatApi/Constants.cs9
-rw-r--r--api/WhatApi/Database/AppDatabase.cs (renamed from api/WhatApi/Database.cs)6
-rw-r--r--api/WhatApi/Database/Tables/AuditTrail.cs (renamed from api/WhatApi/Tables/AuditTrail.cs)2
-rw-r--r--api/WhatApi/Database/Tables/BaseAuditableEntity.cs18
-rw-r--r--api/WhatApi/Database/Tables/Content.cs (renamed from api/WhatApi/Tables/Content.cs)8
-rw-r--r--api/WhatApi/Database/Tables/IAuditableEntity.cs (renamed from api/WhatApi/Tables/IAuditableEntity.cs)2
-rw-r--r--api/WhatApi/Database/Tables/Place.cs (renamed from api/WhatApi/Tables/Place.cs)8
-rw-r--r--api/WhatApi/Database/Tables/User.cs (renamed from api/WhatApi/Tables/User.cs)20
-rw-r--r--api/WhatApi/Dockerfile4
-rw-r--r--api/WhatApi/Endpoints/BaseEndpoint.cs1
-rw-r--r--api/WhatApi/Endpoints/CreateUserEndpoint.cs30
-rw-r--r--api/WhatApi/Endpoints/GetPlacesEndpoint.cs3
-rw-r--r--api/WhatApi/Endpoints/LoginEndpoint.cs52
-rw-r--r--api/WhatApi/Endpoints/UploadContentEndpoint.cs6
-rw-r--r--api/WhatApi/Extras/IConfigurationExtensions.cs9
-rw-r--r--api/WhatApi/Extras/PasswordHasher.cs23
-rw-r--r--api/WhatApi/Middleware/AuthenticationMiddleware.cs2
-rw-r--r--api/WhatApi/Middleware/AuthorizationMiddleware.cs2
-rw-r--r--api/WhatApi/Middleware/UserLastSeenMiddleware.cs2
-rw-r--r--api/WhatApi/Migrations/20251026215643_Initial.Designer.cs214
-rw-r--r--api/WhatApi/Migrations/20251202203059_PasswordMaxLength.cs36
-rw-r--r--api/WhatApi/Migrations/20251203204812_Initial.Designer.cs (renamed from api/WhatApi/Migrations/20251202203059_PasswordMaxLength.Designer.cs)26
-rw-r--r--api/WhatApi/Migrations/20251203204812_Initial.cs (renamed from api/WhatApi/Migrations/20251026215643_Initial.cs)8
-rw-r--r--api/WhatApi/Migrations/AppDatabaseModelSnapshot.cs (renamed from api/WhatApi/Migrations/DatabaseModelSnapshot.cs)24
-rw-r--r--api/WhatApi/Program.cs47
-rw-r--r--api/WhatApi/Seed.cs5
-rw-r--r--api/WhatApi/WhatApi.csproj54
-rw-r--r--api/http/http-client.env.json5
-rw-r--r--api/http/login.http17
32 files changed, 307 insertions, 352 deletions
diff --git a/api/.idea/.idea.WhatApi/.idea/data_source_mapping.xml b/api/.idea/.idea.WhatApi/.idea/data_source_mapping.xml
new file mode 100644
index 0000000..16f7743
--- /dev/null
+++ b/api/.idea/.idea.WhatApi/.idea/data_source_mapping.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="DataSourcePerFileMappings">
+ <file url="file://$PROJECT_DIR$/.idea/.idea.WhatApi/.idea/queries/Query.sql" value="4705593e-7cb4-468e-aaf4-fd47704f2409" />
+ </component>
+</project> \ No newline at end of file
diff --git a/api/.idea/.idea.WhatApi/.idea/indexLayout.xml b/api/.idea/.idea.WhatApi/.idea/indexLayout.xml
index 7b08163..323a9b7 100644
--- a/api/.idea/.idea.WhatApi/.idea/indexLayout.xml
+++ b/api/.idea/.idea.WhatApi/.idea/indexLayout.xml
@@ -1,7 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
- <attachedFolders />
+ <attachedFolders>
+ <Path>.</Path>
+ </attachedFolders>
<explicitIncludes />
<explicitExcludes />
</component>
diff --git a/api/.idea/.idea.WhatApi/.idea/sqldialects.xml b/api/.idea/.idea.WhatApi/.idea/sqldialects.xml
new file mode 100644
index 0000000..f21172d
--- /dev/null
+++ b/api/.idea/.idea.WhatApi/.idea/sqldialects.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="SqlDialectMappings">
+ <file url="file://$PROJECT_DIR$/.idea/.idea.WhatApi/.idea/queries/Query.sql" dialect="PostgreSQL" />
+ </component>
+</project> \ No newline at end of file
diff --git a/api/WhatApi/Constants.cs b/api/WhatApi/Constants.cs
index 01385c1..dab7ab2 100644
--- a/api/WhatApi/Constants.cs
+++ b/api/WhatApi/Constants.cs
@@ -3,4 +3,13 @@ namespace WhatApi;
public static class Constants
{
public const int Wgs84SpatialReferenceId = 4326;
+ public static readonly Guid SystemUid = new("8c3b23aa-e759-4cf0-b405-c13979d15586");
+
+ public static class Env
+ {
+ public const string MasterDbConnectionString = "MasterDbConnectionString";
+ public const string TokenEntropy = "TokenEntropy";
+ public const string TokenIssuer = "TokenIssuer";
+ public const string TokenAudience = "TokenAudience";
+ }
} \ No newline at end of file
diff --git a/api/WhatApi/Database.cs b/api/WhatApi/Database/AppDatabase.cs
index 8aeffed..64c138f 100644
--- a/api/WhatApi/Database.cs
+++ b/api/WhatApi/Database/AppDatabase.cs
@@ -1,10 +1,8 @@
using System.Security.Claims;
-using WhatApi.Extras;
-using WhatApi.Tables;
-namespace WhatApi;
+namespace WhatApi.Database;
-public class Database(DbContextOptions<Database> options, IHttpContextAccessor httpContextAccessor, IConfiguration configuration) : DbContext(options)
+public class AppDatabase(DbContextOptions<AppDatabase> options, IHttpContextAccessor httpContextAccessor, IConfiguration configuration) : DbContext(options)
{
public DbSet<Content> Content => Set<Content>();
public DbSet<Place> Places => Set<Place>();
diff --git a/api/WhatApi/Tables/AuditTrail.cs b/api/WhatApi/Database/Tables/AuditTrail.cs
index 9613af4..4ded46c 100644
--- a/api/WhatApi/Tables/AuditTrail.cs
+++ b/api/WhatApi/Database/Tables/AuditTrail.cs
@@ -1,4 +1,4 @@
-namespace WhatApi.Tables;
+namespace WhatApi.Database.Tables;
public class AuditTrail
{
diff --git a/api/WhatApi/Database/Tables/BaseAuditableEntity.cs b/api/WhatApi/Database/Tables/BaseAuditableEntity.cs
new file mode 100644
index 0000000..25fb3fa
--- /dev/null
+++ b/api/WhatApi/Database/Tables/BaseAuditableEntity.cs
@@ -0,0 +1,18 @@
+namespace WhatApi.Database.Tables;
+
+public class BaseAuditableEntity : IAuditableEntity
+{
+ public DateTimeOffset CreatedAtUtc { get; set; }
+ public DateTimeOffset? UpdatedAtUtc { get; set; }
+ public Guid CreatedBy { get; set; }
+ public Guid? UpdatedBy { get; set; }
+
+ public void SetCreated(Guid createdBy) {
+ CreatedBy = createdBy;
+ CreatedAtUtc = DateTimeOffset.UtcNow;
+ }
+ public void SetUpdated(Guid updatedBy) {
+ UpdatedBy = updatedBy;
+ UpdatedAtUtc = DateTimeOffset.UtcNow;
+ }
+} \ No newline at end of file
diff --git a/api/WhatApi/Tables/Content.cs b/api/WhatApi/Database/Tables/Content.cs
index 0b26b82..8148f81 100644
--- a/api/WhatApi/Tables/Content.cs
+++ b/api/WhatApi/Database/Tables/Content.cs
@@ -1,17 +1,13 @@
using System.Net;
-namespace WhatApi.Tables;
+namespace WhatApi.Database.Tables;
-public class Content : IAuditableEntity
+public class Content : BaseAuditableEntity
{
public Guid Id { get; set; }
public required string Mime { get; set; }
public Guid BlobId { get; set; }
public required IPAddress Ip { get; set; }
- public DateTimeOffset CreatedAtUtc { get; set; }
- public DateTimeOffset? UpdatedAtUtc { get; set; }
- public Guid CreatedBy { get; set; }
- public Guid? UpdatedBy { get; set; }
}
public class ContentConfiguration : IEntityTypeConfiguration<Content>
diff --git a/api/WhatApi/Tables/IAuditableEntity.cs b/api/WhatApi/Database/Tables/IAuditableEntity.cs
index a11e7f2..61d64fd 100644
--- a/api/WhatApi/Tables/IAuditableEntity.cs
+++ b/api/WhatApi/Database/Tables/IAuditableEntity.cs
@@ -1,4 +1,4 @@
-namespace WhatApi.Tables;
+namespace WhatApi.Database.Tables;
public interface IAuditableEntity
{
diff --git a/api/WhatApi/Tables/Place.cs b/api/WhatApi/Database/Tables/Place.cs
index 969007b..2914aa7 100644
--- a/api/WhatApi/Tables/Place.cs
+++ b/api/WhatApi/Database/Tables/Place.cs
@@ -1,15 +1,11 @@
-namespace WhatApi.Tables;
+namespace WhatApi.Database.Tables;
-public class Place : IAuditableEntity
+public class Place : BaseAuditableEntity
{
public Guid Id { get; set; }
public Guid ContentId { get; set; }
public Content Content { get; set; } = null!;
public required Point Location { get; set; }
- public DateTimeOffset CreatedAtUtc { get; set; }
- public DateTimeOffset? UpdatedAtUtc { get; set; }
- public Guid CreatedBy { get; set; }
- public Guid? UpdatedBy { get; set; }
}
public class PlaceConfiguration : IEntityTypeConfiguration<Place>
diff --git a/api/WhatApi/Tables/User.cs b/api/WhatApi/Database/Tables/User.cs
index 9044439..bfcdb50 100644
--- a/api/WhatApi/Tables/User.cs
+++ b/api/WhatApi/Database/Tables/User.cs
@@ -1,21 +1,17 @@
-using WhatApi.Extras;
+namespace WhatApi.Database.Tables;
-namespace WhatApi.Tables;
-
-public class User : IAuditableEntity
+public class User : BaseAuditableEntity
{
public Guid Id { get; set; }
public required string Name { get; set; }
public required string Email { get; set; }
-
- [AuditTrailIgnore]
- public required string Password { get; set; }
+ public required string PasswordHash { get; set; }
public DateTimeOffset? LastSeen { get; set; }
public IEnumerable<Place> Places { get; set; } = null!;
- public DateTimeOffset CreatedAtUtc { get; set; }
- public DateTimeOffset? UpdatedAtUtc { get; set; }
- public Guid CreatedBy { get; set; }
- public Guid? UpdatedBy { get; set; }
+
+ public void SetLastSeen() {
+ LastSeen = DateTimeOffset.UtcNow;
+ }
}
public class UserConfiguration : IEntityTypeConfiguration<User>
@@ -24,7 +20,7 @@ public class UserConfiguration : IEntityTypeConfiguration<User>
builder.HasKey(x => x.Id);
builder.Property(x => x.Name).HasMaxLength(50);
builder.Property(x => x.Email).HasMaxLength(100);
- builder.Property(x => x.Password).HasMaxLength(100);
+ builder.Property(x => x.PasswordHash).HasMaxLength(100);
builder.HasMany(x => x.Places);
builder.ToTable("user");
}
diff --git a/api/WhatApi/Dockerfile b/api/WhatApi/Dockerfile
index d559b98..2655750 100644
--- a/api/WhatApi/Dockerfile
+++ b/api/WhatApi/Dockerfile
@@ -1,10 +1,10 @@
-FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
+FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
-FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
+FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["WhatApi/WhatApi.csproj", "WhatApi/"]
diff --git a/api/WhatApi/Endpoints/BaseEndpoint.cs b/api/WhatApi/Endpoints/BaseEndpoint.cs
index 1fdf14f..0820ac1 100644
--- a/api/WhatApi/Endpoints/BaseEndpoint.cs
+++ b/api/WhatApi/Endpoints/BaseEndpoint.cs
@@ -2,6 +2,7 @@ using System.Net;
namespace WhatApi.Endpoints;
+[Authorize]
[ApiController]
public class BaseEndpoint : ControllerBase
{
diff --git a/api/WhatApi/Endpoints/CreateUserEndpoint.cs b/api/WhatApi/Endpoints/CreateUserEndpoint.cs
new file mode 100644
index 0000000..dc89c16
--- /dev/null
+++ b/api/WhatApi/Endpoints/CreateUserEndpoint.cs
@@ -0,0 +1,30 @@
+namespace WhatApi.Endpoints;
+
+public class CreateUserEndpoint(AppDatabase db, IConfiguration configuration) : BaseEndpoint
+{
+ public class CreateUserRequest
+ {
+ public required string Email { get; set; }
+ public required string Password { get; set; }
+ public required string Name { get; set; }
+ }
+
+ [AllowAnonymous]
+ [HttpPost("~/create-user")]
+ public async Task<ActionResult> HandleAsync(CreateUserRequest req, CancellationToken ct = default) {
+ var userList = await db.Users.Select(c => new {
+ c.Name
+ }).ToListAsync(ct);
+ if (userList.Count == 0 && !configuration.IsDevelopment) return Unauthorized();
+ if (userList.Any(c => c.Name.Equals(req.Name, StringComparison.InvariantCultureIgnoreCase))) return BadRequest("Username taken");
+ var user = new User {
+ Name = req.Name,
+ Email = req.Email,
+ PasswordHash = PasswordHasher.HashPassword(req.Password)
+ };
+ user.SetCreated(Constants.SystemUid);
+ db.Users.Add(user);
+ await db.SaveChangesAsync(ct);
+ return Ok();
+ }
+} \ No newline at end of file
diff --git a/api/WhatApi/Endpoints/GetPlacesEndpoint.cs b/api/WhatApi/Endpoints/GetPlacesEndpoint.cs
index 08068c8..28b1613 100644
--- a/api/WhatApi/Endpoints/GetPlacesEndpoint.cs
+++ b/api/WhatApi/Endpoints/GetPlacesEndpoint.cs
@@ -1,9 +1,8 @@
using NetTopologySuite.Features;
-using WhatApi.Tables;
namespace WhatApi.Endpoints;
-public class GetPlacesEndpoint(Database db) : BaseEndpoint
+public class GetPlacesEndpoint(AppDatabase db) : BaseEndpoint
{
[HttpGet("~/places")]
public async Task<ActionResult> HandleAsync(double w, double s, double e, double n, CancellationToken ct = default) {
diff --git a/api/WhatApi/Endpoints/LoginEndpoint.cs b/api/WhatApi/Endpoints/LoginEndpoint.cs
new file mode 100644
index 0000000..ee697ef
--- /dev/null
+++ b/api/WhatApi/Endpoints/LoginEndpoint.cs
@@ -0,0 +1,52 @@
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.IdentityModel.Tokens;
+
+namespace WhatApi.Endpoints;
+
+public class LoginEndpoint(AppDatabase db, IConfiguration configuration) : BaseEndpoint
+{
+ public class LoginRequest
+ {
+ public required string Username { get; set; }
+ public required string Password { get; set; }
+ }
+
+ [HttpPost("~/login")]
+ public async Task<ActionResult> HandleAsync(LoginRequest login, CancellationToken ct = default) {
+ var user = await db.Users.FirstOrDefaultAsync(c => c.Name == login.Username, ct);
+ if (user?.PasswordHash is null) return Unauthorized();
+
+ var verificationResult = PasswordHasher.VerifyHashedPassword(user.PasswordHash, login.Password);
+ if (verificationResult == PasswordVerificationResult.Failed) return Unauthorized();
+
+ var tokenEntropy = configuration.GetValue<string>(Constants.Env.TokenEntropy);
+
+ ArgumentException.ThrowIfNullOrWhiteSpace(tokenEntropy);
+
+ var key = Encoding.ASCII.GetBytes(tokenEntropy);
+ var tokenIssuer = configuration.GetValue<string>(Constants.Env.TokenIssuer);
+ var tokenAudience = configuration.GetValue<string>(Constants.Env.TokenAudience);
+ var tokenHandler = new JwtSecurityTokenHandler();
+
+ var tokenDescriptor = new SecurityTokenDescriptor {
+ Subject = new ClaimsIdentity([
+ new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
+ new Claim(ClaimTypes.Name, user.Name)
+ ]),
+ Expires = DateTime.UtcNow.AddMinutes(60),
+ Issuer = tokenIssuer,
+ Audience = tokenAudience,
+ SigningCredentials = new SigningCredentials(
+ new SymmetricSecurityKey(key),
+ SecurityAlgorithms.HmacSha256Signature)
+ };
+
+ var token = tokenHandler.CreateToken(tokenDescriptor);
+ var tokenString = tokenHandler.WriteToken(token);
+ user.SetLastSeen();
+ await db.SaveChangesAsync(ct);
+ return Ok(tokenString);
+ }
+} \ No newline at end of file
diff --git a/api/WhatApi/Endpoints/UploadContentEndpoint.cs b/api/WhatApi/Endpoints/UploadContentEndpoint.cs
index 2c84252..26dbdba 100644
--- a/api/WhatApi/Endpoints/UploadContentEndpoint.cs
+++ b/api/WhatApi/Endpoints/UploadContentEndpoint.cs
@@ -1,6 +1,6 @@
namespace WhatApi.Endpoints;
-public class UploadContentEndpoint(Database db) : BaseEndpoint
+public class UploadContentEndpoint(AppDatabase db) : BaseEndpoint
{
public record UploadContent(IFormFile File, string LatLong);
@@ -19,12 +19,12 @@ public class UploadContentEndpoint(Database db) : BaseEndpoint
var gf = NtsGeometryServices.Instance.CreateGeometryFactory(srid: Constants.Wgs84SpatialReferenceId);
var point = gf.CreatePoint(new Coordinate(double.Parse(longitude), double.Parse(latitude)));
- var place = new Tables.Place() {
+ var place = new Place() {
ContentId = contentId,
Location = point
};
- var content = new Tables.Content() {
+ var content = new Content() {
Id = contentId,
Mime = request.File.ContentType,
BlobId = blobId,
diff --git a/api/WhatApi/Extras/IConfigurationExtensions.cs b/api/WhatApi/Extras/IConfigurationExtensions.cs
new file mode 100644
index 0000000..5453699
--- /dev/null
+++ b/api/WhatApi/Extras/IConfigurationExtensions.cs
@@ -0,0 +1,9 @@
+namespace WhatApi.Extras;
+
+public static class IConfigurationExtensions
+{
+ extension(IConfiguration configuration)
+ {
+ public bool IsDevelopment => configuration.GetValue<string>("ASPNETCORE_ENVIRONMENT") == "Development";
+ }
+} \ No newline at end of file
diff --git a/api/WhatApi/Extras/PasswordHasher.cs b/api/WhatApi/Extras/PasswordHasher.cs
new file mode 100644
index 0000000..88e69a7
--- /dev/null
+++ b/api/WhatApi/Extras/PasswordHasher.cs
@@ -0,0 +1,23 @@
+using Microsoft.AspNetCore.Identity;
+
+namespace WhatApi.Extras;
+
+public class VendorPasswordHasher : PasswordHasher<User>;
+
+public static class PasswordHasher
+{
+ private static readonly VendorPasswordHasher _ = new();
+ private static readonly User User = new() {
+ Name = "",
+ Email = "",
+ PasswordHash = ""
+ };
+
+ public static string HashPassword(string password) {
+ return _.HashPassword(User, password);
+ }
+
+ public static PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword) {
+ return _.VerifyHashedPassword(User, hashedPassword, providedPassword);
+ }
+} \ No newline at end of file
diff --git a/api/WhatApi/Middleware/AuthenticationMiddleware.cs b/api/WhatApi/Middleware/AuthenticationMiddleware.cs
index 8cdce53..a691d3c 100644
--- a/api/WhatApi/Middleware/AuthenticationMiddleware.cs
+++ b/api/WhatApi/Middleware/AuthenticationMiddleware.cs
@@ -1,6 +1,6 @@
namespace WhatApi.Middleware;
-public class AuthenticationMiddleware(RequestDelegate next,Database db)
+public class AuthenticationMiddleware(RequestDelegate next)
{
public async Task InvokeAsync(HttpContext context) {
await next(context);
diff --git a/api/WhatApi/Middleware/AuthorizationMiddleware.cs b/api/WhatApi/Middleware/AuthorizationMiddleware.cs
index 26b3f4a..f8db6c4 100644
--- a/api/WhatApi/Middleware/AuthorizationMiddleware.cs
+++ b/api/WhatApi/Middleware/AuthorizationMiddleware.cs
@@ -1,6 +1,6 @@
namespace WhatApi.Middleware;
-public class AuthorizationMiddleware(RequestDelegate next, Database db)
+public class AuthorizationMiddleware(RequestDelegate next)
{
public async Task InvokeAsync(HttpContext context) {
await next(context);
diff --git a/api/WhatApi/Middleware/UserLastSeenMiddleware.cs b/api/WhatApi/Middleware/UserLastSeenMiddleware.cs
index 5c7f1e7..b3fcd89 100644
--- a/api/WhatApi/Middleware/UserLastSeenMiddleware.cs
+++ b/api/WhatApi/Middleware/UserLastSeenMiddleware.cs
@@ -4,7 +4,7 @@ namespace WhatApi.Middleware;
public class UserLastSeenMiddleware(RequestDelegate next)
{
- public async Task InvokeAsync(HttpContext context,Database db) {
+ public async Task InvokeAsync(HttpContext context, AppDatabase db) {
var userIdString = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
if (Guid.TryParse(userIdString, out var userId)) {
var user = await db.Users.FirstOrDefaultAsync(c => c.Id == userId);
diff --git a/api/WhatApi/Migrations/20251026215643_Initial.Designer.cs b/api/WhatApi/Migrations/20251026215643_Initial.Designer.cs
deleted file mode 100644
index c430ed1..0000000
--- a/api/WhatApi/Migrations/20251026215643_Initial.Designer.cs
+++ /dev/null
@@ -1,214 +0,0 @@
-// <auto-generated />
-using System;
-using System.Collections.Generic;
-using System.Net;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using NetTopologySuite.Geometries;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-using WhatApi;
-
-#nullable disable
-
-namespace WhatApi.Migrations
-{
- [DbContext(typeof(Database))]
- [Migration("20251026215643_Initial")]
- partial class Initial
- {
- /// <inheritdoc />
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "9.0.9")
- .HasAnnotation("Relational:MaxIdentifierLength", 63);
-
- NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis");
- NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
-
- modelBuilder.Entity("WhatApi.Tables.AuditTrail", b =>
- {
- b.Property<Guid>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("uuid");
-
- b.PrimitiveCollection<List<string>>("ChangedColumns")
- .IsRequired()
- .HasColumnType("jsonb");
-
- b.Property<DateTimeOffset>("DateUtc")
- .HasColumnType("timestamp with time zone");
-
- b.Property<string>("EntityName")
- .IsRequired()
- .HasMaxLength(100)
- .HasColumnType("character varying(100)");
-
- b.Property<Dictionary<string, object>>("NewValues")
- .IsRequired()
- .HasColumnType("jsonb");
-
- b.Property<Dictionary<string, object>>("OldValues")
- .IsRequired()
- .HasColumnType("jsonb");
-
- b.Property<string>("PrimaryKey")
- .HasMaxLength(100)
- .HasColumnType("character varying(100)");
-
- b.Property<string>("TrailType")
- .IsRequired()
- .HasColumnType("text");
-
- b.Property<Guid?>("UserId")
- .HasColumnType("uuid");
-
- b.HasKey("Id");
-
- b.HasIndex("EntityName");
-
- b.ToTable("audit_trails", (string)null);
- });
-
- modelBuilder.Entity("WhatApi.Tables.Content", b =>
- {
- b.Property<Guid>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("uuid");
-
- b.Property<Guid>("BlobId")
- .HasColumnType("uuid");
-
- b.Property<DateTimeOffset>("CreatedAtUtc")
- .HasColumnType("timestamp with time zone");
-
- b.Property<Guid>("CreatedBy")
- .HasColumnType("uuid");
-
- b.Property<IPAddress>("Ip")
- .IsRequired()
- .HasColumnType("inet");
-
- b.Property<string>("Mime")
- .IsRequired()
- .HasMaxLength(100)
- .HasColumnType("character varying(100)");
-
- b.Property<DateTimeOffset?>("UpdatedAtUtc")
- .HasColumnType("timestamp with time zone");
-
- b.Property<Guid?>("UpdatedBy")
- .HasColumnType("uuid");
-
- b.HasKey("Id");
-
- b.ToTable("content", (string)null);
- });
-
- modelBuilder.Entity("WhatApi.Tables.Place", b =>
- {
- b.Property<Guid>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("uuid");
-
- b.Property<Guid>("ContentId")
- .HasColumnType("uuid");
-
- b.Property<DateTimeOffset>("CreatedAtUtc")
- .HasColumnType("timestamp with time zone");
-
- b.Property<Guid>("CreatedBy")
- .HasColumnType("uuid");
-
- b.Property<Point>("Location")
- .IsRequired()
- .HasColumnType("geometry(point,4326)");
-
- b.Property<DateTimeOffset?>("UpdatedAtUtc")
- .HasColumnType("timestamp with time zone");
-
- b.Property<Guid?>("UpdatedBy")
- .HasColumnType("uuid");
-
- b.Property<Guid?>("UserId")
- .HasColumnType("uuid");
-
- b.HasKey("Id");
-
- b.HasIndex("ContentId");
-
- b.HasIndex("Location");
-
- NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Location"), "gist");
-
- b.HasIndex("UserId");
-
- b.ToTable("place", (string)null);
- });
-
- modelBuilder.Entity("WhatApi.Tables.User", b =>
- {
- b.Property<Guid>("Id")
- .ValueGeneratedOnAdd()
- .HasColumnType("uuid");
-
- b.Property<DateTimeOffset>("CreatedAtUtc")
- .HasColumnType("timestamp with time zone");
-
- b.Property<Guid>("CreatedBy")
- .HasColumnType("uuid");
-
- b.Property<string>("Email")
- .IsRequired()
- .HasMaxLength(100)
- .HasColumnType("character varying(100)");
-
- b.Property<DateTimeOffset?>("LastSeen")
- .HasColumnType("timestamp with time zone");
-
- b.Property<string>("Name")
- .IsRequired()
- .HasMaxLength(50)
- .HasColumnType("character varying(50)");
-
- b.Property<string>("Password")
- .IsRequired()
- .HasColumnType("text");
-
- b.Property<DateTimeOffset?>("UpdatedAtUtc")
- .HasColumnType("timestamp with time zone");
-
- b.Property<Guid?>("UpdatedBy")
- .HasColumnType("uuid");
-
- b.HasKey("Id");
-
- b.ToTable("user", (string)null);
- });
-
- modelBuilder.Entity("WhatApi.Tables.Place", b =>
- {
- b.HasOne("WhatApi.Tables.Content", "Content")
- .WithMany()
- .HasForeignKey("ContentId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.HasOne("WhatApi.Tables.User", null)
- .WithMany("Places")
- .HasForeignKey("UserId");
-
- b.Navigation("Content");
- });
-
- modelBuilder.Entity("WhatApi.Tables.User", b =>
- {
- b.Navigation("Places");
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/api/WhatApi/Migrations/20251202203059_PasswordMaxLength.cs b/api/WhatApi/Migrations/20251202203059_PasswordMaxLength.cs
deleted file mode 100644
index 8129df8..0000000
--- a/api/WhatApi/Migrations/20251202203059_PasswordMaxLength.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using Microsoft.EntityFrameworkCore.Migrations;
-
-#nullable disable
-
-namespace WhatApi.Migrations
-{
- /// <inheritdoc />
- public partial class PasswordMaxLength : Migration
- {
- /// <inheritdoc />
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.AlterColumn<string>(
- name: "Password",
- table: "user",
- type: "character varying(100)",
- maxLength: 100,
- nullable: false,
- oldClrType: typeof(string),
- oldType: "text");
- }
-
- /// <inheritdoc />
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.AlterColumn<string>(
- name: "Password",
- table: "user",
- type: "text",
- nullable: false,
- oldClrType: typeof(string),
- oldType: "character varying(100)",
- oldMaxLength: 100);
- }
- }
-}
diff --git a/api/WhatApi/Migrations/20251202203059_PasswordMaxLength.Designer.cs b/api/WhatApi/Migrations/20251203204812_Initial.Designer.cs
index f23a067..39847ba 100644
--- a/api/WhatApi/Migrations/20251202203059_PasswordMaxLength.Designer.cs
+++ b/api/WhatApi/Migrations/20251203204812_Initial.Designer.cs
@@ -8,15 +8,15 @@ using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using NetTopologySuite.Geometries;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-using WhatApi;
+using WhatApi.Database;
#nullable disable
namespace WhatApi.Migrations
{
- [DbContext(typeof(Database))]
- [Migration("20251202203059_PasswordMaxLength")]
- partial class PasswordMaxLength
+ [DbContext(typeof(AppDatabase))]
+ [Migration("20251203204812_Initial")]
+ partial class Initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@@ -29,7 +29,7 @@ namespace WhatApi.Migrations
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis");
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
- modelBuilder.Entity("WhatApi.Tables.AuditTrail", b =>
+ modelBuilder.Entity("WhatApi.Database.Tables.AuditTrail", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -73,7 +73,7 @@ namespace WhatApi.Migrations
b.ToTable("audit_trails", (string)null);
});
- modelBuilder.Entity("WhatApi.Tables.Content", b =>
+ modelBuilder.Entity("WhatApi.Database.Tables.Content", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -108,7 +108,7 @@ namespace WhatApi.Migrations
b.ToTable("content", (string)null);
});
- modelBuilder.Entity("WhatApi.Tables.Place", b =>
+ modelBuilder.Entity("WhatApi.Database.Tables.Place", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -149,7 +149,7 @@ namespace WhatApi.Migrations
b.ToTable("place", (string)null);
});
- modelBuilder.Entity("WhatApi.Tables.User", b =>
+ modelBuilder.Entity("WhatApi.Database.Tables.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -174,7 +174,7 @@ namespace WhatApi.Migrations
.HasMaxLength(50)
.HasColumnType("character varying(50)");
- b.Property<string>("Password")
+ b.Property<string>("PasswordHash")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
@@ -190,22 +190,22 @@ namespace WhatApi.Migrations
b.ToTable("user", (string)null);
});
- modelBuilder.Entity("WhatApi.Tables.Place", b =>
+ modelBuilder.Entity("WhatApi.Database.Tables.Place", b =>
{
- b.HasOne("WhatApi.Tables.Content", "Content")
+ b.HasOne("WhatApi.Database.Tables.Content", "Content")
.WithMany()
.HasForeignKey("ContentId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
- b.HasOne("WhatApi.Tables.User", null)
+ b.HasOne("WhatApi.Database.Tables.User", null)
.WithMany("Places")
.HasForeignKey("UserId");
b.Navigation("Content");
});
- modelBuilder.Entity("WhatApi.Tables.User", b =>
+ modelBuilder.Entity("WhatApi.Database.Tables.User", b =>
{
b.Navigation("Places");
});
diff --git a/api/WhatApi/Migrations/20251026215643_Initial.cs b/api/WhatApi/Migrations/20251203204812_Initial.cs
index b8f0444..f49a9f3 100644
--- a/api/WhatApi/Migrations/20251026215643_Initial.cs
+++ b/api/WhatApi/Migrations/20251203204812_Initial.cs
@@ -29,7 +29,7 @@ namespace WhatApi.Migrations
DateUtc = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
OldValues = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: false),
NewValues = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: false),
- ChangedColumns = table.Column<List<string>>(type: "jsonb", nullable: false)
+ ChangedColumns = table.Column<string>(type: "jsonb", nullable: false)
},
constraints: table =>
{
@@ -61,7 +61,7 @@ namespace WhatApi.Migrations
Id = table.Column<Guid>(type: "uuid", nullable: false),
Name = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
Email = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
- Password = table.Column<string>(type: "text", nullable: false),
+ PasswordHash = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
LastSeen = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
CreatedAtUtc = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
UpdatedAtUtc = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
@@ -80,11 +80,11 @@ namespace WhatApi.Migrations
Id = table.Column<Guid>(type: "uuid", nullable: false),
ContentId = table.Column<Guid>(type: "uuid", nullable: false),
Location = table.Column<Point>(type: "geometry(point,4326)", nullable: false),
+ UserId = table.Column<Guid>(type: "uuid", nullable: true),
CreatedAtUtc = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
UpdatedAtUtc = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
CreatedBy = table.Column<Guid>(type: "uuid", nullable: false),
- UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true),
- UserId = table.Column<Guid>(type: "uuid", nullable: true)
+ UpdatedBy = table.Column<Guid>(type: "uuid", nullable: true)
},
constraints: table =>
{
diff --git a/api/WhatApi/Migrations/DatabaseModelSnapshot.cs b/api/WhatApi/Migrations/AppDatabaseModelSnapshot.cs
index babdf01..24973b0 100644
--- a/api/WhatApi/Migrations/DatabaseModelSnapshot.cs
+++ b/api/WhatApi/Migrations/AppDatabaseModelSnapshot.cs
@@ -7,14 +7,14 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using NetTopologySuite.Geometries;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-using WhatApi;
+using WhatApi.Database;
#nullable disable
namespace WhatApi.Migrations
{
- [DbContext(typeof(Database))]
- partial class DatabaseModelSnapshot : ModelSnapshot
+ [DbContext(typeof(AppDatabase))]
+ partial class AppDatabaseModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
@@ -26,7 +26,7 @@ namespace WhatApi.Migrations
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "postgis");
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
- modelBuilder.Entity("WhatApi.Tables.AuditTrail", b =>
+ modelBuilder.Entity("WhatApi.Database.Tables.AuditTrail", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -70,7 +70,7 @@ namespace WhatApi.Migrations
b.ToTable("audit_trails", (string)null);
});
- modelBuilder.Entity("WhatApi.Tables.Content", b =>
+ modelBuilder.Entity("WhatApi.Database.Tables.Content", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -105,7 +105,7 @@ namespace WhatApi.Migrations
b.ToTable("content", (string)null);
});
- modelBuilder.Entity("WhatApi.Tables.Place", b =>
+ modelBuilder.Entity("WhatApi.Database.Tables.Place", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -146,7 +146,7 @@ namespace WhatApi.Migrations
b.ToTable("place", (string)null);
});
- modelBuilder.Entity("WhatApi.Tables.User", b =>
+ modelBuilder.Entity("WhatApi.Database.Tables.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -171,7 +171,7 @@ namespace WhatApi.Migrations
.HasMaxLength(50)
.HasColumnType("character varying(50)");
- b.Property<string>("Password")
+ b.Property<string>("PasswordHash")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
@@ -187,22 +187,22 @@ namespace WhatApi.Migrations
b.ToTable("user", (string)null);
});
- modelBuilder.Entity("WhatApi.Tables.Place", b =>
+ modelBuilder.Entity("WhatApi.Database.Tables.Place", b =>
{
- b.HasOne("WhatApi.Tables.Content", "Content")
+ b.HasOne("WhatApi.Database.Tables.Content", "Content")
.WithMany()
.HasForeignKey("ContentId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
- b.HasOne("WhatApi.Tables.User", null)
+ b.HasOne("WhatApi.Database.Tables.User", null)
.WithMany("Places")
.HasForeignKey("UserId");
b.Navigation("Content");
});
- modelBuilder.Entity("WhatApi.Tables.User", b =>
+ modelBuilder.Entity("WhatApi.Database.Tables.User", b =>
{
b.Navigation("Places");
});
diff --git a/api/WhatApi/Program.cs b/api/WhatApi/Program.cs
index ac7e825..fa50661 100644
--- a/api/WhatApi/Program.cs
+++ b/api/WhatApi/Program.cs
@@ -7,15 +7,24 @@ global using NetTopologySuite.Geometries;
global using Microsoft.AspNetCore.Http.Extensions;
global using Microsoft.AspNetCore.Mvc;
global using NetTopologySuite;
+global using WhatApi.Database.Tables;
+global using WhatApi.Database;
+global using System.Text;
+global using WhatApi.Extras;
+global using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.IdentityModel.Tokens;
using Npgsql;
using WhatApi;
using WhatApi.Middleware;
var builder = WebApplication.CreateBuilder(args);
var dev = builder.Environment.IsDevelopment();
+
builder.Services.AddHttpContextAccessor();
-builder.Services.AddDbContextPool<Database>(b => {
- var dataSourceBuilder = new NpgsqlDataSourceBuilder(builder.Configuration.GetConnectionString("Master"));
+builder.Services.AddDbContextPool<AppDatabase>(b => {
+ var connectionString = builder.Configuration.GetValue<string>(Constants.Env.MasterDbConnectionString);
+ var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
dataSourceBuilder.EnableDynamicJson();
if (dev) {
b.EnableSensitiveDataLogging();
@@ -28,9 +37,35 @@ builder.Services.AddDbContextPool<Database>(b => {
o.UseNetTopologySuite();
});
});
+
if (dev) builder.Configuration["DISABLE_AUDIT_TRAILS"] = "true";
+
builder.Services.AddCors(o => o.AddDefaultPolicy(p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()));
+var tokenEntropy = builder.Configuration.GetValue<string>(Constants.Env.TokenEntropy);
+ArgumentException.ThrowIfNullOrEmpty(tokenEntropy);
+var tokenIssuer = builder.Configuration.GetValue<string>(Constants.Env.TokenIssuer);
+var tokenAudience = builder.Configuration.GetValue<string>(Constants.Env.TokenAudience);
+
+builder.Services.AddAuthentication(options => {
+ options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
+ options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
+ })
+ .AddJwtBearer(options => {
+ options.RequireHttpsMetadata = false;
+ options.SaveToken = true;
+ options.TokenValidationParameters = new TokenValidationParameters {
+ ValidateIssuer = true,
+ ValidateAudience = true,
+ ValidateLifetime = true,
+ ValidateIssuerSigningKey = true,
+ ValidIssuer = tokenIssuer,
+ ValidAudience = tokenAudience,
+ IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(tokenEntropy)),
+ ClockSkew = TimeSpan.Zero
+ };
+ });
+builder.Services.AddAuthorization();
builder.Services.AddControllers()
.AddJsonOptions(o => {
o.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
@@ -39,19 +74,25 @@ builder.Services.AddControllers()
o.JsonSerializerOptions.Converters.Add(new GeoJsonConverterFactory());
});
+
var app = builder.Build();
+
if (dev) {
using var scope = app.Services.CreateScope();
- var db = scope.ServiceProvider.GetRequiredService<Database>();
+ var db = scope.ServiceProvider.GetRequiredService<AppDatabase>();
Seed.Full(db, opt => {
opt.ClearTables = false;
});
}
+
app.UseRouting();
app.UseForwardedHeaders();
app.UseCors();
app.MapStaticAssets();
app.UseMiddleware<UserLastSeenMiddleware>();
+app.UseAuthentication();
app.MapControllers();
+app.MapGet("/", () => Results.Redirect("/map"));
app.Run();
+
return 0; \ No newline at end of file
diff --git a/api/WhatApi/Seed.cs b/api/WhatApi/Seed.cs
index 0e8ab59..4b6361e 100644
--- a/api/WhatApi/Seed.cs
+++ b/api/WhatApi/Seed.cs
@@ -1,18 +1,17 @@
using Bogus;
using Bogus.Locations;
-using WhatApi.Tables;
namespace WhatApi;
public static class Seed
{
- public static void Full(Database db, Action<SeedOptions> seedOptions) {
+ public static void Full(AppDatabase db, Action<SeedOptions> seedOptions) {
var opt = new SeedOptions();
seedOptions.Invoke(opt);
Content(db, opt);
}
- public static void Content(Database db, SeedOptions opt) {
+ public static void Content(AppDatabase db, SeedOptions opt) {
var any = db.Places.Any();
if (any) {
if (!opt.ClearTables) return;
diff --git a/api/WhatApi/WhatApi.csproj b/api/WhatApi/WhatApi.csproj
index c97c55e..91f3d3f 100644
--- a/api/WhatApi/WhatApi.csproj
+++ b/api/WhatApi/WhatApi.csproj
@@ -5,43 +5,45 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
- <UserSecretsId>5506c159-f534-4090-b80b-2703e1eb7f6c</UserSecretsId>
- </PropertyGroup>
+ <UserSecretsId>5506c159-f534-4090-b80b-2703e1eb7f6c</UserSecretsId>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
<ItemGroup>
- <Content Include="..\.dockerignore">
- <Link>.dockerignore</Link>
- </Content>
+ <Content Include="..\.dockerignore">
+ <Link>.dockerignore</Link>
+ </Content>
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Bogus" Version="35.6.5" />
- <PackageReference Include="Bogus.Locations" Version="35.6.5" />
- <PackageReference Include="Fluid.Core" Version="3.0.0-beta.2" />
- <PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.0">
- <PrivateAssets>all</PrivateAssets>
- <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
- </PackageReference>
- <PackageReference Include="NetTopologySuite.IO.GeoJSON4STJ" Version="4.0.0" />
- <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
- <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite" Version="10.0.0" />
- <PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
+ <PackageReference Include="Bogus" Version="35.6.5"/>
+ <PackageReference Include="Bogus.Locations" Version="35.6.5"/>
+ <PackageReference Include="Fluid.Core" Version="3.0.0-beta.2"/>
+ <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.0"/>
+ <PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.0"/>
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.0">
+ <PrivateAssets>all</PrivateAssets>
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+ </PackageReference>
+ <PackageReference Include="NetTopologySuite.IO.GeoJSON4STJ" Version="4.0.0"/>
+ <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0"/>
+ <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite" Version="10.0.0"/>
+ <PackageReference Include="Serilog.AspNetCore" Version="10.0.0"/>
</ItemGroup>
<ItemGroup>
- <Folder Include="files\" />
+ <Folder Include="files\"/>
</ItemGroup>
<ItemGroup>
- <None Remove="Templates\web_login.liquid" />
- <Resource Include="Templates\web_map.liquid">
- <CopyToOutputDirectory>Always</CopyToOutputDirectory>
- </Resource>
- <None Remove="Templates\web_upload.liquid" />
- <Resource Include="Templates\web_upload.liquid">
- <CopyToOutputDirectory>Always</CopyToOutputDirectory>
- </Resource>
+ <None Remove="Templates\web_login.liquid"/>
+ <Resource Include="Templates\web_map.liquid">
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </Resource>
+ <None Remove="Templates\web_upload.liquid"/>
+ <Resource Include="Templates\web_upload.liquid">
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </Resource>
</ItemGroup>
</Project>
diff --git a/api/http/http-client.env.json b/api/http/http-client.env.json
new file mode 100644
index 0000000..af19690
--- /dev/null
+++ b/api/http/http-client.env.json
@@ -0,0 +1,5 @@
+{
+ "dev": {
+ "canonical": "http://localhost:5281"
+ }
+} \ No newline at end of file
diff --git a/api/http/login.http b/api/http/login.http
new file mode 100644
index 0000000..8ebba0c
--- /dev/null
+++ b/api/http/login.http
@@ -0,0 +1,17 @@
+### GET request to example server
+POST {{canonical}}/login
+
+{
+ "username": "",
+ "password": ""
+}
+
+### Create user
+POST {{canonical}}/create-user
+Content-Type: application/json
+
+{
+ "name": "ivarsu",
+ "email": "ivarsu@oiee.no",
+ "password": "ivargangertre"
+}