...docker test
This commit is contained in:
@@ -254,6 +254,9 @@ paket-files/
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# Docker environment variables
|
||||
.env
|
||||
|
||||
# CodeRush
|
||||
.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
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{90F32056-6983-4224-8A8C-E797C71633F3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server", "Server\Server.csproj", "{BFB7B73F-EFDD-4DE8-89F2-E1CBE1B55C27}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{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|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.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.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.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.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.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.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.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.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.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
|
||||
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"
|
||||
@inject HttpClient Http
|
||||
|
||||
<PageTitle>Card Gallery</PageTitle>
|
||||
|
||||
@@ -211,6 +212,19 @@
|
||||
<span class="field-value">@selectedCard.ImmortalizeFrom</span>
|
||||
</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>
|
||||
@@ -225,6 +239,8 @@
|
||||
private string costFilter = "";
|
||||
private CardData? selectedCard;
|
||||
private List<string> factions = [];
|
||||
private string currentNote = "";
|
||||
private bool isSaving = false;
|
||||
|
||||
private bool HasActiveFilters => categoryFilter != "" || factionFilter != "" || costFilter != "";
|
||||
|
||||
@@ -276,9 +292,41 @@
|
||||
search = "";
|
||||
}
|
||||
|
||||
private void SelectCard(CardData card)
|
||||
private async Task SelectCard(CardData 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()
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
|
||||
<OverrideHtmlAssetPlaceholders>false</OverrideHtmlAssetPlaceholders>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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="Telerik.UI.for.Blazor" Version="14.0.0"/>
|
||||
<PackageReference Include="Telerik.UI.for.Blazor" Version="14.0.0" PrivateAssets="none"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -225,3 +225,26 @@ a, .btn-link {
|
||||
.loading-status::after {
|
||||
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 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 id="webassembly" rel="preload"/>
|
||||
<link href="lib/bootstrap/dist/css/bootstrap.min.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"/>
|
||||
<script src="_content/Telerik.UI.for.Blazor/js/telerik-blazor.js" defer></script>
|
||||
<link href="favicon.png" rel="icon" type="image/png"/>
|
||||
<link href="Web.styles.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="/Web.styles.css" rel="stylesheet"/>
|
||||
<script src="/_content/Telerik.UI.for.Blazor/js/telerik-blazor.js" defer></script>
|
||||
<link href="/favicon.png" rel="icon" type="image/png"/>
|
||||
<script type="importmap"></script>
|
||||
</head>
|
||||
|
||||
@@ -46,7 +45,7 @@
|
||||
<a class="reload" href=".">Reload</a>
|
||||
<span class="dismiss">🗙</span>
|
||||
</div>
|
||||
<script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
|
||||
<script src="/_framework/blazor.webassembly.js"></script>
|
||||
</body>
|
||||
|
||||
</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