Merge branch 'eliminate-public-api' into dev

This commit is contained in:
Daniel Harvey
2026-05-25 17:48:56 -04:00
13 changed files with 30 additions and 281 deletions
+1 -2
View File
@@ -47,8 +47,7 @@ public class TrackController : ControllerBase
// resolution never treats "page", "upload", or "meta" as a trackId.
// GET api/track/page?page=1&pageSize=20&sortColumn=TrackName&sortDescending=false
// CMS metadata listing — paged read straight from SQL.
[ApiKeyAuthorize]
// Public track listing — paged read straight from SQL. Unauthenticated, like GET api/track/{id}.
[HttpGet("page")]
public async Task<ActionResult> GetPage(
[FromQuery] int page = 1,
+16 -11
View File
@@ -17,32 +17,37 @@ public class TrackClient
}
public async Task<ApiResult<PagedResult<TrackDto>>> GetPage(
int pageNumber,
int pageSize,
string? sortColumn = null,
int pageNumber,
int pageSize,
string? sortColumn = null,
bool sortDescending = false)
{
var queryArgs = new Dictionary<string, string?>(){
["pageNumber"] = pageNumber.ToString(),
["page"] = pageNumber.ToString(),
["pageSize"] = pageSize.ToString()
};
if (!string.IsNullOrEmpty(sortColumn))
queryArgs["sortColumn"] = sortColumn;
if (sortDescending)
queryArgs["sortDescending"] = "true";
string query = QueryString.Create(queryArgs).ToString();
var response = await _http.GetAsync($"api/track/page{query}");
if (!response.IsSuccessStatusCode)
return ApiResult<PagedResult<TrackDto>>.CreateFailResult($"HTTP {(int)response.StatusCode}");
var json = await response.Content.ReadAsStringAsync();
var dto = JsonSerializer.Deserialize<ApiResultDto<PagedResult<TrackDto>>>(json, new JsonSerializerOptions
var paged = JsonSerializer.Deserialize<PagedResult<TrackDto>>(json, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
return dto?.From() ?? ApiResult<PagedResult<TrackDto>>.CreateFailResult("Failed to deserialize response");
return paged is not null
? ApiResult<PagedResult<TrackDto>>.CreatePassResult(paged)
: ApiResult<PagedResult<TrackDto>>.CreateFailResult("Failed to deserialize response");
}
}
+2 -1
View File
@@ -7,10 +7,11 @@ var builder = WebAssemblyHostBuilder.CreateDefault(args);
Console.WriteLine(builder.HostEnvironment.BaseAddress);
var contentApiUrl = builder.Configuration["ApiUrls:ContentApi"] ?? "https://localhost:7001";
var sqlApiUrl = builder.Configuration["ApiUrls:SqlApi"] ?? "https://localhost:5002";
builder.Services.AddMudServices();
Startup.ConfigureApiHttpClient(builder.Services, builder.HostEnvironment.BaseAddress);
Startup.ConfigureApiHttpClient(builder.Services, sqlApiUrl);
Startup.ConfigureContentServices(builder.Services, contentApiUrl);
Startup.ConfigureDomainServices(builder.Services);
@@ -5,13 +5,9 @@ using NetBlocks.Models;
namespace DeepDrftPublic.Client.Services;
/// <summary>
/// Track metadata fetch abstraction with two render-mode-specific implementations:
///
/// - Server prerender pass: <c>TrackDirectDataService</c> in the DeepDrftPublic host
/// resolves <see cref="DeepDrftData.ITrackService"/> in-process (EF Core / SQL) and
/// avoids a loopback HTTP hop.
/// - WASM interactive pass: <c>TrackClientDataService</c> in this assembly delegates
/// to <see cref="Clients.TrackClient"/> over HTTP.
/// Track metadata fetch abstraction. Both SSR and WASM passes are served by
/// <c>TrackClientDataService</c> in this assembly, which delegates to
/// <see cref="Clients.TrackClient"/> over HTTP.
///
/// Components inject this single seam so they do not branch on render mode.
/// </summary>
+2 -3
View File
@@ -14,9 +14,8 @@ public static class Startup
services.AddScoped<DarkModeSettings>();
services.AddScoped<DarkModeCookieService>();
// Track Client. The HTTP-backed ITrackDataService registration here is the WASM
// default; the server host overrides it with an in-process implementation after
// this method runs, so SSR prerender skips the loopback hop.
// Track Client. The HTTP-backed ITrackDataService is used by both WASM and SSR
// prerender — both call DeepDrftAPI over the "DeepDrft.API" client.
services.AddScoped<TrackClient>();
services.AddScoped<ITrackDataService, TrackClientDataService>();
services.AddScoped<TracksViewModel>();
@@ -6,6 +6,7 @@
}
},
"ApiUrls": {
"ContentApi": "https://media.deepdrft.com/"
"ContentApi": "https://media.deepdrft.com/",
"SqlApi": "https://api.deepdrft.com/"
}
}
@@ -1,33 +0,0 @@
using DeepDrftData;
using DeepDrftModels.DTOs;
using Microsoft.AspNetCore.Mvc;
using Models.Common;
using NetBlocks.Models;
namespace DeepDrftPublic.Controllers;
[ApiController]
[Route("api/[controller]")]
public class TrackController : ControllerBase
{
private readonly ITrackService _trackService;
public TrackController(ITrackService trackService)
{
_trackService = trackService;
}
[HttpGet("page")]
public async Task<ActionResult<ApiResultDto<PagedResult<TrackDto>>>> GetPage(
[FromQuery] int pageNumber,
[FromQuery] int pageSize,
[FromQuery] string? sortColumn = null,
[FromQuery] bool sortDescending = false)
{
var result = await _trackService.GetPaged(pageNumber, pageSize, sortColumn, sortDescending);
var apiResult = ApiResult<PagedResult<TrackDto>>.From(result);
var dto = new ApiResultDto<PagedResult<TrackDto>>(apiResult);
return result.Success ? Ok(dto) : StatusCode(500, dto);
}
}
-13
View File
@@ -8,21 +8,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<!-- Npgsql 10.0.1 requires Microsoft.EntityFrameworkCore >= 10.0.4; keep in sync -->
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
<ProjectReference Include="..\DeepDrftModels\DeepDrftModels.csproj" />
<ProjectReference Include="..\DeepDrftPublic.Client\DeepDrftPublic.Client.csproj" />
<ProjectReference Include="..\DeepDrftData\DeepDrftData.csproj" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.7" />
</ItemGroup>
+2 -14
View File
@@ -2,32 +2,21 @@ using DeepDrftPublic;
using MudBlazor.Services;
using DeepDrftPublic.Components;
using Microsoft.AspNetCore.HttpOverrides;
using NetBlocks.Utilities.Environment;
var builder = WebApplication.CreateBuilder(args);
// Add MudBlazor services
builder.Services.AddMudServices();
// Required credential files — must exist before the app will start.
// In dev: create the files under DeepDrftPublic/environment/ (gitignored).
// In prod: systemd CREDENTIALS_DIRECTORY points to encrypted credential blobs.
// - environment/connections.json: { "ConnectionStrings": { "DefaultConnection": "..." } }
// AuthBlocks and the DeepDrftAPI API key now live on DeepDrftManager;
// the public host has no auth surface and no CMS upload proxy.
var connectionsPath = CredentialTools.ResolvePathOrThrow("connections", "environment/connections.json");
builder.Configuration.AddJsonFile(connectionsPath, optional: false, reloadOnChange: false);
var contentApiUrl = builder.Configuration["ApiUrls:ContentApi"] ?? throw new Exception("Content API URL is not configured");
var sqlApiUrl = builder.Configuration["ApiUrls:SqlApi"] ?? throw new Exception("ApiUrls:SqlApi is not configured");
DeepDrftPublic.Client.Startup.ConfigureApiHttpClient(builder.Services, builder.GetKestrelUrl());
DeepDrftPublic.Client.Startup.ConfigureApiHttpClient(builder.Services, sqlApiUrl);
DeepDrftPublic.Client.Startup.ConfigureDomainServices(builder.Services);
DeepDrftPublic.Client.Startup.ConfigureContentServices(builder.Services, contentApiUrl);
Startup.ConfigureDomainServices(builder);
builder.Services.AddControllers();
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
@@ -110,7 +99,6 @@ if (app.Environment.IsDevelopment())
});
}
app.MapControllers();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
@@ -1,33 +0,0 @@
using DeepDrftData;
using DeepDrftModels.DTOs;
using DeepDrftPublic.Client.Services;
using Models.Common;
using NetBlocks.Models;
namespace DeepDrftPublic.Services;
/// <summary>
/// Server-side <see cref="ITrackDataService"/> that calls <see cref="ITrackService"/>
/// in-process (EF Core / SQL). Replaces the loopback HTTP hop during SSR prerender:
/// the WASM interactive pass still uses <see cref="Client.Services.TrackClientDataService"/>
/// over HTTP, but on the server we already have the domain service in DI.
/// </summary>
public class TrackDirectDataService : ITrackDataService
{
private readonly ITrackService _trackService;
public TrackDirectDataService(ITrackService trackService)
{
_trackService = trackService;
}
public async Task<ApiResult<PagedResult<TrackDto>>> GetPage(
int pageNumber,
int pageSize,
string? sortColumn = null,
bool sortDescending = false)
{
var result = await _trackService.GetPaged(pageNumber, pageSize, sortColumn, sortDescending);
return ApiResult<PagedResult<TrackDto>>.From(result);
}
}
-50
View File
@@ -1,9 +1,4 @@
using DeepDrftData;
using DeepDrftData.Data;
using DeepDrftData.Repositories;
using DeepDrftPublic.Client.Services;
using DeepDrftPublic.Services; // DarkModeService namespace (within this host project)
using Microsoft.EntityFrameworkCore;
namespace DeepDrftPublic;
@@ -11,55 +6,10 @@ public static class Startup
{
public static void ConfigureDomainServices(WebApplicationBuilder builder)
{
// Add Entity Framework services
builder.Services.AddDbContext<DeepDrftContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
// Add Server Prerendering Theming Support
// DarkModeSettings is registered in DeepDrftPublic.Client.Startup.ConfigureDomainServices
builder.Services
.AddHttpContextAccessor()
.AddScoped<DarkModeService>();
// Add Track services. TrackManager implements ITrackService for backward compatibility
// with pages that inject the interface; resolving ITrackService returns the same scoped
// TrackManager instance so the manager surface (DTO-space) and the service surface
// (entity-space) share state.
builder.Services
.AddScoped<TrackRepository>()
.AddScoped<TrackManager>()
.AddScoped<ITrackService>(sp => sp.GetRequiredService<TrackManager>());
// Override the WASM HTTP-backed ITrackDataService (registered earlier by
// DeepDrftPublic.Client.Startup.ConfigureDomainServices) with an in-process
// adapter for SSR prerender. Last registration wins for single-resolution.
builder.Services.AddScoped<ITrackDataService, TrackDirectDataService>();
}
public static string GetKestrelUrl(this WebApplicationBuilder builder)
{
// Check all the places Kestrel URL can be configured
var urls = builder.Configuration["ASPNETCORE_URLS"]
?? builder.Configuration["urls"];
if (!string.IsNullOrEmpty(urls))
{
return urls.Split(';')[0].Trim();
}
// Check Kestrel endpoints configuration
var kestrelSection = builder.Configuration.GetSection("Kestrel:Endpoints");
var firstEndpoint = kestrelSection.GetChildren().FirstOrDefault();
var endpointUrl = firstEndpoint?["Url"];
if (!string.IsNullOrEmpty(endpointUrl))
{
return endpointUrl;
}
// ASP.NET Core defaults
return builder.Environment.IsDevelopment()
? "https://localhost:5001"
: "http://localhost:5000";
}
}
+2 -1
View File
@@ -7,7 +7,8 @@
},
"AllowedHosts": "*",
"ApiUrls": {
"ContentApi": "http://localhost:12777/"
"ContentApi": "http://localhost:12777/",
"SqlApi": "https://localhost:5002/"
},
"ForwardedHeaders": {
"DisableHttpsRedirection": "true"
-112
View File
@@ -1,112 +0,0 @@
#!/bin/bash
echo "🚀 Starting CLI deployment process..."
# start SSH agent and add key
echo "🔑 Starting SSH agent and adding deployment key..."
eval $(ssh-agent -s)
ssh-add /c/.ssh/deepdrft_dch6_ed25519
echo "✅ SSH agent configured"
CLI_PROJ="DeepDrftCli"
CLI_APP="deepdrft-cli.tar.gz"
# Publish CLI with framework-dependent single file
echo "🔨 Publishing CLI project for linux-x64..."
dotnet publish $CLI_PROJ -c Release -f net9.0 -o $CLI_PROJ/publish -r linux-x64 \
--self-contained false \
-p:PublishSingleFile=true \
-p:Platform="Any CPU" \
--verbosity normal
if [ $? -eq 0 ]; then
echo "✅ CLI project published successfully"
else
echo "❌ Failed to publish CLI project"
exit 1
fi
# Eliminate local environment from package
echo "🧹 Removing local environment from package..."
rm -rf $CLI_PROJ/publish/environment
echo "✅ Local environment removed"
# Compress published files
echo "📦 Compressing published files..."
tar -czf $CLI_APP -C $CLI_PROJ/publish .
echo "✅ Package created: $CLI_APP"
# Deploy
REMOTE="deepdrft@dch6.snailbird.net"
CLI_APPROOT="/deepdrft/cli"
echo "🌐 Deploying to remote server: $REMOTE"
echo "📁 Target directory: $CLI_APPROOT"
echo "🗑️ Cleaning existing deployment..."
ssh $REMOTE "rm -rf $CLI_APPROOT/bin/*"
echo "✅ Remote directory cleaned"
echo "📤 Uploading package to remote server..."
scp $CLI_APP $REMOTE:$CLI_APPROOT/$CLI_APP
if [ $? -eq 0 ]; then
echo "✅ Package uploaded successfully"
else
echo "❌ Failed to upload package"
exit 1
fi
echo "📦 Extracting and setting up CLI on remote server..."
ssh $REMOTE "tar -xzf $CLI_APPROOT/$CLI_APP -C $CLI_APPROOT/bin && \
chmod +x $CLI_APPROOT/bin/DeepDrftCli && \
rm $CLI_APPROOT/$CLI_APP"
if [ $? -eq 0 ]; then
echo "✅ CLI extracted and configured on remote server"
else
echo "❌ Failed to extract CLI on remote server"
exit 1
fi
# Apply Local Environment (if exists)
echo "🔧 Checking for local environment configuration..."
if ssh $REMOTE "[ -d $CLI_APPROOT/environment ]"; then
echo "📋 Local environment found, applying configuration..."
# Ensure environment directory exists in the binary location
ssh $REMOTE "mkdir -p $CLI_APPROOT/bin/environment"
# Copy environment files with better error handling
if ssh $REMOTE "cp $CLI_APPROOT/environment/* $CLI_APPROOT/bin/environment/ 2>/dev/null"; then
echo "✅ Local environment configuration applied successfully"
else
echo "⚠️ Warning: Some environment files may not have been copied"
fi
else
echo "️ No local environment configuration found - skipping"
fi
echo "🔗 Setting up user-accessible command symlink..."
# Create user-accessible symlink without sudo
ssh $REMOTE "mkdir -p ~/bin && ln -sf $CLI_APPROOT/bin/DeepDrftCli ~/bin/deepdrft"
if [ $? -eq 0 ]; then
echo "✅ Symlink created successfully"
else
echo "❌ Failed to create symlink"
exit 1
fi
echo "🛣️ Ensuring ~/bin is in PATH..."
# Ensure ~/bin is in PATH (add to .bashrc if not present)
ssh $REMOTE "grep -q '~/bin' ~/.bashrc || echo 'export PATH=\"\$HOME/bin:\$PATH\"' >> ~/.bashrc"
echo "✅ PATH configuration updated"
echo "🧹 Cleaning up local files..."
# Clean up
rm -rf ./$CLI_PROJ/publish
rm -f ./$CLI_APP
ssh-agent -k
echo "✅ Local cleanup completed"
echo ""
echo "🎉 CLI deployment complete!"
echo "📝 Note: Run 'source ~/.bashrc' or start a new shell session to activate the deepdrft command in PATH"