aboutsummaryrefslogtreecommitdiff
path: root/back-end
diff options
context:
space:
mode:
Diffstat (limited to 'back-end')
-rw-r--r--back-end/src/CMNextEntry.cs4
-rw-r--r--back-end/src/Content.Publishing.Blog.Admin.csproj5
-rw-r--r--back-end/src/Endpoints/ChannelEndpoint.cs5
-rw-r--r--back-end/src/Model/ChannelManager.cs4
-rw-r--r--back-end/src/Model/ContentManager.cs4
-rw-r--r--back-end/src/Model/PostManager.cs19
-rw-r--r--back-end/src/Storage/FtpStorageManager.cs3
-rw-r--r--back-end/src/Storage/IStorageFacade.cs71
-rw-r--r--back-end/src/Storage/ManagedStorage.cs10
-rw-r--r--back-end/src/Storage/MinioClientManager.cs5
-rw-r--r--back-end/src/Storage/StorageBase.cs6
-rw-r--r--back-end/src/StorageExtensions.cs23
12 files changed, 54 insertions, 105 deletions
diff --git a/back-end/src/CMNextEntry.cs b/back-end/src/CMNextEntry.cs
index 2800c08..4041122 100644
--- a/back-end/src/CMNextEntry.cs
+++ b/back-end/src/CMNextEntry.cs
@@ -75,8 +75,8 @@ namespace Content.Publishing.Blog.Admin
Your server is now running at the following locations:
{0}
-******************************************************************************
-";
+******************************************************************************";
+
string[] interfaces = HostConfig.GetProperty("virtual_hosts")
.EnumerateArray()
.Select(e =>
diff --git a/back-end/src/Content.Publishing.Blog.Admin.csproj b/back-end/src/Content.Publishing.Blog.Admin.csproj
index 1a0a365..19839b7 100644
--- a/back-end/src/Content.Publishing.Blog.Admin.csproj
+++ b/back-end/src/Content.Publishing.Blog.Admin.csproj
@@ -7,7 +7,6 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
<RootNamespace>Content.Publishing.Blog.Admin</RootNamespace>
<AssemblyName>CMNext</AssemblyName>
- <Version>0.1.1</Version>
</PropertyGroup>
<PropertyGroup>
@@ -34,8 +33,8 @@
<ItemGroup>
<PackageReference Include="FluentFTP" Version="47.1.0" />
<PackageReference Include="Minio" Version="5.0.0" />
- <PackageReference Include="VNLib.Plugins.Extensions.Loading" Version="0.1.0-ci0031" />
- <PackageReference Include="VNLib.Plugins.Extensions.Validation" Version="0.1.0-ci0031" />
+ <PackageReference Include="VNLib.Plugins.Extensions.Loading" Version="0.1.0-ci0033" />
+ <PackageReference Include="VNLib.Plugins.Extensions.Validation" Version="0.1.0-ci0033" />
</ItemGroup>
<ItemGroup>
diff --git a/back-end/src/Endpoints/ChannelEndpoint.cs b/back-end/src/Endpoints/ChannelEndpoint.cs
index c63d093..d51ad3a 100644
--- a/back-end/src/Endpoints/ChannelEndpoint.cs
+++ b/back-end/src/Endpoints/ChannelEndpoint.cs
@@ -45,6 +45,7 @@ namespace Content.Publishing.Blog.Admin.Endpoints
private static readonly IValidator<FeedMeta> FeedValidator = FeedMeta.GetValidator();
private readonly IChannelContextManager ContentManager;
+ private readonly PostManager PostManager;
public ChannelEndpoint(PluginBase plugin, IConfigScope config)
@@ -54,6 +55,7 @@ namespace Content.Publishing.Blog.Admin.Endpoints
InitPathAndLog(path, plugin.Log);
ContentManager = plugin.GetOrCreateSingleton<ChannelManager>();
+ PostManager = plugin.GetOrCreateSingleton<PostManager>();
}
protected override async ValueTask<VfReturnType> GetAsync(HttpEntity entity)
@@ -172,6 +174,9 @@ namespace Content.Publishing.Blog.Admin.Endpoints
return VfReturnType.VirtualSkip;
}
+ //Update post feeds
+ await PostManager.UpdateFeedForChannelAsync(channel, entity.EventCancellation);
+
//Return the new blog context to the client
entity.CloseResponse(HttpStatusCode.Created);
return VfReturnType.VirtualSkip;
diff --git a/back-end/src/Model/ChannelManager.cs b/back-end/src/Model/ChannelManager.cs
index adcc3b2..22dbab9 100644
--- a/back-end/src/Model/ChannelManager.cs
+++ b/back-end/src/Model/ChannelManager.cs
@@ -26,19 +26,21 @@ using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
+using VNLib.Utils.IO;
using VNLib.Hashing;
using VNLib.Plugins;
using VNLib.Plugins.Extensions.Loading;
using Content.Publishing.Blog.Admin.Storage;
+
namespace Content.Publishing.Blog.Admin.Model
{
[ConfigurationName("blog_channels")]
internal sealed class ChannelManager : IChannelContextManager
{
- private readonly IStorageFacade Storage;
+ private readonly ISimpleFilesystem Storage;
private readonly string _indexPath;
diff --git a/back-end/src/Model/ContentManager.cs b/back-end/src/Model/ContentManager.cs
index a0ed94f..b5fc385 100644
--- a/back-end/src/Model/ContentManager.cs
+++ b/back-end/src/Model/ContentManager.cs
@@ -28,18 +28,18 @@ using System.Threading.Tasks;
using Content.Publishing.Blog.Admin.Storage;
using VNLib.Hashing;
+using VNLib.Utils.IO;
using VNLib.Net.Http;
using VNLib.Plugins;
using VNLib.Plugins.Extensions.Loading;
-
namespace Content.Publishing.Blog.Admin.Model
{
internal sealed class ContentManager
{
private const string ContentIndex = "content.json";
- private readonly IStorageFacade Storage;
+ private readonly ISimpleFilesystem Storage;
public ContentManager(PluginBase plugin)
{
diff --git a/back-end/src/Model/PostManager.cs b/back-end/src/Model/PostManager.cs
index 22e3682..922fba4 100644
--- a/back-end/src/Model/PostManager.cs
+++ b/back-end/src/Model/PostManager.cs
@@ -42,7 +42,7 @@ namespace Content.Publishing.Blog.Admin.Model
internal sealed class PostManager : IBlogPostManager
{
- private readonly IStorageFacade Storage;
+ private readonly ISimpleFilesystem Storage;
private readonly IRssFeedGenerator FeedGenerator;
private readonly ContentManager ContentMan;
@@ -166,6 +166,23 @@ namespace Content.Publishing.Blog.Admin.Model
return true;
}
+ /// <summary>
+ /// Updates the index and feed for all posts for the given channel.
+ /// </summary>
+ /// <param name="context">The channel context to update the feed for</param>
+ /// <param name="cancellation">A token to cancel the operatio</param>
+ /// <returns>A task that completes when the channel feed has been updated</returns>
+ /// <exception cref="ArgumentNullException"></exception>
+ public async Task UpdateFeedForChannelAsync(IChannelContext context, CancellationToken cancellation)
+ {
+ _ = context ?? throw new ArgumentNullException(nameof(context));
+
+ //Get the index
+ IRecordDb<PostMeta> db = await GetPostIndexAsync(context, cancellation);
+
+ //Update the index and feed after post update
+ await UpdateIndexAndFeed(context, db, cancellation);
+ }
private async Task UpdateIndexAndFeed(IChannelContext context, IRecordDb<PostMeta> index, CancellationToken cancellation)
{
diff --git a/back-end/src/Storage/FtpStorageManager.cs b/back-end/src/Storage/FtpStorageManager.cs
index abcf5e1..d64d4ea 100644
--- a/back-end/src/Storage/FtpStorageManager.cs
+++ b/back-end/src/Storage/FtpStorageManager.cs
@@ -29,7 +29,6 @@ using System.Collections.Generic;
using FluentFTP;
using FluentFTP.Exceptions;
-using VNLib.Net.Http;
using VNLib.Utils.Logging;
using VNLib.Utils.Resources;
using VNLib.Plugins;
@@ -102,7 +101,7 @@ namespace Content.Publishing.Blog.Admin.Storage
}
///<inheritdoc/>
- public override async Task SetFileAsync(string filePath, Stream data, ContentType ct, CancellationToken cancellation)
+ public override async Task WriteFileAsync(string filePath, Stream data, string ct, CancellationToken cancellation)
{
//Upload the file to the server
FtpStatus status = await _client.UploadStream(data, GetExternalFilePath(filePath), FtpRemoteExists.Overwrite, true, token: cancellation);
diff --git a/back-end/src/Storage/IStorageFacade.cs b/back-end/src/Storage/IStorageFacade.cs
deleted file mode 100644
index 703394b..0000000
--- a/back-end/src/Storage/IStorageFacade.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
-* Copyright (c) 2023 Vaughn Nugent
-*
-* Library: CMNext
-* Package: Content.Publishing.Blog.Admin
-* File: IStorageFacade.cs
-*
-* CMNext is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* CMNext is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-
-using VNLib.Net.Http;
-
-namespace Content.Publishing.Blog.Admin.Storage
-{
- /// <summary>
- /// Represents an opaque storage interface that abstracts simple storage operations
- /// ignorant of the underlying storage system.
- /// </summary>
- internal interface IStorageFacade
- {
- /// <summary>
- /// Gets the full public file path for the given relative file path
- /// </summary>
- /// <param name="filePath">The relative file path of the item to get the full path for</param>
- /// <returns>The full relative file path</returns>
- string GetExternalFilePath(string filePath);
-
- /// <summary>
- /// Deletes a file from the storage system asynchronously
- /// </summary>
- /// <param name="filePath">The path to the file to delete</param>
- /// <param name="cancellation">A token to cancel the operation</param>
- /// <returns>A task that represents and asynchronous work</returns>
- Task DeleteFileAsync(string filePath, CancellationToken cancellation);
-
- /// <summary>
- /// Writes a file from the stream to the given file location
- /// </summary>
- /// <param name="filePath">The path to the file to write to</param>
- /// <param name="data">The file data to stream</param>
- /// <param name="ct">The content type of the file to write</param>
- /// <param name="cancellation">A token to cancel the operation</param>
- /// <returns>A task that represents and asynchronous work</returns>
- Task SetFileAsync(string filePath, Stream data, ContentType ct, CancellationToken cancellation);
-
- /// <summary>
- /// Reads a file from the storage system at the given path asynchronously
- /// </summary>
- /// <param name="filePath">The file to read</param>
- /// <param name="output">The stream to write the file output to</param>
- /// <param name="cancellation">A token to cancel the operation</param>
- /// <returns>The number of bytes read, -1 if the operation failed</returns>
- Task<long> ReadFileAsync(string filePath, Stream output, CancellationToken cancellation);
- }
-}
diff --git a/back-end/src/Storage/ManagedStorage.cs b/back-end/src/Storage/ManagedStorage.cs
index 654695f..19e208c 100644
--- a/back-end/src/Storage/ManagedStorage.cs
+++ b/back-end/src/Storage/ManagedStorage.cs
@@ -24,15 +24,15 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
-using VNLib.Net.Http;
+using VNLib.Utils.IO;
using VNLib.Plugins;
using VNLib.Plugins.Extensions.Loading;
namespace Content.Publishing.Blog.Admin.Storage
{
- internal sealed class ManagedStorage : IStorageFacade
+ internal sealed class ManagedStorage : ISimpleFilesystem
{
- private readonly IStorageFacade _backingStorage;
+ private readonly ISimpleFilesystem _backingStorage;
public ManagedStorage(PluginBase plugin)
{
@@ -81,7 +81,7 @@ namespace Content.Publishing.Blog.Admin.Storage
}
///<inheritdoc/>
- public Task SetFileAsync(string filePath, Stream data, ContentType ct, CancellationToken cancellation)
+ public Task WriteFileAsync(string filePath, Stream data, string ct, CancellationToken cancellation)
{
//Try to reset the stream if allowed
if (data.CanSeek)
@@ -90,7 +90,7 @@ namespace Content.Publishing.Blog.Admin.Storage
data.Seek(0, SeekOrigin.Begin);
}
- return _backingStorage.SetFileAsync(filePath, data, ct, cancellation);
+ return _backingStorage.WriteFileAsync(filePath, data, ct, cancellation);
}
}
}
diff --git a/back-end/src/Storage/MinioClientManager.cs b/back-end/src/Storage/MinioClientManager.cs
index e9f7c9a..6dd0f5c 100644
--- a/back-end/src/Storage/MinioClientManager.cs
+++ b/back-end/src/Storage/MinioClientManager.cs
@@ -26,7 +26,6 @@ using System.Threading.Tasks;
using Minio;
using Minio.DataModel;
-using VNLib.Net.Http;
using VNLib.Utils.Memory;
using VNLib.Utils.Extensions;
using VNLib.Plugins;
@@ -94,11 +93,11 @@ namespace Content.Publishing.Blog.Admin.Storage
}
///<inheritdoc/>
- public override Task SetFileAsync(string filePath, Stream data, ContentType ct, CancellationToken cancellation)
+ public override Task WriteFileAsync(string filePath, Stream data, string ct, CancellationToken cancellation)
{
PutObjectArgs args = new();
args.WithBucket(Config.BaseBucket)
- .WithContentType(HttpHelpers.GetContentTypeString(ct))
+ .WithContentType(ct)
.WithObject(filePath)
.WithObjectSize(data.Length)
.WithStreamData(data);
diff --git a/back-end/src/Storage/StorageBase.cs b/back-end/src/Storage/StorageBase.cs
index b33d6f1..fe11c39 100644
--- a/back-end/src/Storage/StorageBase.cs
+++ b/back-end/src/Storage/StorageBase.cs
@@ -23,13 +23,13 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
-using VNLib.Net.Http;
using VNLib.Plugins;
+using VNLib.Utils.IO;
using VNLib.Plugins.Extensions.Loading;
namespace Content.Publishing.Blog.Admin.Storage
{
- internal abstract class StorageBase : IAsyncConfigurable, IStorageFacade
+ internal abstract class StorageBase : IAsyncConfigurable, ISimpleFilesystem
{
/// <summary>
/// The base file path within the remote file system to use for external urls
@@ -46,7 +46,7 @@ namespace Content.Publishing.Blog.Admin.Storage
public abstract Task<long> ReadFileAsync(string filePath, Stream output, CancellationToken cancellation);
///<inheritdoc/>
- public abstract Task SetFileAsync(string filePath, Stream data, ContentType ct, CancellationToken cancellation);
+ public abstract Task WriteFileAsync(string filePath, Stream data, string ct, CancellationToken cancellation);
///<inheritdoc/>
public virtual string GetExternalFilePath(string filePath)
diff --git a/back-end/src/StorageExtensions.cs b/back-end/src/StorageExtensions.cs
index cb4ba5a..8acb26e 100644
--- a/back-end/src/StorageExtensions.cs
+++ b/back-end/src/StorageExtensions.cs
@@ -27,7 +27,6 @@ using VNLib.Utils.IO;
using VNLib.Net.Http;
using Content.Publishing.Blog.Admin.Model;
-using Content.Publishing.Blog.Admin.Storage;
namespace Content.Publishing.Blog.Admin
{
@@ -43,9 +42,9 @@ namespace Content.Publishing.Blog.Admin
/// <param name="ct">The file content type</param>
/// <param name="cancellation">A token to cancel the operation</param>
/// <returns>A task that completes when the file data has been written to the storage layer</returns>
- public static Task SetObjectDataAsync(this IStorageFacade storage, IChannelContext context, Stream data, string path, ContentType ct, CancellationToken cancellation)
+ public static Task SetObjectDataAsync(this ISimpleFilesystem storage, IChannelContext context, Stream data, string path, ContentType ct, CancellationToken cancellation)
{
- return storage.SetFileAsync($"{context.BaseDir}/{path}", data, ct, cancellation);
+ return storage.WriteFileAsync($"{context.BaseDir}/{path}", data, HttpHelpers.GetContentTypeString(ct), cancellation);
}
/// <summary>
@@ -56,7 +55,7 @@ namespace Content.Publishing.Blog.Admin
/// <param name="path">The item path within the channel to delete</param>
/// <param name="cancellation">A token to cancel the operation</param>
/// <returns>A task that completes when the deletion operation has completed</returns>
- public static Task RemoveObjectAsync(this IStorageFacade storage, IChannelContext context, string path, CancellationToken cancellation)
+ public static Task RemoveObjectAsync(this ISimpleFilesystem storage, IChannelContext context, string path, CancellationToken cancellation)
{
return storage.DeleteFileAsync($"{context.BaseDir}/{path}", cancellation);
}
@@ -71,7 +70,7 @@ namespace Content.Publishing.Blog.Admin
/// <param name="fileName">The relative path inside the channel to load the database from</param>
/// <param name="cancellation">A token to cancel the operation</param>
/// <returns>A task that resolves the new <see cref="IRecordDb{T}"/> from file</returns>
- public static Task<IRecordDb<T>> LoadDbAsync<T>(this IStorageFacade storage, IChannelContext context, string fileName, CancellationToken cancellation) where T : IRecord
+ public static Task<IRecordDb<T>> LoadDbAsync<T>(this ISimpleFilesystem storage, IChannelContext context, string fileName, CancellationToken cancellation) where T : IRecord
{
return storage.LoadDbAsync<T>($"{context.BaseDir}/{fileName}", cancellation);
}
@@ -86,7 +85,7 @@ namespace Content.Publishing.Blog.Admin
/// <param name="store">The database to capture the file data from</param>
/// <param name="cancellation">A token to cancel the operation</param>
/// <returns>A task that completes when the database has been stored</returns>
- public static Task StoreAsync<T>(this IStorageFacade storage, IChannelContext context, string fileName, IRecordDb<T> store, CancellationToken cancellation)
+ public static Task StoreAsync<T>(this ISimpleFilesystem storage, IChannelContext context, string fileName, IRecordDb<T> store, CancellationToken cancellation)
{
return storage.StoreAsync($"{context.BaseDir}/{fileName}", store, cancellation);
}
@@ -101,7 +100,7 @@ namespace Content.Publishing.Blog.Admin
/// <param name="stream">The stream to write the file data to from the storage layer</param>
/// <param name="cancellation">A token to cancel the operation</param>
/// <returns>A task that resolves the number of bytes read into the output stream</returns>
- public static Task<long> ReadFileAsync(this IStorageFacade storage, IChannelContext context, string fileName, Stream stream, CancellationToken cancellation)
+ public static Task<long> ReadFileAsync(this ISimpleFilesystem storage, IChannelContext context, string fileName, Stream stream, CancellationToken cancellation)
{
return storage.ReadFileAsync($"{context.BaseDir}/{fileName}", stream, cancellation);
}
@@ -113,7 +112,7 @@ namespace Content.Publishing.Blog.Admin
/// <param name="context">The channel context that contains the item</param>
/// <param name="path">The realtive path inside the channel to the item to get the path for</param>
/// <returns>The full external path of the item</returns>
- public static string GetExternalFilePath(this IStorageFacade storage, IChannelContext context, string path)
+ public static string GetExternalFilePath(this ISimpleFilesystem storage, IChannelContext context, string path)
{
return storage.GetExternalFilePath($"{context.BaseDir}/{path}");
}
@@ -127,7 +126,7 @@ namespace Content.Publishing.Blog.Admin
/// <param name="path">The file path to store the record at</param>
/// <param name="cancellation">A token to cancel the operation</param>
/// <returns>A task that completes when the operation has completed</returns>
- public static async Task StoreAsync<T>(this IStorageFacade storage, string path, IRecordDb<T> store, CancellationToken cancellation)
+ public static async Task StoreAsync<T>(this ISimpleFilesystem storage, string path, IRecordDb<T> store, CancellationToken cancellation)
{
//Alloc ms to write records to
using VnMemoryStream ms = new();
@@ -135,7 +134,7 @@ namespace Content.Publishing.Blog.Admin
//Write the records to the stream
store.Store(ms);
- await storage.SetFileAsync(path, ms, ContentType.Json, cancellation);
+ await storage.WriteFileAsync(path, ms, HttpHelpers.GetContentTypeString(ContentType.Json), cancellation);
}
/// <summary>
@@ -147,7 +146,7 @@ namespace Content.Publishing.Blog.Admin
/// <param name="objPath">The path to the stored database file</param>
/// <param name="cancellation">A token to cancel the operation</param>
/// <returns>The populated <see cref="IRecordDb{T}"/>, if loading fails (file not found etc) the store will be returned empty</returns>
- public static async Task<IRecordDb<T>> LoadDbAsync<T>(this IStorageFacade storage, string objPath, CancellationToken cancellation) where T : IRecord
+ public static async Task<IRecordDb<T>> LoadDbAsync<T>(this ISimpleFilesystem storage, string objPath, CancellationToken cancellation) where T : IRecord
{
//Create the db
IRecordDb<T> db = JsonRecordDb<T>.Create();
@@ -166,7 +165,7 @@ namespace Content.Publishing.Blog.Admin
/// <param name="db">The record database store ready to accept the database content</param>
/// <param name="cancellation">A token to cancel the operation</param>
/// <returns>A task that completes when the database has been populated</returns>
- public static async Task LoadDbAsync<T>(this IStorageFacade storage, string objPath, IRecordDb<T> db, CancellationToken cancellation) where T : IRecord
+ public static async Task LoadDbAsync<T>(this ISimpleFilesystem storage, string objPath, IRecordDb<T> db, CancellationToken cancellation) where T : IRecord
{
//Mem stream to read the object into
using VnMemoryStream ms = new();