diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..9798fa4 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,41 @@ +stages: + - build + - test + - publish + +variables: + DOCKER_IMAGE: registry.leon-hoppe.de/leon.hoppe/spotiparty + +build: + stage: build + image: mcr.microsoft.com/dotnet/sdk:9.0 + script: + - dotnet restore + - dotnet build --configuration Release --no-restore + artifacts: + paths: + - "**/bin/Release" + expire_in: 10 minutes + +test: + stage: test + image: mcr.microsoft.com/dotnet/sdk:9.0 + script: + - dotnet test --verbosity normal + dependencies: + - build + +publish: + stage: publish + tags: + - docker + before_script: + - git lfs pull + script: + - export VERSION=$(echo $CI_COMMIT_TAG | sed 's/^v//') + - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin registry.leon-hoppe.de + - docker build -t $DOCKER_IMAGE:$VERSION -t $DOCKER_IMAGE:latest -f SpotiParty.Web/Dockerfile . + - docker push $DOCKER_IMAGE:$VERSION + - docker push $DOCKER_IMAGE:latest + only: + - tags diff --git a/SpotiParty.AppHost/SpotiParty.AppHost.csproj b/SpotiParty.AppHost/SpotiParty.AppHost.csproj index 6b1b894..8007f83 100644 --- a/SpotiParty.AppHost/SpotiParty.AppHost.csproj +++ b/SpotiParty.AppHost/SpotiParty.AppHost.csproj @@ -4,7 +4,7 @@ Exe - net10.0 + net9.0 enable enable fe08e5bf-d119-470a-a4cc-7686f019ce64 diff --git a/SpotiParty.Defaults/SpotiParty.Defaults.csproj b/SpotiParty.Defaults/SpotiParty.Defaults.csproj index 0f873c9..778990b 100644 --- a/SpotiParty.Defaults/SpotiParty.Defaults.csproj +++ b/SpotiParty.Defaults/SpotiParty.Defaults.csproj @@ -1,7 +1,7 @@ - net10.0 + net9.0 enable enable true diff --git a/SpotiParty.Web/Components/App.razor b/SpotiParty.Web/Components/App.razor index 8a2a405..1f62f7b 100644 --- a/SpotiParty.Web/Components/App.razor +++ b/SpotiParty.Web/Components/App.razor @@ -9,7 +9,7 @@ - + diff --git a/SpotiParty.Web/Components/Pages/EnqueuePage.razor b/SpotiParty.Web/Components/Pages/EnqueuePage.razor index 313dcc3..8c33bb7 100644 --- a/SpotiParty.Web/Components/Pages/EnqueuePage.razor +++ b/SpotiParty.Web/Components/Pages/EnqueuePage.razor @@ -2,8 +2,10 @@ @using SpotiParty.Web.Components.Components @rendermode InteractiveServer +SpotiParty +
-

🎵 SpotiParty

+

@(_event?.Name ?? " ")

Suche ein Lied und füge es zur Warteschlange hinzu

@@ -27,7 +29,7 @@
-

SpotiParty © @_currentYear

+

SpotiParty © @_currentYear

