Blog Files model

This commit is contained in:
Paul Schneider 2026-03-01 02:13:26 +00:00
commit bfb37bc214
12 changed files with 318 additions and 173 deletions

34
.vscode/launch.json vendored
View file

@ -16,37 +16,14 @@
"stopAtEntry": false,
"console": "internalConsole"
},
{
"name": "org",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/Org/bin/Debug/net9.0/Org.dll",
"args": [],
"cwd": "${workspaceFolder}/src/Org",
"stopAtEntry": false,
"justMyCode": false,
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)",
"killOnServerStop": true
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/src/Org/Views"
}
},
{
"name": "Web",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/Web/bin/Debug/net9.0/Web.dll",
"program": "${workspaceFolder}/src/Yavsc.Web/bin/Debug/net9.0/Yavsc.Web.dll",
"args": [],
"cwd": "${workspaceFolder}/src/Web",
"cwd": "${workspaceFolder}/src/Yavsc.Web",
"stopAtEntry": false,
"serverReadyAction": {
"action": "openExternally",
@ -64,7 +41,7 @@
"name": "Api",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-web",
"preLaunchTask": "build-webapi",
"program": "${workspaceFolder}/src/Api/bin/Debug/net9.0/Api.dll",
"args": [],
"cwd": "${workspaceFolder}/src/Api",
@ -72,10 +49,10 @@
"console": "internalConsole"
},
{
"name": "dotnet org",
"name": "org",
"type": "dotnet",
"request": "launch",
"projectPath": "${workspaceFolder}/src/Org/Org.csproj",
"projectPath": "${workspaceFolder}/src/Yavsc.Org/Yavsc.Org.csproj",
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
@ -85,6 +62,7 @@
"group": "run",
"order": 1
}
}
]
}

182
.vscode/tasks.json vendored
View file

@ -1,97 +1,91 @@
{
"version": "2.0.0",
"tasks": [
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": ["build", "/property:GenerateFullPaths=true"],
"group": "build",
"isBuildCommand": true,
"isTestCommand": false,
"problemMatcher": ["$msCompile"],
"isBackground": true
},
{
"label": "build-web",
"type": "process",
"problemMatcher": ["$msCompile"],
"command": "dotnet",
"args": ["build"],
"options": {
"cwd": "src/Yavsc"
},
"group": {
"kind": "build"
},
"isBackground": true
},
{
"label": "build-webapi",
"type": "process",
"problemMatcher": ["$msCompile"],
"command": "dotnet",
"args": ["build"],
"options": {
"cwd": "src/Api"
},
"group": {
"kind": "build"
},
"isBackground": true
},
{
"label": "build-web",
"type": "process",
"problemMatcher": ["$msCompile"],
"command": "dotnet",
"args": ["build"],
"runOptions": {},
"options": {
"cwd": "src/Yavsc.Web"
},
"group": {
"kind": "build"
},
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
}
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": ["watch", "--project",
"src/Yavsc.Org/Yavsc.Org.csproj"
],
"problemMatcher": "$msCompile",
"runOptions": {
}
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"/property:GenerateFullPaths=true"
],
"group": "build",
"isBuildCommand": true,
"isTestCommand": false
},
{
"label": "build-web",
"type": "process",
"problemMatcher": ["$msCompile"],
"command": "dotnet",
"args": [
"build"
],
"options": {
"cwd": "src/Yavsc"
},
"group": {
"kind": "build"
},
"isBackground": true
},
{
"label": "build-webapi",
"type": "process",
"problemMatcher": ["$msCompile"],
"command": "dotnet",
"args": [
"build"
],
"options": {
"cwd": "src/Api"
},
"group": {
"kind": "build"
},
"isBackground": true
},
{
"label": "build-web",
"type": "process",
"problemMatcher": ["$msCompile"],
"command": "dotnet",
"args": [
"build"
],
"runOptions": {
},
"options": {
"cwd": "src/Web"
},
"group": {
"kind": "build"
},
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
}
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary;ForceNoAlign"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run"
],
"problemMatcher": "$msCompile"
}
]
}
]
}

105
src/Yavsc.Blogs/Program.cs Normal file
View file

@ -0,0 +1,105 @@
/*
Copyright (c) 2024 HigginsSoft, Alexander Higgins - https://github.com/alexhiggins732/
Copyright (c) 2018, Brock Allen & Dominick Baier. All rights reserved.
Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
Source code and license this software can be found
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
using IdentityModel;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Yavsc;
using Yavsc.Helpers;
using Yavsc.Interface;
using Yavsc.Models;
using Yavsc.Services;
internal class Program
{
private static async Task Main(string[] args)
{
Console.Title = "API";
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
// builder.Services.AddDistributedMemoryCache();
// accepts any access token issued by identity server
// adds an authorization policy for scope 'scope1'
services
.AddAuthorization(options =>
{
options.AddPolicy("ApiScope", policy =>
{
policy
.RequireAuthenticatedUser()
.RequireClaim(JwtClaimTypes.Scope, new string[] { "scope2" });
});
})
.AddCors(options =>
{
// this defines a CORS policy called "default"
options.AddPolicy("default", policy =>
{
policy.WithOrigins("https://localhost:5003")
.AllowAnyHeader()
.AllowAnyMethod();
});
})
.AddControllers();
// accepts any access token issued by identity server
var authenticationBuilder = services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.IncludeErrorDetails = true;
options.Authority = "https://localhost:5001";
options.TokenValidationParameters =
new() { ValidateAudience = false, RoleClaimType = Constants.RoleClaimType };
options.MapInboundClaims = true;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
services.AddTransient<ITrueEmailSender, MailSender>()
.AddTransient<IBillingService, BillingService>()
.AddTransient<ICalendarManager, CalendarManager>();
services.AddTransient<IFileSystemAuthManager, FileSystemAuthManager>();
WorkflowHelpers.ConfigureBillingService();
using (var app = builder.Build())
{
if (app.Environment.IsDevelopment())
app.UseDeveloperExceptionPage();
app
.UseRouting()
.UseAuthentication()
.UseAuthorization()
.UseCors("default")
/* .UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute()
.RequireAuthorization();
})*/
;
// app.MapIdentityApi<ApplicationUser>().RequireAuthorization("ApiScope");
app.MapDefaultControllerRoute();
app.MapGet("/identity", (HttpContext context) =>
new JsonResult(context?.User?.Claims.Select(c => new { c.Type, c.Value }))
);
// app.UseSession();
await app.RunAsync();
}
}
}

View file

@ -109,7 +109,7 @@ namespace Yavsc.Controllers
if (ModelState.IsValid)
{
BlogPost post = blogSpotService.Create(User.GetUserId(),
blogInput);
blogInput, Request.Form.Files);
return RedirectToAction("Index");
}
return View("Edit", blogInput);

View file

@ -1,6 +1,7 @@
using System.Diagnostics;
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.DotNet.Scaffolding.Shared;
using Microsoft.EntityFrameworkCore;
using Yavsc;
using Yavsc.Helpers;
@ -8,22 +9,29 @@ using Yavsc.Models;
using Yavsc.Models.Blog;
using Yavsc.Server.Exceptions;
using Yavsc.Server.Helpers;
using Yavsc.Services;
using Yavsc.ViewModels.Auth;
public class BlogSpotService
{
private readonly ApplicationDbContext _context;
private readonly IAuthorizationService _authorizationService;
private readonly IFileSystemAuthManager fileSystemAuthManager;
public BlogSpotService(ApplicationDbContext context,
IAuthorizationService authorizationService)
IAuthorizationService authorizationService,
IFileSystemAuthManager fileSystemAuthManager)
{
_authorizationService = authorizationService;
_context = context;
this.fileSystemAuthManager = fileSystemAuthManager;
}
public BlogPost Create(string userId, BlogPost post)
public BlogPost Create(string userId, BlogPost post, IFormFileCollection files)
{
foreach (var file in files)
{
}
_context.BlogSpot.Add(post);
_context.SaveChanges(userId);
return post;

View file

@ -76,7 +76,7 @@ namespace Yavsc.Models
builder.Entity<Notification>().Property(n => n.icon).HasDefaultValue("exclam");
builder.Entity<ChatRoomAccess>().HasKey(p => new { room = p.ChannelName, user = p.UserId });
builder.Entity<InstrumentRating>().HasAlternateKey(i => new { Instrument = i.InstrumentId, owner = i.OwnerId });
builder.Entity<BlogAttachedFile>().HasKey(i => new { i.FileId, i.PostId});
foreach (var et in builder.Model.GetEntityTypes())
{
if (et.ClrType.GetInterface("IBaseTrackedEntity") != null)
@ -283,5 +283,8 @@ namespace Yavsc.Models
public DbSet<ApiResourceProperty> ApiResourceProperties { get; set; }
public DbSet<ApiScopeClaim> ApiScopeClaims { get; set; }
public DbSet<ApiScopeProperty> ApiScopeProperties { get; set; }
public DbSet<BlogAttachedFile> blogAttachedFiles { get; set; }
public DbSet<UploadedFile> UploadedFiles { get; set; }
}
}

View file

@ -0,0 +1,59 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Org.BouncyCastle.Crypto.Modes;
namespace Yavsc.Models.Blog
{
public class UploadedFile
{
/// <summary>
/// File Identifier
/// </summary>
/// <value></value>
[Key(), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Display(Name = "Identifiant du fichier")]
public long Id { get; set; }
/// <summary>
/// relative path to the file, relative to the poster space prefix
/// </summary>
/// <value></value>
public string Path { get; set; }
/// <summary>
/// Length
/// </summary> <summary>
///
/// </summary>
/// <value></value>
public long Length { get; set; }
/// <summary>
/// File Content type
/// </summary>
/// <value></value>
public string ContentType { get; set; }
}
public class BlogAttachedFile
{
/// <summary>
/// Post Id
/// </summary>
/// <value></value>
public long FileId { get; set; }
[ForeignKey("FileId")]
public virtual UploadedFile File { get; set; }
/// <summary>
/// Post Id
/// </summary>
/// <value></value>
public long PostId { get; set; }
[ForeignKey("PostId")]
public virtual BlogPost Post { get; set; }
}
}

View file

@ -9,26 +9,6 @@ using Yavsc.Models.Relationship;
namespace Yavsc.Models.Blog
{
public class BlogPostEditViewModel : BlogPost
{
public bool Publish { get; set; }
public BlogPostEditViewModel()
{
}
public BlogPostEditViewModel(BlogPost post, bool publish)
{
Id = post.Id;
AuthorId = post.AuthorId;
Photo = post.Photo;
Title = post.Title;
Article = post.Article;
Publish = publish;
ACL = post.ACL;
}
}
public class BlogPost :
IBlogPost, ICircleAuthorized, ITaggable<long>

View file

@ -0,0 +1,23 @@
namespace Yavsc.Models.Blog
{
public class BlogPostEditViewModel : BlogPost
{
public bool Publish { get; set; }
public BlogPostEditViewModel()
{
}
public BlogPostEditViewModel(BlogPost post, bool publish)
{
Id = post.Id;
AuthorId = post.AuthorId;
Photo = post.Photo;
Title = post.Title;
Article = post.Article;
Publish = publish;
ACL = post.ACL;
}
}
}

View file

@ -20,8 +20,6 @@ namespace Yavsc.Models.Haircut
public class HairCutQuery : NominativeServiceCommand
{
// Bill description
string _description = null;
public override string Description
{
get; set;
@ -62,7 +60,6 @@ namespace Yavsc.Models.Haircut
public string AdditionalInfo { get; set; }
public override List<IBillItem> GetBillItems()
{
string longhairsuffix = " (cheveux longs)";

View file

@ -42,29 +42,33 @@ namespace Yavsc.Services
ruleSetParser = new RuleSetParser(false);
}
public FileAccessRight GetFilePathAccess(ClaimsPrincipal user, string fileRelativePath)
/// <summary>
/// Get the file access info, for a given user, to a given FS absolute path
/// </summary>
/// <param name="clientUser"></param>
/// <param name="providerUserFilePath"></param>
/// <returns></returns>
public FileAccessRight GetFilePathAccess(ClaimsPrincipal clientUser, string providerUserFilePath)
{
var cusername = user.GetUserName();
var clientUserName = clientUser.GetUserName();
FileInfo fi = new FileInfo(
Path.Combine(Config.UserFilesDirName, fileRelativePath));
if (fileRelativePath.StartsWith(cusername+'/'))
Path.Combine(Config.UserFilesDirName, providerUserFilePath));
if (providerUserFilePath.StartsWith(clientUserName + '/'))
{
return FileAccessRight.Read | FileAccessRight.Write;
}
var funame = fileRelativePath.Split('/')[0];
// TODO Assert valid user name
var providerUserName = providerUserFilePath.Split('/')[0];
ruleSetParser.Reset();
var cuserid = user.GetUserId();
var cuserid = clientUser.GetUserId();
var fuserid = _dbContext.Users.SingleOrDefault(u => u.UserName == funame).Id;
var providerUserId = _dbContext.Users.SingleOrDefault(u => u.UserName == providerUserName).Id;
if (string.IsNullOrEmpty(fuserid)) return FileAccessRight.None;
if (string.IsNullOrEmpty(providerUserId)) return FileAccessRight.None;
var circles = _dbContext.Circle.Include(mb => mb.Members).Where(c => c.OwnerId == fuserid).ToArray();
var circles = _dbContext.Circle.Include(mb => mb.Members).Where(c => c.OwnerId == providerUserId).ToArray();
foreach (var circle in circles)
{
if (circle.Members.Any(m => m.MemberId == cuserid))
@ -72,11 +76,11 @@ namespace Yavsc.Services
else ruleSetParser.Definitions.Add(circle.Name, Out);
}
var userFilesDir = new DirectoryInfo(
Path.Combine(Config.UserFilesDirName, funame));
Path.Combine(Config.UserFilesDirName, providerUserName));
var currentACLDir = fi.Directory;
do
{
var aclfileName = Path.Combine(currentACLDir.FullName,
var aclfileName = Path.Combine(currentACLDir.FullName,
SiteSettings.AccessListFileName);
FileInfo accessFileInfo = new FileInfo(aclfileName);
if (accessFileInfo.Exists)
@ -85,20 +89,15 @@ namespace Yavsc.Services
} while (currentACLDir != userFilesDir);
if (ruleSetParser.Rules.Allow(cusername))
if (ruleSetParser.Rules.Allow(clientUserName))
{
return FileAccessRight.Read;
}
return FileAccessRight.None;
return FileAccessRight.None;
// TODO default user scoped file access policy
}
public string NormalizePath(string path)
{
throw new NotImplementedException();
}
public void SetAccess(long circleId, string normalizedFullPath, FileAccessRight access)
{
throw new NotImplementedException();

View file

@ -5,7 +5,7 @@ using Microsoft.Extensions.FileProviders;
namespace Yavsc.Services
{
[Flags]
public enum FileAccessRight {
public enum FileAccessRight : Byte {
None = 0,
Read = 1,
@ -13,7 +13,6 @@ namespace Yavsc.Services
}
public interface IFileSystemAuthManager {
string NormalizePath (string path);
/// <summary>
///