diff --git a/SpotiParty.AppHost/AppHost.cs b/SpotiParty.AppHost/AppHost.cs index af99f1e..9340bf1 100644 --- a/SpotiParty.AppHost/AppHost.cs +++ b/SpotiParty.AppHost/AppHost.cs @@ -2,13 +2,86 @@ using Projects; var builder = DistributedApplication.CreateBuilder(args); +var compose = builder.AddDockerComposeEnvironment("compose") + .WithProperties(env => { + env.DefaultNetworkName = "system"; + }) + .ConfigureComposeFile(file => { + file.Networks.Add("main", new() { + Name = "main", + External = true + }); + + file.Volumes.Clear(); + }) + .ConfigureEnvFile(env => { + env["CLIENT_ID"] = new() { + Name = "CLIENT_ID", + DefaultValue = "SpotifyClientId" + }; + + env["CLIENT_SECRET"] = new() { + Name = "CLIENT_SECRET", + DefaultValue = "SpotifyClientSecret" + }; + }) + .WithDashboard(dashboard => { + dashboard.WithForwardedHeaders(); + dashboard.PublishAsDockerComposeService((_, service) => { + service.Name = "dashboard"; + service.ContainerName = "spotiparty-dashboard"; + service.Networks.Add("main"); + service.Restart = "unless-stopped"; + + service.Labels.Add("traefik.enable", "true"); + service.Labels.Add("traefik.http.routers.spotiparty-dashboard.tls", "true"); + service.Labels.Add("traefik.http.routers.spotiparty-dashboard.tls.certresolver", "cloudflare"); + service.Labels.Add("traefik.http.routers.spotiparty-dashboard.entrypoints", "websecure"); + service.Labels.Add("traefik.http.routers.spotiparty-dashboard.rule", "Host(`dash.spotiparty.leon-hoppe.de`)"); + service.Labels.Add("traefik.http.routers.spotiparty-dashboard.service", "spotiparty-dashboard"); + service.Labels.Add("traefik.http.services.spotiparty-dashboard.loadbalancer.server.port", "18888"); + service.Labels.Add("traefik.docker.network", "main"); + service.Labels.Add("com.centurylinklabs.watchtower.enable", "true"); + + service.Ports.Clear(); + service.Expose.Clear(); + }); + }); + var dbServer = builder.AddPostgres("database") - .WithDataVolume(); + .WithDataVolume() + .PublishAsDockerComposeService((_, service) => { + service.ContainerName = "spotiparty-db"; + service.Restart = "unless-stopped"; + service.Expose.Clear(); + }); var database = dbServer.AddDatabase("SpotiParty"); var web = builder.AddProject("web") .WithReference(database) - .WaitForStart(database); + .WaitForStart(database) + .PublishAsDockerComposeService((_, service) => { + service.Name = "web"; + service.ContainerName = "spotiparty-web"; + service.Image = "registry.leon-hoppe.de/leon.hoppe/spotiparty:latest"; + service.Networks.Add("main"); + service.Restart = "unless-stopped"; + service.Environment.Add("ClientId", "${CLIENT_ID}"); + service.Environment.Add("ClientSecret", "${CLIENT_SECRET}"); + + service.Labels.Add("traefik.enable", "true"); + service.Labels.Add("traefik.http.routers.spotiparty-web.tls", "true"); + service.Labels.Add("traefik.http.routers.spotiparty-web.tls.certresolver", "cloudflare"); + service.Labels.Add("traefik.http.routers.spotiparty-web.entrypoints", "websecure"); + service.Labels.Add("traefik.http.routers.spotiparty-web.rule", "Host(`spotiparty.leon-hoppe.de`)"); + service.Labels.Add("traefik.http.routers.spotiparty-web.service", "spotiparty-web"); + service.Labels.Add("traefik.http.services.spotiparty-web.loadbalancer.server.port", "${WEB_PORT}"); + service.Labels.Add("traefik.docker.network", "main"); + service.Labels.Add("com.centurylinklabs.watchtower.enable", "true"); + + service.Ports.Clear(); + service.Expose.Clear(); + }); builder.Build().Run(); \ No newline at end of file diff --git a/SpotiParty.AppHost/SpotiParty.AppHost.csproj b/SpotiParty.AppHost/SpotiParty.AppHost.csproj index 8007f83..e6028ab 100644 --- a/SpotiParty.AppHost/SpotiParty.AppHost.csproj +++ b/SpotiParty.AppHost/SpotiParty.AppHost.csproj @@ -12,6 +12,7 @@ + diff --git a/SpotiParty.Web/Migrations/20260118172802_Initial.Designer.cs b/SpotiParty.Web/Migrations/20260118172802_Initial.Designer.cs new file mode 100644 index 0000000..0f67e02 --- /dev/null +++ b/SpotiParty.Web/Migrations/20260118172802_Initial.Designer.cs @@ -0,0 +1,97 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using SpotiParty.Web; + +#nullable disable + +namespace SpotiParty.Web.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20260118172802_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.12") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("SpotiParty.Web.Models.Event", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("End") + .HasColumnType("timestamp without time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Start") + .HasColumnType("timestamp without time zone"); + + b.Property("host") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("host"); + + b.ToTable("Events"); + }); + + modelBuilder.Entity("SpotiParty.Web.Models.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IsAdmin") + .HasColumnType("boolean"); + + b.Property("RefreshToken") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SpotifyUserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("UserId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("SpotiParty.Web.Models.Event", b => + { + b.HasOne("SpotiParty.Web.Models.User", "Host") + .WithMany() + .HasForeignKey("host") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Host"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/SpotiParty.Web/Migrations/20260118172802_Initial.cs b/SpotiParty.Web/Migrations/20260118172802_Initial.cs new file mode 100644 index 0000000..df3b0ca --- /dev/null +++ b/SpotiParty.Web/Migrations/20260118172802_Initial.cs @@ -0,0 +1,66 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace SpotiParty.Web.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + UserId = table.Column(type: "uuid", nullable: false), + SpotifyUserId = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + DisplayName = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + RefreshToken = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + IsAdmin = table.Column(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.UserId); + }); + + migrationBuilder.CreateTable( + name: "Events", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + host = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(255)", maxLength: 255, nullable: false), + Start = table.Column(type: "timestamp without time zone", nullable: false), + End = table.Column(type: "timestamp without time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Events", x => x.Id); + table.ForeignKey( + name: "FK_Events_Users_host", + column: x => x.host, + principalTable: "Users", + principalColumn: "UserId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Events_host", + table: "Events", + column: "host"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Events"); + + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/SpotiParty.Web/Migrations/DatabaseContextModelSnapshot.cs b/SpotiParty.Web/Migrations/DatabaseContextModelSnapshot.cs new file mode 100644 index 0000000..7e9fbf3 --- /dev/null +++ b/SpotiParty.Web/Migrations/DatabaseContextModelSnapshot.cs @@ -0,0 +1,94 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using SpotiParty.Web; + +#nullable disable + +namespace SpotiParty.Web.Migrations +{ + [DbContext(typeof(DatabaseContext))] + partial class DatabaseContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.12") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("SpotiParty.Web.Models.Event", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("End") + .HasColumnType("timestamp without time zone"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("Start") + .HasColumnType("timestamp without time zone"); + + b.Property("host") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("host"); + + b.ToTable("Events"); + }); + + modelBuilder.Entity("SpotiParty.Web.Models.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("IsAdmin") + .HasColumnType("boolean"); + + b.Property("RefreshToken") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("SpotifyUserId") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.HasKey("UserId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("SpotiParty.Web.Models.Event", b => + { + b.HasOne("SpotiParty.Web.Models.User", "Host") + .WithMany() + .HasForeignKey("host") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Host"); + }); +#pragma warning restore 612, 618 + } + } +}