diff --git a/SpotiParty.Web/Components/Pages/EnqueuePage.razor.cs b/SpotiParty.Web/Components/Pages/EnqueuePage.razor.cs index 637bd6d..3ecd2d5 100644 --- a/SpotiParty.Web/Components/Pages/EnqueuePage.razor.cs +++ b/SpotiParty.Web/Components/Pages/EnqueuePage.razor.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Components; using Microsoft.EntityFrameworkCore; using SpotifyAPI.Web; +using SpotiParty.Web.Models; using SpotiParty.Web.Services; namespace SpotiParty.Web.Components.Pages; @@ -9,6 +10,8 @@ public partial class EnqueuePage(AuthorizationHandler authHandler, NavigationMan [Parameter] public string EventId { get; set; } = string.Empty; + + private Event? _event; private readonly int _currentYear = DateTime.Now.Year; private SpotifyClient _client = null!; @@ -28,22 +31,24 @@ public partial class EnqueuePage(AuthorizationHandler authHandler, NavigationMan return; } - var eventEntry = await context.Events + _event = await context.Events .Include(e => e.Host) .FirstOrDefaultAsync(e => e.Id == guid); - if (eventEntry is null) { - navigator.NavigateTo("/", forceLoad: true); - return; - } - - var now = DateTime.Now; - if (eventEntry.Start > now || eventEntry.End < now) { + if (_event is null) { navigator.NavigateTo("/", forceLoad: true); return; } - var client = await authHandler.ConfigureClient(eventEntry.Host.UserId); + StateHasChanged(); + + var now = DateTime.Now; + if (_event.Start > now || _event.End < now) { + navigator.NavigateTo("/", forceLoad: true); + return; + } + + var client = await authHandler.ConfigureClient(_event.Host.UserId); if (client is null) { navigator.NavigateTo("/", forceLoad: true); diff --git a/SpotiParty.Web/Components/Pages/EnqueuePage.razor.css b/SpotiParty.Web/Components/Pages/EnqueuePage.razor.css index 3a1ded2..9125161 100644 --- a/SpotiParty.Web/Components/Pages/EnqueuePage.razor.css +++ b/SpotiParty.Web/Components/Pages/EnqueuePage.razor.css @@ -8,6 +8,7 @@ header h1 { margin: 0; font-size: 2rem; + height: 2rem; &:focus { outline: none; @@ -63,6 +64,10 @@ footer { text-align: center; padding: 0.5rem; background: var(--color-primary); + + a { + color: var(--color-text); + } } .confirm-dialog { diff --git a/SpotiParty.Web/Components/Pages/HomePage.razor b/SpotiParty.Web/Components/Pages/HomePage.razor new file mode 100644 index 0000000..4d45be9 --- /dev/null +++ b/SpotiParty.Web/Components/Pages/HomePage.razor @@ -0,0 +1,6 @@ +@page "/" +

HomePage

+ +@code { + +} \ No newline at end of file diff --git a/SpotiParty.Web/Components/Pages/HomePage.razor.css b/SpotiParty.Web/Components/Pages/HomePage.razor.css new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/SpotiParty.Web/Components/Pages/HomePage.razor.css @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/SpotiParty.Web/Components/Pages/NotFound.razor b/SpotiParty.Web/Components/Pages/NotFound.razor index 76193cc..1ca63b4 100644 --- a/SpotiParty.Web/Components/Pages/NotFound.razor +++ b/SpotiParty.Web/Components/Pages/NotFound.razor @@ -6,7 +6,7 @@ protected override void OnInitialized() { base.OnInitialized(); - Navigator.NavigateTo("/login", forceLoad: true); + Navigator.NavigateTo("/", forceLoad: true); } } diff --git a/SpotiParty.Web/Components/Routes.razor b/SpotiParty.Web/Components/Routes.razor index 95b320b..bdac4d7 100644 --- a/SpotiParty.Web/Components/Routes.razor +++ b/SpotiParty.Web/Components/Routes.razor @@ -1,4 +1,4 @@ - + diff --git a/SpotiParty.Web/Dockerfile b/SpotiParty.Web/Dockerfile index 26ca620..460f58e 100644 --- a/SpotiParty.Web/Dockerfile +++ b/SpotiParty.Web/Dockerfile @@ -1,10 +1,10 @@ -FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base +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:10.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src COPY ["SpotiParty.Web/SpotiParty.Web.csproj", "SpotiParty.Web/"] diff --git a/SpotiParty.Web/Models/Event.cs b/SpotiParty.Web/Models/Event.cs index a7b9ee3..41dc28c 100644 --- a/SpotiParty.Web/Models/Event.cs +++ b/SpotiParty.Web/Models/Event.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace SpotiParty.Web.Models; @@ -6,7 +7,8 @@ public class Event { [Key] public Guid Id { get; init; } = Guid.CreateVersion7(); - public required User Host { get; set; } + [ForeignKey("host")] + public virtual required User Host { get; set; } [MaxLength(255)] public required string Name { get; set; } diff --git a/SpotiParty.Web/Program.cs b/SpotiParty.Web/Program.cs index 8ac0ee7..53c8539 100644 --- a/SpotiParty.Web/Program.cs +++ b/SpotiParty.Web/Program.cs @@ -1,4 +1,3 @@ -using HopFrame.Core.Callbacks; using HopFrame.Core.Services; using HopFrame.Web; using Microsoft.EntityFrameworkCore; @@ -9,6 +8,8 @@ using SpotiParty.Web.Services; var builder = WebApplication.CreateBuilder(args); +builder.Configuration.AddEnvironmentVariables(); + // Add services to the container. builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); @@ -44,16 +45,15 @@ builder.Services.AddHopFrame(config => { .List(false) .DisplayValue(false) .SetEditable(false); - - table.ShowSearchSuggestions(false); }); context.Table() + .SetDisplayName(Guid.NewGuid().ToString()) .Ignore(true); }); config.AddCustomRepository(e => e.Id, table => { - //table.SetDisplayName("Events"); + table.SetDisplayName("Events"); table.Property(e => e.Id) .List(false) @@ -61,18 +61,15 @@ builder.Services.AddHopFrame(config => { .SetCreatable(false); table.Property(e => e.Host) + .List(false) .SetEditable(false) .SetCreatable(false) .SetDisplayedProperty(u => u.DisplayName); table.ShowSearchSuggestions(false); - - table.AddCallbackHandler(CallbackType.CreateEntry, async (entry, services) => { - var auth = services.GetRequiredService(); - var user = await auth.GetCurrentUser(); - entry.Host = user!; - }); }); + + config.AddPlugin(); }); var app = builder.Build(); @@ -91,7 +88,7 @@ await using (var scope = app.Services.CreateAsyncScope()) { app.MapDefaultEndpoints(); -app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true); +app.UseStatusCodePagesWithReExecute("/not-found"); //app.UseHttpsRedirection(); app.UseAntiforgery(); diff --git a/SpotiParty.Web/Services/AdminDashboardPlugin.cs b/SpotiParty.Web/Services/AdminDashboardPlugin.cs new file mode 100644 index 0000000..f750b52 --- /dev/null +++ b/SpotiParty.Web/Services/AdminDashboardPlugin.cs @@ -0,0 +1,27 @@ +using HopFrame.Core.Config; +using HopFrame.Web.Plugins.Events; +using Microsoft.AspNetCore.Components; +using Microsoft.FluentUI.AspNetCore.Components; +using SpotiParty.Web.Models; + +namespace SpotiParty.Web.Services; + +public class AdminDashboardPlugin(NavigationManager navigator) { + + [HopFrame.Web.Plugins.Annotations.EventHandler] + public void OnTableInitialized(TableInitializedEvent e) { + if (e.Table.TableType != typeof(Event)) return; + + e.AddEntityButton(new IconInfo { + Variant = IconVariant.Regular, + Size = IconSize.Size16, + Name = "Open" + }, OnButtonClicked); + } + + private void OnButtonClicked(object o, TableConfig config) { + var entity = (Event)o; + navigator.NavigateTo(navigator.BaseUri + $"enqueue/{entity.Id}"); + } + +} \ No newline at end of file diff --git a/SpotiParty.Web/Services/AuthorizationHandler.cs b/SpotiParty.Web/Services/AuthorizationHandler.cs index 8a3f5bc..8ef99f4 100644 --- a/SpotiParty.Web/Services/AuthorizationHandler.cs +++ b/SpotiParty.Web/Services/AuthorizationHandler.cs @@ -5,11 +5,17 @@ using SpotiParty.Web.Models; namespace SpotiParty.Web.Services; -public sealed class AuthorizationHandler(NavigationManager navigator, DatabaseContext context, ClientSideStorage storage) { +public sealed class AuthorizationHandler(NavigationManager navigator, DatabaseContext context, ClientSideStorage storage, IConfiguration configuration) { private async Task<(string clientId, string clientSecret)> GetClientSecrets() { + #if DEBUG var fileLines = await File.ReadAllLinesAsync(Path.Combine(Environment.CurrentDirectory, ".dev-token")); return (fileLines[0], fileLines[1]); + #endif + +#pragma warning disable CS0162 // Unreachable code detected + return (configuration["ClientId"]!, configuration["ClientSecret"]!); +#pragma warning restore CS0162 // Unreachable code detected } public async Task ConfigureClient(Guid userId) { diff --git a/SpotiParty.Web/Services/EventsDashboardRepo.cs b/SpotiParty.Web/Services/EventsDashboardRepo.cs index 52475ce..496b58e 100644 --- a/SpotiParty.Web/Services/EventsDashboardRepo.cs +++ b/SpotiParty.Web/Services/EventsDashboardRepo.cs @@ -29,6 +29,10 @@ public class EventsDashboardRepo(DatabaseContext context, DashboardAuthHandler h } public async Task CreateItem(Event item) { + var creator = await handler.GetCurrentUser(); + context.Attach(creator!); + item.Host = creator!; + await context.Events.AddAsync(item); await context.SaveChangesAsync(); } diff --git a/SpotiParty.Web/SpotiParty.Web.csproj b/SpotiParty.Web/SpotiParty.Web.csproj index fdd4d21..a24811a 100644 --- a/SpotiParty.Web/SpotiParty.Web.csproj +++ b/SpotiParty.Web/SpotiParty.Web.csproj @@ -1,7 +1,7 @@ - net10.0 + net9.0 enable enable true @@ -19,11 +19,10 @@ - - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/SpotiParty.Web/appsettings.Development.json b/SpotiParty.Web/appsettings.Development.json index f8063ef..f120a01 100644 --- a/SpotiParty.Web/appsettings.Development.json +++ b/SpotiParty.Web/appsettings.Development.json @@ -2,7 +2,9 @@ "Logging": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "HopFrame": "Debug", + "Microsoft.EntityFrameworkCore.Database.Command": "Warning" } }, "DetailedErrors": true diff --git a/SpotiParty.Web/appsettings.json b/SpotiParty.Web/appsettings.json index 10f68b8..d268177 100644 --- a/SpotiParty.Web/appsettings.json +++ b/SpotiParty.Web/appsettings.json @@ -5,5 +5,7 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "ClientId": null, + "ClientSecret": null } diff --git a/SpotiParty.Web/wwwroot/favicon.ico b/SpotiParty.Web/wwwroot/favicon.ico new file mode 100644 index 0000000..402e5c8 Binary files /dev/null and b/SpotiParty.Web/wwwroot/favicon.ico differ diff --git a/SpotiParty.Web/wwwroot/favicon.png b/SpotiParty.Web/wwwroot/favicon.png deleted file mode 100644 index 8422b59..0000000 Binary files a/SpotiParty.Web/wwwroot/favicon.png and /dev/null differ diff --git a/SpotiParty.sln.DotSettings.user b/SpotiParty.sln.DotSettings.user index 39a1eca..fe55006 100644 --- a/SpotiParty.sln.DotSettings.user +++ b/SpotiParty.sln.DotSettings.user @@ -1,7 +1,10 @@  + True + True ForceIncluded ForceIncluded ForceIncluded ForceIncluded ForceIncluded - ForceIncluded \ No newline at end of file + ForceIncluded + ForceIncluded \ No newline at end of file