diff options
Diffstat (limited to 'api/WhatApi')
28 files changed, 840 insertions, 0 deletions
diff --git a/api/WhatApi/Constants.cs b/api/WhatApi/Constants.cs new file mode 100644 index 0000000..01385c1 --- /dev/null +++ b/api/WhatApi/Constants.cs @@ -0,0 +1,6 @@ +namespace WhatApi; + +public static class Constants +{ + public const int Wgs84SpatialReferenceId = 4326; +}
\ No newline at end of file diff --git a/api/WhatApi/Database.cs b/api/WhatApi/Database.cs new file mode 100644 index 0000000..39de79a --- /dev/null +++ b/api/WhatApi/Database.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; + +namespace WhatApi; + +public class Database(DbContextOptions<Database> options) : DbContext(options) +{ + public DbSet<Tables.Content> Content { get; set; } + public DbSet<Tables.Place> Places { get; set; } + protected override void OnModelCreating(ModelBuilder b) { + b.HasPostgresExtension("postgis"); + b.Entity<Tables.Place>(e => { + e.Property(x => x.Location).HasColumnType($"geometry(point,{Constants.Wgs84SpatialReferenceId})"); + e.HasIndex(x => x.Location).HasMethod("gist"); + e.ToTable("Place"); + }); + b.Entity<Tables.Content>(); + base.OnModelCreating(b); + } +}
\ No newline at end of file diff --git a/api/WhatApi/Dockerfile b/api/WhatApi/Dockerfile new file mode 100644 index 0000000..d559b98 --- /dev/null +++ b/api/WhatApi/Dockerfile @@ -0,0 +1,23 @@ +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base +USER $APP_UID +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["WhatApi/WhatApi.csproj", "WhatApi/"] +RUN dotnet restore "WhatApi/WhatApi.csproj" +COPY . . +WORKDIR "/src/WhatApi" +RUN dotnet build "./WhatApi.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./WhatApi.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "WhatApi.dll"] diff --git a/api/WhatApi/Endpoints/BaseEndpoint.cs b/api/WhatApi/Endpoints/BaseEndpoint.cs new file mode 100644 index 0000000..4d8f9ad --- /dev/null +++ b/api/WhatApi/Endpoints/BaseEndpoint.cs @@ -0,0 +1,12 @@ +using System.Net; +using Microsoft.AspNetCore.Mvc; + +namespace WhatApi.Endpoints; + +[ApiController] +public class BaseEndpoint : ControllerBase +{ + protected IPAddress GetIp() => Request.Headers.TryGetValue("X-Forwarded-For", out var ip) + ? IPAddress.Parse(ip.ToString()) + : HttpContext.Connection.RemoteIpAddress!; +}
\ No newline at end of file diff --git a/api/WhatApi/Endpoints/DownloadContentEndpoint.cs b/api/WhatApi/Endpoints/DownloadContentEndpoint.cs new file mode 100644 index 0000000..dbe6bff --- /dev/null +++ b/api/WhatApi/Endpoints/DownloadContentEndpoint.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Mvc; + +namespace WhatApi.Endpoints; + +public class DownloadContentEndpoint : BaseEndpoint +{ + [HttpGet("~/{id:guid}")] + public async Task<ActionResult> HandleAsync(Guid id, CancellationToken ct = default) { + try { + var path = Path.Combine(Directory.GetCurrentDirectory(), "files", id.ToString()); + await using var file = new FileStream(path, FileMode.Open); + if (!file.CanRead) return NotFound(); + return File(file, "application/octet-stream", id.ToString()); + } catch (Exception e) { + if (e is not FileNotFoundException) Console.WriteLine(e); + return NotFound(); + } + } +}
\ No newline at end of file diff --git a/api/WhatApi/Endpoints/GetPlacesEndpoint.cs b/api/WhatApi/Endpoints/GetPlacesEndpoint.cs new file mode 100644 index 0000000..5630229 --- /dev/null +++ b/api/WhatApi/Endpoints/GetPlacesEndpoint.cs @@ -0,0 +1,57 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using NetTopologySuite; +using NetTopologySuite.Features; +using NetTopologySuite.Geometries; +using WhatApi.Tables; + +namespace WhatApi.Endpoints; + +public class GetPlacesEndpoint(Database db) : BaseEndpoint +{ + [HttpGet("~/places")] + public async Task<ActionResult> HandleAsync(string w, string s, string e, string n, CancellationToken ct = default) { + var north = double.Parse(n); + var east = double.Parse(e); + var south = double.Parse(s); + var west = double.Parse(w); + + IQueryable<Place> resultingQuery; + + if (west > east) { + resultingQuery = db.Places + .FromSqlInterpolated($""" + SELECT * FROM "Place" + WHERE ST_Intersects( + "Location", + ST_MakeEnvelope({west}, {south}, 180, {north}, {Constants.Wgs84SpatialReferenceId}) || ST_MakeEnvelope(-180, {south}, {east}, {north}, {Constants.Wgs84SpatialReferenceId}) + ) + """); + } else { + resultingQuery = db.Places + .FromSqlInterpolated($""" + SELECT * FROM "Place" + WHERE ST_Intersects( + "Location", + ST_MakeEnvelope({west}, {south}, {east}, {north}, {Constants.Wgs84SpatialReferenceId}) + ) + """); + } + + var gf = NtsGeometryServices.Instance.CreateGeometryFactory(srid: Constants.Wgs84SpatialReferenceId); + var fc = new FeatureCollection(); + + await foreach (var p in resultingQuery.AsAsyncEnumerable().WithCancellation(ct)) { + var point = gf.CreatePoint(new Coordinate(p.Location.X, p.Location.Y)); + fc.Add(new Feature(point, new AttributesTable { + { + "id", p.Id + }, { + "cid", p.ContentId + } + })); + } + + return Ok(fc); + } +}
\ No newline at end of file diff --git a/api/WhatApi/Endpoints/UploadContentEndpoint.cs b/api/WhatApi/Endpoints/UploadContentEndpoint.cs new file mode 100644 index 0000000..82fb71b --- /dev/null +++ b/api/WhatApi/Endpoints/UploadContentEndpoint.cs @@ -0,0 +1,49 @@ +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Mvc; +using NetTopologySuite; +using NetTopologySuite.Geometries; + +namespace WhatApi.Endpoints; + +public class UploadContentEndpoint(Database db) : BaseEndpoint +{ + public record UploadContent(IFormFile File, string LatLong); + + [HttpPost("~/upload")] + public async Task<ActionResult> HandleAsync([FromForm] UploadContent request, CancellationToken ct = default) { + if (string.IsNullOrWhiteSpace(Request.GetMultipartBoundary())) { + return StatusCode(415, "Unsupported Media Type"); + } + + var blobId = Guid.NewGuid(); + var contentId = Guid.NewGuid(); + + var latitude = request.LatLong.Split(',')[0]; + var longitude = request.LatLong.Split(',')[1]; + + 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() { + ContentId = contentId, + Location = point + }; + + var content = new Tables.Content() { + Id = contentId, + Mime = request.File.ContentType, + Created = DateTime.UtcNow, + BlobId = blobId, + Ip = GetIp() + }; + + var path = Path.Combine(Directory.GetCurrentDirectory(), "files", blobId.ToString()); + + await using var writer = new FileStream(path, FileMode.CreateNew); + await request.File.CopyToAsync(writer, ct); + await db.Content.AddAsync(content, ct); + await db.Places.AddAsync(place, ct); + await db.SaveChangesAsync(ct); + return Ok(contentId); + } +}
\ No newline at end of file diff --git a/api/WhatApi/Middleware/AuthenticationMiddleware.cs b/api/WhatApi/Middleware/AuthenticationMiddleware.cs new file mode 100644 index 0000000..8cdce53 --- /dev/null +++ b/api/WhatApi/Middleware/AuthenticationMiddleware.cs @@ -0,0 +1,8 @@ +namespace WhatApi.Middleware; + +public class AuthenticationMiddleware(RequestDelegate next,Database db) +{ + public async Task InvokeAsync(HttpContext context) { + await next(context); + } +}
\ No newline at end of file diff --git a/api/WhatApi/Middleware/AuthorizationMiddleware.cs b/api/WhatApi/Middleware/AuthorizationMiddleware.cs new file mode 100644 index 0000000..26b3f4a --- /dev/null +++ b/api/WhatApi/Middleware/AuthorizationMiddleware.cs @@ -0,0 +1,8 @@ +namespace WhatApi.Middleware; + +public class AuthorizationMiddleware(RequestDelegate next, Database db) +{ + public async Task InvokeAsync(HttpContext context) { + await next(context); + } +}
\ No newline at end of file diff --git a/api/WhatApi/Middleware/UserLastSeenMiddleware.cs b/api/WhatApi/Middleware/UserLastSeenMiddleware.cs new file mode 100644 index 0000000..ef1b685 --- /dev/null +++ b/api/WhatApi/Middleware/UserLastSeenMiddleware.cs @@ -0,0 +1,8 @@ +namespace WhatApi.Middleware; + +public class UserLastSeenMiddleware(RequestDelegate next, Database db) +{ + public async Task InvokeAsync(HttpContext context) { + await next(context); + } +}
\ No newline at end of file diff --git a/api/WhatApi/Migrations/20251013213511_Initial.Designer.cs b/api/WhatApi/Migrations/20251013213511_Initial.Designer.cs new file mode 100644 index 0000000..5ddcc9f --- /dev/null +++ b/api/WhatApi/Migrations/20251013213511_Initial.Designer.cs @@ -0,0 +1,93 @@ +// <auto-generated /> +using System; +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("20251013213511_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.Content", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<Guid>("BlobId") + .HasColumnType("uuid"); + + b.Property<DateTime>("Created") + .HasColumnType("timestamp with time zone"); + + b.Property<IPAddress>("Ip") + .IsRequired() + .HasColumnType("inet"); + + b.Property<string>("Mime") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Content"); + }); + + modelBuilder.Entity("WhatApi.Tables.Place", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<Guid>("ContentId") + .HasColumnType("uuid"); + + b.Property<Point>("Location") + .IsRequired() + .HasColumnType("geometry(point,4326)"); + + b.HasKey("Id"); + + b.HasIndex("ContentId"); + + b.HasIndex("Location"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Location"), "gist"); + + b.ToTable("Place", (string)null); + }); + + modelBuilder.Entity("WhatApi.Tables.Place", b => + { + b.HasOne("WhatApi.Tables.Content", "Content") + .WithMany() + .HasForeignKey("ContentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Content"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/api/WhatApi/Migrations/20251013213511_Initial.cs b/api/WhatApi/Migrations/20251013213511_Initial.cs new file mode 100644 index 0000000..1fa8bbf --- /dev/null +++ b/api/WhatApi/Migrations/20251013213511_Initial.cs @@ -0,0 +1,75 @@ +using System; +using System.Net; +using Microsoft.EntityFrameworkCore.Migrations; +using NetTopologySuite.Geometries; + +#nullable disable + +namespace WhatApi.Migrations +{ + /// <inheritdoc /> + public partial class Initial : Migration + { + /// <inheritdoc /> + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("Npgsql:PostgresExtension:postgis", ",,"); + + migrationBuilder.CreateTable( + name: "Content", + columns: table => new + { + Id = table.Column<Guid>(type: "uuid", nullable: false), + Mime = table.Column<string>(type: "text", nullable: false), + Created = table.Column<DateTime>(type: "timestamp with time zone", nullable: false), + BlobId = table.Column<Guid>(type: "uuid", nullable: false), + Ip = table.Column<IPAddress>(type: "inet", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Content", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Place", + columns: table => new + { + 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) + }, + constraints: table => + { + table.PrimaryKey("PK_Place", x => x.Id); + table.ForeignKey( + name: "FK_Place_Content_ContentId", + column: x => x.ContentId, + principalTable: "Content", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Place_ContentId", + table: "Place", + column: "ContentId"); + + migrationBuilder.CreateIndex( + name: "IX_Place_Location", + table: "Place", + column: "Location") + .Annotation("Npgsql:IndexMethod", "gist"); + } + + /// <inheritdoc /> + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Place"); + + migrationBuilder.DropTable( + name: "Content"); + } + } +} diff --git a/api/WhatApi/Migrations/DatabaseModelSnapshot.cs b/api/WhatApi/Migrations/DatabaseModelSnapshot.cs new file mode 100644 index 0000000..f1e5fcb --- /dev/null +++ b/api/WhatApi/Migrations/DatabaseModelSnapshot.cs @@ -0,0 +1,90 @@ +// <auto-generated /> +using System; +using System.Net; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NetTopologySuite.Geometries; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using WhatApi; + +#nullable disable + +namespace WhatApi.Migrations +{ + [DbContext(typeof(Database))] + partial class DatabaseModelSnapshot : ModelSnapshot + { + protected override void BuildModel(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.Content", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<Guid>("BlobId") + .HasColumnType("uuid"); + + b.Property<DateTime>("Created") + .HasColumnType("timestamp with time zone"); + + b.Property<IPAddress>("Ip") + .IsRequired() + .HasColumnType("inet"); + + b.Property<string>("Mime") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Content"); + }); + + modelBuilder.Entity("WhatApi.Tables.Place", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<Guid>("ContentId") + .HasColumnType("uuid"); + + b.Property<Point>("Location") + .IsRequired() + .HasColumnType("geometry(point,4326)"); + + b.HasKey("Id"); + + b.HasIndex("ContentId"); + + b.HasIndex("Location"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex("Location"), "gist"); + + b.ToTable("Place", (string)null); + }); + + modelBuilder.Entity("WhatApi.Tables.Place", b => + { + b.HasOne("WhatApi.Tables.Content", "Content") + .WithMany() + .HasForeignKey("ContentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Content"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/api/WhatApi/Pages/Map.cshtml b/api/WhatApi/Pages/Map.cshtml new file mode 100644 index 0000000..31adf86 --- /dev/null +++ b/api/WhatApi/Pages/Map.cshtml @@ -0,0 +1,92 @@ +@page +@model WhatApi.Pages.Map + +@{ + Layout = null; +} + +<!DOCTYPE html> + +<html> +<head> + <link href="https://unpkg.com/maplibre-gl@@^5.7.2/dist/maplibre-gl.css"/> + <style> + body { + margin: 0; + padding: 0; + } + + html, body, #map { + height: 90%; + } + </style> + <title></title> +</head> +<body> +<div id="map"></div> +<script src="https://unpkg.com/maplibre-gl@@^5.7.2/dist/maplibre-gl.js"></script> +<script> + const map = new maplibregl.Map({ + container: "map", + style: "https://tiles.openfreemap.org/styles/bright", + center: [10.253494570441944, 59.937419399772125], + zoom: 7 + }); + + const markers = new Map(); + + let t = null; + + map.on("moveend", () => { + clearTimeout(t); + t = setTimeout(updateData, 150); + }); + + map.on("load", () => { + map.loadImage("/pin.png").then(image => map.addImage("custom-marker", image.data)); + map.addSource("places", { + type: "geojson", + data: {type: "FeatureCollection", features: []}, + // cluster: true, + // clusterRadius: 40, + // clusterMaxZoom: 14 + }); + + map.addLayer({ + id: "places-layer", + type: "symbol", + source: "places", + layout: { + "icon-image": "custom-marker" + } + }); + + updateData(); + }); + + let aborter = new AbortController(); + + async function updateData() { + const b = map.getBounds(); + const south = b.getSouth(), west = b.getWest(), north = b.getNorth(), east = b.getEast(); + + if (aborter) { + aborter.abort(); + } + + aborter = new AbortController(); + + const res = await fetch(`/places?w=${west}&s=${south}&e=${east}&n=${north}`, { + signal: aborter.signal + }); + + if (!res.ok) { + return; + } + + const data = await res.json().finally(() => aborter = null); + map.getSource("places").setData(data); + } +</script> +</body> +</html>
\ No newline at end of file diff --git a/api/WhatApi/Pages/Map.cshtml.cs b/api/WhatApi/Pages/Map.cshtml.cs new file mode 100644 index 0000000..786180b --- /dev/null +++ b/api/WhatApi/Pages/Map.cshtml.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace WhatApi.Pages; + +public class Map : PageModel +{ + public void OnGet() { + + } +}
\ No newline at end of file diff --git a/api/WhatApi/Pages/Upload.cshtml b/api/WhatApi/Pages/Upload.cshtml new file mode 100644 index 0000000..4c87b11 --- /dev/null +++ b/api/WhatApi/Pages/Upload.cshtml @@ -0,0 +1,82 @@ +@page +@model WhatApi.Pages.Upload + +@{ + Layout = null; +} + +<!DOCTYPE html> + +<html> +<head> + <link href="https://unpkg.com/maplibre-gl@5.9.0/dist/maplibre-gl.css" + rel="stylesheet"/> + <style> + body { + margin: 0; + padding: 0; + } + + html, body, #map { + height: 100%; + } + + .coordinates { + background: rgba(0, 0, 0, 0.5); + color: #fff; + position: absolute; + bottom: 40px; + left: 10px; + padding: 5px 10px; + margin: 0; + font-size: 11px; + line-height: 18px; + border-radius: 3px; + display: none; + } + </style> + <title></title> +</head> +<body> +<div style="display: flex; flex-direction: row; align-items: center; z-index: 10; position: absolute; background: white"> + <form action="/upload" + enctype="multipart/form-data" + method="post"> + <input type="hidden" + name="LatLong"> + <input type="file" + required="required" + accept="image/png, image/jpeg" + name="File"> + <input type="submit"> + </form> +</div> +<div id="map"></div> +<pre id="coordinates" + class="coordinates"></pre> +<script src='https://unpkg.com/maplibre-gl@5.9.0/dist/maplibre-gl.js'></script> +<script> + const latlongInput = document.querySelector("[name=LatLong]"); + const coordinates = document.getElementById("coordinates"); + const map = new maplibregl.Map({ + container: "map", + style: "https://tiles.openfreemap.org/styles/bright", + center: [10.253494, 59.937419], + zoom: 7 + }); + + const center = map.getCenter(); + const marker = new maplibregl.Marker({draggable: true}).setLngLat([center.lng, center.lat]).addTo(map); + + function onDragEnd() { + const {lat: lat, lng: lng} = marker.getLngLat(); + coordinates.style.display = "block"; + latlongInput.value = lat + "," + lng; + coordinates.innerHTML = + `Longitude: ${lng}<br />Latitude: ${lat}`; + } + + marker.on("dragend", onDragEnd); +</script> +</body> +</html>
\ No newline at end of file diff --git a/api/WhatApi/Pages/Upload.cshtml.cs b/api/WhatApi/Pages/Upload.cshtml.cs new file mode 100644 index 0000000..2fe362e --- /dev/null +++ b/api/WhatApi/Pages/Upload.cshtml.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace WhatApi.Pages; + +public class Upload : PageModel +{ + public void OnGet() { + + } +}
\ No newline at end of file diff --git a/api/WhatApi/Program.cs b/api/WhatApi/Program.cs new file mode 100644 index 0000000..80d06e8 --- /dev/null +++ b/api/WhatApi/Program.cs @@ -0,0 +1,45 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.EntityFrameworkCore; +using NetTopologySuite.IO.Converters; + +namespace WhatApi; + +public partial class Program +{ + public static int Main(string[] args) { + var builder = WebApplication.CreateBuilder(args); + + builder.Services.AddDbContextPool<Database>(b => { + b.EnableSensitiveDataLogging(); + b.UseNpgsql(builder.Configuration.GetConnectionString("Master"), o => o.UseNetTopologySuite()); + }); + + builder.Services.AddRazorPages(); + builder.Services.AddCors(o => o.AddDefaultPolicy(p => p.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader())); + builder.Services.AddControllers() + .AddJsonOptions(o => { + o.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + o.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals; + o.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; + o.JsonSerializerOptions.Converters.Add(new GeoJsonConverterFactory()); + }); + + var app = builder.Build(); + +#if DEBUG + using var scope = app.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService<Database>(); + Seed(db); +#endif + + app.UseRouting(); + app.UseForwardedHeaders(); + app.UseCors(); + app.MapStaticAssets(); + app.MapRazorPages(); + app.MapControllers(); + app.Run(); + return 0; + } +}
\ No newline at end of file diff --git a/api/WhatApi/Properties/launchSettings.json b/api/WhatApi/Properties/launchSettings.json new file mode 100644 index 0000000..581ee7b --- /dev/null +++ b/api/WhatApi/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5281", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/api/WhatApi/Seed.cs b/api/WhatApi/Seed.cs new file mode 100644 index 0000000..c3127cc --- /dev/null +++ b/api/WhatApi/Seed.cs @@ -0,0 +1,24 @@ +using Bogus; +using Bogus.Locations; +using NetTopologySuite.Geometries; +using WhatApi.Tables; + +namespace WhatApi; + +public partial class Program +{ + private static void Seed(Database db) { + if (db.Places.Any() || true) return; + var places = new List<Place>(); + var location = new Faker().Location(); + for (var i = 0; i < 1000; i++) { + var point = location.AreaCircle(59.91838, 10.73861, 30000); + places.Add(new Place() { + Location = new Point(new Coordinate(point.Longitude, point.Latitude)), + ContentId = new Guid("1337710a-8cdb-4d50-815f-772c0e9f1482") + }); + } + db.Places.AddRange(places); + db.SaveChanges(); + } +}
\ No newline at end of file diff --git a/api/WhatApi/Tables/Content.cs b/api/WhatApi/Tables/Content.cs new file mode 100644 index 0000000..79f2579 --- /dev/null +++ b/api/WhatApi/Tables/Content.cs @@ -0,0 +1,12 @@ +using System.Net; + +namespace WhatApi.Tables; + +public class Content +{ + public Guid Id { get; set; } + public string Mime { get; set; } + public DateTime Created { get; set; } + public Guid BlobId { get; set; } + public IPAddress Ip { get; set; } +}
\ No newline at end of file diff --git a/api/WhatApi/Tables/Place.cs b/api/WhatApi/Tables/Place.cs new file mode 100644 index 0000000..ff95c96 --- /dev/null +++ b/api/WhatApi/Tables/Place.cs @@ -0,0 +1,11 @@ +using NetTopologySuite.Geometries; + +namespace WhatApi.Tables; + +public class Place +{ + public Guid Id { get; set; } + public Guid ContentId { get; set; } + public Content Content { get; set; } + public required Point Location { get; set; } +}
\ No newline at end of file diff --git a/api/WhatApi/Tables/Session.cs b/api/WhatApi/Tables/Session.cs new file mode 100644 index 0000000..a0affa8 --- /dev/null +++ b/api/WhatApi/Tables/Session.cs @@ -0,0 +1,10 @@ +namespace WhatApi.Tables; + +public class Session +{ + public Guid Id { get; set; } + public string Token { get; set; } + public DateTime Created { get; set; } + public DateTime? Expires { get; set; } + public User User { get; set; } +}
\ No newline at end of file diff --git a/api/WhatApi/Tables/User.cs b/api/WhatApi/Tables/User.cs new file mode 100644 index 0000000..0ebe9b6 --- /dev/null +++ b/api/WhatApi/Tables/User.cs @@ -0,0 +1,12 @@ +namespace WhatApi.Tables; + +public class User +{ + public Guid Id { get; set; } + public string Name { get; set; } + public string Email { get; set; } + public string Password { get; set; } + public DateTime Created { get; set; } + public DateTime? LastSeen { get; set; } + public IEnumerable<Place> Places { get; set; } +}
\ No newline at end of file diff --git a/api/WhatApi/WhatApi.csproj b/api/WhatApi/WhatApi.csproj new file mode 100644 index 0000000..84dac71 --- /dev/null +++ b/api/WhatApi/WhatApi.csproj @@ -0,0 +1,34 @@ +<Project Sdk="Microsoft.NET.Sdk.Web"> + + <PropertyGroup> + <TargetFramework>net9.0</TargetFramework> + <Nullable>enable</Nullable> + <ImplicitUsings>enable</ImplicitUsings> + <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> + <UserSecretsId>5506c159-f534-4090-b80b-2703e1eb7f6c</UserSecretsId> + </PropertyGroup> + + <ItemGroup> + <Content Include="..\.dockerignore"> + <Link>.dockerignore</Link> + </Content> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="Bogus" Version="35.6.3" /> + <PackageReference Include="Bogus.Locations" Version="35.6.3" /> + <PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.9" /> + <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.9"> + <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="9.0.4" /> + <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite" Version="9.0.4" /> + </ItemGroup> + + <ItemGroup> + <Folder Include="files\" /> + </ItemGroup> + +</Project> diff --git a/api/WhatApi/appsettings.Development.json b/api/WhatApi/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/api/WhatApi/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/api/WhatApi/appsettings.json b/api/WhatApi/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/api/WhatApi/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/api/WhatApi/wwwroot/pin.png b/api/WhatApi/wwwroot/pin.png Binary files differnew file mode 100644 index 0000000..3603031 --- /dev/null +++ b/api/WhatApi/wwwroot/pin.png |
