...docker test
This commit is contained in:
@@ -254,6 +254,9 @@ paket-files/
|
|||||||
.idea/
|
.idea/
|
||||||
*.sln.iml
|
*.sln.iml
|
||||||
|
|
||||||
|
# Docker environment variables
|
||||||
|
.env
|
||||||
|
|
||||||
# CodeRush
|
# CodeRush
|
||||||
.cr/
|
.cr/
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
**/.git
|
||||||
|
**/.obj
|
||||||
|
**/.bin
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/.idea
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
publish/
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Telerik License Key
|
||||||
|
# Get your license key from: https://www.telerik.com/kendo-blazor-ui/my-license/
|
||||||
|
# This is a long signed string, different from your NuGet password.
|
||||||
|
TELERIK_LICENSE=your_telerik_license_key_here
|
||||||
|
|
||||||
|
# Telerik NuGet Credentials
|
||||||
|
# Use 'api-key' for Username if using an API Key
|
||||||
|
TELERIK_USERNAME=your_telerik_username_or_api-key
|
||||||
|
TELERIK_PASSWORD=your_telerik_password_or_api_key
|
||||||
@@ -10,31 +10,92 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Deploy", "Deploy\Deploy.csp
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{90F32056-6983-4224-8A8C-E797C71633F3}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{90F32056-6983-4224-8A8C-E797C71633F3}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server", "Server\Server.csproj", "{BFB7B73F-EFDD-4DE8-89F2-E1CBE1B55C27}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{18981096-443A-44BF-AE56-6499C2B03AEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{18981096-443A-44BF-AE56-6499C2B03AEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{18981096-443A-44BF-AE56-6499C2B03AEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{18981096-443A-44BF-AE56-6499C2B03AEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{18981096-443A-44BF-AE56-6499C2B03AEF}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{18981096-443A-44BF-AE56-6499C2B03AEF}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{18981096-443A-44BF-AE56-6499C2B03AEF}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{18981096-443A-44BF-AE56-6499C2B03AEF}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{18981096-443A-44BF-AE56-6499C2B03AEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{18981096-443A-44BF-AE56-6499C2B03AEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{18981096-443A-44BF-AE56-6499C2B03AEF}.Release|Any CPU.Build.0 = Release|Any CPU
|
{18981096-443A-44BF-AE56-6499C2B03AEF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{18981096-443A-44BF-AE56-6499C2B03AEF}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{18981096-443A-44BF-AE56-6499C2B03AEF}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{18981096-443A-44BF-AE56-6499C2B03AEF}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{18981096-443A-44BF-AE56-6499C2B03AEF}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{36E3775C-0E28-4EAE-AE92-4FB493E3787F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{36E3775C-0E28-4EAE-AE92-4FB493E3787F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{36E3775C-0E28-4EAE-AE92-4FB493E3787F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{36E3775C-0E28-4EAE-AE92-4FB493E3787F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{36E3775C-0E28-4EAE-AE92-4FB493E3787F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{36E3775C-0E28-4EAE-AE92-4FB493E3787F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{36E3775C-0E28-4EAE-AE92-4FB493E3787F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{36E3775C-0E28-4EAE-AE92-4FB493E3787F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{36E3775C-0E28-4EAE-AE92-4FB493E3787F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{36E3775C-0E28-4EAE-AE92-4FB493E3787F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{36E3775C-0E28-4EAE-AE92-4FB493E3787F}.Release|Any CPU.Build.0 = Release|Any CPU
|
{36E3775C-0E28-4EAE-AE92-4FB493E3787F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{36E3775C-0E28-4EAE-AE92-4FB493E3787F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{36E3775C-0E28-4EAE-AE92-4FB493E3787F}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{36E3775C-0E28-4EAE-AE92-4FB493E3787F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{36E3775C-0E28-4EAE-AE92-4FB493E3787F}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{3358AF7A-603B-4BAC-A7F5-07978FB87843}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{3358AF7A-603B-4BAC-A7F5-07978FB87843}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{3358AF7A-603B-4BAC-A7F5-07978FB87843}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{3358AF7A-603B-4BAC-A7F5-07978FB87843}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3358AF7A-603B-4BAC-A7F5-07978FB87843}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{3358AF7A-603B-4BAC-A7F5-07978FB87843}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{3358AF7A-603B-4BAC-A7F5-07978FB87843}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{3358AF7A-603B-4BAC-A7F5-07978FB87843}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{3358AF7A-603B-4BAC-A7F5-07978FB87843}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{3358AF7A-603B-4BAC-A7F5-07978FB87843}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{3358AF7A-603B-4BAC-A7F5-07978FB87843}.Release|Any CPU.Build.0 = Release|Any CPU
|
{3358AF7A-603B-4BAC-A7F5-07978FB87843}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3358AF7A-603B-4BAC-A7F5-07978FB87843}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{3358AF7A-603B-4BAC-A7F5-07978FB87843}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{3358AF7A-603B-4BAC-A7F5-07978FB87843}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{3358AF7A-603B-4BAC-A7F5-07978FB87843}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{E5E9A799-76C8-43B3-B9C2-3CAEC2B5E1DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{E5E9A799-76C8-43B3-B9C2-3CAEC2B5E1DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{E5E9A799-76C8-43B3-B9C2-3CAEC2B5E1DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{E5E9A799-76C8-43B3-B9C2-3CAEC2B5E1DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{E5E9A799-76C8-43B3-B9C2-3CAEC2B5E1DD}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{E5E9A799-76C8-43B3-B9C2-3CAEC2B5E1DD}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{E5E9A799-76C8-43B3-B9C2-3CAEC2B5E1DD}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{E5E9A799-76C8-43B3-B9C2-3CAEC2B5E1DD}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{E5E9A799-76C8-43B3-B9C2-3CAEC2B5E1DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{E5E9A799-76C8-43B3-B9C2-3CAEC2B5E1DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{E5E9A799-76C8-43B3-B9C2-3CAEC2B5E1DD}.Release|Any CPU.Build.0 = Release|Any CPU
|
{E5E9A799-76C8-43B3-B9C2-3CAEC2B5E1DD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{E5E9A799-76C8-43B3-B9C2-3CAEC2B5E1DD}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{E5E9A799-76C8-43B3-B9C2-3CAEC2B5E1DD}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{E5E9A799-76C8-43B3-B9C2-3CAEC2B5E1DD}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{E5E9A799-76C8-43B3-B9C2-3CAEC2B5E1DD}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{90F32056-6983-4224-8A8C-E797C71633F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{90F32056-6983-4224-8A8C-E797C71633F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{90F32056-6983-4224-8A8C-E797C71633F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{90F32056-6983-4224-8A8C-E797C71633F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{90F32056-6983-4224-8A8C-E797C71633F3}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{90F32056-6983-4224-8A8C-E797C71633F3}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{90F32056-6983-4224-8A8C-E797C71633F3}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{90F32056-6983-4224-8A8C-E797C71633F3}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{90F32056-6983-4224-8A8C-E797C71633F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{90F32056-6983-4224-8A8C-E797C71633F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{90F32056-6983-4224-8A8C-E797C71633F3}.Release|Any CPU.Build.0 = Release|Any CPU
|
{90F32056-6983-4224-8A8C-E797C71633F3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{90F32056-6983-4224-8A8C-E797C71633F3}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{90F32056-6983-4224-8A8C-E797C71633F3}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{90F32056-6983-4224-8A8C-E797C71633F3}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{90F32056-6983-4224-8A8C-E797C71633F3}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{BFB7B73F-EFDD-4DE8-89F2-E1CBE1B55C27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{BFB7B73F-EFDD-4DE8-89F2-E1CBE1B55C27}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{BFB7B73F-EFDD-4DE8-89F2-E1CBE1B55C27}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{BFB7B73F-EFDD-4DE8-89F2-E1CBE1B55C27}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{BFB7B73F-EFDD-4DE8-89F2-E1CBE1B55C27}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{BFB7B73F-EFDD-4DE8-89F2-E1CBE1B55C27}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{BFB7B73F-EFDD-4DE8-89F2-E1CBE1B55C27}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{BFB7B73F-EFDD-4DE8-89F2-E1CBE1B55C27}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{BFB7B73F-EFDD-4DE8-89F2-E1CBE1B55C27}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{BFB7B73F-EFDD-4DE8-89F2-E1CBE1B55C27}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{BFB7B73F-EFDD-4DE8-89F2-E1CBE1B55C27}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{BFB7B73F-EFDD-4DE8-89F2-E1CBE1B55C27}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||||
|
ARG TELERIK_LICENSE
|
||||||
|
ARG TELERIK_USERNAME
|
||||||
|
ARG TELERIK_PASSWORD
|
||||||
|
ENV TELERIK_LICENSE=$TELERIK_LICENSE
|
||||||
|
ENV TELERIK_USERNAME=$TELERIK_USERNAME
|
||||||
|
ENV TELERIK_PASSWORD=$TELERIK_PASSWORD
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
# Copy the entire parent directory into the build context
|
||||||
|
# We expect Chrono and chrono.docs to be siblings
|
||||||
|
COPY Chrono/ Chrono/
|
||||||
|
COPY chrono.docs/ chrono.docs/
|
||||||
|
|
||||||
|
WORKDIR /src/Chrono
|
||||||
|
# Ensure Telerik credentials are set and not placeholders
|
||||||
|
RUN if [ -z "$TELERIK_USERNAME" ] || [ "$TELERIK_USERNAME" = "your_telerik_username_or_api-key" ]; then \
|
||||||
|
echo "ERROR: TELERIK_USERNAME is not set in the build environment."; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Inject credentials into nuget.config to ensure they are available for restore
|
||||||
|
RUN dotnet nuget update source TelerikServer \
|
||||||
|
--username "$TELERIK_USERNAME" \
|
||||||
|
--password "$TELERIK_PASSWORD" \
|
||||||
|
--store-password-in-clear-text \
|
||||||
|
--configfile nuget.config
|
||||||
|
|
||||||
|
RUN dotnet restore "Chrono.sln" --configfile nuget.config
|
||||||
|
RUN dotnet publish "Server/Server.csproj" -c Release -o /app/publish /p:TelerikLicense="$TELERIK_LICENSE"
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /app/publish .
|
||||||
|
EXPOSE 8080
|
||||||
|
ENV ASPNETCORE_URLS=http://+:8080
|
||||||
|
ENTRYPOINT ["dotnet", "Server.dll"]
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Chrono.Model;
|
||||||
|
|
||||||
|
public class CardNote
|
||||||
|
{
|
||||||
|
public string CardName { get; set; } = "";
|
||||||
|
public string Note { get; set; } = "";
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Chrono.Model;
|
||||||
|
|
||||||
|
namespace Server;
|
||||||
|
|
||||||
|
public class AppDbContext : DbContext
|
||||||
|
{
|
||||||
|
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DbSet<CardNote> CardNotes { get; set; }
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder.Entity<CardNote>().HasKey(n => n.CardName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Chrono.Model;
|
||||||
|
|
||||||
|
namespace Server.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class NotesController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly AppDbContext _context;
|
||||||
|
|
||||||
|
public NotesController(AppDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{cardName}")]
|
||||||
|
public async Task<ActionResult<CardNote>> GetNote(string cardName)
|
||||||
|
{
|
||||||
|
var note = await _context.CardNotes.FindAsync(cardName);
|
||||||
|
if (note == null)
|
||||||
|
{
|
||||||
|
return Ok(new CardNote { CardName = cardName, Note = "" });
|
||||||
|
}
|
||||||
|
return Ok(note);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<ActionResult<CardNote>> UpdateNote(CardNote note)
|
||||||
|
{
|
||||||
|
var existing = await _context.CardNotes.FindAsync(note.CardName);
|
||||||
|
if (existing == null)
|
||||||
|
{
|
||||||
|
_context.CardNotes.Add(note);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
existing.Note = note.Note;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
return Ok(note);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
using Server;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Server.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
[Migration("20260618210058_InitialCreate")]
|
||||||
|
partial class InitialCreate
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.9")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Chrono.Model.CardNote", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("CardName")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Note")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("CardName");
|
||||||
|
|
||||||
|
b.ToTable("CardNotes");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Server.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class InitialCreate : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "CardNotes",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
CardName = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Note = table.Column<string>(type: "text", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_CardNotes", x => x.CardName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "CardNotes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
using Server;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Server.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(AppDbContext))]
|
||||||
|
partial class AppDbContextModelSnapshot : ModelSnapshot
|
||||||
|
{
|
||||||
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.9")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("Chrono.Model.CardNote", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("CardName")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Note")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("CardName");
|
||||||
|
|
||||||
|
b.ToTable("CardNotes");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Server;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
builder.WebHost.UseStaticWebAssets();
|
||||||
|
|
||||||
|
// Add services to the container.
|
||||||
|
builder.Services.AddControllersWithViews();
|
||||||
|
builder.Services.AddRazorPages();
|
||||||
|
|
||||||
|
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
|
||||||
|
builder.Services.AddDbContext<AppDbContext>(options =>
|
||||||
|
options.UseNpgsql(connectionString));
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
// Apply migrations
|
||||||
|
using (var scope = app.Services.CreateScope())
|
||||||
|
{
|
||||||
|
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||||
|
db.Database.Migrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure the HTTP request pipeline.
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseWebAssemblyDebugging();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
app.UseExceptionHandler("/Error");
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseBlazorFrameworkFiles();
|
||||||
|
app.UseStaticFiles();
|
||||||
|
|
||||||
|
app.UseRouting();
|
||||||
|
|
||||||
|
app.MapRazorPages();
|
||||||
|
app.MapControllers();
|
||||||
|
app.MapFallbackToFile("index.html");
|
||||||
|
|
||||||
|
app.Run();
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
|
"profiles": {
|
||||||
|
"http": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": false,
|
||||||
|
"applicationUrl": "http://localhost:5256",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"https": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": false,
|
||||||
|
"applicationUrl": "https://localhost:7266;http://localhost:5256",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Model\Model.csproj" />
|
||||||
|
<ProjectReference Include="..\Web\Web.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.9" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.9">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.9">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@Server_HostAddress = http://localhost:5256
|
||||||
|
|
||||||
|
GET {{Server_HostAddress}}/weatherforecast/
|
||||||
|
Accept: application/json
|
||||||
|
|
||||||
|
###
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "Host=localhost;Database=chrono;Username=postgres;Password=postgres"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using Microsoft.Playwright.NUnit;
|
||||||
|
using Microsoft.Playwright;
|
||||||
|
|
||||||
|
namespace Tests;
|
||||||
|
|
||||||
|
[Parallelizable(ParallelScope.Self)]
|
||||||
|
[TestFixture]
|
||||||
|
public class PlaywrightTests : PageTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public async Task CanWriteAndSaveNote()
|
||||||
|
{
|
||||||
|
// 1. Navigate to the cards gallery
|
||||||
|
await Page.GotoAsync("http://localhost:8080/cards");
|
||||||
|
|
||||||
|
// 2. Wait for cards to load - looking for at least one card-cell
|
||||||
|
await Expect(Page.Locator(".card-cell").First).ToBeVisibleAsync();
|
||||||
|
|
||||||
|
// 3. Find an Agent card and click it.
|
||||||
|
var agentCard = Page.Locator(".card-cell:has(.card-category-badge.agent)").First;
|
||||||
|
await agentCard.ClickAsync();
|
||||||
|
|
||||||
|
// 4. Wait for the detail view to show the note textarea
|
||||||
|
var noteInput = Page.Locator(".note-input");
|
||||||
|
await Expect(noteInput).ToBeVisibleAsync();
|
||||||
|
|
||||||
|
// 5. Type a unique note
|
||||||
|
string uniqueNote = "Test note " + Guid.NewGuid().ToString();
|
||||||
|
await noteInput.FillAsync(uniqueNote);
|
||||||
|
|
||||||
|
// 6. Blur to trigger save
|
||||||
|
await noteInput.BlurAsync();
|
||||||
|
|
||||||
|
// 7. Wait for saving indicator to disappear (if it appeared)
|
||||||
|
var savingIndicator = Page.Locator(".saving-indicator");
|
||||||
|
if (await savingIndicator.IsVisibleAsync())
|
||||||
|
{
|
||||||
|
await Expect(savingIndicator).Not.ToBeVisibleAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. Close the detail view by clicking the backdrop
|
||||||
|
await Page.Locator(".modal-backdrop").ClickAsync();
|
||||||
|
await Expect(noteInput).Not.ToBeVisibleAsync();
|
||||||
|
|
||||||
|
// 9. Re-open the same agent card
|
||||||
|
await agentCard.ClickAsync();
|
||||||
|
|
||||||
|
// 10. Verify the note is still there
|
||||||
|
await Expect(noteInput).ToHaveValueAsync(uniqueNote);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using Microsoft.Playwright.NUnit;
|
||||||
|
using Microsoft.Playwright;
|
||||||
|
|
||||||
|
namespace Tests;
|
||||||
|
|
||||||
|
[Parallelizable(ParallelScope.Self)]
|
||||||
|
[TestFixture]
|
||||||
|
public class TelerikLicenseTests : PageTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public async Task TelerikLicenseBannerIsNotVisible()
|
||||||
|
{
|
||||||
|
// 1. Navigate to the agents page which uses Telerik components
|
||||||
|
await Page.GotoAsync("http://localhost:8080/agents");
|
||||||
|
|
||||||
|
// 2. Wait for the page to load and the grid to be visible
|
||||||
|
await Page.WaitForLoadStateAsync(LoadState.NetworkIdle);
|
||||||
|
var grid = Page.Locator(".agents-grid");
|
||||||
|
await Expect(grid).ToBeVisibleAsync();
|
||||||
|
|
||||||
|
// 3. Verify that the Telerik license warning banner is NOT present
|
||||||
|
// According to Telerik docs, the warning text is:
|
||||||
|
// "We couldn't verify your license key for Telerik UI for Blazor. Please see the build log for details and resolution steps"
|
||||||
|
var licenseWarning = Page.GetByText("We couldn't verify your license key for Telerik UI for Blazor");
|
||||||
|
await Expect(licenseWarning).Not.ToBeVisibleAsync();
|
||||||
|
|
||||||
|
// 4. Also verify that no "Trial" banner is visible
|
||||||
|
var trialBanner = Page.GetByText("Telerik UI for Blazor Trial", new() { Exact = false });
|
||||||
|
await Expect(trialBanner).Not.ToBeVisibleAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
@page "/cards"
|
@page "/cards"
|
||||||
|
@inject HttpClient Http
|
||||||
|
|
||||||
<PageTitle>Card Gallery</PageTitle>
|
<PageTitle>Card Gallery</PageTitle>
|
||||||
|
|
||||||
@@ -211,6 +212,19 @@
|
|||||||
<span class="field-value">@selectedCard.ImmortalizeFrom</span>
|
<span class="field-value">@selectedCard.ImmortalizeFrom</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (selectedCard.IsAgent)
|
||||||
|
{
|
||||||
|
<div class="detail-field note">
|
||||||
|
<span class="field-label"><i class="bi bi-pencil-fill"></i> Personal Note</span>
|
||||||
|
<textarea class="form-control note-input" @bind="currentNote" @onblur="SaveNote"
|
||||||
|
placeholder="Add a private note about this agent..."></textarea>
|
||||||
|
@if (isSaving)
|
||||||
|
{
|
||||||
|
<span class="saving-indicator">Saving...</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -225,6 +239,8 @@
|
|||||||
private string costFilter = "";
|
private string costFilter = "";
|
||||||
private CardData? selectedCard;
|
private CardData? selectedCard;
|
||||||
private List<string> factions = [];
|
private List<string> factions = [];
|
||||||
|
private string currentNote = "";
|
||||||
|
private bool isSaving = false;
|
||||||
|
|
||||||
private bool HasActiveFilters => categoryFilter != "" || factionFilter != "" || costFilter != "";
|
private bool HasActiveFilters => categoryFilter != "" || factionFilter != "" || costFilter != "";
|
||||||
|
|
||||||
@@ -276,9 +292,41 @@
|
|||||||
search = "";
|
search = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SelectCard(CardData card)
|
private async Task SelectCard(CardData card)
|
||||||
{
|
{
|
||||||
selectedCard = card;
|
selectedCard = card;
|
||||||
|
if (card.IsAgent)
|
||||||
|
{
|
||||||
|
currentNote = "";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var note = await Http.GetFromJsonAsync<CardNote>($"api/notes/{Uri.EscapeDataString(card.Name)}");
|
||||||
|
currentNote = note?.Note ?? "";
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
currentNote = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveNote()
|
||||||
|
{
|
||||||
|
if (selectedCard == null || !selectedCard.IsAgent) return;
|
||||||
|
|
||||||
|
isSaving = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Http.PostAsJsonAsync("api/notes", new CardNote { CardName = selectedCard.Name, Note = currentNote });
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Error handling
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
isSaving = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CloseDetail()
|
private void CloseDetail()
|
||||||
|
|||||||
@@ -4,13 +4,13 @@
|
|||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
|
<OverrideHtmlAssetPlaceholders>false</OverrideHtmlAssetPlaceholders>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.9"/>
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.9"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.9" PrivateAssets="all"/>
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.9" PrivateAssets="all"/>
|
||||||
<PackageReference Include="Telerik.UI.for.Blazor" Version="14.0.0"/>
|
<PackageReference Include="Telerik.UI.for.Blazor" Version="14.0.0" PrivateAssets="none"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -225,3 +225,26 @@ a, .btn-link {
|
|||||||
.loading-status::after {
|
.loading-status::after {
|
||||||
content: var(--blazor-load-percentage-text, "Loading...");
|
content: var(--blazor-load-percentage-text, "Loading...");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-detail .detail-field.note {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-input {
|
||||||
|
min-height: 100px;
|
||||||
|
resize: vertical;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.saving-indicator {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-style: italic;
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,13 +10,12 @@
|
|||||||
<link crossorigin href="https://fonts.gstatic.com" rel="preconnect"/>
|
<link crossorigin href="https://fonts.gstatic.com" rel="preconnect"/>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"/>
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"/>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet"/>
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet"/>
|
||||||
<link id="webassembly" rel="preload"/>
|
<link href="/lib/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet"/>
|
||||||
<link href="lib/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet"/>
|
<link href="/_content/Telerik.UI.for.Blazor/css/kendo-theme-default/all.css" rel="stylesheet"/>
|
||||||
<link href="css/app.css" rel="stylesheet"/>
|
<link href="/css/app.css" rel="stylesheet"/>
|
||||||
<link href="_content/Telerik.UI.for.Blazor/css/kendo-theme-default/all.css" rel="stylesheet"/>
|
<link href="/Web.styles.css" rel="stylesheet"/>
|
||||||
<script src="_content/Telerik.UI.for.Blazor/js/telerik-blazor.js" defer></script>
|
<script src="/_content/Telerik.UI.for.Blazor/js/telerik-blazor.js" defer></script>
|
||||||
<link href="favicon.png" rel="icon" type="image/png"/>
|
<link href="/favicon.png" rel="icon" type="image/png"/>
|
||||||
<link href="Web.styles.css" rel="stylesheet"/>
|
|
||||||
<script type="importmap"></script>
|
<script type="importmap"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -46,7 +45,7 @@
|
|||||||
<a class="reload" href=".">Reload</a>
|
<a class="reload" href=".">Reload</a>
|
||||||
<span class="dismiss">🗙</span>
|
<span class="dismiss">🗙</span>
|
||||||
</div>
|
</div>
|
||||||
<script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
|
<script src="/_framework/blazor.webassembly.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Chrono CCG - Project Guide
|
||||||
|
|
||||||
|
This project is a hosted Blazor WebAssembly application with a PostgreSQL database for persisting agent notes.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
- **Docker Desktop**: Required for the recommended containerized setup.
|
||||||
|
- **.NET 10 SDK**: Required if you want to build or run the project locally without Docker.
|
||||||
|
|
||||||
|
## 1. Running with Docker (Recommended)
|
||||||
|
The easiest way to get everything running (App + PostgreSQL) is using Docker Compose.
|
||||||
|
|
||||||
|
1. **Open a terminal** in the project root (`Chrono/`).
|
||||||
|
2. **Run the following command**:
|
||||||
|
```bash
|
||||||
|
docker-compose up --build
|
||||||
|
```
|
||||||
|
3. **Access the Application**:
|
||||||
|
- Web Interface: http://localhost:8080
|
||||||
|
- API Endpoint: http://localhost:8080/api/notes
|
||||||
|
|
||||||
|
The database will be automatically initialized and migrations will be applied on startup.
|
||||||
|
|
||||||
|
## 2. Running Locally (Development)
|
||||||
|
If you need to run the app directly (e.g., for faster debugging):
|
||||||
|
|
||||||
|
1. **Start a PostgreSQL database**. You can use the one from docker-compose if you want:
|
||||||
|
```bash
|
||||||
|
docker-compose up db
|
||||||
|
```
|
||||||
|
2. **Verify Connection String**: `Server/appsettings.Development.json` is pre-configured to point to `localhost`.
|
||||||
|
3. **Run the Server project**:
|
||||||
|
```bash
|
||||||
|
cd Server
|
||||||
|
dotnet run --launch-profile https
|
||||||
|
```
|
||||||
|
4. The app will be served at the URL shown in the terminal (e.g., https://localhost:7266).
|
||||||
|
|
||||||
|
## 3. Running Tests
|
||||||
|
To verify the core domain logic:
|
||||||
|
```bash
|
||||||
|
dotnet test
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Key Features
|
||||||
|
- **Agent Notes**: In the "Cards" gallery, select an Agent to see the "Personal Note" field. Changes are auto-saved to the PostgreSQL database when you click away from the text area.
|
||||||
|
- **Auto-Migrations**: The Server project automatically handles database schema updates on startup.
|
||||||
|
- **Dockerized Architecture**: Complete orchestration of the web server and database.
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: Chrono/Dockerfile
|
||||||
|
args:
|
||||||
|
- TELERIK_LICENSE=${TELERIK_LICENSE}
|
||||||
|
- TELERIK_USERNAME=${TELERIK_USERNAME}
|
||||||
|
- TELERIK_PASSWORD=${TELERIK_PASSWORD}
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
environment:
|
||||||
|
- TELERIK_LICENSE=${TELERIK_LICENSE}
|
||||||
|
- ConnectionStrings__DefaultConnection=Host=db;Database=chrono;Username=postgres;Password=postgres
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:17-alpine
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
environment:
|
||||||
|
- POSTGRES_DB=chrono
|
||||||
|
- POSTGRES_USER=postgres
|
||||||
|
- POSTGRES_PASSWORD=postgres
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<packageSources>
|
||||||
|
<clear />
|
||||||
|
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
||||||
|
<add key="TelerikServer" value="https://nuget.telerik.com/v3/index.json" protocolVersion="3" />
|
||||||
|
</packageSources>
|
||||||
|
<packageSourceCredentials>
|
||||||
|
<TelerikServer>
|
||||||
|
<add key="Username" value="%TELERIK_USERNAME%" />
|
||||||
|
<add key="ClearTextPassword" value="%TELERIK_PASSWORD%" />
|
||||||
|
</TelerikServer>
|
||||||
|
</packageSourceCredentials>
|
||||||
|
</configuration>
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"theme": "obsidian"
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"file-explorer": true,
|
||||||
|
"global-search": true,
|
||||||
|
"switcher": true,
|
||||||
|
"graph": true,
|
||||||
|
"backlink": true,
|
||||||
|
"canvas": true,
|
||||||
|
"outgoing-link": true,
|
||||||
|
"tag-pane": true,
|
||||||
|
"footnotes": false,
|
||||||
|
"properties": true,
|
||||||
|
"page-preview": true,
|
||||||
|
"daily-notes": true,
|
||||||
|
"templates": true,
|
||||||
|
"note-composer": true,
|
||||||
|
"command-palette": true,
|
||||||
|
"slash-command": false,
|
||||||
|
"editor-status": true,
|
||||||
|
"bookmarks": true,
|
||||||
|
"markdown-importer": false,
|
||||||
|
"zk-prefixer": false,
|
||||||
|
"random-note": false,
|
||||||
|
"outline": true,
|
||||||
|
"word-count": true,
|
||||||
|
"slides": false,
|
||||||
|
"audio-recorder": false,
|
||||||
|
"workspaces": false,
|
||||||
|
"file-recovery": true,
|
||||||
|
"publish": false,
|
||||||
|
"sync": true,
|
||||||
|
"bases": true,
|
||||||
|
"webviewer": false
|
||||||
|
}
|
||||||
+190
@@ -0,0 +1,190 @@
|
|||||||
|
{
|
||||||
|
"main": {
|
||||||
|
"id": "56a668de34c26d11",
|
||||||
|
"type": "split",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "07a0aa696acfaf87",
|
||||||
|
"type": "tabs",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "c7bccd0272521a1f",
|
||||||
|
"type": "leaf",
|
||||||
|
"state": {
|
||||||
|
"type": "markdown",
|
||||||
|
"state": {
|
||||||
|
"file": "Docker.md",
|
||||||
|
"mode": "source",
|
||||||
|
"source": false
|
||||||
|
},
|
||||||
|
"icon": "lucide-file",
|
||||||
|
"title": "Docker"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"direction": "vertical"
|
||||||
|
},
|
||||||
|
"left": {
|
||||||
|
"id": "c74cb397c5dc27bb",
|
||||||
|
"type": "split",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "973f93e99f586721",
|
||||||
|
"type": "tabs",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "dad8458b7399dc0a",
|
||||||
|
"type": "leaf",
|
||||||
|
"state": {
|
||||||
|
"type": "file-explorer",
|
||||||
|
"state": {
|
||||||
|
"sortOrder": "alphabetical",
|
||||||
|
"autoReveal": false
|
||||||
|
},
|
||||||
|
"icon": "lucide-folder-closed",
|
||||||
|
"title": "Files"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e5d5e7559ed306af",
|
||||||
|
"type": "leaf",
|
||||||
|
"state": {
|
||||||
|
"type": "search",
|
||||||
|
"state": {
|
||||||
|
"query": "",
|
||||||
|
"matchingCase": false,
|
||||||
|
"explainSearch": false,
|
||||||
|
"collapseAll": false,
|
||||||
|
"extraContext": false,
|
||||||
|
"sortOrder": "alphabetical"
|
||||||
|
},
|
||||||
|
"icon": "lucide-search",
|
||||||
|
"title": "Search"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "aed67689a5311182",
|
||||||
|
"type": "leaf",
|
||||||
|
"state": {
|
||||||
|
"type": "bookmarks",
|
||||||
|
"state": {},
|
||||||
|
"icon": "lucide-bookmark",
|
||||||
|
"title": "Bookmarks"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"direction": "horizontal",
|
||||||
|
"width": 300
|
||||||
|
},
|
||||||
|
"right": {
|
||||||
|
"id": "4cc6aabbe0f085e2",
|
||||||
|
"type": "split",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "d6f7737d064d74af",
|
||||||
|
"type": "tabs",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": "d0dca54f7dc291e1",
|
||||||
|
"type": "leaf",
|
||||||
|
"state": {
|
||||||
|
"type": "backlink",
|
||||||
|
"state": {
|
||||||
|
"file": "Docker.md",
|
||||||
|
"collapseAll": false,
|
||||||
|
"extraContext": false,
|
||||||
|
"sortOrder": "alphabetical",
|
||||||
|
"showSearch": false,
|
||||||
|
"searchQuery": "",
|
||||||
|
"backlinkCollapsed": false,
|
||||||
|
"unlinkedCollapsed": true
|
||||||
|
},
|
||||||
|
"icon": "links-coming-in",
|
||||||
|
"title": "Backlinks for Docker"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "b1ca8745b20e876a",
|
||||||
|
"type": "leaf",
|
||||||
|
"state": {
|
||||||
|
"type": "outgoing-link",
|
||||||
|
"state": {
|
||||||
|
"file": "Docker.md",
|
||||||
|
"linksCollapsed": false,
|
||||||
|
"unlinkedCollapsed": true
|
||||||
|
},
|
||||||
|
"icon": "links-going-out",
|
||||||
|
"title": "Outgoing links from Docker"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d80519389a50945c",
|
||||||
|
"type": "leaf",
|
||||||
|
"state": {
|
||||||
|
"type": "tag",
|
||||||
|
"state": {
|
||||||
|
"sortOrder": "frequency",
|
||||||
|
"useHierarchy": true,
|
||||||
|
"showSearch": false,
|
||||||
|
"searchQuery": ""
|
||||||
|
},
|
||||||
|
"icon": "lucide-tags",
|
||||||
|
"title": "Tags"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "a3d721b40278455a",
|
||||||
|
"type": "leaf",
|
||||||
|
"state": {
|
||||||
|
"type": "all-properties",
|
||||||
|
"state": {
|
||||||
|
"sortOrder": "frequency",
|
||||||
|
"showSearch": false,
|
||||||
|
"searchQuery": ""
|
||||||
|
},
|
||||||
|
"icon": "lucide-archive",
|
||||||
|
"title": "All properties"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "0d722d47271821e0",
|
||||||
|
"type": "leaf",
|
||||||
|
"state": {
|
||||||
|
"type": "outline",
|
||||||
|
"state": {
|
||||||
|
"file": "Docker.md",
|
||||||
|
"followCursor": false,
|
||||||
|
"showSearch": false,
|
||||||
|
"searchQuery": ""
|
||||||
|
},
|
||||||
|
"icon": "lucide-list",
|
||||||
|
"title": "Outline of Docker"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"direction": "horizontal",
|
||||||
|
"width": 300,
|
||||||
|
"collapsed": true
|
||||||
|
},
|
||||||
|
"left-ribbon": {
|
||||||
|
"hiddenItems": {
|
||||||
|
"switcher:Open quick switcher": false,
|
||||||
|
"graph:Open graph view": false,
|
||||||
|
"canvas:Create new canvas": false,
|
||||||
|
"daily-notes:Open today's daily note": false,
|
||||||
|
"templates:Insert template": false,
|
||||||
|
"command-palette:Open command palette": false,
|
||||||
|
"bases:Create new base": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": "c7bccd0272521a1f",
|
||||||
|
"lastOpenFiles": [
|
||||||
|
"Docker.md"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Chrono CCG - Project Guide
|
||||||
|
|
||||||
|
This project is a hosted Blazor WebAssembly application with a PostgreSQL database for persisting agent notes.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
- **Docker Desktop**: Required for the recommended containerized setup.
|
||||||
|
- **.NET 10 SDK**: Required if you want to build or run the project locally without Docker.
|
||||||
|
|
||||||
|
## 1. Running with Docker (Recommended)
|
||||||
|
The easiest way to get everything running (App + PostgreSQL) is using Docker Compose.
|
||||||
|
|
||||||
|
1. **Open a terminal** in the project root (`Chrono/`).
|
||||||
|
2. **Run the following command**:
|
||||||
|
```bash
|
||||||
|
docker-compose up --build
|
||||||
|
```
|
||||||
|
3. **Access the Application**:
|
||||||
|
- Web Interface: http://localhost:8080
|
||||||
|
- API Endpoint: http://localhost:8080/api/notes
|
||||||
|
|
||||||
|
The database will be automatically initialized and migrations will be applied on startup.
|
||||||
|
|
||||||
|
## 2. Running Locally (Development)
|
||||||
|
If you need to run the app directly (e.g., for faster debugging):
|
||||||
|
|
||||||
|
1. **Start a PostgreSQL database**. You can use the one from docker-compose if you want:
|
||||||
|
```bash
|
||||||
|
docker-compose up db
|
||||||
|
```
|
||||||
|
2. **Verify Connection String**: `Server/appsettings.Development.json` is pre-configured to point to `localhost`.
|
||||||
|
3. **Run the Server project**:
|
||||||
|
```bash
|
||||||
|
cd Server
|
||||||
|
dotnet run --launch-profile https
|
||||||
|
```
|
||||||
|
4. The app will be served at the URL shown in the terminal (e.g., https://localhost:7266).
|
||||||
|
|
||||||
|
## 3. Running Tests
|
||||||
|
To verify the core domain logic:
|
||||||
|
```bash
|
||||||
|
dotnet test
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Key Features
|
||||||
|
- **Agent Notes**: In the "Cards" gallery, select an Agent to see the "Personal Note" field. Changes are auto-saved to the PostgreSQL database when you click away from the text area.
|
||||||
|
- **Auto-Migrations**: The Server project automatically handles database schema updates on startup.
|
||||||
|
- **Dockerized Architecture**: Complete orchestration of the web server and database.
|
||||||
Reference in New Issue
Block a user