Estimate managment from PostIt #53
33 changed files with 399 additions and 315 deletions
commit
3bc4142383
|
|
@ -7,8 +7,6 @@ namespace Yavsc.Server.Helpers
|
||||||
{
|
{
|
||||||
public static class AbstractFileSystemHelpers
|
public static class AbstractFileSystemHelpers
|
||||||
{
|
{
|
||||||
public static string UserBillsDirName { set; get; }
|
|
||||||
public static string UserFilesDirName { set; get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Is Valid this Path?
|
/// Is Valid this Path?
|
||||||
|
|
@ -64,17 +62,6 @@ namespace Yavsc.Server.Helpers
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UserDirectoryInfo GetUserFiles(string userId, string subdir)
|
|
||||||
{
|
|
||||||
UserDirectoryInfo di = new UserDirectoryInfo(UserFilesDirName, userId, subdir);
|
|
||||||
return di;
|
|
||||||
}
|
|
||||||
public static bool IsRegularFile(string userName, string subdir)
|
|
||||||
{
|
|
||||||
FileInfo fi = new FileInfo( Path.Combine(Path.Combine(UserFilesDirName, userName), subdir));
|
|
||||||
return fi.Exists;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Server side only supports POSIX file systems
|
// Server side only supports POSIX file systems
|
||||||
public const char RemoteDirectorySeparator = '/';
|
public const char RemoteDirectorySeparator = '/';
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ namespace Yavsc.ApiControllers
|
||||||
|
|
||||||
private readonly ILogger logger;
|
private readonly ILogger logger;
|
||||||
private readonly IBillingService billingService;
|
private readonly IBillingService billingService;
|
||||||
|
private readonly SiteSettings siteSettings;
|
||||||
|
|
||||||
public BillingController(
|
public BillingController(
|
||||||
IAuthorizationService authorizationService,
|
IAuthorizationService authorizationService,
|
||||||
|
|
@ -43,7 +44,8 @@ namespace Yavsc.ApiControllers
|
||||||
ApplicationDbContext context,
|
ApplicationDbContext context,
|
||||||
IOptions<GoogleAuthSettings> googleSettings,
|
IOptions<GoogleAuthSettings> googleSettings,
|
||||||
IYavscMessageSender GCMSender,
|
IYavscMessageSender GCMSender,
|
||||||
IBillingService billingService
|
IBillingService billingService,
|
||||||
|
IOptions<SiteSettings> siteSettings
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_googleSettings=googleSettings.Value;
|
_googleSettings=googleSettings.Value;
|
||||||
|
|
@ -53,6 +55,7 @@ namespace Yavsc.ApiControllers
|
||||||
this._localizer = SR;
|
this._localizer = SR;
|
||||||
_GCMSender=GCMSender;
|
_GCMSender=GCMSender;
|
||||||
this.billingService=billingService;
|
this.billingService=billingService;
|
||||||
|
this.siteSettings = siteSettings.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("facture-{billingCode}-{id}.pdf"), Authorize]
|
[HttpGet("facture-{billingCode}-{id}.pdf"), Authorize]
|
||||||
|
|
@ -65,7 +68,7 @@ namespace Yavsc.ApiControllers
|
||||||
return new ChallengeResult();
|
return new ChallengeResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
var fi = bill.GetBillInfo(billingService);
|
var fi = bill.GetBillInfo(billingService, siteSettings);
|
||||||
|
|
||||||
if (!fi.Exists) return Ok(new { Error = "Not generated" });
|
if (!fi.Exists) return Ok(new { Error = "Not generated" });
|
||||||
return File(fi.OpenRead(), "application/x-pdf", fi.Name);
|
return File(fi.OpenRead(), "application/x-pdf", fi.Name);
|
||||||
|
|
@ -229,7 +232,7 @@ namespace Yavsc.ApiControllers
|
||||||
}
|
}
|
||||||
if (Request.Form.Files.Count!=1)
|
if (Request.Form.Files.Count!=1)
|
||||||
return new BadRequestResult();
|
return new BadRequestResult();
|
||||||
await User.ReceiveProSignatureAsync(billingCode,id,Request.Form.Files[0],"pro");
|
await User.ReceiveProSignatureAsync(billingCode,id,Request.Form.Files[0],"pro", siteSettings);
|
||||||
estimate.ProviderValidationDate = DateTime.UtcNow;
|
estimate.ProviderValidationDate = DateTime.UtcNow;
|
||||||
dbContext.SaveChanges(User.GetUserId());
|
dbContext.SaveChanges(User.GetUserId());
|
||||||
// Notify the client
|
// Notify the client
|
||||||
|
|
@ -273,7 +276,7 @@ namespace Yavsc.ApiControllers
|
||||||
}
|
}
|
||||||
|
|
||||||
var filename = AbstractFileSystemHelpers.SignFileNameFormat("pro", billingCode, id);
|
var filename = AbstractFileSystemHelpers.SignFileNameFormat("pro", billingCode, id);
|
||||||
FileInfo fi = new FileInfo(Path.Combine(AbstractFileSystemHelpers.UserBillsDirName, filename));
|
FileInfo fi = new FileInfo(Path.Combine(siteSettings.Bills, filename));
|
||||||
if (!fi.Exists) return NotFound(new { Error = "Professional signature not found" });
|
if (!fi.Exists) return NotFound(new { Error = "Professional signature not found" });
|
||||||
return File(fi.OpenRead(), "application/x-pdf", filename); ;
|
return File(fi.OpenRead(), "application/x-pdf", filename); ;
|
||||||
}
|
}
|
||||||
|
|
@ -310,7 +313,7 @@ namespace Yavsc.ApiControllers
|
||||||
}
|
}
|
||||||
if (Request.Form.Files.Count!=1)
|
if (Request.Form.Files.Count!=1)
|
||||||
return new BadRequestResult();
|
return new BadRequestResult();
|
||||||
await User.ReceiveProSignatureAsync(billingCode,id,Request.Form.Files[0],"cli");
|
await User.ReceiveProSignatureAsync(billingCode,id,Request.Form.Files[0],"cli", siteSettings);
|
||||||
estimate.ClientValidationDate = DateTime.UtcNow;
|
estimate.ClientValidationDate = DateTime.UtcNow;
|
||||||
dbContext.SaveChanges(User.GetUserId());
|
dbContext.SaveChanges(User.GetUserId());
|
||||||
return Ok (new { ClientValidationDate = estimate.ClientValidationDate });
|
return Ok (new { ClientValidationDate = estimate.ClientValidationDate });
|
||||||
|
|
@ -327,9 +330,9 @@ namespace Yavsc.ApiControllers
|
||||||
}
|
}
|
||||||
|
|
||||||
var filename = AbstractFileSystemHelpers.SignFileNameFormat("pro", billingCode, id);
|
var filename = AbstractFileSystemHelpers.SignFileNameFormat("pro", billingCode, id);
|
||||||
FileInfo fi = new FileInfo(Path.Combine(AbstractFileSystemHelpers.UserBillsDirName, filename));
|
FileInfo fi = new FileInfo(Path.Combine(siteSettings.Bills, filename));
|
||||||
if (!fi.Exists) return NotFound(new { Error = "Professional signature not found" });
|
if (!fi.Exists) return NotFound(new { Error = "Professional signature not found" });
|
||||||
return File(fi.OpenRead(), "application/x-pdf", filename); ;
|
return File(fi.OpenRead(), "application/x-pdf", filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -405,7 +408,8 @@ namespace Yavsc.ApiControllers
|
||||||
FileReceivedInfo fi;
|
FileReceivedInfo fi;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
fi = await User.ReceiveEstimateSignatureAsync(id, type, payload, token);
|
fi = await User.ReceiveEstimateSignatureAsync(id, type, payload,
|
||||||
|
siteSettings, token);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using ImageMagick;
|
using ImageMagick;
|
||||||
|
|
||||||
using Yavsc.Models;
|
using Yavsc.Models;
|
||||||
using Yavsc.Api.Helpers;
|
using Yavsc.Api.Helpers;
|
||||||
using Yavsc.Server.Helpers;
|
using Yavsc.Server.Helpers;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Yavsc.WebApi.Controllers
|
namespace Yavsc.WebApi.Controllers
|
||||||
{
|
{
|
||||||
|
|
@ -28,13 +28,16 @@ namespace Yavsc.WebApi.Controllers
|
||||||
];
|
];
|
||||||
|
|
||||||
readonly ApplicationDbContext _dbContext;
|
readonly ApplicationDbContext _dbContext;
|
||||||
|
private readonly SiteSettings siteSettings;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public ApiAccountController(
|
public ApiAccountController(
|
||||||
ILoggerFactory loggerFactory, ApplicationDbContext dbContext)
|
ILoggerFactory loggerFactory, ApplicationDbContext dbContext,
|
||||||
|
IOptions<SiteSettings> siteSettings)
|
||||||
{
|
{
|
||||||
_logger = loggerFactory.CreateLogger(nameof(ApiAccountController));
|
_logger = loggerFactory.CreateLogger(nameof(ApiAccountController));
|
||||||
_dbContext = dbContext;
|
_dbContext = dbContext;
|
||||||
|
this.siteSettings = siteSettings.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("me")]
|
[HttpGet("me")]
|
||||||
|
|
@ -146,7 +149,7 @@ namespace Yavsc.WebApi.Controllers
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var info = user.ReceiveAvatar(avatarFile);
|
var info = user.ReceiveAvatar(avatarFile, siteSettings);
|
||||||
await _dbContext.SaveChangesAsync();
|
await _dbContext.SaveChangesAsync();
|
||||||
return Ok(new
|
return Ok(new
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ internal class Program
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.AddConfiguration("api");
|
builder.AddConfiguration("api");
|
||||||
Config.SiteSetup = builder.Configuration.GetSection("Site").Get<SiteSettings>() ?? new SiteSettings();
|
|
||||||
|
|
||||||
var services = builder.Services;
|
var services = builder.Services;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@
|
||||||
"CorsAllowedOrigins": [
|
"CorsAllowedOrigins": [
|
||||||
"https://yavsc.pschneider.fr",
|
"https://yavsc.pschneider.fr",
|
||||||
"https://blogs.pschneider.fr"
|
"https://blogs.pschneider.fr"
|
||||||
]
|
],
|
||||||
|
"Bills": "bills",
|
||||||
|
"TempDir": "temp"
|
||||||
},
|
},
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ using Yavsc.Server.Helpers;
|
||||||
using Yavsc.Tests.Shared;
|
using Yavsc.Tests.Shared;
|
||||||
using Yavsc.Blogs.Tests.Fixtures;
|
using Yavsc.Blogs.Tests.Fixtures;
|
||||||
using BlogPostDto = Yavsc.Blogspot.BlogPostDto;
|
using BlogPostDto = Yavsc.Blogspot.BlogPostDto;
|
||||||
|
using NuGet.Protocol;
|
||||||
|
|
||||||
namespace Yavsc.Blogs.Tests;
|
namespace Yavsc.Blogs.Tests;
|
||||||
|
|
||||||
|
|
@ -338,10 +339,9 @@ public sealed class BlogApiTests : IClassFixture<BlogsWebServerFixture>
|
||||||
ResetAndSeedDefaultUser();
|
ResetAndSeedDefaultUser();
|
||||||
using var http = NewClient(subject: "tester");
|
using var http = NewClient(subject: "tester");
|
||||||
|
|
||||||
var previousRoot = AbstractFileSystemHelpers.UserFilesDirName;
|
// AttachFiles writes under the fixture-configured
|
||||||
var tempRoot = Path.Combine(Path.GetTempPath(), "yavsc-blogs-tests-files-" + Guid.NewGuid().ToString("N"));
|
// SiteSettings.Blog (BlogFilesRoot), not a local temp dir.
|
||||||
Directory.CreateDirectory(tempRoot);
|
var filesRoot = _fixture.BlogFilesRoot;
|
||||||
AbstractFileSystemHelpers.UserFilesDirName = tempRoot;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -374,8 +374,10 @@ public sealed class BlogApiTests : IClassFixture<BlogsWebServerFixture>
|
||||||
DateModified = DateTime.UtcNow
|
DateModified = DateTime.UtcNow
|
||||||
};
|
};
|
||||||
|
|
||||||
var form = new MultipartFormDataContent();
|
var form = new MultipartFormDataContent
|
||||||
form.Add(new StringContent(JsonSerializer.Serialize(update)), "blog");
|
{
|
||||||
|
{ new StringContent(JsonSerializer.Serialize(update)), "blog" }
|
||||||
|
};
|
||||||
|
|
||||||
var fileBytes = System.Text.Encoding.UTF8.GetBytes("payload test");
|
var fileBytes = System.Text.Encoding.UTF8.GetBytes("payload test");
|
||||||
var fileContent = new ByteArrayContent(fileBytes);
|
var fileContent = new ByteArrayContent(fileBytes);
|
||||||
|
|
@ -405,8 +407,9 @@ public sealed class BlogApiTests : IClassFixture<BlogsWebServerFixture>
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
AbstractFileSystemHelpers.UserFilesDirName = previousRoot;
|
// BlogFilesRoot is the fixture-shared temp root (one per
|
||||||
try { Directory.Delete(tempRoot, recursive: true); } catch { }
|
// test run); leave it in place for the other multipart
|
||||||
|
// tests. The OS reclaims it.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -480,10 +483,9 @@ public sealed class BlogApiTests : IClassFixture<BlogsWebServerFixture>
|
||||||
ResetAndSeedDefaultUser();
|
ResetAndSeedDefaultUser();
|
||||||
using var http = NewClient(subject: "tester");
|
using var http = NewClient(subject: "tester");
|
||||||
|
|
||||||
var previousRoot = AbstractFileSystemHelpers.UserFilesDirName;
|
// AttachFiles writes under the fixture-configured
|
||||||
var tempRoot = Path.Combine(Path.GetTempPath(), "yavsc-blogs-tests-files-" + Guid.NewGuid().ToString("N"));
|
// SiteSettings.Blog (BlogFilesRoot), not a local temp dir.
|
||||||
Directory.CreateDirectory(tempRoot);
|
var filesRoot = _fixture.BlogFilesRoot;
|
||||||
AbstractFileSystemHelpers.UserFilesDirName = tempRoot;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -569,17 +571,18 @@ public sealed class BlogApiTests : IClassFixture<BlogsWebServerFixture>
|
||||||
// markdown link points:
|
// markdown link points:
|
||||||
// {UserFilesDirName}/{user}/blogs/{postId}/{fileName}.
|
// {UserFilesDirName}/{user}/blogs/{postId}/{fileName}.
|
||||||
var expectedFile = Path.Combine(
|
var expectedFile = Path.Combine(
|
||||||
tempRoot, "tester", "blogs", created.Id.ToString(), "note.txt");
|
filesRoot, "tester", "blogs", created.Id.ToString(), "note.txt");
|
||||||
var written = Directory.Exists(tempRoot)
|
var written = Directory.Exists(filesRoot)
|
||||||
? string.Join(", ", Directory.EnumerateFiles(tempRoot, "*", SearchOption.AllDirectories))
|
? string.Join(", ", Directory.EnumerateFiles(filesRoot, "*", SearchOption.AllDirectories))
|
||||||
: "<none>";
|
: "<none>";
|
||||||
Assert.True(File.Exists(expectedFile),
|
Assert.True(File.Exists(expectedFile),
|
||||||
$"Expected uploaded file at {expectedFile}. Actually written: {written}");
|
$"Expected uploaded file at {expectedFile}. Actually written: {written}");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
AbstractFileSystemHelpers.UserFilesDirName = previousRoot;
|
// BlogFilesRoot is the fixture-shared temp root (one per
|
||||||
try { Directory.Delete(tempRoot, recursive: true); } catch { }
|
// test run); leave it in place for the other multipart
|
||||||
|
// tests. The OS reclaims it.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,16 @@ public sealed class BlogsWebServerFixture : WebHostFixture
|
||||||
public long PostId { get; private set; }
|
public long PostId { get; private set; }
|
||||||
public string DefaultUserLogin { get => "alice"; }
|
public string DefaultUserLogin { get => "alice"; }
|
||||||
|
|
||||||
|
/// <summary>Root directory under which
|
||||||
|
/// <c>BlogSpotService.AttachFiles</c> writes uploaded files
|
||||||
|
/// during tests (the configured <c>SiteSettings.Blog</c>).
|
||||||
|
/// Attachments land at
|
||||||
|
/// <c>{BlogFilesRoot}/{userName}/blogs/{postId}/{fileName}</c>.
|
||||||
|
/// A fresh per-run temp directory, so tests can assert on the
|
||||||
|
/// physical file without touching the developer's real
|
||||||
|
/// user-files tree.</summary>
|
||||||
|
public string BlogFilesRoot { get; private set; } = string.Empty;
|
||||||
|
|
||||||
// A single SqliteConnection held open at the static level,
|
// A single SqliteConnection held open at the static level,
|
||||||
// mirroring how Yavsc.Org.Tests.WebServerFixture hoists its
|
// mirroring how Yavsc.Org.Tests.WebServerFixture hoists its
|
||||||
// shared configuration into static slots. Closing the
|
// shared configuration into static slots. Closing the
|
||||||
|
|
@ -112,6 +122,18 @@ public sealed class BlogsWebServerFixture : WebHostFixture
|
||||||
builder.Services.AddSingleton<IFileSystemAuthManager>(
|
builder.Services.AddSingleton<IFileSystemAuthManager>(
|
||||||
new NoopFileSystemAuthManager());
|
new NoopFileSystemAuthManager());
|
||||||
|
|
||||||
|
// BlogSpotService.AttachFiles writes uploaded files under
|
||||||
|
// siteSettings.Blog. Without a configured SiteSettings the
|
||||||
|
// IOptions<SiteSettings> value has Blog == null and
|
||||||
|
// Path.Combine(null, …) throws inside AttachFiles' catch-all,
|
||||||
|
// silently persisting no attachment. Point Blog at a
|
||||||
|
// per-run temp directory so the multipart tests can assert
|
||||||
|
// on real files.
|
||||||
|
builder.Services.Configure<SiteSettings>(s =>
|
||||||
|
{
|
||||||
|
s.Blog = BlogFilesRoot;
|
||||||
|
});
|
||||||
|
|
||||||
// Real BlogSpotService — same instance the production host
|
// Real BlogSpotService — same instance the production host
|
||||||
// builds (ApplicationDbContext, IAuthorizationService,
|
// builds (ApplicationDbContext, IAuthorizationService,
|
||||||
// IFileSystemAuthManager). With PermissionHandler registered
|
// IFileSystemAuthManager). With PermissionHandler registered
|
||||||
|
|
|
||||||
|
|
@ -57,12 +57,31 @@ namespace Yavsc.Blogs.Controllers
|
||||||
[HttpPut("{id}")]
|
[HttpPut("{id}")]
|
||||||
public async Task<IActionResult> PutBlog(long id)
|
public async Task<IActionResult> PutBlog(long id)
|
||||||
{
|
{
|
||||||
var blog = await ReadPutBlogRequestAsync();
|
var blog = await ReadBlogRequestAsync();
|
||||||
if (blog is null)
|
if (blog is null)
|
||||||
{
|
{
|
||||||
return BadRequest(ModelState);
|
return BadRequest(ModelState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Without [FromBody], the data-annotations attributes
|
||||||
|
// ([Required] on Title, etc.) are no longer evaluated by
|
||||||
|
// the model binder — the body is read manually. Run
|
||||||
|
// validation explicitly so an empty Title still yields
|
||||||
|
// 400 (and not a 500 from the DB NOT NULL constraint).
|
||||||
|
var validationContext = new System.ComponentModel.DataAnnotations.ValidationContext(blog);
|
||||||
|
var validationResults = new List<System.ComponentModel.DataAnnotations.ValidationResult>();
|
||||||
|
if (!System.ComponentModel.DataAnnotations.Validator.TryValidateObject(
|
||||||
|
blog, validationContext, validationResults, validateAllProperties: true))
|
||||||
|
{
|
||||||
|
foreach (var result in validationResults)
|
||||||
|
{
|
||||||
|
ModelState.AddModelError(
|
||||||
|
result.MemberNames.FirstOrDefault() ?? string.Empty,
|
||||||
|
result.ErrorMessage ?? "Invalid value.");
|
||||||
|
}
|
||||||
|
return BadRequest(ModelState);
|
||||||
|
}
|
||||||
|
|
||||||
// These properties are server-managed or optional graph members and
|
// These properties are server-managed or optional graph members and
|
||||||
// should not block JSON payloads coming from API clients.
|
// should not block JSON payloads coming from API clients.
|
||||||
ModelState.Remove(nameof(Models.Blog.BlogPost.Author));
|
ModelState.Remove(nameof(Models.Blog.BlogPost.Author));
|
||||||
|
|
@ -93,7 +112,8 @@ namespace Yavsc.Blogs.Controllers
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await blogSpotService.Modify(User, blog, files);
|
await blogSpotService.Modify(User, blog);
|
||||||
|
blogSpotService.AttachFiles(files, User.GetUserId(), id);
|
||||||
}
|
}
|
||||||
catch (AuthorizationFailureException)
|
catch (AuthorizationFailureException)
|
||||||
{
|
{
|
||||||
|
|
@ -105,8 +125,14 @@ namespace Yavsc.Blogs.Controllers
|
||||||
|
|
||||||
// POST: api/v1/blogspot
|
// POST: api/v1/blogspot
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public IActionResult PostBlog([FromBody] Models.Blog.BlogPost blog)
|
public async Task<IActionResult> PostBlog()
|
||||||
{
|
{
|
||||||
|
var blog = await ReadBlogRequestAsync();
|
||||||
|
if (blog is null)
|
||||||
|
{
|
||||||
|
return BadRequest(ModelState);
|
||||||
|
}
|
||||||
|
|
||||||
// These properties are server-managed or optional graph members and
|
// These properties are server-managed or optional graph members and
|
||||||
// should not block JSON payloads coming from API clients.
|
// should not block JSON payloads coming from API clients.
|
||||||
ModelState.Remove(nameof(Models.Blog.BlogPost.Author));
|
ModelState.Remove(nameof(Models.Blog.BlogPost.Author));
|
||||||
|
|
@ -115,35 +141,46 @@ namespace Yavsc.Blogs.Controllers
|
||||||
ModelState.Remove(nameof(Models.Blog.BlogPost.UserCreated));
|
ModelState.Remove(nameof(Models.Blog.BlogPost.UserCreated));
|
||||||
ModelState.Remove(nameof(Models.Blog.BlogPost.UserModified));
|
ModelState.Remove(nameof(Models.Blog.BlogPost.UserModified));
|
||||||
|
|
||||||
|
// Without [FromBody], the data-annotations attributes
|
||||||
|
// ([Required] on Title, etc.) are no longer evaluated by
|
||||||
|
// the model binder — the body is read manually. Run
|
||||||
|
// validation explicitly so an empty Title still yields
|
||||||
|
// 400 (and not a 500 from the DB NOT NULL constraint).
|
||||||
|
var validationContext = new System.ComponentModel.DataAnnotations.ValidationContext(blog);
|
||||||
|
var validationResults = new List<System.ComponentModel.DataAnnotations.ValidationResult>();
|
||||||
|
if (!System.ComponentModel.DataAnnotations.Validator.TryValidateObject(
|
||||||
|
blog, validationContext, validationResults, validateAllProperties: true))
|
||||||
|
{
|
||||||
|
foreach (var result in validationResults)
|
||||||
|
{
|
||||||
|
ModelState.AddModelError(
|
||||||
|
result.MemberNames.FirstOrDefault() ?? string.Empty,
|
||||||
|
result.ErrorMessage ?? "Invalid value.");
|
||||||
|
}
|
||||||
|
return BadRequest(ModelState);
|
||||||
|
}
|
||||||
|
|
||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
{
|
{
|
||||||
return BadRequest(ModelState);
|
return BadRequest(ModelState);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The BlogSpotService.Create() signature requires an
|
|
||||||
// IFormFileCollection for file uploads. Reading
|
|
||||||
// Request.Form.Files when the request is a plain JSON
|
|
||||||
// body (e.g. from PostIt) throws
|
|
||||||
// "This request does not have a Content-Type header.
|
|
||||||
// Forms are available from requests with bodies like
|
|
||||||
// POSTs and a form Content-Type of either
|
|
||||||
// application/x-www-form-urlencoded or
|
|
||||||
// multipart/form-data."
|
|
||||||
//
|
|
||||||
// Two valid use cases for this endpoint:
|
// Two valid use cases for this endpoint:
|
||||||
// 1. JSON body only (no files) — PostIt path.
|
// 1. JSON body only (no files) — PostIt path.
|
||||||
// 2. multipart/form-data with a 'blog' field + 0..N
|
// 2. multipart/form-data with a 'blog' field + 0..N
|
||||||
// files — future browser / server-rendered path.
|
// files — PostIt "create with attachments" path.
|
||||||
//
|
//
|
||||||
// Branch on HasFormContentType: pass the form files when
|
// Branch on HasFormContentType: pass the form files when
|
||||||
// present, pass an empty collection otherwise. The
|
// present, pass an empty collection otherwise. Reading
|
||||||
// FileSystem branch in BlogSpotService.Create then
|
// Request.Form.Files on a plain JSON request throws
|
||||||
// short-circuits to "no files to handle".
|
// "This request does not have a Content-Type header…",
|
||||||
|
// and AttachFiles short-circuits on an empty collection.
|
||||||
var files = Request.HasFormContentType
|
var files = Request.HasFormContentType
|
||||||
? Request.Form.Files
|
? Request.Form.Files
|
||||||
: (IFormFileCollection)new FormFileCollection();
|
: (IFormFileCollection)new FormFileCollection();
|
||||||
var uid = User.GetUserId();
|
var uid = User.GetUserId();
|
||||||
var post = blogSpotService.Create(uid, blog, files);
|
var post = blogSpotService.Create(uid, blog);
|
||||||
|
blogSpotService.AttachFiles(files, uid, post.Id);
|
||||||
return CreatedAtRoute("GetBlog", new { id = post.Id },
|
return CreatedAtRoute("GetBlog", new { id = post.Id },
|
||||||
post.GetPayload());
|
post.GetPayload());
|
||||||
}
|
}
|
||||||
|
|
@ -210,7 +247,17 @@ namespace Yavsc.Blogs.Controllers
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<Models.Blog.BlogPost?> ReadPutBlogRequestAsync()
|
/// <summary>
|
||||||
|
/// Read a <see cref="Models.Blog.BlogPost"/> from either a
|
||||||
|
/// JSON body or a multipart/form-data request carrying the
|
||||||
|
/// payload in a <c>blog</c> form field. Shared by
|
||||||
|
/// <see cref="PostBlog"/> and <see cref="PutBlog"/>: both
|
||||||
|
/// accept the two shapes so PostIt can attach files on
|
||||||
|
/// create as well as on update. Returns <c>null</c> (with
|
||||||
|
/// the reason recorded in <c>ModelState</c>) when the
|
||||||
|
/// multipart form has no usable <c>blog</c> field.
|
||||||
|
/// </summary>
|
||||||
|
private async Task<Models.Blog.BlogPost?> ReadBlogRequestAsync()
|
||||||
{
|
{
|
||||||
if (!Request.HasFormContentType)
|
if (!Request.HasFormContentType)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ namespace Yavsc.Blogs.Controllers
|
||||||
using Yavsc.Server.Helpers;
|
using Yavsc.Server.Helpers;
|
||||||
using Yavsc.Abstract.Helpers;
|
using Yavsc.Abstract.Helpers;
|
||||||
using Yavsc.Server.Models.FileSystem;
|
using Yavsc.Server.Models.FileSystem;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
[Authorize,Route(APIPrefix + "/fs")]
|
[Authorize,Route(APIPrefix + "/fs")]
|
||||||
public partial class FileSystemApiController : Controller
|
public partial class FileSystemApiController : Controller
|
||||||
|
|
@ -19,15 +20,18 @@ namespace Yavsc.Blogs.Controllers
|
||||||
readonly ApplicationDbContext dbContext;
|
readonly ApplicationDbContext dbContext;
|
||||||
private readonly IAuthorizationService AuthorizationService;
|
private readonly IAuthorizationService AuthorizationService;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private readonly SiteSettings siteSettings;
|
||||||
|
|
||||||
public FileSystemApiController(ApplicationDbContext context,
|
public FileSystemApiController(ApplicationDbContext context,
|
||||||
IAuthorizationService authorizationService,
|
IAuthorizationService authorizationService,
|
||||||
ILoggerFactory loggerFactory)
|
ILoggerFactory loggerFactory,
|
||||||
|
IOptions<SiteSettings> siteSettings)
|
||||||
|
|
||||||
{
|
{
|
||||||
AuthorizationService = authorizationService;
|
AuthorizationService = authorizationService;
|
||||||
dbContext = context;
|
dbContext = context;
|
||||||
_logger = loggerFactory.CreateLogger<FileSystemApiController>();
|
_logger = loggerFactory.CreateLogger<FileSystemApiController>();
|
||||||
|
this.siteSettings = siteSettings.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet()]
|
[HttpGet()]
|
||||||
|
|
@ -41,7 +45,7 @@ namespace Yavsc.Blogs.Controllers
|
||||||
{
|
{
|
||||||
if (!ModelState.IsValid) return new BadRequestObjectResult(ModelState);
|
if (!ModelState.IsValid) return new BadRequestObjectResult(ModelState);
|
||||||
// _logger.LogInformation($"listing files from {User.Identity.Name}{subdir}");
|
// _logger.LogInformation($"listing files from {User.Identity.Name}{subdir}");
|
||||||
var files = AbstractFileSystemHelpers.GetUserFiles(User.GetUserId(), subdir);
|
var files = FileSystemHelpers.GetUserFiles(siteSettings, User.GetUserId(), subdir);
|
||||||
return Ok(files);
|
return Ok(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,7 +57,7 @@ namespace Yavsc.Blogs.Controllers
|
||||||
List<FileReceivedInfo> received = new List<FileReceivedInfo>();
|
List<FileReceivedInfo> received = new List<FileReceivedInfo>();
|
||||||
InvalidPathException pathex = null;
|
InvalidPathException pathex = null;
|
||||||
try {
|
try {
|
||||||
destDir = User.EnsureDestinationDirectory(subdir);
|
destDir = User.EnsureDestinationDirectory(subdir, siteSettings);
|
||||||
} catch (InvalidPathException ex) {
|
} catch (InvalidPathException ex) {
|
||||||
pathex = ex;
|
pathex = ex;
|
||||||
}
|
}
|
||||||
|
|
@ -108,7 +112,7 @@ namespace Yavsc.Blogs.Controllers
|
||||||
var user = dbContext.Users.Single(
|
var user = dbContext.Users.Single(
|
||||||
u => u.Id == uid
|
u => u.Id == uid
|
||||||
);
|
);
|
||||||
var info = user.MoveUserFileToDir(query.Id, query.To);
|
var info = user.MoveUserFileToDir(query.Id, query.To, siteSettings);
|
||||||
if (!info.Done) return new BadRequestObjectResult(info);
|
if (!info.Done) return new BadRequestObjectResult(info);
|
||||||
return Ok(new { moved = query.Id });
|
return Ok(new { moved = query.Id });
|
||||||
}
|
}
|
||||||
|
|
@ -131,11 +135,11 @@ namespace Yavsc.Blogs.Controllers
|
||||||
try {
|
try {
|
||||||
if (Config.UserFilesOptions.FileProvider.GetFileInfo(Path.Combine(user.UserName, query.Id)).Exists)
|
if (Config.UserFilesOptions.FileProvider.GetFileInfo(Path.Combine(user.UserName, query.Id)).Exists)
|
||||||
{
|
{
|
||||||
var result = user.MoveUserFile(query.Id, query.To);
|
var result = user.MoveUserFile(query.Id, query.To, siteSettings);
|
||||||
if (!result.Done) return new BadRequestObjectResult(result);
|
if (!result.Done) return new BadRequestObjectResult(result);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var result = user.MoveUserDir(query.Id, query.To);
|
var result = user.MoveUserDir(query.Id, query.To, siteSettings);
|
||||||
if (!result.Done) return new BadRequestObjectResult(result);
|
if (!result.Done) return new BadRequestObjectResult(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +165,7 @@ namespace Yavsc.Blogs.Controllers
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var result = user.DeleteUserDirOrFile(id);
|
var result = user.DeleteUserDirOrFile(id, siteSettings);
|
||||||
if (!result.Done)
|
if (!result.Done)
|
||||||
return new BadRequestObjectResult(result);
|
return new BadRequestObjectResult(result);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ using Microsoft.AspNetCore.SignalR;
|
||||||
using Yavsc.Server.Helpers;
|
using Yavsc.Server.Helpers;
|
||||||
using static Yavsc.Constants;
|
using static Yavsc.Constants;
|
||||||
using Yavsc.Server.Hubs;
|
using Yavsc.Server.Hubs;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Yavsc.Blogs.Controllers
|
namespace Yavsc.Blogs.Controllers
|
||||||
{
|
{
|
||||||
|
|
@ -21,13 +22,20 @@ namespace Yavsc.Blogs.Controllers
|
||||||
private readonly IHubContext<ChatHub> hubContext;
|
private readonly IHubContext<ChatHub> hubContext;
|
||||||
readonly ApplicationDbContext dbContext;
|
readonly ApplicationDbContext dbContext;
|
||||||
|
|
||||||
public FileSystemStreamController(ApplicationDbContext context, ILiveProcessor liveProcessor, ILoggerFactory loggerFactory,
|
private readonly SiteSettings siteSettings;
|
||||||
IHubContext<ChatHub> hubContext)
|
|
||||||
|
public FileSystemStreamController(
|
||||||
|
ApplicationDbContext context,
|
||||||
|
ILiveProcessor liveProcessor,
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
|
IHubContext<ChatHub> hubContext,
|
||||||
|
IOptions<SiteSettings> siteSettings)
|
||||||
{
|
{
|
||||||
this.dbContext = context;
|
this.dbContext = context;
|
||||||
this.logger = loggerFactory.CreateLogger<FileSystemStreamController>();
|
this.logger = loggerFactory.CreateLogger<FileSystemStreamController>();
|
||||||
this.liveProcessor = liveProcessor;
|
this.liveProcessor = liveProcessor;
|
||||||
this.hubContext = hubContext;
|
this.hubContext = hubContext;
|
||||||
|
this.siteSettings = siteSettings.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize, Route("put/{filename}")]
|
[Authorize, Route("put/{filename}")]
|
||||||
|
|
@ -55,7 +63,7 @@ namespace Yavsc.Blogs.Controllers
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
string destDir = HttpContext.User.EnsureDestinationDirectory(filePath);
|
string destDir = HttpContext.User.EnsureDestinationDirectory(filePath, siteSettings);
|
||||||
logger.LogInformation($"Saving flow to {destDir}");
|
logger.LogInformation($"Saving flow to {destDir}");
|
||||||
var userId = User.GetUserId();
|
var userId = User.GetUserId();
|
||||||
var user = await dbContext.Users.FirstAsync(u => u.Id == userId);
|
var user = await dbContext.Users.FirstAsync(u => u.Id == userId);
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@
|
||||||
"CorsAllowedOrigins": [
|
"CorsAllowedOrigins": [
|
||||||
"https://localhost:5005",
|
"https://localhost:5005",
|
||||||
"https://api.pschneider.fr"
|
"https://api.pschneider.fr"
|
||||||
]
|
],
|
||||||
|
"Blog": "blog"
|
||||||
},
|
},
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"YavscConnection": "Server=localhost;Port=5432;Database=lame-db-name;Username=lame-user-name;Password=lame-password;"
|
"YavscConnection": "Server=localhost;Port=5432;Database=lame-db-name;Username=lame-user-name;Password=lame-password;"
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ namespace Yavsc.Org.Tests;
|
||||||
public class EstimateSignatureFileHelperTests : IDisposable
|
public class EstimateSignatureFileHelperTests : IDisposable
|
||||||
{
|
{
|
||||||
private readonly string _tempRoot;
|
private readonly string _tempRoot;
|
||||||
|
private SiteSettings siteSettings;
|
||||||
|
|
||||||
public EstimateSignatureFileHelperTests()
|
public EstimateSignatureFileHelperTests()
|
||||||
{
|
{
|
||||||
|
|
@ -28,7 +29,7 @@ public class EstimateSignatureFileHelperTests : IDisposable
|
||||||
Path.GetTempPath(),
|
Path.GetTempPath(),
|
||||||
"yavsc-sig-tests-" + Guid.NewGuid().ToString("N"));
|
"yavsc-sig-tests-" + Guid.NewGuid().ToString("N"));
|
||||||
Directory.CreateDirectory(_tempRoot);
|
Directory.CreateDirectory(_tempRoot);
|
||||||
AbstractFileSystemHelpers.UserFilesDirName = _tempRoot;
|
this.siteSettings = new SiteSettings { Blog = _tempRoot };
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
@ -71,7 +72,7 @@ public class EstimateSignatureFileHelperTests : IDisposable
|
||||||
};
|
};
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var fi = Run(user, 123L, SignatureType.Pro, payload);
|
var fi = Run(user, 123L, SignatureType.Pro, payload, this.siteSettings);
|
||||||
|
|
||||||
// Assert: file exists, sits under the user's root, and
|
// Assert: file exists, sits under the user's root, and
|
||||||
// parses as a yavsc.signature/v1 envelope.
|
// parses as a yavsc.signature/v1 envelope.
|
||||||
|
|
@ -98,7 +99,8 @@ public class EstimateSignatureFileHelperTests : IDisposable
|
||||||
var ct = TestContext.Current.CancellationToken;
|
var ct = TestContext.Current.CancellationToken;
|
||||||
await Assert.ThrowsAsync<ArgumentNullException>(() =>
|
await Assert.ThrowsAsync<ArgumentNullException>(() =>
|
||||||
EstimateSignatureFileHelper.ReceiveEstimateSignatureAsync(
|
EstimateSignatureFileHelper.ReceiveEstimateSignatureAsync(
|
||||||
user, 1L, SignatureType.Pro, payload: null!, token: ct));
|
user, 1L, SignatureType.Pro, payload: null!,
|
||||||
|
siteSettings, token: ct));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -109,19 +111,19 @@ public class EstimateSignatureFileHelperTests : IDisposable
|
||||||
var ct = TestContext.Current.CancellationToken;
|
var ct = TestContext.Current.CancellationToken;
|
||||||
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() =>
|
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(() =>
|
||||||
EstimateSignatureFileHelper.ReceiveEstimateSignatureAsync(
|
EstimateSignatureFileHelper.ReceiveEstimateSignatureAsync(
|
||||||
user, 0L, SignatureType.Pro, payload, token: ct));
|
user, 0L, SignatureType.Pro, payload, this.siteSettings, token: ct));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- helpers ----------------------------------------------------
|
// --- helpers ----------------------------------------------------
|
||||||
|
|
||||||
private static FileReceivedInfo Run(
|
private static FileReceivedInfo Run(
|
||||||
ClaimsPrincipal user, long estimateId, SignatureType type, SignaturePadPayload payload)
|
ClaimsPrincipal user, long estimateId, SignatureType type, SignaturePadPayload payload, SiteSettings siteSettings)
|
||||||
{
|
{
|
||||||
// The helper is async; tests that don't care about the
|
// The helper is async; tests that don't care about the
|
||||||
// result can call it sync via .GetAwaiter().GetResult()
|
// result can call it sync via .GetAwaiter().GetResult()
|
||||||
// because we know it never throws in the happy path.
|
// because we know it never throws in the happy path.
|
||||||
return EstimateSignatureFileHelper
|
return EstimateSignatureFileHelper
|
||||||
.ReceiveEstimateSignatureAsync(user, estimateId, type, payload, CancellationToken.None)
|
.ReceiveEstimateSignatureAsync(user, estimateId, type, payload, siteSettings, CancellationToken.None)
|
||||||
.GetAwaiter().GetResult();
|
.GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,10 @@
|
||||||
"FavIcon": "/favicon.ico",
|
"FavIcon": "/favicon.ico",
|
||||||
"Icon": "/images/yavsc.png",
|
"Icon": "/images/yavsc.png",
|
||||||
"GitRepository": "testingrepo",
|
"GitRepository": "testingrepo",
|
||||||
|
"Blog": "blog",
|
||||||
|
"Bills": "bills",
|
||||||
|
"TempDir": "temp",
|
||||||
|
"Avatars": "avatars",
|
||||||
"Owner": {
|
"Owner": {
|
||||||
"Name": "Site Owner Name",
|
"Name": "Site Owner Name",
|
||||||
"EMail": "your@email",
|
"EMail": "your@email",
|
||||||
|
|
|
||||||
|
|
@ -490,14 +490,17 @@ IHtmlLocalizerFactory htmlLocalizerFactory,
|
||||||
if (result.Succeeded)
|
if (result.Succeeded)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(3, "User created a new account with password.");
|
_logger.LogInformation(3, "User created a new account with password.");
|
||||||
await _emailSender.SendEmailAsync(Config.SiteSetup.Owner.Name, Config.SiteSetup.Owner.EMail,
|
await _emailSender.SendEmailAsync(
|
||||||
$"[{_siteSettings.Title}] Inscription avec mot de passe: {user.UserName} ", $"{user.Id}/{user.UserName}/{user.Email}");
|
_siteSettings.Owner.Name,
|
||||||
|
_siteSettings.Owner.EMail,
|
||||||
|
$"[{_siteSettings.Title}] Inscription avec mot de passe: {user.UserName} ",
|
||||||
|
$"{user.Id}/{user.UserName}/{user.Email}");
|
||||||
|
|
||||||
// TODO user.DiskQuota = Startup.SiteSetup.UserFiles.Quota;
|
// TODO user.DiskQuota = Startup.SiteSetup.UserFiles.Quota;
|
||||||
// For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=532713
|
// For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=532713
|
||||||
// Send an email with this link
|
// Send an email with this link
|
||||||
|
|
||||||
Uri authority = new Uri(Config.Authority);
|
Uri authority = new Uri(_siteSettings.Authority);
|
||||||
|
|
||||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
||||||
var callbackUrl = Url.Action("ConfirmEmail", "Account",
|
var callbackUrl = Url.Action("ConfirmEmail", "Account",
|
||||||
|
|
@ -703,10 +706,15 @@ IHtmlLocalizerFactory htmlLocalizerFactory,
|
||||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||||
|
|
||||||
|
|
||||||
await _emailSender.SendEmailAsync(Config.SiteSetup.Owner.Name, Config.SiteSetup.Owner.EMail,
|
await _emailSender.SendEmailAsync(
|
||||||
$"[{_siteSettings.Title}] Inscription via {info.LoginProvider}: {user.UserName} ", $"{user.Id}/{user.UserName}/{user.Email}");
|
_siteSettings.Owner.Name,
|
||||||
|
_siteSettings.Owner.EMail,
|
||||||
|
$"[{_siteSettings.Title}] Inscription via {info.LoginProvider}: {user.UserName} ",
|
||||||
|
$"{user.Id}/{user.UserName}/{user.Email}");
|
||||||
|
|
||||||
_logger.LogInformation(6, "User created an account using {Name} provider.", info.LoginProvider);
|
_logger.LogInformation(6,
|
||||||
|
"User created an account using {Name} provider.",
|
||||||
|
info.LoginProvider);
|
||||||
|
|
||||||
return Redirect(returnUrl);
|
return Redirect(returnUrl);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,8 +109,9 @@ namespace Yavsc.Org.Controllers
|
||||||
{
|
{
|
||||||
if (ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
Models.Blog.BlogPost post = blogSpotService.Create(User.GetUserId(),
|
var uid = User.GetUserId();
|
||||||
blogInput, Request.Form.Files);
|
Models.Blog.BlogPost post = blogSpotService.Create(uid, blogInput);
|
||||||
|
blogSpotService.AttachFiles(Request.Form.Files, uid, post.Id);
|
||||||
return RedirectToAction("Index");
|
return RedirectToAction("Index");
|
||||||
}
|
}
|
||||||
return View("Edit", blogInput);
|
return View("Edit", blogInput);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ namespace Yavsc.Controllers
|
||||||
using Helpers;
|
using Helpers;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Models;
|
using Models;
|
||||||
using ViewModels.FrontOffice;
|
using ViewModels.FrontOffice;
|
||||||
using Yavsc.Server.Helpers;
|
using Yavsc.Server.Helpers;
|
||||||
|
|
@ -20,18 +21,21 @@ namespace Yavsc.Controllers
|
||||||
readonly ILogger _logger;
|
readonly ILogger _logger;
|
||||||
readonly IStringLocalizer _SR;
|
readonly IStringLocalizer _SR;
|
||||||
private readonly IBillingService _billing;
|
private readonly IBillingService _billing;
|
||||||
|
private readonly SiteSettings _siteSettings;
|
||||||
|
|
||||||
public FrontOfficeController(ApplicationDbContext context,
|
public FrontOfficeController(ApplicationDbContext context,
|
||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
IBillingService billing,
|
IBillingService billing,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IStringLocalizer<FrontOfficeController> SR)
|
IStringLocalizer<FrontOfficeController> SR,
|
||||||
|
IOptions<SiteSettings> siteSettings)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_logger = loggerFactory.CreateLogger<FrontOfficeController>();
|
_logger = loggerFactory.CreateLogger<FrontOfficeController>();
|
||||||
_SR = SR;
|
_SR = SR;
|
||||||
_billing = billing;
|
_billing = billing;
|
||||||
|
_siteSettings = siteSettings.Value;
|
||||||
}
|
}
|
||||||
public ActionResult Index()
|
public ActionResult Index()
|
||||||
{
|
{
|
||||||
|
|
@ -95,8 +99,8 @@ namespace Yavsc.Controllers
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public IActionResult EstimatePdf(long id)
|
public IActionResult EstimatePdf(long id)
|
||||||
{
|
{
|
||||||
ViewBag.TempDir = Config.SiteSetup.TempDir;
|
ViewBag.TempDir = _siteSettings.TempDir;
|
||||||
ViewBag.BillsDir = AbstractFileSystemHelpers.UserBillsDirName;
|
ViewBag.BillsDir = _siteSettings.Bills;
|
||||||
var estimate = _context.Estimates.Include(x => x.Query)
|
var estimate = _context.Estimates.Include(x => x.Query)
|
||||||
.Include(x => x.Query.Client)
|
.Include(x => x.Query.Client)
|
||||||
.Include(x => x.Query.PerformerProfile)
|
.Include(x => x.Query.PerformerProfile)
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,25 @@
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Yavsc.Server.Helpers;
|
using Yavsc.Server.Helpers;
|
||||||
|
|
||||||
namespace Yavsc.Controllers
|
namespace Yavsc.Org.Controllers
|
||||||
{
|
{
|
||||||
public class FileSystemController : Controller
|
public class FileSystemController : Controller
|
||||||
{
|
{
|
||||||
public FileSystemController()
|
private SiteSettings _siteSettings;
|
||||||
|
|
||||||
|
public FileSystemController(IOptions<SiteSettings> siteSettings)
|
||||||
{
|
{
|
||||||
|
_siteSettings = siteSettings.Value;
|
||||||
}
|
}
|
||||||
|
// Removed redundant empty constructor block
|
||||||
|
|
||||||
public IActionResult Index(string subdir="")
|
public IActionResult Index(string subdir="")
|
||||||
{
|
{
|
||||||
if (subdir !=null)
|
if (subdir !=null)
|
||||||
if (!subdir.IsValidYavscPath())
|
if (!subdir.IsValidYavscPath())
|
||||||
return new BadRequestResult();
|
return new BadRequestResult();
|
||||||
var files = AbstractFileSystemHelpers.GetUserFiles(User.GetUserId(), subdir);
|
var files = FileSystemHelpers.GetUserFiles(_siteSettings, User.GetUserId(), subdir);
|
||||||
return View(files);
|
return View(files);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1044,7 +1044,7 @@ public static class HostingExtensions
|
||||||
Startup.Configure(app, siteSettings, smtpSettings,
|
Startup.Configure(app, siteSettings, smtpSettings,
|
||||||
payPalSettings, googleAuthSettings, localization, loggerFactory,
|
payPalSettings, googleAuthSettings, localization, loggerFactory,
|
||||||
app.Environment.EnvironmentName);
|
app.Environment.EnvironmentName);
|
||||||
app.ConfigureFileServerApp();
|
app.ConfigureFileServerApp(siteSettings.Value);
|
||||||
app.UseSession();
|
app.UseSession();
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
@ -1238,24 +1238,44 @@ ADD COLUMN IF NOT EXISTS ""Moderated"" boolean NOT NULL DEFAULT FALSE;");
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
public static IApplicationBuilder ConfigureFileServerApp(this IApplicationBuilder app,
|
public static IApplicationBuilder ConfigureFileServerApp(this IApplicationBuilder app,
|
||||||
|
SiteSettings settings,
|
||||||
bool enableDirectoryBrowsing = false)
|
bool enableDirectoryBrowsing = false)
|
||||||
{
|
{
|
||||||
|
if (settings.GitRepository == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("GitRepository path is not configured in SiteSettings.");
|
||||||
|
}
|
||||||
|
if (settings.Blog == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Blog path is not configured in SiteSettings.");
|
||||||
|
}
|
||||||
|
if (settings.Avatars == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Avatars path is not configured in SiteSettings.");
|
||||||
|
}
|
||||||
|
if (settings.Bills == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Bills path is not configured in SiteSettings.");
|
||||||
|
}
|
||||||
|
if (settings.TempDir == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("TempDir path is not configured in SiteSettings.");
|
||||||
|
}
|
||||||
|
|
||||||
var userFilesDirInfo = new DirectoryInfo(Config.SiteSetup.Blog);
|
var userFilesDirInfo = new DirectoryInfo(settings.Blog);
|
||||||
AbstractFileSystemHelpers.UserFilesDirName = userFilesDirInfo.FullName;
|
|
||||||
|
|
||||||
if (!userFilesDirInfo.Exists) userFilesDirInfo.Create();
|
if (!userFilesDirInfo.Exists) userFilesDirInfo.Create();
|
||||||
|
|
||||||
Config.UserFilesOptions = new FileServerOptions()
|
Config.UserFilesOptions = new FileServerOptions()
|
||||||
{
|
{
|
||||||
FileProvider = new PhysicalFileProvider(AbstractFileSystemHelpers.UserFilesDirName),
|
FileProvider = new PhysicalFileProvider(userFilesDirInfo.FullName),
|
||||||
RequestPath = PathString.FromUriComponent(Constants.UserFilesPath),
|
RequestPath = PathString.FromUriComponent(Constants.UserFilesPath),
|
||||||
EnableDirectoryBrowsing = enableDirectoryBrowsing,
|
EnableDirectoryBrowsing = enableDirectoryBrowsing,
|
||||||
};
|
};
|
||||||
Config.UserFilesOptions.EnableDefaultFiles = true;
|
Config.UserFilesOptions.EnableDefaultFiles = true;
|
||||||
Config.UserFilesOptions.StaticFileOptions.ServeUnknownFileTypes = true;
|
Config.UserFilesOptions.StaticFileOptions.ServeUnknownFileTypes = true;
|
||||||
|
|
||||||
var avatarsDirInfo = new DirectoryInfo(Config.SiteSetup.Avatars);
|
var avatarsDirInfo = new DirectoryInfo(settings.Avatars);
|
||||||
if (!avatarsDirInfo.Exists) avatarsDirInfo.Create();
|
if (!avatarsDirInfo.Exists) avatarsDirInfo.Create();
|
||||||
Config.AvatarsDirName = avatarsDirInfo.FullName;
|
Config.AvatarsDirName = avatarsDirInfo.FullName;
|
||||||
|
|
||||||
|
|
@ -1267,7 +1287,7 @@ ADD COLUMN IF NOT EXISTS ""Moderated"" boolean NOT NULL DEFAULT FALSE;");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
var gitdirinfo = new DirectoryInfo(Config.SiteSetup.GitRepository);
|
var gitdirinfo = new DirectoryInfo(settings.GitRepository);
|
||||||
Config.GitDirName = gitdirinfo.FullName;
|
Config.GitDirName = gitdirinfo.FullName;
|
||||||
if (!gitdirinfo.Exists) gitdirinfo.Create();
|
if (!gitdirinfo.Exists) gitdirinfo.Create();
|
||||||
Config.GitOptions = new FileServerOptions()
|
Config.GitOptions = new FileServerOptions()
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ namespace Yavsc.Helpers
|
||||||
result = result.Replace("\n","\\tabularnewline ");
|
result = result.Replace("\n","\\tabularnewline ");
|
||||||
return new TeXString(result);
|
return new TeXString(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static string NewLinesWith(this string target, string separator)
|
public static string NewLinesWith(this string target, string separator)
|
||||||
{
|
{
|
||||||
|
|
@ -115,11 +115,11 @@ namespace Yavsc.Helpers
|
||||||
return new TeXString(string.Join(lineSeparator,texlines));
|
return new TeXString(string.Join(lineSeparator,texlines));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool GenerateEstimatePdf(this PdfGenerationViewModel Model)
|
public static bool GenerateEstimatePdf(this SiteSettings settings, PdfGenerationViewModel Model)
|
||||||
{
|
{
|
||||||
string errorMsg = null;
|
string errorMsg = null;
|
||||||
var billdir = Model.DestDir;
|
var billdir = Model.DestDir;
|
||||||
var tempdir = Config.SiteSetup.TempDir;
|
var tempdir = settings.TempDir;
|
||||||
string name = Model.BaseFileName;
|
string name = Model.BaseFileName;
|
||||||
string fullname = new FileInfo(
|
string fullname = new FileInfo(
|
||||||
System.IO.Path.Combine(tempdir, name)).FullName;
|
System.IO.Path.Combine(tempdir, name)).FullName;
|
||||||
|
|
@ -154,7 +154,7 @@ namespace Yavsc.Helpers
|
||||||
{
|
{
|
||||||
errorMsg = $"Pdf generation failed with exit code: {p.ExitCode}";
|
errorMsg = $"Pdf generation failed with exit code: {p.ExitCode}";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
fi.Delete();
|
fi.Delete();
|
||||||
var di = new DirectoryInfo(Path.Combine(tempdir,$"{Model.BaseFileName}.t2d"));
|
var di = new DirectoryInfo(Path.Combine(tempdir,$"{Model.BaseFileName}.t2d"));
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ using System.Diagnostics;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Yavsc;
|
||||||
using Yavsc.Blogspot;
|
using Yavsc.Blogspot;
|
||||||
using Yavsc.Models;
|
using Yavsc.Models;
|
||||||
using Yavsc.Models.Access;
|
using Yavsc.Models.Access;
|
||||||
|
|
@ -18,14 +20,17 @@ public class OldBlogSpotService
|
||||||
private readonly ApplicationDbContext _context;
|
private readonly ApplicationDbContext _context;
|
||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
private readonly IFileSystemAuthManager fileSystemAuthManager;
|
private readonly IFileSystemAuthManager fileSystemAuthManager;
|
||||||
|
private readonly SiteSettings siteSettings;
|
||||||
|
|
||||||
public OldBlogSpotService(ApplicationDbContext context,
|
public OldBlogSpotService(ApplicationDbContext context,
|
||||||
IAuthorizationService authorizationService,
|
IAuthorizationService authorizationService,
|
||||||
IFileSystemAuthManager fileSystemAuthManager)
|
IFileSystemAuthManager fileSystemAuthManager,
|
||||||
|
IOptions<SiteSettings> siteSettings)
|
||||||
{
|
{
|
||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
_context = context;
|
_context = context;
|
||||||
this.fileSystemAuthManager = fileSystemAuthManager;
|
this.fileSystemAuthManager = fileSystemAuthManager;
|
||||||
|
this.siteSettings = siteSettings.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Yavsc.Models.Blog.BlogPost Create(string userId, Yavsc.Models.Blog.BlogPost post, IFormFileCollection files)
|
public Yavsc.Models.Blog.BlogPost Create(string userId, Yavsc.Models.Blog.BlogPost post, IFormFileCollection files)
|
||||||
|
|
@ -45,7 +50,7 @@ public class OldBlogSpotService
|
||||||
// Créer un répertoire pour les fichiers du blog
|
// Créer un répertoire pour les fichiers du blog
|
||||||
string blogFilesSubdir = $"blogs/{post.Id}";
|
string blogFilesSubdir = $"blogs/{post.Id}";
|
||||||
string destDir = Path.Combine(
|
string destDir = Path.Combine(
|
||||||
AbstractFileSystemHelpers.UserFilesDirName,
|
siteSettings.Blog,
|
||||||
user.UserName,
|
user.UserName,
|
||||||
blogFilesSubdir
|
blogFilesSubdir
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,10 @@ public class Startup
|
||||||
{
|
{
|
||||||
Config.GoogleSettings = googleSettings.Value;
|
Config.GoogleSettings = googleSettings.Value;
|
||||||
ResourcesHelpers.GlobalLocalizer = localizer;
|
ResourcesHelpers.GlobalLocalizer = localizer;
|
||||||
Config.SiteSetup = siteSettings.Value;
|
|
||||||
Config.SmtpSetup = smtpSettings.Value;
|
Config.SmtpSetup = smtpSettings.Value;
|
||||||
Config.Authority = siteSettings.Value.Authority;
|
Config.Authority = siteSettings.Value.Authority;
|
||||||
string blogsDir = siteSettings.Value.Blog ?? throw new Exception("blogsDir is not set.");
|
string blogsDir = siteSettings.Value.Blog ?? throw new Exception("blogsDir is not set.");
|
||||||
string billsDir = siteSettings.Value.Bills ?? throw new Exception("billsDir is not set.");
|
string billsDir = siteSettings.Value.Bills ?? throw new Exception("billsDir is not set.");
|
||||||
AbstractFileSystemHelpers.UserFilesDirName = new DirectoryInfo(blogsDir).FullName;
|
|
||||||
AbstractFileSystemHelpers.UserBillsDirName = new DirectoryInfo(billsDir).FullName;
|
|
||||||
Config.Temp = siteSettings.Value.TempDir;
|
Config.Temp = siteSettings.Value.TempDir;
|
||||||
Config.PayPalSettings = payPalSettings.Value;
|
Config.PayPalSettings = payPalSettings.Value;
|
||||||
|
|
||||||
|
|
@ -35,7 +32,7 @@ public class Startup
|
||||||
|
|
||||||
// TODO implement an installation & upgrade procedure
|
// TODO implement an installation & upgrade procedure
|
||||||
// Create required directories
|
// Create required directories
|
||||||
foreach (string dir in new string[] { AbstractFileSystemHelpers.UserFilesDirName, AbstractFileSystemHelpers.UserBillsDirName, Config.Temp })
|
foreach (string dir in new string[] { blogsDir, billsDir, Config.Temp })
|
||||||
{
|
{
|
||||||
if (dir == null)
|
if (dir == null)
|
||||||
{
|
{
|
||||||
|
|
@ -48,27 +45,27 @@ public class Startup
|
||||||
di.Create();
|
di.Create();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CheckApp(loggerFactory.CreateLogger<Startup>(), environmentName);
|
CheckApp(siteSettings.Value, loggerFactory.CreateLogger<Startup>(), environmentName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CheckApp(ILogger logger, string environmentName)
|
public static void CheckApp(SiteSettings settings, ILogger logger, string environmentName)
|
||||||
{
|
{
|
||||||
var appData = Environment.GetEnvironmentVariable("APPDATA");
|
var appData = Environment.GetEnvironmentVariable("APPDATA");
|
||||||
if (appData == null)
|
if (appData == null)
|
||||||
{
|
{
|
||||||
logger.LogWarning("AppData was not found in environment variables");
|
logger.LogWarning("AppData was not found in environment variables");
|
||||||
if (Config.SiteSetup.DataDir == null) {
|
if (settings.DataDir == null) {
|
||||||
Config.SiteSetup.DataDir = "AppData"+environmentName;
|
settings.DataDir = "AppData"+environmentName;
|
||||||
logger.LogInformation("Using: "+Config.SiteSetup.DataDir);
|
logger.LogInformation("Using: "+settings.DataDir);
|
||||||
} else logger.LogInformation("Using value from settings: "+Config.SiteSetup.DataDir);
|
} else logger.LogInformation("Using value from settings: "+settings.DataDir);
|
||||||
DirectoryInfo di = new DirectoryInfo(Config.SiteSetup.DataDir);
|
DirectoryInfo di = new DirectoryInfo(settings.DataDir);
|
||||||
if (!di.Exists)
|
if (!di.Exists)
|
||||||
{
|
{
|
||||||
di.Create();
|
di.Create();
|
||||||
logger.LogWarning("Created dir : "+di.FullName);
|
logger.LogWarning("Created dir : "+di.FullName);
|
||||||
}
|
}
|
||||||
logger.LogInformation("Using existing directory: "+di.FullName);
|
logger.LogInformation("Using existing directory: "+di.FullName);
|
||||||
Environment.SetEnvironmentVariable("APPDATA", Config.SiteSetup.DataDir);
|
Environment.SetEnvironmentVariable("APPDATA", settings.DataDir);
|
||||||
logger.LogWarning("It has been set to : "+Environment.GetEnvironmentVariable("APPDATA"));
|
logger.LogWarning("It has been set to : "+Environment.GetEnvironmentVariable("APPDATA"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ using Yavsc.ViewModels;
|
||||||
using Yavsc.ViewModels.Gen;
|
using Yavsc.ViewModels.Gen;
|
||||||
using Yavsc.Services;
|
using Yavsc.Services;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Yavsc.Server.Helpers;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Yavsc.ViewComponents
|
namespace Yavsc.ViewComponents
|
||||||
{
|
{
|
||||||
|
|
@ -17,20 +17,29 @@ namespace Yavsc.ViewComponents
|
||||||
readonly ApplicationDbContext dbContext;
|
readonly ApplicationDbContext dbContext;
|
||||||
readonly IBillingService billing;
|
readonly IBillingService billing;
|
||||||
readonly IStringLocalizer<BillViewComponent> localizer;
|
readonly IStringLocalizer<BillViewComponent> localizer;
|
||||||
|
private readonly SiteSettings siteSettings;
|
||||||
|
|
||||||
public BillViewComponent(ApplicationDbContext dbContext,
|
public BillViewComponent(ApplicationDbContext dbContext,
|
||||||
IStringLocalizer<BillViewComponent> localizer,
|
IStringLocalizer<BillViewComponent> localizer,
|
||||||
IBillingService billing)
|
IBillingService billing,
|
||||||
|
IOptions<SiteSettings> siteSettings)
|
||||||
{
|
{
|
||||||
this.billing = billing;
|
this.billing = billing;
|
||||||
this.dbContext = dbContext;
|
this.dbContext = dbContext;
|
||||||
this.localizer = localizer;
|
this.localizer = localizer;
|
||||||
|
this.siteSettings = siteSettings.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IViewComponentResult> InvokeAsync(string code, IBillable billable, OutputFormat format, bool asBill)
|
public async Task<IViewComponentResult> InvokeAsync(
|
||||||
|
string code,
|
||||||
|
IBillable billable,
|
||||||
|
OutputFormat format,
|
||||||
|
bool asBill,
|
||||||
|
SiteSettings settings
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var di = new DirectoryInfo(Config.SiteSetup.Bills);
|
var di = new DirectoryInfo(settings.Bills);
|
||||||
var dia = new DirectoryInfo(Config.SiteSetup.Avatars);
|
var dia = new DirectoryInfo(settings.Avatars);
|
||||||
ViewBag.BillsDir = di.FullName;
|
ViewBag.BillsDir = di.FullName;
|
||||||
ViewBag.AvatarsDir = dia.FullName;
|
ViewBag.AvatarsDir = dia.FullName;
|
||||||
ViewBag.AsBill = asBill; // vrai pour une facture, sinon, c'est un devis
|
ViewBag.AsBill = asBill; // vrai pour une facture, sinon, c'est un devis
|
||||||
|
|
@ -56,9 +65,9 @@ namespace Yavsc.ViewComponents
|
||||||
ViewBag.PerformerProfile = profile;
|
ViewBag.PerformerProfile = profile;
|
||||||
ViewBag.ActivityLabel = (await dbContext.Activities.SingleAsync(a => a.Code == billable.ActivityCode)).Name;
|
ViewBag.ActivityLabel = (await dbContext.Activities.SingleAsync(a => a.Code == billable.ActivityCode)).Name;
|
||||||
|
|
||||||
var proaddr = profile.OrganizationAddress.Address;
|
var proAddr = profile.OrganizationAddress.Address;
|
||||||
ViewBag.PerformerOrganizationAddress = proaddr.SplitAddressToTeX() ;
|
ViewBag.PerformerOrganizationAddress = proAddr.SplitAddressToTeX() ;
|
||||||
ViewBag.FooterPerformerOrganizationAddress = proaddr.SplitAddressToTeX(", ");
|
ViewBag.FooterPerformerOrganizationAddress = proAddr.SplitAddressToTeX(", ");
|
||||||
|
|
||||||
ViewBag.PerformerAddress = performer.PostalAddress?.Address.SplitAddressToTeX() ;
|
ViewBag.PerformerAddress = performer.PostalAddress?.Address.SplitAddressToTeX() ;
|
||||||
switch (format) {
|
switch (format) {
|
||||||
|
|
@ -75,23 +84,24 @@ namespace Yavsc.ViewComponents
|
||||||
tex = writer.ToString();
|
tex = writer.ToString();
|
||||||
}
|
}
|
||||||
ViewComponentContext.ViewContext.Writer = oldWriter;
|
ViewComponentContext.ViewContext.Writer = oldWriter;
|
||||||
|
|
||||||
var genrtrData = new PdfGenerationViewModel
|
var pdfGenerationViewModel = new PdfGenerationViewModel
|
||||||
{
|
{
|
||||||
Temp = Config.Temp,
|
Temp = Config.Temp,
|
||||||
TeXSource = tex,
|
TeXSource = tex,
|
||||||
DestDir = AbstractFileSystemHelpers.UserBillsDirName,
|
DestDir = settings.Blog,
|
||||||
BaseFileName = billable.GetFileBaseName(billing)
|
BaseFileName = billable.GetFileBaseName(billing)
|
||||||
};
|
};
|
||||||
if (genrtrData.GenerateEstimatePdf()) {
|
if (settings.GenerateEstimatePdf(pdfGenerationViewModel)) {
|
||||||
return this.View(new { Generated = genrtrData.BaseFileName+".pdf" });
|
return this.View(new { Generated = pdfGenerationViewModel.BaseFileName+".pdf" });
|
||||||
} else {
|
} else {
|
||||||
return View(new { Error = genrtrData.GenerationErrorMessage } );
|
return View(new { Error = pdfGenerationViewModel.GenerationErrorMessage } );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ViewBag.BillFileInfo = billable.GetBillInfo(billing);
|
ViewBag.BillFileInfo = billable.GetBillInfo(billing, siteSettings);
|
||||||
|
ViewBag.Settings = settings;
|
||||||
return View("Default",billable);
|
return View("Default",billable);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Yavsc.Models;
|
using Yavsc.Models;
|
||||||
using Yavsc.Server.Helpers;
|
using Yavsc.Server.Helpers;
|
||||||
using Yavsc.ViewModels.UserFiles;
|
using Yavsc.ViewModels.UserFiles;
|
||||||
|
|
@ -11,24 +12,24 @@ namespace Yavsc.ViewComponents
|
||||||
{
|
{
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly UserManager<ApplicationUser> _userManager;
|
||||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||||
public DirectoryViewComponent(UserManager<ApplicationUser> userManager,
|
private readonly SiteSettings siteSettings;
|
||||||
SignInManager<ApplicationUser> signInManager)
|
|
||||||
|
public DirectoryViewComponent(
|
||||||
|
UserManager<ApplicationUser> userManager,
|
||||||
|
SignInManager<ApplicationUser> signInManager,
|
||||||
|
IOptions<SiteSettings> siteSettings)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_signInManager = signInManager;
|
_signInManager = signInManager;
|
||||||
|
this.siteSettings = siteSettings.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IViewComponentResult> InvokeAsync(string dirname)
|
public async Task<IViewComponentResult> InvokeAsync(string dirname)
|
||||||
{
|
{
|
||||||
string uid = ViewContext.HttpContext.User.GetUserId();
|
string uid = ViewContext.HttpContext.User.GetUserId();
|
||||||
|
return View(new UserDirectoryInfo(
|
||||||
IViewComponentResult result = null;
|
siteSettings.Blog,
|
||||||
|
|
||||||
result = View(new UserDirectoryInfo(
|
|
||||||
AbstractFileSystemHelpers.UserFilesDirName,
|
|
||||||
uid, dirname));
|
uid, dirname));
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
@using Yavsc.Helpers
|
@using Yavsc.Helpers
|
||||||
@model Yavsc.ViewModels.Gen.PdfGenerationViewModel
|
@model Yavsc.ViewModels.Gen.PdfGenerationViewModel
|
||||||
|
|
||||||
@{
|
@{
|
||||||
Model.GenerateEstimatePdf();
|
ViewBag.Settings.GenerateEstimatePdf(Model);
|
||||||
}
|
}
|
||||||
@if (Model.Generated) {
|
@if (Model.Generated) {
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
@model Yavsc.ViewModels.Gen.PdfGenerationViewModel
|
@model Yavsc.ViewModels.Gen.PdfGenerationViewModel
|
||||||
|
|
||||||
@{
|
@{
|
||||||
Model.GenerateEstimatePdf();
|
SiteSettings.Value.GenerateEstimatePdf(Model);
|
||||||
}
|
}
|
||||||
@if (Model.Generated) {
|
@if (Model.Generated) {
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
<nav class="navbar navbar-expand-sm navbar-dark bg-dark" aria-label="Yavsc"
|
<nav class="navbar navbar-expand-sm navbar-dark bg-dark" aria-label="Yavsc"
|
||||||
style="background-image: url('@Config.SiteSetup.Banner');
|
style="background-image: url('@SiteSettings.Value.Banner');
|
||||||
background-position: 0%;
|
background-position: 0%;
|
||||||
background-attachment: fixed;">
|
background-attachment: fixed;">
|
||||||
<a class="navbar-brand" href="/">
|
<a class="navbar-brand" href="/">
|
||||||
<img src="~/images/yavsc-logo.svg" alt="Yavsc" style="height:32px; margin-right:8px; display:inline-block; vertical-align:middle;" />
|
<img src="~/images/yavsc-logo.svg" alt="Yavsc" style="height:32px; margin-right:8px; display:inline-block; vertical-align:middle;" />
|
||||||
@Config.SiteSetup.Title
|
@SiteSettings.Value.Title
|
||||||
</a>
|
</a>
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
||||||
data-bs-target="#navbar" aria-controls="navbar"
|
data-bs-target="#navbar" aria-controls="navbar"
|
||||||
aria-expanded="true" aria-label="Toggle navigation">
|
aria-expanded="true" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,8 @@
|
||||||
"Bills": "bills",
|
"Bills": "bills",
|
||||||
"Blog": "blog",
|
"Blog": "blog",
|
||||||
"TempDir": "temp",
|
"TempDir": "temp",
|
||||||
"DataDir": "data"
|
"DataDir": "data",
|
||||||
|
"GitRepository": "git"
|
||||||
},
|
},
|
||||||
"Smtp": {
|
"Smtp": {
|
||||||
"Host": "[YOURSMTPHOST]",
|
"Host": "[YOURSMTPHOST]",
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,11 @@ public static class Config
|
||||||
throw new InvalidOperationException("Invalid Authority URL");
|
throw new InvalidOperationException("Invalid Authority URL");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static IConfigurationRoot? GoogleWebClientConfiguration { get; set; }
|
public static IConfigurationRoot? GoogleWebClientConfiguration { get; set; }
|
||||||
public static GoogleServiceAccount? GServiceAccount { get; set; }
|
public static GoogleServiceAccount? GServiceAccount { get; set; }
|
||||||
|
|
||||||
public static SiteSettings SiteSetup { get; set; } = new SiteSettings();
|
|
||||||
public static FileServerOptions? UserFilesOptions { get; set; }
|
public static FileServerOptions? UserFilesOptions { get; set; }
|
||||||
public static FileServerOptions? GitOptions { get; set; }
|
public static FileServerOptions? GitOptions { get; set; }
|
||||||
public static string AvatarsDirName { set; get; } = "Avatars";
|
public static string AvatarsDirName { set; get; } = "Avatars";
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,12 @@ namespace Yavsc.Helpers
|
||||||
return bill;
|
return bill;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileInfo GetBillInfo(this IBillable bill, IBillingService service)
|
public static FileInfo GetBillInfo(this IBillable bill,
|
||||||
|
IBillingService service, SiteSettings siteSettings)
|
||||||
{
|
{
|
||||||
var suffix = bill.GetIsAcquitted() ? "-ack":null;
|
var suffix = bill.GetIsAcquitted() ? "-ack":null;
|
||||||
var filename = bill.GetFileBaseName(service)+".pdf";
|
var filename = bill.GetFileBaseName(service)+".pdf";
|
||||||
return new FileInfo(Path.Combine(AbstractFileSystemHelpers.UserBillsDirName, filename));
|
return new FileInfo(Path.Combine(siteSettings.Bills, filename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ public static class EstimateSignatureFileHelper
|
||||||
long estimateId,
|
long estimateId,
|
||||||
SignatureType type,
|
SignatureType type,
|
||||||
SignaturePadPayload payload,
|
SignaturePadPayload payload,
|
||||||
|
SiteSettings siteSettings,
|
||||||
CancellationToken token = default)
|
CancellationToken token = default)
|
||||||
{
|
{
|
||||||
if (user is null) throw new ArgumentNullException(nameof(user));
|
if (user is null) throw new ArgumentNullException(nameof(user));
|
||||||
|
|
@ -77,7 +78,7 @@ public static class EstimateSignatureFileHelper
|
||||||
// (not user-derived), so we skip the IsValidYavscPath
|
// (not user-derived), so we skip the IsValidYavscPath
|
||||||
// check that ReceiveUserFile performs on user-supplied
|
// check that ReceiveUserFile performs on user-supplied
|
||||||
// subpaths.
|
// subpaths.
|
||||||
var root = user.EnsureDestinationDirectory(SignaturesSubdir);
|
var root = user.EnsureDestinationDirectory(SignaturesSubdir, siteSettings);
|
||||||
|
|
||||||
var fileName = FileNameFormat(type, estimateId, DateTime.UtcNow.Ticks);
|
var fileName = FileNameFormat(type, estimateId, DateTime.UtcNow.Ticks);
|
||||||
var fullPath = Path.Combine(root, fileName);
|
var fullPath = Path.Combine(root, fileName);
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ using Yavsc.Exceptions;
|
||||||
using Yavsc.Abstract.Helpers;
|
using Yavsc.Abstract.Helpers;
|
||||||
using ImageMagick;
|
using ImageMagick;
|
||||||
using Yavsc.Server.Models.FileSystem;
|
using Yavsc.Server.Models.FileSystem;
|
||||||
|
using Yavsc.ViewModels.UserFiles;
|
||||||
namespace Yavsc.Server.Helpers
|
namespace Yavsc.Server.Helpers
|
||||||
{
|
{
|
||||||
public static class FileSystemHelpers
|
public static class FileSystemHelpers
|
||||||
|
|
@ -27,10 +28,12 @@ namespace Yavsc.Server.Helpers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static async Task<FileReceivedInfo> ReceiveProSignatureAsync(this ClaimsPrincipal user, string billingCode, long estimateId, IFormFile formFile, string signType)
|
public static async Task<FileReceivedInfo> ReceiveProSignatureAsync(this ClaimsPrincipal user, string billingCode, long estimateId, IFormFile formFile, string signType,
|
||||||
|
SiteSettings settings
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var item = new FileReceivedInfo(
|
var item = new FileReceivedInfo(
|
||||||
Config.SiteSetup.Bills,
|
settings.Bills,
|
||||||
AbstractFileSystemHelpers.SignFileNameFormat("pro", billingCode, estimateId));
|
AbstractFileSystemHelpers.SignFileNameFormat("pro", billingCode, estimateId));
|
||||||
|
|
||||||
var fi = new FileInfo(item.FullName);
|
var fi = new FileInfo(item.FullName);
|
||||||
|
|
@ -39,16 +42,11 @@ namespace Yavsc.Server.Helpers
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetAvatarUri(this ApplicationUser user)
|
|
||||||
{
|
|
||||||
return $"/{Config.SiteSetup.Avatars}/{user.UserName}.png";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string EnsureDestinationDirectory(
|
public static string EnsureDestinationDirectory(
|
||||||
this ClaimsPrincipal user,
|
this ClaimsPrincipal user,
|
||||||
string subpath)
|
string subpath, SiteSettings siteSettings)
|
||||||
{
|
{
|
||||||
var root = Path.Combine(AbstractFileSystemHelpers.UserFilesDirName, user.Identity.Name);
|
var root = Path.Combine(siteSettings.Blog, user.Identity.Name);
|
||||||
if (!string.IsNullOrWhiteSpace(subpath))
|
if (!string.IsNullOrWhiteSpace(subpath))
|
||||||
{
|
{
|
||||||
if (!subpath.IsValidYavscPath())
|
if (!subpath.IsValidYavscPath())
|
||||||
|
|
@ -69,18 +67,18 @@ namespace Yavsc.Server.Helpers
|
||||||
/// <param name="user"></param>
|
/// <param name="user"></param>
|
||||||
/// <param name="fileName"></param>
|
/// <param name="fileName"></param>
|
||||||
|
|
||||||
public static void DeleteUserFile(this ApplicationUser user, string fileName)
|
public static void DeleteUserFile(this ApplicationUser user, string fileName, SiteSettings siteSettings)
|
||||||
{
|
{
|
||||||
var root = Path.Combine(AbstractFileSystemHelpers.UserFilesDirName, user.UserName);
|
var root = Path.Combine(siteSettings.Blog, user.UserName);
|
||||||
var fi = new FileInfo(Path.Combine(root, fileName));
|
var fi = new FileInfo(Path.Combine(root, fileName));
|
||||||
if (!fi.Exists) return;
|
if (!fi.Exists) return;
|
||||||
fi.Delete();
|
fi.Delete();
|
||||||
user.DiskUsage -= fi.Length;
|
user.DiskUsage -= fi.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FsOperationInfo DeleteUserDirOrFile(this ApplicationUser user, string dirName)
|
public static FsOperationInfo DeleteUserDirOrFile(this ApplicationUser user, string dirName, SiteSettings siteSettings)
|
||||||
{
|
{
|
||||||
var root = Path.Combine(AbstractFileSystemHelpers.UserFilesDirName, user.UserName);
|
var root = Path.Combine(siteSettings.Blog, user.UserName);
|
||||||
if (string.IsNullOrEmpty(dirName))
|
if (string.IsNullOrEmpty(dirName))
|
||||||
return new FsOperationInfo { Done = false, ErrorCode = ErrorCode.InvalidRequest, ErrorMessage = "specify a directory or file name" };
|
return new FsOperationInfo { Done = false, ErrorCode = ErrorCode.InvalidRequest, ErrorMessage = "specify a directory or file name" };
|
||||||
|
|
||||||
|
|
@ -101,9 +99,9 @@ namespace Yavsc.Server.Helpers
|
||||||
return new FsOperationInfo { Done = true };
|
return new FsOperationInfo { Done = true };
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FsOperationInfo MoveUserDir(this ApplicationUser user, string fromDirName, string toDirName)
|
public static FsOperationInfo MoveUserDir(this ApplicationUser user, string fromDirName, string toDirName, SiteSettings siteSettings)
|
||||||
{
|
{
|
||||||
var root = Path.Combine(AbstractFileSystemHelpers.UserFilesDirName, user.UserName);
|
var root = Path.Combine(siteSettings.Blog, user.UserName);
|
||||||
if (string.IsNullOrEmpty(fromDirName))
|
if (string.IsNullOrEmpty(fromDirName))
|
||||||
return new FsOperationInfo { Done = false, ErrorCode = ErrorCode.InvalidRequest, ErrorMessage = "specify a dir name " };
|
return new FsOperationInfo { Done = false, ErrorCode = ErrorCode.InvalidRequest, ErrorMessage = "specify a dir name " };
|
||||||
|
|
||||||
|
|
@ -127,28 +125,28 @@ namespace Yavsc.Server.Helpers
|
||||||
di.MoveTo(destPath);
|
di.MoveTo(destPath);
|
||||||
return new FsOperationInfo { Done = true };
|
return new FsOperationInfo { Done = true };
|
||||||
}
|
}
|
||||||
public static FsOperationInfo MoveUserFileToDir(this ApplicationUser user, string fileNameFrom, string fileNameDest)
|
public static FsOperationInfo MoveUserFileToDir(this ApplicationUser user, string fileNameFrom, string fileNameDest, SiteSettings siteSettings)
|
||||||
{
|
{
|
||||||
var root = Path.Combine(AbstractFileSystemHelpers.UserFilesDirName, user.UserName);
|
var root = Path.Combine(siteSettings.Blog, user.UserName);
|
||||||
var fi = new FileInfo(Path.Combine(root, fileNameFrom));
|
var fi = new FileInfo(Path.Combine(root, fileNameFrom));
|
||||||
if (!fi.Exists) return new FsOperationInfo { ErrorCode = ErrorCode.NotFound, ErrorMessage = "no file to move" };
|
if (!fi.Exists) return new FsOperationInfo { Done = false, ErrorCode = ErrorCode.NotFound, ErrorMessage = "no file to move" };
|
||||||
string dest;
|
string dest;
|
||||||
if (!string.IsNullOrEmpty(fileNameDest)) dest = Path.Combine(root, fileNameDest);
|
if (!string.IsNullOrEmpty(fileNameDest)) dest = Path.Combine(root, fileNameDest);
|
||||||
else dest = root;
|
else dest = root;
|
||||||
var fo = new FileInfo(dest);
|
var fo = new FileInfo(dest);
|
||||||
if (fo.Exists) return new FsOperationInfo { ErrorCode = ErrorCode.DestExists, ErrorMessage = "destination file name is an existing file" };
|
if (fo.Exists) return new FsOperationInfo { Done = false, ErrorCode = ErrorCode.DestExists, ErrorMessage = "destination file name is an existing file" };
|
||||||
var dout = new DirectoryInfo(dest);
|
var dout = new DirectoryInfo(dest);
|
||||||
if (!dout.Exists) dout.Create();
|
if (!dout.Exists) dout.Create();
|
||||||
fi.MoveTo(Path.Combine(dout.FullName, fi.Name));
|
fi.MoveTo(Path.Combine(dout.FullName, fi.Name));
|
||||||
return new FsOperationInfo { Done = true };
|
return new FsOperationInfo { Done = true };
|
||||||
}
|
}
|
||||||
public static FsOperationInfo MoveUserFile(this ApplicationUser user, string fileNameFrom, string fileNameDest)
|
public static FsOperationInfo MoveUserFile(this ApplicationUser user, string fileNameFrom, string fileNameDest, SiteSettings siteSettings)
|
||||||
{
|
{
|
||||||
var root = Path.Combine(AbstractFileSystemHelpers.UserFilesDirName, user.UserName);
|
var root = Path.Combine(siteSettings.Blog, user.UserName);
|
||||||
var fi = new FileInfo(Path.Combine(root, fileNameFrom));
|
var fi = new FileInfo(Path.Combine(root, fileNameFrom));
|
||||||
if (!fi.Exists) return new FsOperationInfo { ErrorCode = ErrorCode.NotFound, ErrorMessage = "no file to move" };
|
if (!fi.Exists) return new FsOperationInfo { Done = false, ErrorCode = ErrorCode.NotFound, ErrorMessage = "no file to move" };
|
||||||
var fo = new FileInfo(Path.Combine(root, fileNameDest));
|
var fo = new FileInfo(Path.Combine(root, fileNameDest));
|
||||||
if (fo.Exists) return new FsOperationInfo { ErrorCode = ErrorCode.DestExists, ErrorMessage = "destination file name is an existing file" };
|
if (fo.Exists) return new FsOperationInfo { Done = false, ErrorCode = ErrorCode.DestExists, ErrorMessage = "destination file name is an existing file" };
|
||||||
fi.MoveTo(fo.FullName);
|
fi.MoveTo(fo.FullName);
|
||||||
return new FsOperationInfo { Done = true };
|
return new FsOperationInfo { Done = true };
|
||||||
}
|
}
|
||||||
|
|
@ -245,7 +243,9 @@ namespace Yavsc.Server.Helpers
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileReceivedInfo ReceiveAvatar(this ApplicationUser user, IFormFile formFile)
|
public static FileReceivedInfo ReceiveAvatar(this ApplicationUser user,
|
||||||
|
IFormFile formFile,
|
||||||
|
SiteSettings settings)
|
||||||
{
|
{
|
||||||
if (user == null) throw new ArgumentNullException(nameof(user));
|
if (user == null) throw new ArgumentNullException(nameof(user));
|
||||||
if (formFile == null) throw new ArgumentNullException(nameof(formFile));
|
if (formFile == null) throw new ArgumentNullException(nameof(formFile));
|
||||||
|
|
@ -256,7 +256,7 @@ namespace Yavsc.Server.Helpers
|
||||||
avatarsRequestPath = Constants.AvatarsPath;
|
avatarsRequestPath = Constants.AvatarsPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
var avatarsDirectory = Config.SiteSetup?.Avatars;
|
var avatarsDirectory = settings.Avatars;
|
||||||
if (string.IsNullOrWhiteSpace(avatarsDirectory))
|
if (string.IsNullOrWhiteSpace(avatarsDirectory))
|
||||||
{
|
{
|
||||||
avatarsDirectory = "avatars";
|
avatarsDirectory = "avatars";
|
||||||
|
|
@ -306,5 +306,12 @@ namespace Yavsc.Server.Helpers
|
||||||
return $"{basename}-{flow.SequenceNumber}{ext}";
|
return $"{basename}-{flow.SequenceNumber}{ext}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static UserDirectoryInfo GetUserFiles(SiteSettings settings, string userId, string subdir)
|
||||||
|
{
|
||||||
|
UserDirectoryInfo di = new UserDirectoryInfo(settings.Blog, userId, subdir);
|
||||||
|
return di;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ using System.Security.Claims;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Yavsc;
|
||||||
using Yavsc.Blogspot;
|
using Yavsc.Blogspot;
|
||||||
using Yavsc.Models;
|
using Yavsc.Models;
|
||||||
using Yavsc.Models.Access;
|
using Yavsc.Models.Access;
|
||||||
|
|
@ -19,72 +21,77 @@ public class BlogSpotService
|
||||||
private readonly ApplicationDbContext _context;
|
private readonly ApplicationDbContext _context;
|
||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
private readonly IFileSystemAuthManager fileSystemAuthManager;
|
private readonly IFileSystemAuthManager fileSystemAuthManager;
|
||||||
|
private readonly SiteSettings siteSettings;
|
||||||
|
|
||||||
public BlogSpotService(
|
public BlogSpotService(
|
||||||
ApplicationDbContext context,
|
ApplicationDbContext context,
|
||||||
IAuthorizationService authorizationService,
|
IAuthorizationService authorizationService,
|
||||||
IFileSystemAuthManager fileSystemAuthManager)
|
IFileSystemAuthManager fileSystemAuthManager,
|
||||||
|
IOptions<SiteSettings> siteSettings)
|
||||||
{
|
{
|
||||||
_authorizationService = authorizationService;
|
_authorizationService = authorizationService;
|
||||||
_context = context;
|
_context = context;
|
||||||
this.fileSystemAuthManager = fileSystemAuthManager;
|
this.fileSystemAuthManager = fileSystemAuthManager;
|
||||||
|
this.siteSettings = siteSettings.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlogPost Create(string userId, BlogPost post, IFormFileCollection files)
|
public void AttachFiles(IFormFileCollection files, string userId, long postId)
|
||||||
|
{
|
||||||
|
// Traiter les fichiers attaches s'il y en a
|
||||||
|
if (files != null && files.Count > 0)
|
||||||
|
{
|
||||||
|
var user = _context.Users.FirstOrDefault(u => u.Id == userId);
|
||||||
|
if (user == null)
|
||||||
|
throw new InvalidOperationException($"Utilisateur {userId} non trouvé.");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string blogFilesSubdir = $"blogs/{postId}";
|
||||||
|
string destDir = Path.Combine(
|
||||||
|
siteSettings.Blog,
|
||||||
|
user.UserName,
|
||||||
|
blogFilesSubdir);
|
||||||
|
var di = new DirectoryInfo(destDir);
|
||||||
|
if (!di.Exists) di.Create();
|
||||||
|
|
||||||
|
foreach (var formFile in files)
|
||||||
|
{
|
||||||
|
var fileInfo = user.ReceiveUserFile(destDir, formFile);
|
||||||
|
if (fileInfo != null && !fileInfo.QuotaOffense)
|
||||||
|
{
|
||||||
|
var uploadedFile = new UploadedFile
|
||||||
|
{
|
||||||
|
Path = fileInfo.FileName,
|
||||||
|
ContentType = formFile.ContentType,
|
||||||
|
Length = formFile.Length
|
||||||
|
};
|
||||||
|
_context.UploadedFiles.Add(uploadedFile);
|
||||||
|
_context.SaveChanges(userId);
|
||||||
|
|
||||||
|
var attachment = new BlogAttachedFile
|
||||||
|
{
|
||||||
|
PostId = postId,
|
||||||
|
FileId = uploadedFile.Id
|
||||||
|
};
|
||||||
|
_context.BlogAttachedFiles.Add(attachment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_context.SaveChanges(userId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"Erreur lors du traitement des fichiers : {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlogPost Create(string userId, BlogPost post )
|
||||||
{
|
{
|
||||||
// Sauvegarder le post d'abord pour obtenir son ID
|
// Sauvegarder le post d'abord pour obtenir son ID
|
||||||
// Le createur vient de l'authentification, donc on ne le prend pas du post
|
// Le createur vient de l'authentification, donc on ne le prend pas du post
|
||||||
post.AuthorId = userId;
|
post.AuthorId = userId;
|
||||||
_context.BlogSpot.Add(post);
|
_context.BlogSpot.Add(post);
|
||||||
_context.SaveChanges(userId);
|
_context.SaveChanges(userId);
|
||||||
|
|
||||||
// Traiter les fichiers attaches s'il y en a
|
|
||||||
if (files != null && files.Count > 0)
|
|
||||||
{
|
|
||||||
var user = _context.Users.FirstOrDefault(u => u.Id == userId);
|
|
||||||
if (user != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string blogFilesSubdir = $"blogs/{post.Id}";
|
|
||||||
string destDir = Path.Combine(
|
|
||||||
AbstractFileSystemHelpers.UserFilesDirName,
|
|
||||||
user.UserName,
|
|
||||||
blogFilesSubdir);
|
|
||||||
var di = new DirectoryInfo(destDir);
|
|
||||||
if (!di.Exists) di.Create();
|
|
||||||
|
|
||||||
foreach (var formFile in files)
|
|
||||||
{
|
|
||||||
var fileInfo = user.ReceiveUserFile(destDir, formFile);
|
|
||||||
if (fileInfo != null && !fileInfo.QuotaOffense)
|
|
||||||
{
|
|
||||||
var uploadedFile = new UploadedFile
|
|
||||||
{
|
|
||||||
Path = fileInfo.FileName,
|
|
||||||
ContentType = formFile.ContentType,
|
|
||||||
Length = formFile.Length
|
|
||||||
};
|
|
||||||
_context.UploadedFiles.Add(uploadedFile);
|
|
||||||
_context.SaveChanges(userId);
|
|
||||||
|
|
||||||
var attachment = new BlogAttachedFile
|
|
||||||
{
|
|
||||||
PostId = post.Id,
|
|
||||||
FileId = uploadedFile.Id
|
|
||||||
};
|
|
||||||
_context.BlogAttachedFiles.Add(attachment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_context.SaveChanges(userId);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.WriteLine($"Erreur lors du traitement des fichiers : {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return post;
|
return post;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,58 +193,6 @@ public class BlogSpotService
|
||||||
_context.SaveChanges(user.GetUserId());
|
_context.SaveChanges(user.GetUserId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Modify(ClaimsPrincipal user, BlogPost blog, IFormFileCollection files)
|
|
||||||
{
|
|
||||||
await Modify(user, blog);
|
|
||||||
|
|
||||||
if (files == null || files.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var userId = user.GetUserId();
|
|
||||||
var userEntity = _context.Users.FirstOrDefault(u => u.Id == userId);
|
|
||||||
if (userEntity == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string blogFilesSubdir = $"blogs/{blog.Id}";
|
|
||||||
string destDir = Path.Combine(
|
|
||||||
AbstractFileSystemHelpers.UserFilesDirName,
|
|
||||||
userEntity.UserName,
|
|
||||||
blogFilesSubdir);
|
|
||||||
var di = new DirectoryInfo(destDir);
|
|
||||||
if (!di.Exists) di.Create();
|
|
||||||
|
|
||||||
foreach (var formFile in files)
|
|
||||||
{
|
|
||||||
var fileInfo = userEntity.ReceiveUserFile(destDir, formFile);
|
|
||||||
if (fileInfo != null && !fileInfo.QuotaOffense)
|
|
||||||
{
|
|
||||||
var uploadedFile = new UploadedFile
|
|
||||||
{
|
|
||||||
Path = fileInfo.FileName,
|
|
||||||
ContentType = formFile.ContentType,
|
|
||||||
Length = formFile.Length
|
|
||||||
};
|
|
||||||
_context.UploadedFiles.Add(uploadedFile);
|
|
||||||
_context.SaveChanges(userId);
|
|
||||||
|
|
||||||
var attachment = new BlogAttachedFile
|
|
||||||
{
|
|
||||||
PostId = blog.Id,
|
|
||||||
FileId = uploadedFile.Id
|
|
||||||
};
|
|
||||||
_context.BlogAttachedFiles.Add(attachment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_context.SaveChanges(userId);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.WriteLine($"Erreur lors du traitement des fichiers : {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<IBlogPost>> Index(ClaimsPrincipal user, string id, int skip = 0, int take = 25)
|
public async Task<IEnumerable<IBlogPost>> Index(ClaimsPrincipal user, string id, int skip = 0, int take = 25)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,89 +6,72 @@ namespace Yavsc
|
||||||
{
|
{
|
||||||
public class SiteSettings
|
public class SiteSettings
|
||||||
{
|
{
|
||||||
public string Title { get; set; } = "Yavsc";
|
public string Title { get; set; }
|
||||||
|
|
||||||
public string Slogan { get; set; } = "";
|
public string Slogan { get; set; }
|
||||||
public string Banner { get; set; } = "";
|
public string Banner { get; set; }
|
||||||
|
|
||||||
public string StyleSheet { get; set; } = "site.css";
|
public string StyleSheet { get; set; }
|
||||||
public string FavIcon { get; set; } = "favicon.ico";
|
public string FavIcon { get; set; }
|
||||||
public string Logo { get; set; } = "logo.png";
|
public string Logo { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Origins to allow via the CORS "default" policy.
|
/// Origins to allow via the CORS "default" policy.
|
||||||
/// Each entry must be a full origin (scheme + host [+ port]), e.g. "https://app.example.com".
|
/// Each entry must be a full origin (scheme + host [+ port]), e.g. "https://app.example.com".
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IList<string> CorsAllowedOrigins { get; set; } = new List<string>
|
public IList<string> CorsAllowedOrigins { get; set; }
|
||||||
{
|
|
||||||
"https://localhost:5001"
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// External Url
|
/// External Url
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value></value>
|
/// <value></value>
|
||||||
public string ExternalUrl { get; set; } = "http://lua.pschneider.fr";
|
public string ExternalUrl { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base URL of the API fronting this site.
|
/// Base URL of the API fronting this site.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string ApiUrl { get; set; } = "";
|
public string ApiUrl { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Must be a fqdn.
|
/// Must be a fqdn.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public string Authority { get; set; } = "lua.pschneider.fr";
|
public string Authority { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Owner's email
|
/// Owner's email
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public StaticContact Owner { get; set; } = new StaticContact
|
public StaticContact Owner { get; set; }
|
||||||
{
|
|
||||||
EMail = "root@lua.pschneider.fr",
|
|
||||||
Name = "Root"
|
|
||||||
} ;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Administrator's email
|
/// Administrator's email
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public StaticContact Admin { get; set; } = new StaticContact
|
public StaticContact Admin { get; set; }
|
||||||
{
|
|
||||||
EMail = "root@lua.pschneider.fr",
|
|
||||||
Name = "Root"
|
|
||||||
} ;
|
|
||||||
|
|
||||||
public string DataDir { get; set; } = "data";
|
public string DataDir { get; set; }
|
||||||
public string Avatars { get; set; } = "avatars";
|
public string Avatars { get; set; }
|
||||||
public long Quota { get; set; }
|
public long Quota { get; set; }
|
||||||
public string Blog { get; set; } = "blogs";
|
public string Blog { get; set; }
|
||||||
public string Bills { get; set; } = "bills";
|
public string Bills { get; set; }
|
||||||
public string GitRepository { get; set; } = "sources";
|
public string GitRepository { get; set; }
|
||||||
|
|
||||||
public string BusinessName { get; set; } = "Yavsc";
|
|
||||||
public string? Street { get; set; }
|
|
||||||
public string? PostalCode { get; set; }
|
|
||||||
public string? CountryCode { get; set; }
|
|
||||||
|
|
||||||
public string HomeViewName { get; set; } = "Home";
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specifies the directory where should be
|
/// Specifies the directory where should be
|
||||||
/// generated pdf files using pandoc
|
/// generated pdf files using pandoc
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The temporary directory to use</returns>
|
/// <returns>The temporary directory to use</returns>
|
||||||
public string TempDir { get; set; } = "temp";
|
public string TempDir { get; set; }
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disk usage user list maximum length in memory
|
/// Disk usage user list maximum length in memory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value></value>
|
/// <value></value>
|
||||||
public int DUUserListLen { get; set; } = 256;
|
public int DUUserListLen { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default acl file name
|
/// Default acl file name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value></value>
|
/// <value></value>
|
||||||
public string AccessListFileName { get; set; } = ".access";
|
public string AccessListFileName { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue