mirror of
https://github.com/Theodor-Springmann-Stiftung/hamann-ausgabe-core.git
synced 2025-10-29 09:15:33 +00:00
Self-Hosted Git
This commit is contained in:
168
HaWeb/CLAUDE.md
Normal file
168
HaWeb/CLAUDE.md
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
HaWeb is a digital edition website for the Hamann correspondence (Hamann-Ausgabe), built with ASP.NET Core 6 and modern frontend tooling. It renders scholarly editions of historical letters and commentary from XML sources, with features for search, indexing, and editorial workflow management.
|
||||||
|
|
||||||
|
The project depends on two sibling projects:
|
||||||
|
- **HaDocumentV6**: Parses XML files into convenient C# models
|
||||||
|
- **HaXMLReaderV6**: Forward-parses XML elements (letters, comments, traditions, marginals) into HTML
|
||||||
|
|
||||||
|
## Build and Development Commands
|
||||||
|
|
||||||
|
### Initial Setup
|
||||||
|
```bash
|
||||||
|
npm install # Install frontend dependencies
|
||||||
|
dotnet restore # Restore .NET packages
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development (run both in separate terminals)
|
||||||
|
```bash
|
||||||
|
npm run dev # Watch and rebuild CSS/JS (uses Vite)
|
||||||
|
dotnet watch run # Watch and rebuild ASP.NET app
|
||||||
|
```
|
||||||
|
|
||||||
|
Set `DOTNET_ENVIRONMENT=Development` for development features. On Windows PowerShell: `$Env:ASPNETCORE_ENVIRONMENT = "Development"`
|
||||||
|
|
||||||
|
### Production Build
|
||||||
|
```bash
|
||||||
|
npm run build # Build CSS/JS first (required!)
|
||||||
|
dotnet build HaWeb.csproj # Build the web application
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production Deployment (Linux)
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
dotnet publish --runtime linux-x64 -c Release
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Frontend Build System
|
||||||
|
- **Vite**: Bundles JavaScript modules from `wwwroot/js/main.js` → `wwwroot/dist/scripts.js`
|
||||||
|
- **PostCSS + Tailwind**: Processes CSS from `wwwroot/css/*.css` → `wwwroot/dist/styles.css`
|
||||||
|
- **Build order matters**: Always run `npm run build` before `dotnet build` for production
|
||||||
|
|
||||||
|
PostCSS plugin order in `postcss.config.cjs` is critical: `tailwindcss` → `postcss-import` → `autoprefixer`
|
||||||
|
|
||||||
|
### Backend Architecture (ASP.NET Core MVC)
|
||||||
|
|
||||||
|
**Core Services** (registered as singletons in Program.cs):
|
||||||
|
- `XMLTestService`: XML validation and testing
|
||||||
|
- `XMLInteractionService`: Central XML interaction layer
|
||||||
|
- `HaDocumentWrapper`: Wraps HaDocumentV6 models
|
||||||
|
- `GitService`: Git repository management with LibGit2Sharp
|
||||||
|
- `XMLFileProvider`: File system abstraction for XML files
|
||||||
|
- `WebSocketMiddleware`: Real-time notifications for XML changes
|
||||||
|
|
||||||
|
**Controllers** (main routes):
|
||||||
|
- `BriefeContoller`: Letter display and navigation
|
||||||
|
- `RegisterController`: Index/register views (persons, places, etc.)
|
||||||
|
- `SucheController`: Search functionality
|
||||||
|
- `EditionController`: Edition-specific views
|
||||||
|
- `AdminController`: Administrative functions (feature-gated)
|
||||||
|
- `XMLStateController`: XML state management for editors
|
||||||
|
- `WebhookController`: Git webhook endpoint for automated pulls
|
||||||
|
- `CMIF`: CMIF (Correspondence Metadata Interchange Format) export
|
||||||
|
|
||||||
|
**Key Architectural Components**:
|
||||||
|
- `HTMLParser/`: Custom XML→HTML transformation rules and state machines
|
||||||
|
- `FileHelpers/`: XML file reading and management
|
||||||
|
- `SearchHelpers/`: Search implementation with state pattern
|
||||||
|
- `Settings/`: Configuration for XML node parsing, collections, and rules
|
||||||
|
|
||||||
|
### Feature Flags
|
||||||
|
Managed via `Microsoft.FeatureManagement.AspNetCore` in `appsettings.json`:
|
||||||
|
- `AdminService`: Enables admin routes
|
||||||
|
- `LocalPublishService`: Local publishing features
|
||||||
|
- `SyntaxCheck`: XML syntax validation
|
||||||
|
- `Notifications`: WebSocket notifications
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
Environment-specific settings in `appsettings.{Environment}.json`. Key settings:
|
||||||
|
- `FileStoragePath`: Base directory for all data storage (absolute path)
|
||||||
|
- Compiled Hamann.xml files stored in `[FileStoragePath]/HAMANN/`
|
||||||
|
- Git repository content in `[FileStoragePath]/GIT/`
|
||||||
|
- `RepositoryBranch`: Git branch to track (e.g., "main")
|
||||||
|
- `RepositoryURL`: Git repository URL (e.g., "https://github.com/user/repo")
|
||||||
|
- `WebhookSecret`: Optional HMAC-SHA256 secret for webhook validation (GitHub format)
|
||||||
|
|
||||||
|
An external `settings.json` can be loaded from `[FileStoragePath]/GIT/settings.json` for runtime configuration overrides.
|
||||||
|
|
||||||
|
### Data Flow
|
||||||
|
1. Root XML file (`Hamann.xml`) must exist at build output root
|
||||||
|
2. `HaDocumentV6` parses XML into document models
|
||||||
|
3. `HaXMLReaderV6` transforms XML elements to HTML
|
||||||
|
4. Controllers fetch models via services and render Razor views
|
||||||
|
5. Frontend JavaScript adds interactivity (marginals, search highlighting, themes)
|
||||||
|
|
||||||
|
### Frontend JavaScript Modules
|
||||||
|
Modular ES6 structure in `wwwroot/js/`:
|
||||||
|
- `main.js`: Entry point, exports startup functions
|
||||||
|
- `marginals.mjs`: Marginal notes display
|
||||||
|
- `search.mjs`: Search highlighting with mark.js
|
||||||
|
- `theme.mjs`: Theme switching (light/dark)
|
||||||
|
- `websocket.mjs`: WebSocket connection for live updates
|
||||||
|
- `filelistform.mjs`: XML state management forms
|
||||||
|
- `htmx.min.js`: HTMX for dynamic interactions
|
||||||
|
|
||||||
|
Each module exports a `startup_*` function called during initialization.
|
||||||
|
|
||||||
|
## Important Development Notes
|
||||||
|
|
||||||
|
### XML Parsing System
|
||||||
|
The project uses a custom forward-parsing system for XML transformation defined in:
|
||||||
|
- `Settings/NodeRules/`: Rules for specific XML node types (letters, comments, marginals)
|
||||||
|
- `Settings/ParsingRules/`: Parsing rules for text, links, edits, comments
|
||||||
|
- `Settings/ParsingState/`: State machines for parsing contexts
|
||||||
|
- `HTMLParser/`: Core XML parsing helpers
|
||||||
|
|
||||||
|
When modifying XML handling, update both the node rules and corresponding parsing state.
|
||||||
|
|
||||||
|
### Working with Views
|
||||||
|
Views follow standard Razor conventions. Shared layouts in `Views/Shared/`. The project uses tag helpers defined in `HTMLHelpers/TagHelpers.cs` for custom rendering.
|
||||||
|
|
||||||
|
### CSS Architecture
|
||||||
|
- `tailwind.css`: Main Tailwind input
|
||||||
|
- Component-specific CSS files (`letter.css`, `register.css`, `search.css`, etc.)
|
||||||
|
- `site.css`: Imported by `main.js`, serves as CSS entry point
|
||||||
|
- Production builds include autoprefixer and cssnano minification
|
||||||
|
|
||||||
|
### WebSocket Notifications
|
||||||
|
Real-time updates for XML changes via WebSocket. Configured with:
|
||||||
|
- Keep-alive: 30 minutes
|
||||||
|
- Connection filtering via `AllowedWebSocketConnections` in appsettings
|
||||||
|
- Middleware registered before static files in Program.cs
|
||||||
|
|
||||||
|
### Git Integration
|
||||||
|
The application uses **LibGit2Sharp** for Git operations on XML source files:
|
||||||
|
|
||||||
|
**GitService** (`FileHelpers/GitService.cs`):
|
||||||
|
- Manages Git repository at `[FileStoragePath]/GIT/`
|
||||||
|
- Pulls latest changes from remote on webhook trigger
|
||||||
|
- Retrieves current commit SHA, branch, and timestamp
|
||||||
|
- Supports default credentials (SSH agent, credential manager)
|
||||||
|
- Auto-initializes or clones repository if needed
|
||||||
|
|
||||||
|
**Webhook Endpoint** (`/api/webhook/git`):
|
||||||
|
- POST endpoint for Git webhooks (GitHub, GitLab, etc.)
|
||||||
|
- Optional signature validation using `WebhookSecret` (HMAC-SHA256)
|
||||||
|
- Triggers `git pull` operation
|
||||||
|
- Automatically scans and parses XML files after pull
|
||||||
|
- Returns status with commit info
|
||||||
|
|
||||||
|
**Status Endpoint** (`/api/webhook/status`):
|
||||||
|
- GET endpoint to check current Git state
|
||||||
|
- Returns commit SHA, branch, timestamp
|
||||||
|
|
||||||
|
**Workflow**:
|
||||||
|
1. Webhook triggered by push to repository
|
||||||
|
2. `GitService.Pull()` fetches and merges latest changes
|
||||||
|
3. `XMLFileProvider.Scan()` detects changed files
|
||||||
|
4. XML files are collected, validated, and parsed
|
||||||
|
5. New compiled `Hamann.xml` is generated
|
||||||
|
6. Website reflects updated content
|
||||||
|
|
||||||
|
**Configuration**: Set `WebhookSecret` in appsettings to enable signature validation for webhooks.
|
||||||
149
HaWeb/Controllers/WebhookController.cs
Normal file
149
HaWeb/Controllers/WebhookController.cs
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
namespace HaWeb.Controllers;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using HaWeb.FileHelpers;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/webhook")]
|
||||||
|
public class WebhookController : Controller {
|
||||||
|
private readonly IGitService _gitService;
|
||||||
|
private readonly IXMLFileProvider _xmlProvider;
|
||||||
|
private readonly IConfiguration _config;
|
||||||
|
private readonly ILogger<WebhookController> _logger;
|
||||||
|
|
||||||
|
public WebhookController(
|
||||||
|
IGitService gitService,
|
||||||
|
IXMLFileProvider xmlProvider,
|
||||||
|
IConfiguration config,
|
||||||
|
ILogger<WebhookController> logger) {
|
||||||
|
_gitService = gitService;
|
||||||
|
_xmlProvider = xmlProvider;
|
||||||
|
_config = config;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("git")]
|
||||||
|
public async Task<IActionResult> GitWebhook([FromHeader(Name = "X-Hub-Signature-256")] string? signature) {
|
||||||
|
try {
|
||||||
|
// Validate webhook secret if configured
|
||||||
|
var webhookSecret = _config.GetValue<string>("WebhookSecret");
|
||||||
|
if (!string.IsNullOrEmpty(webhookSecret)) {
|
||||||
|
Request.EnableBuffering();
|
||||||
|
|
||||||
|
using var reader = new StreamReader(Request.Body, leaveOpen: true);
|
||||||
|
var body = await reader.ReadToEndAsync();
|
||||||
|
Request.Body.Position = 0;
|
||||||
|
|
||||||
|
_logger.LogInformation("Webhook received - Content-Type: {ContentType}, Body length: {Length}, Body SHA256: {BodyHash}",
|
||||||
|
Request.ContentType, body.Length,
|
||||||
|
Convert.ToHexString(System.Security.Cryptography.SHA256.HashData(Encoding.UTF8.GetBytes(body))).ToLower());
|
||||||
|
|
||||||
|
if (!ValidateSignature(body, signature, webhookSecret)) {
|
||||||
|
_logger.LogWarning("Webhook signature validation failed - check ValidateSignature logs above");
|
||||||
|
return Unauthorized(new { error = "Invalid signature" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Git webhook triggered, initiating pull...");
|
||||||
|
|
||||||
|
// Pull latest changes
|
||||||
|
var hasChanges = _gitService.Pull();
|
||||||
|
|
||||||
|
if (!hasChanges) {
|
||||||
|
_logger.LogInformation("No changes detected after pull");
|
||||||
|
return Ok(new {
|
||||||
|
success = true,
|
||||||
|
message = "Pull completed, no changes detected",
|
||||||
|
hasChanges = false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Changes detected, triggering repository scan and parse...");
|
||||||
|
|
||||||
|
// Trigger full reload: scan files, parse XML, generate and load Hamann.xml
|
||||||
|
_xmlProvider.Reload();
|
||||||
|
|
||||||
|
var gitState = _gitService.GetGitState();
|
||||||
|
|
||||||
|
_logger.LogInformation("Repository updated and library reloaded successfully to commit {Commit}", gitState?.Commit?[..7]);
|
||||||
|
|
||||||
|
return Ok(new {
|
||||||
|
success = true,
|
||||||
|
message = "Repository pulled and parsed successfully",
|
||||||
|
hasChanges = true,
|
||||||
|
commit = gitState?.Commit,
|
||||||
|
branch = gitState?.Branch,
|
||||||
|
timestamp = gitState?.PullTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
_logger.LogError(ex, "Error processing git webhook");
|
||||||
|
return StatusCode(500, new {
|
||||||
|
success = false,
|
||||||
|
error = ex.Message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("status")]
|
||||||
|
public IActionResult GetStatus() {
|
||||||
|
try {
|
||||||
|
var gitState = _gitService.GetGitState();
|
||||||
|
|
||||||
|
if (gitState == null) {
|
||||||
|
return NotFound(new { error = "Could not retrieve git state" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(new {
|
||||||
|
commit = gitState.Commit,
|
||||||
|
commitShort = gitState.Commit?[..7],
|
||||||
|
branch = gitState.Branch,
|
||||||
|
url = gitState.URL,
|
||||||
|
timestamp = gitState.PullTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
_logger.LogError(ex, "Error getting git status");
|
||||||
|
return StatusCode(500, new { error = ex.Message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ValidateSignature(string payload, string? signatureHeader, string secret) {
|
||||||
|
if (string.IsNullOrEmpty(signatureHeader)) {
|
||||||
|
_logger.LogWarning("Signature validation failed: No signature header provided");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GitHub uses HMAC-SHA256 with format "sha256=<hash>"
|
||||||
|
var prefix = "sha256=";
|
||||||
|
if (!signatureHeader.StartsWith(prefix)) {
|
||||||
|
_logger.LogWarning("Signature validation failed: Header doesn't start with '{Prefix}', got: {Header}", prefix, signatureHeader);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var expectedHash = signatureHeader.Substring(prefix.Length);
|
||||||
|
|
||||||
|
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
|
||||||
|
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
|
||||||
|
var computedHash = Convert.ToHexString(hash).ToLower();
|
||||||
|
|
||||||
|
// Test with GitHub's example values
|
||||||
|
var testPayload = "Hello, World!";
|
||||||
|
var testSecret = "It's a Secret to Everybody";
|
||||||
|
var testExpected = "757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17";
|
||||||
|
using var testHmac = new HMACSHA256(Encoding.UTF8.GetBytes(testSecret));
|
||||||
|
var testHash = testHmac.ComputeHash(Encoding.UTF8.GetBytes(testPayload));
|
||||||
|
var testComputed = Convert.ToHexString(testHash).ToLower();
|
||||||
|
_logger.LogWarning("GitHub test case - Expected: {Expected}, Computed: {Computed}, Match: {Match}",
|
||||||
|
testExpected, testComputed, testExpected == testComputed);
|
||||||
|
|
||||||
|
_logger.LogWarning("Signature validation - Expected: {Expected}, Computed: {Computed}, Match: {Match}",
|
||||||
|
expectedHash, computedHash, expectedHash == computedHash);
|
||||||
|
|
||||||
|
return CryptographicOperations.FixedTimeEquals(
|
||||||
|
Encoding.UTF8.GetBytes(computedHash),
|
||||||
|
Encoding.UTF8.GetBytes(expectedHash)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
147
HaWeb/FileHelpers/GitService.cs
Normal file
147
HaWeb/FileHelpers/GitService.cs
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
namespace HaWeb.FileHelpers;
|
||||||
|
using LibGit2Sharp;
|
||||||
|
using HaWeb.Models;
|
||||||
|
|
||||||
|
public class GitService : IGitService {
|
||||||
|
private readonly string _repositoryPath;
|
||||||
|
private readonly string _remoteName;
|
||||||
|
private readonly string _branch;
|
||||||
|
private readonly string _url;
|
||||||
|
private readonly ILogger<GitService>? _logger;
|
||||||
|
|
||||||
|
public GitService(IConfiguration config, ILogger<GitService>? logger = null) {
|
||||||
|
_logger = logger;
|
||||||
|
_remoteName = "origin";
|
||||||
|
_branch = config.GetValue<string>("RepositoryBranch") ?? "main";
|
||||||
|
_url = config.GetValue<string>("RepositoryURL") ?? string.Empty;
|
||||||
|
|
||||||
|
var fileStoragePath = config.GetValue<string>("FileStoragePath") ?? throw new ArgumentException("FileStoragePath not configured");
|
||||||
|
_repositoryPath = Path.Combine(fileStoragePath, "GIT");
|
||||||
|
|
||||||
|
// Ensure repository exists
|
||||||
|
if (!Repository.IsValid(_repositoryPath)) {
|
||||||
|
_logger?.LogWarning("Repository not found at {Path}, attempting to initialize/clone", _repositoryPath);
|
||||||
|
InitializeRepository();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public GitState? GetGitState() {
|
||||||
|
try {
|
||||||
|
using var repo = new Repository(_repositoryPath);
|
||||||
|
var headCommit = repo.Head.Tip;
|
||||||
|
|
||||||
|
if (headCommit == null) {
|
||||||
|
_logger?.LogWarning("No commits found in repository");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GitState {
|
||||||
|
Commit = headCommit.Sha,
|
||||||
|
Branch = repo.Head.FriendlyName,
|
||||||
|
URL = _url,
|
||||||
|
PullTime = headCommit.Author.When.ToLocalTime().DateTime
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
_logger?.LogError(ex, "Failed to get Git state");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Pull() {
|
||||||
|
try {
|
||||||
|
using var repo = new Repository(_repositoryPath);
|
||||||
|
var oldCommitSha = repo.Head.Tip?.Sha;
|
||||||
|
|
||||||
|
// Configure pull options
|
||||||
|
var options = new PullOptions {
|
||||||
|
FetchOptions = new FetchOptions {
|
||||||
|
CredentialsProvider = (_url, _user, _cred) => GetCredentials()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create signature for merge commit (if needed)
|
||||||
|
var signature = new Signature(
|
||||||
|
new Identity("HaWeb Service", "hawebservice@localhost"),
|
||||||
|
DateTimeOffset.Now
|
||||||
|
);
|
||||||
|
|
||||||
|
// Perform pull
|
||||||
|
var result = Commands.Pull(repo, signature, options);
|
||||||
|
|
||||||
|
var newCommitSha = repo.Head.Tip?.Sha;
|
||||||
|
var hasChanges = oldCommitSha != newCommitSha;
|
||||||
|
|
||||||
|
if (hasChanges) {
|
||||||
|
_logger?.LogInformation("Pull successful: {OldCommit} -> {NewCommit}",
|
||||||
|
oldCommitSha?[..7], newCommitSha?[..7]);
|
||||||
|
} else {
|
||||||
|
_logger?.LogInformation("Pull completed but no new changes");
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasChanges;
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
_logger?.LogError(ex, "Failed to pull from remote repository");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasChanged(string? previousCommitSha) {
|
||||||
|
if (string.IsNullOrEmpty(previousCommitSha)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentState = GetGitState();
|
||||||
|
return currentState != null && currentState.Commit != previousCommitSha;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeRepository() {
|
||||||
|
try {
|
||||||
|
if (!Directory.Exists(_repositoryPath)) {
|
||||||
|
Directory.CreateDirectory(_repositoryPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's a .git directory but it's invalid, try to reinitialize
|
||||||
|
var gitDir = Path.Combine(_repositoryPath, ".git");
|
||||||
|
if (Directory.Exists(gitDir)) {
|
||||||
|
_logger?.LogWarning("Invalid .git directory found, attempting to use existing repository");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If URL is provided, clone; otherwise just initialize
|
||||||
|
if (!string.IsNullOrEmpty(_url)) {
|
||||||
|
_logger?.LogInformation("Cloning repository from {Url} to {Path}", _url, _repositoryPath);
|
||||||
|
var cloneOptions = new CloneOptions();
|
||||||
|
cloneOptions.FetchOptions.CredentialsProvider = (_url, _user, _cred) => GetCredentials();
|
||||||
|
|
||||||
|
// Clone with default branch, then checkout the specified branch if different
|
||||||
|
Repository.Clone(_url, _repositoryPath, cloneOptions);
|
||||||
|
_logger?.LogInformation("Repository cloned successfully");
|
||||||
|
|
||||||
|
// Checkout the specified branch if it's not the default
|
||||||
|
using var repo = new Repository(_repositoryPath);
|
||||||
|
var branch = repo.Branches[_branch] ?? repo.Branches[$"origin/{_branch}"];
|
||||||
|
if (branch != null && branch.FriendlyName != repo.Head.FriendlyName) {
|
||||||
|
Commands.Checkout(repo, branch);
|
||||||
|
_logger?.LogInformation("Checked out branch {Branch}", _branch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_logger?.LogInformation("Initializing empty repository at {Path}", _repositoryPath);
|
||||||
|
Repository.Init(_repositoryPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
_logger?.LogError(ex, "Failed to initialize repository");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Credentials? GetCredentials() {
|
||||||
|
// For now, use default credentials (SSH agent, credential manager, etc.)
|
||||||
|
// Can be extended to support username/password or personal access tokens
|
||||||
|
// from configuration if needed
|
||||||
|
return new DefaultCredentials();
|
||||||
|
}
|
||||||
|
}
|
||||||
19
HaWeb/FileHelpers/IGitService.cs
Normal file
19
HaWeb/FileHelpers/IGitService.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
namespace HaWeb.FileHelpers;
|
||||||
|
|
||||||
|
public interface IGitService {
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the current Git state (commit SHA, branch, timestamp)
|
||||||
|
/// </summary>
|
||||||
|
GitState? GetGitState();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pulls latest changes from the remote repository
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if pull was successful and changes were detected, false otherwise</returns>
|
||||||
|
bool Pull();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the repository has a different commit than the provided SHA
|
||||||
|
/// </summary>
|
||||||
|
bool HasChanged(string? previousCommitSha);
|
||||||
|
}
|
||||||
@@ -18,4 +18,5 @@ public interface IXMLFileProvider {
|
|||||||
public bool HasChanged();
|
public bool HasChanged();
|
||||||
public void DeleteHamannFile(string filename);
|
public void DeleteHamannFile(string filename);
|
||||||
public void Scan();
|
public void Scan();
|
||||||
|
public void Reload();
|
||||||
}
|
}
|
||||||
@@ -4,16 +4,15 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|||||||
using HaWeb.Models;
|
using HaWeb.Models;
|
||||||
using HaWeb.XMLParser;
|
using HaWeb.XMLParser;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
// XMLProvider provides a wrapper around the available XML data on a FILE basis
|
// XMLProvider provides a wrapper around the available XML data on a FILE basis
|
||||||
public class XMLFileProvider : IXMLFileProvider {
|
public class XMLFileProvider : IXMLFileProvider {
|
||||||
private readonly IHaDocumentWrappper _Lib;
|
private readonly IHaDocumentWrappper _Lib;
|
||||||
private readonly IXMLInteractionService _XMLService;
|
private readonly IXMLInteractionService _XMLService;
|
||||||
|
private readonly IGitService _GitService;
|
||||||
|
|
||||||
private IFileProvider _hamannFileProvider;
|
private IFileProvider _hamannFileProvider;
|
||||||
private IFileProvider _bareRepositoryFileProvider;
|
|
||||||
private IFileProvider _workingTreeFileProvider;
|
private IFileProvider _workingTreeFileProvider;
|
||||||
|
|
||||||
public event EventHandler<GitState?> FileChange;
|
public event EventHandler<GitState?> FileChange;
|
||||||
@@ -21,9 +20,6 @@ public class XMLFileProvider : IXMLFileProvider {
|
|||||||
public event EventHandler<XMLParsingState?> NewState;
|
public event EventHandler<XMLParsingState?> NewState;
|
||||||
public event EventHandler NewData;
|
public event EventHandler NewData;
|
||||||
|
|
||||||
private string _Branch;
|
|
||||||
private string _URL;
|
|
||||||
|
|
||||||
private List<IFileInfo>? _WorkingTreeFiles;
|
private List<IFileInfo>? _WorkingTreeFiles;
|
||||||
private List<IFileInfo>? _HamannFiles;
|
private List<IFileInfo>? _HamannFiles;
|
||||||
|
|
||||||
@@ -31,24 +27,28 @@ public class XMLFileProvider : IXMLFileProvider {
|
|||||||
private System.Timers.Timer? _changeTokenTimer;
|
private System.Timers.Timer? _changeTokenTimer;
|
||||||
|
|
||||||
// Startup (LAST)
|
// Startup (LAST)
|
||||||
public XMLFileProvider(IXMLInteractionService xmlservice, IHaDocumentWrappper _lib, IConfiguration config) {
|
public XMLFileProvider(IXMLInteractionService xmlservice, IHaDocumentWrappper _lib, IGitService gitService, IConfiguration config) {
|
||||||
// TODO: Test Read / Write Access
|
// TODO: Test Read / Write Access
|
||||||
_Lib = _lib;
|
_Lib = _lib;
|
||||||
_XMLService = xmlservice;
|
_XMLService = xmlservice;
|
||||||
|
_GitService = gitService;
|
||||||
|
|
||||||
_Branch = config.GetValue<string>("RepositoryBranch");
|
var fileStoragePath = config.GetValue<string>("FileStoragePath") ?? throw new ArgumentException("FileStoragePath not configured");
|
||||||
_URL = config.GetValue<string>("RepositoryURL");
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
// Ensure directories exist
|
||||||
_hamannFileProvider = new PhysicalFileProvider(config.GetValue<string>("HamannFileStoreWindows"));
|
var hamannPath = Path.Combine(fileStoragePath, "HAMANN");
|
||||||
_bareRepositoryFileProvider = new PhysicalFileProvider(config.GetValue<string>("BareRepositoryPathWindows"));
|
var gitPath = Path.Combine(fileStoragePath, "GIT");
|
||||||
_workingTreeFileProvider = new PhysicalFileProvider(config.GetValue<string>("WorkingTreePathWindows"));
|
|
||||||
|
if (!Directory.Exists(hamannPath)) {
|
||||||
|
Directory.CreateDirectory(hamannPath);
|
||||||
}
|
}
|
||||||
else {
|
if (!Directory.Exists(gitPath)) {
|
||||||
_hamannFileProvider = new PhysicalFileProvider(config.GetValue<string>("HamannFileStoreLinux"));
|
Directory.CreateDirectory(gitPath);
|
||||||
_bareRepositoryFileProvider = new PhysicalFileProvider(config.GetValue<string>("BareRepositoryPathLinux"));
|
|
||||||
_workingTreeFileProvider = new PhysicalFileProvider(config.GetValue<string>("WorkingTreePathLinux"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_hamannFileProvider = new PhysicalFileProvider(hamannPath);
|
||||||
|
_workingTreeFileProvider = new PhysicalFileProvider(gitPath);
|
||||||
|
|
||||||
// Create File Lists; Here and in xmlservice, which does preliminary checking
|
// Create File Lists; Here and in xmlservice, which does preliminary checking
|
||||||
Scan();
|
Scan();
|
||||||
if (_WorkingTreeFiles != null && _WorkingTreeFiles.Any()) {
|
if (_WorkingTreeFiles != null && _WorkingTreeFiles.Any()) {
|
||||||
@@ -57,7 +57,6 @@ public class XMLFileProvider : IXMLFileProvider {
|
|||||||
}
|
}
|
||||||
_HamannFiles = _ScanHamannFiles();
|
_HamannFiles = _ScanHamannFiles();
|
||||||
|
|
||||||
_RegisterChangeToken();
|
|
||||||
// Check if hamann file already is current working tree status
|
// Check if hamann file already is current working tree status
|
||||||
// -> YES: Load up the file via _lib.SetLibrary();
|
// -> YES: Load up the file via _lib.SetLibrary();
|
||||||
if (_IsAlreadyParsed()) {
|
if (_IsAlreadyParsed()) {
|
||||||
@@ -91,8 +90,6 @@ public class XMLFileProvider : IXMLFileProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void ParseConfiguration(IConfiguration config) {
|
public void ParseConfiguration(IConfiguration config) {
|
||||||
_Branch = config.GetValue<string>("RepositoryBranch");
|
|
||||||
|
|
||||||
Scan();
|
Scan();
|
||||||
// Reset XMLInteractionService
|
// Reset XMLInteractionService
|
||||||
if (_WorkingTreeFiles != null && _WorkingTreeFiles.Any()) {
|
if (_WorkingTreeFiles != null && _WorkingTreeFiles.Any()) {
|
||||||
@@ -158,7 +155,59 @@ public class XMLFileProvider : IXMLFileProvider {
|
|||||||
|
|
||||||
public void Scan() {
|
public void Scan() {
|
||||||
_WorkingTreeFiles = _ScanWorkingTreeFiles();
|
_WorkingTreeFiles = _ScanWorkingTreeFiles();
|
||||||
_GitState = _ScanGitData();
|
_GitState = _GitService.GetGitState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reload() {
|
||||||
|
Scan();
|
||||||
|
|
||||||
|
// Reset XMLInteractionService
|
||||||
|
if (_WorkingTreeFiles != null && _WorkingTreeFiles.Any()) {
|
||||||
|
var state = _XMLService.Collect(_WorkingTreeFiles, _XMLService.GetRootDefs());
|
||||||
|
_XMLService.SetState(state);
|
||||||
|
OnNewState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
_HamannFiles = _ScanHamannFiles();
|
||||||
|
_XMLService.SetSCCache(null);
|
||||||
|
|
||||||
|
// Check if hamann file already is current working tree status
|
||||||
|
if (_IsAlreadyParsed()) {
|
||||||
|
_Lib.SetLibrary(_HamannFiles!.First(), null, null);
|
||||||
|
if (_Lib.GetLibrary() != null) {
|
||||||
|
OnNewData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to create a new file
|
||||||
|
var created = _XMLService.TryCreate(_XMLService.GetState());
|
||||||
|
if (created != null) {
|
||||||
|
var file = SaveHamannFile(created, _hamannFileProvider.GetFileInfo("./").PhysicalPath, null);
|
||||||
|
if (file != null) {
|
||||||
|
_Lib.SetLibrary(file, created.Document, null);
|
||||||
|
if (_Lib.GetLibrary() != null) {
|
||||||
|
OnNewData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// It failed, so use the last best File:
|
||||||
|
if (_HamannFiles != null && _HamannFiles.Any()) {
|
||||||
|
_Lib.SetLibrary(_HamannFiles.First(), null, null);
|
||||||
|
if (_Lib.GetLibrary() != null) {
|
||||||
|
OnNewData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use Fallback:
|
||||||
|
var options = new HaWeb.Settings.HaDocumentOptions();
|
||||||
|
if (_Lib.SetLibrary(null, null, null) == null) {
|
||||||
|
throw new Exception("Die Fallback Hamann.xml unter " + options.HamannXMLFilePath + " kann nicht geparst werden.");
|
||||||
|
}
|
||||||
|
OnNewData();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IFileInfo? SaveHamannFile(XElement element, string basefilepath, ModelStateDictionary? ModelState) {
|
public IFileInfo? SaveHamannFile(XElement element, string basefilepath, ModelStateDictionary? ModelState) {
|
||||||
@@ -191,7 +240,7 @@ public class XMLFileProvider : IXMLFileProvider {
|
|||||||
|
|
||||||
public bool HasChanged() {
|
public bool HasChanged() {
|
||||||
if (_GitState == null) return true;
|
if (_GitState == null) return true;
|
||||||
var current = _ScanGitData();
|
var current = _GitService.GetGitState();
|
||||||
if (current != null && !String.Equals(current.Commit, _GitState.Commit)) {
|
if (current != null && !String.Equals(current.Commit, _GitState.Commit)) {
|
||||||
_GitState = current;
|
_GitState = current;
|
||||||
return true;
|
return true;
|
||||||
@@ -199,22 +248,6 @@ public class XMLFileProvider : IXMLFileProvider {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private GitState? _ScanGitData() {
|
|
||||||
var head = _bareRepositoryFileProvider.GetFileInfo("refs/heads/" + _Branch);
|
|
||||||
// TODO: Failsave reading from FIle
|
|
||||||
try {
|
|
||||||
return new GitState {
|
|
||||||
URL = _URL,
|
|
||||||
Branch = _Branch,
|
|
||||||
PullTime = head.LastModified.ToLocalTime().DateTime,
|
|
||||||
Commit = File.ReadAllText(head.PhysicalPath).Trim()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets all XML Files
|
// Gets all XML Files
|
||||||
private List<IFileInfo>? _ScanWorkingTreeFiles() {
|
private List<IFileInfo>? _ScanWorkingTreeFiles() {
|
||||||
var files = _workingTreeFileProvider.GetDirectoryContents(string.Empty)!.Where(x => !x.IsDirectory && x.Name.EndsWith(".xml"))!.ToList();
|
var files = _workingTreeFileProvider.GetDirectoryContents(string.Empty)!.Where(x => !x.IsDirectory && x.Name.EndsWith(".xml"))!.ToList();
|
||||||
@@ -240,46 +273,6 @@ public class XMLFileProvider : IXMLFileProvider {
|
|||||||
return fhash == ghash;
|
return fhash == ghash;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void _RegisterChangeToken() {
|
|
||||||
ChangeToken.OnChange(
|
|
||||||
() => _bareRepositoryFileProvider.Watch("refs/heads/" + _Branch),
|
|
||||||
async (state) => await this._InvokeChanged(state),
|
|
||||||
this._ScanGitData()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task _InvokeChanged(GitState? gitdata) {
|
|
||||||
if (_changeTokenTimer != null) return;
|
|
||||||
Console.WriteLine("FILECHANGE DETECTED, RELOAD");
|
|
||||||
Scan();
|
|
||||||
|
|
||||||
OnFileChange(_ScanGitData());
|
|
||||||
// Reset XMLInteractionService
|
|
||||||
if (_WorkingTreeFiles != null && _WorkingTreeFiles.Any()) {
|
|
||||||
var state = _XMLService.Collect(_WorkingTreeFiles, _XMLService.GetRootDefs());
|
|
||||||
_XMLService.SetState(state);
|
|
||||||
OnNewState(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -> Try to create a new file
|
|
||||||
var created = _XMLService.TryCreate(_XMLService.GetState());
|
|
||||||
if (created != null) {
|
|
||||||
var file = SaveHamannFile(created, _hamannFileProvider.GetFileInfo("./").PhysicalPath, null);
|
|
||||||
if (file != null) {
|
|
||||||
var ret = _Lib.SetLibrary(file, created.Document, null);
|
|
||||||
if (ret != null) OnNewData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_XMLService.SetSCCache(null);
|
|
||||||
_GitState = _ScanGitData();
|
|
||||||
_changeTokenTimer = new(5000) { AutoReset = false, Enabled = true };
|
|
||||||
_changeTokenTimer.Elapsed += this._OnElapsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _OnElapsed(Object source, System.Timers.ElapsedEventArgs e) {
|
|
||||||
_changeTokenTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnFileChange(GitState? state) {
|
protected virtual void OnFileChange(GitState? state) {
|
||||||
EventHandler<GitState?> eh = FileChange;
|
EventHandler<GitState?> eh = FileChange;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="LibGit2Sharp" Version="0.31.0" />
|
||||||
<PackageReference Include="Microsoft.FeatureManagement.AspNetCore" Version="2.5.1" />
|
<PackageReference Include="Microsoft.FeatureManagement.AspNetCore" Version="2.5.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -4,30 +4,24 @@ using HaWeb.XMLParser;
|
|||||||
using HaWeb.XMLTests;
|
using HaWeb.XMLTests;
|
||||||
using HaWeb.FileHelpers;
|
using HaWeb.FileHelpers;
|
||||||
using Microsoft.FeatureManagement;
|
using Microsoft.FeatureManagement;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Microsoft.AspNetCore.HttpOverrides;
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using Microsoft.Extensions.Primitives;
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// Add additional configuration
|
// Add additional configuration from Git repository
|
||||||
List<string> configpaths = new List<string>();
|
var fileStoragePath = builder.Configuration.GetValue<string>("FileStoragePath");
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
if (!string.IsNullOrEmpty(fileStoragePath)) {
|
||||||
var p = builder.Configuration.GetValue<string>("WorkingTreePathWindows") + "settings.json";
|
var externalSettingsPath = Path.Combine(fileStoragePath, "GIT", "settings.json");
|
||||||
configpaths.Add(p);
|
builder.Configuration.AddJsonFile(externalSettingsPath, optional: true, reloadOnChange: true);
|
||||||
builder.Configuration.AddJsonFile(p, optional: true, reloadOnChange: true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var p = builder.Configuration.GetValue<string>("WorkingTreePathLinux") + "settings.json";
|
|
||||||
configpaths.Add(p);
|
|
||||||
builder.Configuration.AddJsonFile(p, optional: true, reloadOnChange: true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create initial Data
|
// Create initial Data
|
||||||
var tS = new XMLTestService();
|
var tS = new XMLTestService();
|
||||||
var XMLIS = new XMLInteractionService(builder.Configuration, tS);
|
var XMLIS = new XMLInteractionService(builder.Configuration, tS);
|
||||||
var hdW = new HaDocumentWrapper(XMLIS, builder.Configuration);
|
var hdW = new HaDocumentWrapper(XMLIS, builder.Configuration);
|
||||||
var XMLFP = new XMLFileProvider(XMLIS, hdW, builder.Configuration);
|
var gitService = new GitService(builder.Configuration, builder.Services.BuildServiceProvider().GetService<ILogger<GitService>>());
|
||||||
|
var XMLFP = new XMLFileProvider(XMLIS, hdW, gitService, builder.Configuration);
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddControllers().AddXmlSerializerFormatters();
|
builder.Services.AddControllers().AddXmlSerializerFormatters();
|
||||||
@@ -36,6 +30,7 @@ builder.Services.AddHttpContextAccessor();
|
|||||||
builder.Services.AddSingleton<IXMLTestService, XMLTestService>((_) => tS);
|
builder.Services.AddSingleton<IXMLTestService, XMLTestService>((_) => tS);
|
||||||
builder.Services.AddSingleton<IXMLInteractionService, XMLInteractionService>((_) => XMLIS);
|
builder.Services.AddSingleton<IXMLInteractionService, XMLInteractionService>((_) => XMLIS);
|
||||||
builder.Services.AddSingleton<IHaDocumentWrappper, HaDocumentWrapper>((_) => hdW);
|
builder.Services.AddSingleton<IHaDocumentWrappper, HaDocumentWrapper>((_) => hdW);
|
||||||
|
builder.Services.AddSingleton<IGitService, GitService>((_) => gitService);
|
||||||
builder.Services.AddSingleton<IXMLFileProvider, XMLFileProvider>(_ => XMLFP);
|
builder.Services.AddSingleton<IXMLFileProvider, XMLFileProvider>(_ => XMLFP);
|
||||||
builder.Services.AddSingleton<WebSocketMiddleware>();
|
builder.Services.AddSingleton<WebSocketMiddleware>();
|
||||||
builder.Services.AddTransient<IReaderService, ReaderService>();
|
builder.Services.AddTransient<IReaderService, ReaderService>();
|
||||||
|
|||||||
@@ -13,16 +13,10 @@
|
|||||||
},
|
},
|
||||||
"AllowedWebSocketConnections": "*",
|
"AllowedWebSocketConnections": "*",
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"HamannFileStoreLinux": "/home/simon/source/hamann-ausgabe-core/HaWeb/testdata/",
|
"FileStoragePath": "/home/simon/source/hamann-ausgabe-core/HaWeb/testdata/",
|
||||||
"HamannFileStoreWindows": "C:/Users/simon/Downloads/test/",
|
|
||||||
"BareRepositoryPathLinux": "/home/simon/source/hamann-xml/.git/",
|
|
||||||
"BareRepositoryPathWindows": "D:/Simon/source/hamann-xml/.git/",
|
|
||||||
"WorkingTreePathLinux": "/home/simon/source/hamann-xml/",
|
|
||||||
"WorkingTreePathWindows": "D:/Simon/source/hamann-xml/",
|
|
||||||
"RepositoryBranch": "Main",
|
"RepositoryBranch": "Main",
|
||||||
"RepositoryURL": "https://github.com/Theodor-Springmann-Stiftung/hamann-xml",
|
"RepositoryURL": "https://github.com/Theodor-Springmann-Stiftung/hamann-xml",
|
||||||
"StoredPDFPathWindows": "",
|
"WebhookSecret": "secret",
|
||||||
"StoredPDFPathLinux": "",
|
|
||||||
"FileSizeLimit": 52428800,
|
"FileSizeLimit": 52428800,
|
||||||
"AvailableStartYear": 1700,
|
"AvailableStartYear": 1700,
|
||||||
"AvailableEndYear": 1800,
|
"AvailableEndYear": 1800,
|
||||||
|
|||||||
@@ -13,14 +13,10 @@
|
|||||||
},
|
},
|
||||||
"AllowedWebSocketConnections": "*",
|
"AllowedWebSocketConnections": "*",
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"HamannFileStoreLinux": "/var/www/vhosts/development.hamann-ausgabe.de/httpdocs/Hamann/",
|
"FileStoragePath": "/var/www/vhosts/development.hamann-ausgabe.de/httpdocs/Storage/",
|
||||||
"BareRepositoryPathLinux": "/var/www/vhosts/development.hamann-ausgabe.de/httpdocs/Bare/",
|
|
||||||
"BareRepositoryPathWindows": "C:/Users/simon/source/hamann-xml/.git/",
|
|
||||||
"WorkingTreePathLinux": "/var/www/vhosts/development.hamann-ausgabe.de/httpdocs/Repo/",
|
|
||||||
"RepositoryBranch": "main",
|
"RepositoryBranch": "main",
|
||||||
"RepositoryURL": "https://github.com/Theodor-Springmann-Stiftung/hamann-xml",
|
"RepositoryURL": "https://github.com/Theodor-Springmann-Stiftung/hamann-xml",
|
||||||
"StoredPDFPathWindows": "",
|
"WebhookSecret": "",
|
||||||
"StoredPDFPathLinux": "",
|
|
||||||
"FileSizeLimit": 52428800,
|
"FileSizeLimit": 52428800,
|
||||||
"AvailableStartYear": 1700,
|
"AvailableStartYear": 1700,
|
||||||
"AvailableEndYear": 1800,
|
"AvailableEndYear": 1800,
|
||||||
|
|||||||
@@ -13,10 +13,8 @@
|
|||||||
},
|
},
|
||||||
"AllowedWebSocketConnections": "*",
|
"AllowedWebSocketConnections": "*",
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"HamannFileStoreLinux": "/var/www/vhosts/hamann-ausgabe.de/httpdocs/Hamann/",
|
"FileStoragePath": "/var/www/vhosts/hamann-ausgabe.de/httpdocs/Storage/",
|
||||||
"BareRepositoryPathLinux": "/var/www/vhosts/hamann-ausgabe.de/httpdocs/Bare/",
|
"RepositoryBranch": "main",
|
||||||
"BareRepositoryPathWindows": "D:/Simon/source/hamann-xml/.git/",
|
"RepositoryURL": "",
|
||||||
"WorkingTreePathLinux": "/var/www/vhosts/hamann-ausgabe.de/httpdocs/Repo/",
|
"WebhookSecret": ""
|
||||||
"WorkingTreePathWindows": "D:/Simon/source/hamann-xml/",
|
|
||||||
"RepositoryBranch": "main"
|
|
||||||
}
|
}
|
||||||
|
|||||||
2
HaWeb/package-lock.json
generated
2
HaWeb/package-lock.json
generated
@@ -1657,6 +1657,7 @@
|
|||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.0",
|
||||||
@@ -3722,6 +3723,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
|
||||||
"integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
|
"integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"peer": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user