forked from Kieran/void.cat
v5 (#65)
Co-authored-by: Kieran <kieran@harkin.me> Reviewed-on: Kieran/void.cat#65
This commit is contained in:
parent
8289122347
commit
4de977c1dd
19
.drone.yml
Normal file
19
.drone.yml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: kubernetes
|
||||||
|
name: default
|
||||||
|
|
||||||
|
metadata:
|
||||||
|
namespace: git
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
|
image: r.j3ss.co/img
|
||||||
|
privileged: true
|
||||||
|
environment:
|
||||||
|
TOKEN:
|
||||||
|
from_secret: token
|
||||||
|
commands:
|
||||||
|
- img login -u kieran -p $TOKEN git.v0l.io
|
||||||
|
- img build -t git.v0l.io/kieran/void-cat:latest .
|
||||||
|
- img push git.v0l.io/kieran/void-cat:latest
|
21
Dockerfile
21
Dockerfile
@ -1,23 +1,26 @@
|
|||||||
# syntax=docker/dockerfile:1
|
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
|
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
#install npm
|
#install npm
|
||||||
RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash -
|
RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash -
|
||||||
RUN apt-get install -y nodejs
|
RUN apt-get install -y nodejs
|
||||||
|
|
||||||
#run yarn install
|
#run yarn install
|
||||||
COPY VoidCat/spa/package.json VoidCat/spa/yarn.lock spa/
|
|
||||||
RUN cd spa && npx yarn install
|
|
||||||
|
|
||||||
# Copy everything else and build
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN rm -rf VoidCat/appsettings.*.json
|
RUN cd VoidCat/spa \
|
||||||
RUN dotnet publish -c Release -o out VoidCat/VoidCat.csproj
|
&& npx yarn \
|
||||||
|
&& npx yarn build
|
||||||
|
|
||||||
|
RUN rm -rf VoidCat/appsettings.*.json \
|
||||||
|
&& git config --global --add safe.directory /app \
|
||||||
|
&& dotnet publish -c Release -o out VoidCat/VoidCat.csproj
|
||||||
|
|
||||||
# Build runtime image
|
# Build runtime image
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:6.0
|
FROM mcr.microsoft.com/dotnet/aspnet:6.0
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN apt update && apt install -y --no-install-recommends ffmpeg && rm -rf /var/lib/apt/lists/*
|
RUN apt update \
|
||||||
|
&& apt install -y --no-install-recommends ffmpeg \
|
||||||
|
&& apt clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
COPY --from=build-env /app/out .
|
COPY --from=build-env /app/out .
|
||||||
ENTRYPOINT ["dotnet", "VoidCat.dll"]
|
ENTRYPOINT ["dotnet", "VoidCat.dll"]
|
@ -1,7 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using VoidCat.Model;
|
using VoidCat.Model;
|
||||||
using VoidCat.Model.User;
|
|
||||||
using VoidCat.Services.Abstractions;
|
using VoidCat.Services.Abstractions;
|
||||||
using VoidCat.Services.Files;
|
using VoidCat.Services.Files;
|
||||||
|
|
||||||
@ -34,16 +34,16 @@ public class AdminController : Controller
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("file")]
|
[Route("file")]
|
||||||
public async Task<RenderedResults<PublicVoidFile>> ListFiles([FromBody] PagedRequest request)
|
public async Task<RenderedResults<VoidFileResponse>> ListFiles([FromBody] PagedRequest request)
|
||||||
{
|
{
|
||||||
var files = await _fileMetadata.ListFiles<FileMeta>(request);
|
var files = await _fileMetadata.ListFiles(request);
|
||||||
|
|
||||||
return new()
|
return new()
|
||||||
{
|
{
|
||||||
Page = files.Page,
|
Page = files.Page,
|
||||||
PageSize = files.PageSize,
|
PageSize = files.PageSize,
|
||||||
TotalResults = files.TotalResults,
|
TotalResults = files.TotalResults,
|
||||||
Results = (await files.Results.SelectAwait(a => _fileInfo.Get(a.Id)).ToListAsync())!
|
Results = (await files.Results.SelectAwait(a => _fileInfo.Get(a.Id, false)).ToListAsync())!
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ public class AdminController : Controller
|
|||||||
var ret = await result.Results.SelectAwait(async a =>
|
var ret = await result.Results.SelectAwait(async a =>
|
||||||
{
|
{
|
||||||
var uploads = await _userUploads.ListFiles(a.Id, new(0, int.MaxValue));
|
var uploads = await _userUploads.ListFiles(a.Id, new(0, int.MaxValue));
|
||||||
return new AdminListedUser(a, uploads.TotalResults);
|
return new AdminListedUser(a.ToAdminApiUser(true), uploads.TotalResults);
|
||||||
}).ToListAsync();
|
}).ToListAsync();
|
||||||
|
|
||||||
return new()
|
return new()
|
||||||
@ -93,14 +93,27 @@ public class AdminController : Controller
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("update-user")]
|
[Route("update-user")]
|
||||||
public async Task<IActionResult> UpdateUser([FromBody] PrivateUser user)
|
public async Task<IActionResult> UpdateUser([FromBody] AdminUpdateUser user)
|
||||||
{
|
{
|
||||||
var oldUser = await _userStore.Get(user.Id);
|
var oldUser = await _userStore.Get(user.Id);
|
||||||
if (oldUser == default) return BadRequest();
|
if (oldUser == default) return BadRequest();
|
||||||
|
|
||||||
await _userStore.AdminUpdateUser(user);
|
oldUser.Storage = user.Storage;
|
||||||
|
oldUser.Email = user.Email;
|
||||||
|
|
||||||
|
await _userStore.AdminUpdateUser(oldUser);
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
public record AdminListedUser(PrivateUser User, int Uploads);
|
public record AdminListedUser(AdminApiUser User, int Uploads);
|
||||||
|
|
||||||
|
public class AdminUpdateUser
|
||||||
|
{
|
||||||
|
[JsonConverter(typeof(Base58GuidConverter))]
|
||||||
|
public Guid Id { get; init; }
|
||||||
|
|
||||||
|
public string Email { get; init; } = null!;
|
||||||
|
|
||||||
|
public string Storage { get; init; } = null!;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,8 @@ using System.Security.Claims;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using VoidCat.Database;
|
||||||
using VoidCat.Model;
|
using VoidCat.Model;
|
||||||
using VoidCat.Model.User;
|
|
||||||
using VoidCat.Services.Abstractions;
|
using VoidCat.Services.Abstractions;
|
||||||
using VoidCat.Services.Users;
|
using VoidCat.Services.Users;
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ public class AuthController : Controller
|
|||||||
var user = await _manager.Login(req.Username, req.Password);
|
var user = await _manager.Login(req.Username, req.Password);
|
||||||
var token = CreateToken(user, DateTime.UtcNow.AddHours(12));
|
var token = CreateToken(user, DateTime.UtcNow.AddHours(12));
|
||||||
var tokenWriter = new JwtSecurityTokenHandler();
|
var tokenWriter = new JwtSecurityTokenHandler();
|
||||||
return new(tokenWriter.WriteToken(token), Profile: user.ToPublic());
|
return new(tokenWriter.WriteToken(token), Profile: user.ToApiUser(true));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -91,7 +91,7 @@ public class AuthController : Controller
|
|||||||
var newUser = await _manager.Register(req.Username, req.Password);
|
var newUser = await _manager.Register(req.Username, req.Password);
|
||||||
var token = CreateToken(newUser, DateTime.UtcNow.AddHours(12));
|
var token = CreateToken(newUser, DateTime.UtcNow.AddHours(12));
|
||||||
var tokenWriter = new JwtSecurityTokenHandler();
|
var tokenWriter = new JwtSecurityTokenHandler();
|
||||||
return new(tokenWriter.WriteToken(token), Profile: newUser.ToPublic());
|
return new(tokenWriter.WriteToken(token), Profile: newUser.ToApiUser(true));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -189,7 +189,7 @@ public class AuthController : Controller
|
|||||||
new(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString())
|
new(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString())
|
||||||
};
|
};
|
||||||
|
|
||||||
claims.AddRange(user.Roles.Select(a => new Claim(ClaimTypes.Role, a)));
|
claims.AddRange(user.Roles.Select(a => new Claim(ClaimTypes.Role, a.Role)));
|
||||||
|
|
||||||
return new JwtSecurityToken(_settings.JwtSettings.Issuer, claims: claims,
|
return new JwtSecurityToken(_settings.JwtSettings.Issuer, claims: claims,
|
||||||
signingCredentials: credentials);
|
signingCredentials: credentials);
|
||||||
@ -210,7 +210,7 @@ public class AuthController : Controller
|
|||||||
public string? Captcha { get; init; }
|
public string? Captcha { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed record LoginResponse(string? Jwt, string? Error = null, User? Profile = null);
|
public sealed record LoginResponse(string? Jwt, string? Error = null, ApiUser? Profile = null);
|
||||||
|
|
||||||
public sealed record CreateApiKeyRequest(DateTime Expiry);
|
public sealed record CreateApiKeyRequest(DateTime Expiry);
|
||||||
}
|
}
|
@ -1,8 +1,8 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using VoidCat.Database;
|
||||||
using VoidCat.Model;
|
using VoidCat.Model;
|
||||||
using VoidCat.Model.Payments;
|
|
||||||
using VoidCat.Services.Abstractions;
|
using VoidCat.Services.Abstractions;
|
||||||
using VoidCat.Services.Files;
|
using VoidCat.Services.Files;
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ public class DownloadController : Controller
|
|||||||
|
|
||||||
if (id.EndsWith(".torrent"))
|
if (id.EndsWith(".torrent"))
|
||||||
{
|
{
|
||||||
var t = await voidFile.Metadata!.MakeTorrent(
|
var t = await voidFile.Metadata.MakeTorrent(voidFile.Id,
|
||||||
await _storage.Open(new(gid, Enumerable.Empty<RangeRequest>()), CancellationToken.None),
|
await _storage.Open(new(gid, Enumerable.Empty<RangeRequest>()), CancellationToken.None),
|
||||||
_settings.SiteUrl, _settings.TorrentTrackers);
|
_settings.SiteUrl, _settings.TorrentTrackers);
|
||||||
|
|
||||||
@ -107,9 +107,9 @@ public class DownloadController : Controller
|
|||||||
await Response.CompleteAsync();
|
await Response.CompleteAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<PublicVoidFile?> SetupDownload(Guid id)
|
private async Task<VoidFileResponse?> SetupDownload(Guid id)
|
||||||
{
|
{
|
||||||
var meta = await _fileInfo.Get(id);
|
var meta = await _fileInfo.Get(id, false);
|
||||||
if (meta == null)
|
if (meta == null)
|
||||||
{
|
{
|
||||||
Response.StatusCode = 404;
|
Response.StatusCode = 404;
|
||||||
@ -117,10 +117,10 @@ public class DownloadController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check payment order
|
// check payment order
|
||||||
if (meta.Payment != default && meta.Payment.Service != PaymentServices.None && meta.Payment.Required)
|
if (meta.Payment != default && meta.Payment.Service != PaywallService.None && meta.Payment.Required)
|
||||||
{
|
{
|
||||||
var orderId = Request.Headers.GetHeader("V-OrderId") ?? Request.Query["orderId"];
|
var orderId = Request.Headers.GetHeader("V-OrderId") ?? Request.Query["orderId"];
|
||||||
if (!await IsOrderPaid(orderId))
|
if (!await IsOrderPaid(orderId!))
|
||||||
{
|
{
|
||||||
Response.StatusCode = (int)HttpStatusCode.PaymentRequired;
|
Response.StatusCode = (int)HttpStatusCode.PaymentRequired;
|
||||||
return default;
|
return default;
|
||||||
@ -128,7 +128,7 @@ public class DownloadController : Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prevent hot-linking viruses
|
// prevent hot-linking viruses
|
||||||
var referer = Request.Headers.Referer.Count > 0 ? new Uri(Request.Headers.Referer.First()) : null;
|
var referer = Request.Headers.Referer.Count > 0 ? new Uri(Request.Headers.Referer.First()!) : null;
|
||||||
var hasCorrectReferer = referer?.Host.Equals(_settings.SiteUrl.Host, StringComparison.InvariantCultureIgnoreCase) ??
|
var hasCorrectReferer = referer?.Host.Equals(_settings.SiteUrl.Host, StringComparison.InvariantCultureIgnoreCase) ??
|
||||||
false;
|
false;
|
||||||
|
|
||||||
@ -151,7 +151,7 @@ public class DownloadController : Controller
|
|||||||
if (Guid.TryParse(orderId, out var oid))
|
if (Guid.TryParse(orderId, out var oid))
|
||||||
{
|
{
|
||||||
var order = await _paymentOrders.Get(oid);
|
var order = await _paymentOrders.Get(oid);
|
||||||
if (order?.Status == PaymentOrderStatus.Paid)
|
if (order?.Status == PaywallOrderStatus.Paid)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -40,14 +40,16 @@ public class IndexController : Controller
|
|||||||
var jsonManifest = await System.IO.File.ReadAllTextAsync(manifestPath);
|
var jsonManifest = await System.IO.File.ReadAllTextAsync(manifestPath);
|
||||||
return View("~/Pages/Index.cshtml", new IndexModel
|
return View("~/Pages/Index.cshtml", new IndexModel
|
||||||
{
|
{
|
||||||
Meta = await _fileMetadata.Get(gid),
|
Id = gid,
|
||||||
|
Meta = (await _fileMetadata.Get(gid))?.ToMeta(false),
|
||||||
Manifest = JsonConvert.DeserializeObject<AssetManifest>(jsonManifest)!
|
Manifest = JsonConvert.DeserializeObject<AssetManifest>(jsonManifest)!
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public class IndexModel
|
public class IndexModel
|
||||||
{
|
{
|
||||||
public FileMeta? Meta { get; init; }
|
public Guid Id { get; init; }
|
||||||
|
public VoidFileMeta? Meta { get; init; }
|
||||||
|
|
||||||
public AssetManifest Manifest { get; init; }
|
public AssetManifest Manifest { get; init; }
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,11 @@ using Microsoft.AspNetCore.Mvc.Filters;
|
|||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using Microsoft.AspNetCore.StaticFiles;
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using VoidCat.Database;
|
||||||
using VoidCat.Model;
|
using VoidCat.Model;
|
||||||
using VoidCat.Model.Payments;
|
|
||||||
using VoidCat.Model.User;
|
|
||||||
using VoidCat.Services.Abstractions;
|
using VoidCat.Services.Abstractions;
|
||||||
using VoidCat.Services.Files;
|
using VoidCat.Services.Files;
|
||||||
|
using File = VoidCat.Database.File;
|
||||||
|
|
||||||
namespace VoidCat.Controllers
|
namespace VoidCat.Controllers
|
||||||
{
|
{
|
||||||
@ -63,6 +63,7 @@ namespace VoidCat.Controllers
|
|||||||
{
|
{
|
||||||
throw new InvalidOperationException("Site is in maintenance mode");
|
throw new InvalidOperationException("Site is in maintenance mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
var uid = HttpContext.GetUserId();
|
var uid = HttpContext.GetUserId();
|
||||||
var mime = Request.Headers.GetHeader("V-Content-Type");
|
var mime = Request.Headers.GetHeader("V-Content-Type");
|
||||||
var filename = Request.Headers.GetHeader("V-Filename");
|
var filename = Request.Headers.GetHeader("V-Filename");
|
||||||
@ -86,14 +87,14 @@ namespace VoidCat.Controllers
|
|||||||
var store = _settings.DefaultFileStore;
|
var store = _settings.DefaultFileStore;
|
||||||
if (uid.HasValue)
|
if (uid.HasValue)
|
||||||
{
|
{
|
||||||
var user = await _userStore.Get<InternalUser>(uid.Value);
|
var user = await _userStore.Get(uid.Value);
|
||||||
if (user?.Storage != default)
|
if (user?.Storage != default)
|
||||||
{
|
{
|
||||||
store = user.Storage!;
|
store = user.Storage!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var meta = new SecretFileMeta
|
var meta = new File
|
||||||
{
|
{
|
||||||
MimeType = mime,
|
MimeType = mime,
|
||||||
Name = filename,
|
Name = filename,
|
||||||
@ -109,7 +110,7 @@ namespace VoidCat.Controllers
|
|||||||
HttpContext.RequestAborted);
|
HttpContext.RequestAborted);
|
||||||
|
|
||||||
// save metadata
|
// save metadata
|
||||||
await _metadata.Set(vf.Id, vf.Metadata!);
|
await _metadata.Add(vf);
|
||||||
|
|
||||||
// attach file upload to user
|
// attach file upload to user
|
||||||
if (uid.HasValue)
|
if (uid.HasValue)
|
||||||
@ -126,7 +127,7 @@ namespace VoidCat.Controllers
|
|||||||
return Content(urlBuilder.Uri.ToString(), "text/plain");
|
return Content(urlBuilder.Uri.ToString(), "text/plain");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Json(UploadResult.Success(vf));
|
return Json(UploadResult.Success(vf.ToResponse(true)));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -158,8 +159,9 @@ namespace VoidCat.Controllers
|
|||||||
{
|
{
|
||||||
throw new InvalidOperationException("Site is in maintenance mode");
|
throw new InvalidOperationException("Site is in maintenance mode");
|
||||||
}
|
}
|
||||||
|
|
||||||
var gid = id.FromBase58Guid();
|
var gid = id.FromBase58Guid();
|
||||||
var meta = await _metadata.Get<SecretFileMeta>(gid);
|
var meta = await _metadata.Get(gid);
|
||||||
if (meta == default) return UploadResult.Error("File not found");
|
if (meta == default) return UploadResult.Error("File not found");
|
||||||
|
|
||||||
// Parse V-Segment header
|
// Parse V-Segment header
|
||||||
@ -182,8 +184,8 @@ namespace VoidCat.Controllers
|
|||||||
}, HttpContext.RequestAborted);
|
}, HttpContext.RequestAborted);
|
||||||
|
|
||||||
// update file size
|
// update file size
|
||||||
await _metadata.Set(vf.Id, vf.Metadata!);
|
await _metadata.Update(vf.Id, vf);
|
||||||
return UploadResult.Success(vf);
|
return UploadResult.Success(vf.ToResponse(true));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -205,7 +207,10 @@ namespace VoidCat.Controllers
|
|||||||
var uid = HttpContext.GetUserId();
|
var uid = HttpContext.GetUserId();
|
||||||
var isOwner = uid.HasValue && await _userUploads.Uploader(fid) == uid;
|
var isOwner = uid.HasValue && await _userUploads.Uploader(fid) == uid;
|
||||||
|
|
||||||
return isOwner ? Json(await _fileInfo.GetPrivate(fid)) : Json(await _fileInfo.Get(fid));
|
var info = await _fileInfo.Get(fid, isOwner);
|
||||||
|
if (info == default) return StatusCode(404);
|
||||||
|
|
||||||
|
return Json(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -232,10 +237,10 @@ namespace VoidCat.Controllers
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("{id}/payment")]
|
[Route("{id}/payment")]
|
||||||
public async ValueTask<PaymentOrder?> CreateOrder([FromRoute] string id)
|
public async ValueTask<PaywallOrder?> CreateOrder([FromRoute] string id)
|
||||||
{
|
{
|
||||||
var gid = id.FromBase58Guid();
|
var gid = id.FromBase58Guid();
|
||||||
var file = await _fileInfo.Get(gid);
|
var file = await _fileInfo.Get(gid, false);
|
||||||
var config = await _paymentStore.Get(gid);
|
var config = await _paymentStore.Get(gid);
|
||||||
|
|
||||||
var provider = await _paymentFactory.CreateProvider(config!.Service);
|
var provider = await _paymentFactory.CreateProvider(config!.Service);
|
||||||
@ -250,7 +255,7 @@ namespace VoidCat.Controllers
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("{id}/payment/{order:guid}")]
|
[Route("{id}/payment/{order:guid}")]
|
||||||
public async ValueTask<PaymentOrder?> GetOrderStatus([FromRoute] string id, [FromRoute] Guid order)
|
public async ValueTask<PaywallOrder?> GetOrderStatus([FromRoute] string id, [FromRoute] Guid order)
|
||||||
{
|
{
|
||||||
var gid = id.FromBase58Guid();
|
var gid = id.FromBase58Guid();
|
||||||
var config = await _paymentStore.Get(gid);
|
var config = await _paymentStore.Get(gid);
|
||||||
@ -270,17 +275,23 @@ namespace VoidCat.Controllers
|
|||||||
public async Task<IActionResult> SetPaymentConfig([FromRoute] string id, [FromBody] SetPaymentConfigRequest req)
|
public async Task<IActionResult> SetPaymentConfig([FromRoute] string id, [FromBody] SetPaymentConfigRequest req)
|
||||||
{
|
{
|
||||||
var gid = id.FromBase58Guid();
|
var gid = id.FromBase58Guid();
|
||||||
var meta = await _metadata.Get<SecretFileMeta>(gid);
|
var meta = await _metadata.Get(gid);
|
||||||
if (meta == default) return NotFound();
|
if (meta == default) return NotFound();
|
||||||
if (!meta.CanEdit(req.EditSecret)) return Unauthorized();
|
if (!meta.CanEdit(req.EditSecret)) return Unauthorized();
|
||||||
|
|
||||||
if (req.Strike != default)
|
if (req.StrikeHandle != default)
|
||||||
{
|
{
|
||||||
await _paymentStore.Add(gid, new StrikePaymentConfig()
|
await _paymentStore.Delete(gid);
|
||||||
|
await _paymentStore.Add(gid, new Paywall
|
||||||
{
|
{
|
||||||
Service = PaymentServices.Strike,
|
File = meta,
|
||||||
Handle = req.Strike.Handle,
|
Service = PaywallService.Strike,
|
||||||
Cost = req.Strike.Cost,
|
PaywallStrike = new()
|
||||||
|
{
|
||||||
|
Handle = req.StrikeHandle
|
||||||
|
},
|
||||||
|
Amount = req.Amount,
|
||||||
|
Currency = Enum.Parse<PaywallCurrency>(req.Currency),
|
||||||
Required = req.Required
|
Required = req.Required
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -303,14 +314,21 @@ namespace VoidCat.Controllers
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("{id}/meta")]
|
[Route("{id}/meta")]
|
||||||
public async Task<IActionResult> UpdateFileMeta([FromRoute] string id, [FromBody] SecretFileMeta fileMeta)
|
public async Task<IActionResult> UpdateFileMeta([FromRoute] string id, [FromBody] VoidFileMeta fileMeta)
|
||||||
{
|
{
|
||||||
var gid = id.FromBase58Guid();
|
var gid = id.FromBase58Guid();
|
||||||
var meta = await _metadata.Get<SecretFileMeta>(gid);
|
var meta = await _metadata.Get(gid);
|
||||||
if (meta == default) return NotFound();
|
if (meta == default) return NotFound();
|
||||||
if (!meta.CanEdit(fileMeta.EditSecret)) return Unauthorized();
|
if (!meta.CanEdit(fileMeta.EditSecret)) return Unauthorized();
|
||||||
|
|
||||||
await _metadata.Update(gid, fileMeta);
|
await _metadata.Update(gid, new()
|
||||||
|
{
|
||||||
|
Name = fileMeta.Name,
|
||||||
|
Description = fileMeta.Description,
|
||||||
|
Expires = fileMeta.Expires,
|
||||||
|
MimeType = fileMeta.MimeType
|
||||||
|
});
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -349,9 +367,9 @@ namespace VoidCat.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public record UploadResult(bool Ok, PrivateVoidFile? File, string? ErrorMessage)
|
public record UploadResult(bool Ok, VoidFileResponse? File, string? ErrorMessage)
|
||||||
{
|
{
|
||||||
public static UploadResult Success(PrivateVoidFile vf)
|
public static UploadResult Success(VoidFileResponse vf)
|
||||||
=> new(true, vf, null);
|
=> new(true, vf, null);
|
||||||
|
|
||||||
public static UploadResult Error(string message)
|
public static UploadResult Error(string message)
|
||||||
@ -363,7 +381,11 @@ namespace VoidCat.Controllers
|
|||||||
[JsonConverter(typeof(Base58GuidConverter))]
|
[JsonConverter(typeof(Base58GuidConverter))]
|
||||||
public Guid EditSecret { get; init; }
|
public Guid EditSecret { get; init; }
|
||||||
|
|
||||||
public StrikePaymentConfig? Strike { get; init; }
|
public decimal Amount { get; init; }
|
||||||
|
|
||||||
|
public string Currency { get; init; } = null!;
|
||||||
|
|
||||||
|
public string? StrikeHandle { get; init; }
|
||||||
|
|
||||||
public bool Required { get; init; }
|
public bool Required { get; init; }
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using VoidCat.Database;
|
||||||
using VoidCat.Model;
|
using VoidCat.Model;
|
||||||
using VoidCat.Model.User;
|
|
||||||
using VoidCat.Services.Abstractions;
|
using VoidCat.Services.Abstractions;
|
||||||
using VoidCat.Services.Files;
|
using VoidCat.Services.Files;
|
||||||
|
using UserFlags = VoidCat.Database.UserFlags;
|
||||||
|
|
||||||
namespace VoidCat.Controllers;
|
namespace VoidCat.Controllers;
|
||||||
|
|
||||||
@ -41,18 +42,11 @@ public class UserController : Controller
|
|||||||
if (isMe && !loggedUser.HasValue) return Unauthorized();
|
if (isMe && !loggedUser.HasValue) return Unauthorized();
|
||||||
|
|
||||||
var requestedId = isMe ? loggedUser!.Value : id.FromBase58Guid();
|
var requestedId = isMe ? loggedUser!.Value : id.FromBase58Guid();
|
||||||
if (loggedUser == requestedId)
|
var user = await _store.Get(requestedId);
|
||||||
{
|
if (loggedUser != requestedId && !(user?.Flags.HasFlag(UserFlags.PublicProfile) ?? false))
|
||||||
var pUser = await _store.Get<PrivateUser>(requestedId);
|
return NotFound();
|
||||||
if (pUser == default) return NotFound();
|
|
||||||
|
|
||||||
return Json(pUser);
|
return Json(user!.ToApiUser(isMe));
|
||||||
}
|
|
||||||
|
|
||||||
var user = await _store.Get<PublicUser>(requestedId);
|
|
||||||
if (!(user?.Flags.HasFlag(UserFlags.PublicProfile) ?? false)) return NotFound();
|
|
||||||
|
|
||||||
return Json(user);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -63,14 +57,20 @@ public class UserController : Controller
|
|||||||
/// <param name="user"></param>
|
/// <param name="user"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> UpdateUser([FromRoute] string id, [FromBody] PublicUser user)
|
public async Task<IActionResult> UpdateUser([FromRoute] string id, [FromBody] ApiUser user)
|
||||||
{
|
{
|
||||||
var loggedUser = await GetAuthorizedUser(id);
|
var loggedUser = await GetAuthorizedUser(id);
|
||||||
if (loggedUser == default) return Unauthorized();
|
if (loggedUser == default) return Unauthorized();
|
||||||
|
|
||||||
if (!loggedUser.Flags.HasFlag(UserFlags.EmailVerified)) return Forbid();
|
if (!loggedUser.Flags.HasFlag(UserFlags.EmailVerified)) return Forbid();
|
||||||
|
|
||||||
await _store.UpdateProfile(user);
|
loggedUser.Avatar = user.Avatar;
|
||||||
|
loggedUser.DisplayName = user.Name ?? "void user";
|
||||||
|
loggedUser.Flags = UserFlags.EmailVerified | (user.PublicProfile ? UserFlags.PublicProfile : 0) |
|
||||||
|
(user.PublicUploads ? UserFlags.PublicUploads : 0);
|
||||||
|
|
||||||
|
await _store.UpdateProfile(loggedUser);
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,13 +102,13 @@ public class UserController : Controller
|
|||||||
|
|
||||||
var results = await _userUploads.ListFiles(id.FromBase58Guid(), request);
|
var results = await _userUploads.ListFiles(id.FromBase58Guid(), request);
|
||||||
var files = await results.Results.ToListAsync();
|
var files = await results.Results.ToListAsync();
|
||||||
var fileInfo = await Task.WhenAll(files.Select(a => _fileInfoManager.Get(a).AsTask()));
|
var fileInfo = await _fileInfoManager.Get(files.ToArray(), false);
|
||||||
return Json(new RenderedResults<PublicVoidFile>()
|
return Json(new RenderedResults<VoidFileResponse>()
|
||||||
{
|
{
|
||||||
PageSize = results.PageSize,
|
PageSize = results.PageSize,
|
||||||
Page = results.Page,
|
Page = results.Page,
|
||||||
TotalResults = results.TotalResults,
|
TotalResults = results.TotalResults,
|
||||||
Results = fileInfo.Where(a => a != null).ToList()!
|
Results = fileInfo.ToList()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,21 +148,21 @@ public class UserController : Controller
|
|||||||
if (!await _emailVerification.VerifyCode(user, token)) return BadRequest();
|
if (!await _emailVerification.VerifyCode(user, token)) return BadRequest();
|
||||||
|
|
||||||
user.Flags |= UserFlags.EmailVerified;
|
user.Flags |= UserFlags.EmailVerified;
|
||||||
await _store.UpdateProfile(user.ToPublic());
|
await _store.UpdateProfile(user);
|
||||||
return Accepted();
|
return Accepted();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<InternalUser?> GetAuthorizedUser(string id)
|
private async Task<User?> GetAuthorizedUser(string id)
|
||||||
{
|
{
|
||||||
var loggedUser = HttpContext.GetUserId();
|
var loggedUser = HttpContext.GetUserId();
|
||||||
var gid = id.FromBase58Guid();
|
var gid = id.FromBase58Guid();
|
||||||
var user = await _store.Get<InternalUser>(gid);
|
var user = await _store.Get(gid);
|
||||||
return user?.Id != loggedUser ? default : user;
|
return user?.Id != loggedUser ? default : user;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<InternalUser?> GetRequestedUser(string id)
|
private async Task<User?> GetRequestedUser(string id)
|
||||||
{
|
{
|
||||||
var gid = id.FromBase58Guid();
|
var gid = id.FromBase58Guid();
|
||||||
return await _store.Get<InternalUser>(gid);
|
return await _store.Get(gid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11
VoidCat/Database/ApiKey.cs
Normal file
11
VoidCat/Database/ApiKey.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace VoidCat.Database;
|
||||||
|
|
||||||
|
public class ApiKey
|
||||||
|
{
|
||||||
|
public Guid Id { get; init; } = Guid.NewGuid();
|
||||||
|
public Guid UserId { get; init; }
|
||||||
|
public User User { get; init; } = null!;
|
||||||
|
public string Token { get; init; } = null!;
|
||||||
|
public DateTime Expiry { get; init; }
|
||||||
|
public DateTime Created { get; init; }
|
||||||
|
}
|
25
VoidCat/Database/Configurations/ApiKeyConfiguration.cs
Normal file
25
VoidCat/Database/Configurations/ApiKeyConfiguration.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace VoidCat.Database.Configurations;
|
||||||
|
|
||||||
|
public class ApiKeyConfiguration : IEntityTypeConfiguration<ApiKey>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<ApiKey> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("ApiKey");
|
||||||
|
builder.HasKey(a => a.Id);
|
||||||
|
builder.Property(a => a.Token)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Expiry)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Created)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.HasOne(a => a.User)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(a => a.UserId);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace VoidCat.Database.Configurations;
|
||||||
|
|
||||||
|
public class EmailVerficationConfiguration : IEntityTypeConfiguration<EmailVerification>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<EmailVerification> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("EmailVerification");
|
||||||
|
builder.HasKey(a => a.Id);
|
||||||
|
|
||||||
|
builder.Property(a => a.Code)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Expires)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.HasOne(a => a.User)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(a => a.UserId);
|
||||||
|
}
|
||||||
|
}
|
39
VoidCat/Database/Configurations/FileConfiguration.cs
Normal file
39
VoidCat/Database/Configurations/FileConfiguration.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace VoidCat.Database.Configurations;
|
||||||
|
|
||||||
|
public class FileConfiguration : IEntityTypeConfiguration<File>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<File> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("Files");
|
||||||
|
builder.HasKey(a => a.Id);
|
||||||
|
builder.Property(a => a.Name);
|
||||||
|
builder.Property(a => a.Size)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Uploaded)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Description);
|
||||||
|
builder.Property(a => a.MimeType)
|
||||||
|
.IsRequired()
|
||||||
|
.HasDefaultValue("application/octet-stream");
|
||||||
|
|
||||||
|
builder.Property(a => a.Digest);
|
||||||
|
builder.Property(a => a.EditSecret)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Expires);
|
||||||
|
|
||||||
|
builder.Property(a => a.Storage)
|
||||||
|
.IsRequired()
|
||||||
|
.HasDefaultValue("local-disk");
|
||||||
|
|
||||||
|
builder.Property(a => a.EncryptionParams);
|
||||||
|
builder.Property(a => a.MagnetLink);
|
||||||
|
|
||||||
|
builder.HasIndex(a => a.Uploaded);
|
||||||
|
}
|
||||||
|
}
|
28
VoidCat/Database/Configurations/PaywallConfiguration.cs
Normal file
28
VoidCat/Database/Configurations/PaywallConfiguration.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace VoidCat.Database.Configurations;
|
||||||
|
|
||||||
|
public class PaywallConfiguration : IEntityTypeConfiguration<Paywall>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<Paywall> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("Payment");
|
||||||
|
builder.HasKey(a => a.Id);
|
||||||
|
builder.Property(a => a.Service)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Currency)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Amount)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Required)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.HasOne(a => a.File)
|
||||||
|
.WithOne(a => a.Paywall)
|
||||||
|
.HasForeignKey<Paywall>();
|
||||||
|
}
|
||||||
|
}
|
30
VoidCat/Database/Configurations/PaywallOrderConfiguration.cs
Normal file
30
VoidCat/Database/Configurations/PaywallOrderConfiguration.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace VoidCat.Database.Configurations;
|
||||||
|
|
||||||
|
public class PaywallOrderConfiguration : IEntityTypeConfiguration<PaywallOrder>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<PaywallOrder> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("PaymentOrder");
|
||||||
|
builder.HasKey(a => a.Id);
|
||||||
|
builder.Property(a => a.Service)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Currency)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Amount)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Status)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.HasIndex(a => a.Status);
|
||||||
|
|
||||||
|
builder.HasOne(a => a.File)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(a => a.FileId);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace VoidCat.Database.Configurations;
|
||||||
|
|
||||||
|
public class PaywallOrderLightningConfiguration : IEntityTypeConfiguration<PaywallOrderLightning>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<PaywallOrderLightning> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("PaymentOrderLightning");
|
||||||
|
builder.HasKey(a => a.OrderId);
|
||||||
|
builder.Property(a => a.Invoice)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Expire)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.HasOne(a => a.Order)
|
||||||
|
.WithOne(a => a.OrderLightning)
|
||||||
|
.HasForeignKey<PaywallOrderLightning>(a => a.OrderId);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace VoidCat.Database.Configurations;
|
||||||
|
|
||||||
|
public class PaywallStrikeConfiguration : IEntityTypeConfiguration<PaywallStrike>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<PaywallStrike> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("PaymentStrike");
|
||||||
|
builder.HasKey(a => a.Id);
|
||||||
|
builder.Property(a => a.Handle)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.HasOne(a => a.Paywall)
|
||||||
|
.WithOne(a => a.PaywallStrike)
|
||||||
|
.HasForeignKey<PaywallStrike>();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace VoidCat.Database.Configurations;
|
||||||
|
|
||||||
|
public class UserAuthTokenConfiguration : IEntityTypeConfiguration<UserAuthToken>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<UserAuthToken> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("UsersAuthToken");
|
||||||
|
builder.HasKey(a => a.Id);
|
||||||
|
builder.Property(a => a.Provider)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.AccessToken)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.TokenType)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Expires)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.RefreshToken)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Scope)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.IdToken);
|
||||||
|
|
||||||
|
builder.HasOne(a => a.User)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(a => a.UserId);
|
||||||
|
}
|
||||||
|
}
|
38
VoidCat/Database/Configurations/UserConfiguration.cs
Normal file
38
VoidCat/Database/Configurations/UserConfiguration.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace VoidCat.Database.Configurations;
|
||||||
|
|
||||||
|
public class UserConfiguration : IEntityTypeConfiguration<User>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<User> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("Users");
|
||||||
|
builder.HasKey(a => a.Id);
|
||||||
|
builder.Property(a => a.Email)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Password)
|
||||||
|
.IsRequired(false);
|
||||||
|
|
||||||
|
builder.Property(a => a.Created)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.LastLogin);
|
||||||
|
builder.Property(a => a.DisplayName)
|
||||||
|
.IsRequired()
|
||||||
|
.HasDefaultValue("void user");
|
||||||
|
|
||||||
|
builder.Property(a => a.Flags)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Storage)
|
||||||
|
.IsRequired()
|
||||||
|
.HasDefaultValue("local-disk");
|
||||||
|
|
||||||
|
builder.Property(a => a.AuthType)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.HasIndex(a => a.Email);
|
||||||
|
}
|
||||||
|
}
|
21
VoidCat/Database/Configurations/UserFileConfiguration.cs
Normal file
21
VoidCat/Database/Configurations/UserFileConfiguration.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace VoidCat.Database.Configurations;
|
||||||
|
|
||||||
|
public class UserFileConfiguration : IEntityTypeConfiguration<UserFile>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<UserFile> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("UserFiles");
|
||||||
|
builder.HasKey(a => new {a.UserId, a.FileId});
|
||||||
|
|
||||||
|
builder.HasOne(a => a.User)
|
||||||
|
.WithMany(a => a.UserFiles)
|
||||||
|
.HasForeignKey(a => a.UserId);
|
||||||
|
|
||||||
|
builder.HasOne(a => a.File)
|
||||||
|
.WithOne()
|
||||||
|
.HasForeignKey<UserFile>(a => a.FileId);
|
||||||
|
}
|
||||||
|
}
|
20
VoidCat/Database/Configurations/UserRolesConfiguration.cs
Normal file
20
VoidCat/Database/Configurations/UserRolesConfiguration.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace VoidCat.Database.Configurations;
|
||||||
|
|
||||||
|
public class UserRolesConfiguration : IEntityTypeConfiguration<UserRole>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<UserRole> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("UserRoles");
|
||||||
|
builder.HasKey(a => new {a.UserId, a.Role});
|
||||||
|
|
||||||
|
builder.Property(a => a.Role)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.HasOne(a => a.User)
|
||||||
|
.WithMany(a => a.Roles)
|
||||||
|
.HasForeignKey(a => a.UserId);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace VoidCat.Database.Configurations;
|
||||||
|
|
||||||
|
public class VirusScanResultConfiguration : IEntityTypeConfiguration<VirusScanResult>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<VirusScanResult> builder)
|
||||||
|
{
|
||||||
|
builder.ToTable("VirusScanResult");
|
||||||
|
builder.HasKey(a => a.Id);
|
||||||
|
builder.Property(a => a.Scanner)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Score)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(a => a.Names);
|
||||||
|
|
||||||
|
builder.HasOne(a => a.File)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(a => a.FileId);
|
||||||
|
}
|
||||||
|
}
|
11
VoidCat/Database/EmailVerification.cs
Normal file
11
VoidCat/Database/EmailVerification.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace VoidCat.Database;
|
||||||
|
|
||||||
|
public class EmailVerification
|
||||||
|
{
|
||||||
|
public Guid Id { get; init; } = Guid.NewGuid();
|
||||||
|
public Guid UserId { get; init; }
|
||||||
|
public User User { get; init; } = null!;
|
||||||
|
|
||||||
|
public Guid Code { get; init; } = Guid.NewGuid();
|
||||||
|
public DateTime Expires { get; init; }
|
||||||
|
}
|
19
VoidCat/Database/File.cs
Normal file
19
VoidCat/Database/File.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
namespace VoidCat.Database;
|
||||||
|
|
||||||
|
public record File
|
||||||
|
{
|
||||||
|
public Guid Id { get; init; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
public ulong Size { get; init; }
|
||||||
|
public DateTime Uploaded { get; init; } = DateTime.UtcNow;
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public string MimeType { get; set; } = "application/octet-stream";
|
||||||
|
public string? Digest { get; init; }
|
||||||
|
public Guid EditSecret { get; init; }
|
||||||
|
public DateTime? Expires { get; set; }
|
||||||
|
public string Storage { get; set; } = "local-disk";
|
||||||
|
public string? EncryptionParams { get; set; }
|
||||||
|
public string? MagnetLink { get; set; }
|
||||||
|
|
||||||
|
public Paywall? Paywall { get; init; }
|
||||||
|
}
|
47
VoidCat/Database/Paywall.cs
Normal file
47
VoidCat/Database/Paywall.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
namespace VoidCat.Database;
|
||||||
|
|
||||||
|
public enum PaywallCurrency : byte
|
||||||
|
{
|
||||||
|
BTC = 0,
|
||||||
|
USD = 1,
|
||||||
|
EUR = 2,
|
||||||
|
GBP = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PaywallService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No service
|
||||||
|
/// </summary>
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Strike.me payment service
|
||||||
|
/// </summary>
|
||||||
|
Strike,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LNProxy payment
|
||||||
|
/// </summary>
|
||||||
|
LnProxy,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Paywall
|
||||||
|
{
|
||||||
|
public Guid Id { get; init; } = Guid.NewGuid();
|
||||||
|
public File File { get; init; } = null!;
|
||||||
|
public PaywallService Service { get; init; }
|
||||||
|
public PaywallCurrency Currency { get; init; }
|
||||||
|
public decimal Amount { get; init; }
|
||||||
|
|
||||||
|
public bool Required { get; init; } = true;
|
||||||
|
|
||||||
|
public PaywallStrike? PaywallStrike { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PaywallStrike
|
||||||
|
{
|
||||||
|
public Guid Id { get; init; } = Guid.NewGuid();
|
||||||
|
public Paywall Paywall { get; init; } = null!;
|
||||||
|
public string Handle { get; init; } = null!;
|
||||||
|
}
|
40
VoidCat/Database/PaywallOrder.cs
Normal file
40
VoidCat/Database/PaywallOrder.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
namespace VoidCat.Database;
|
||||||
|
|
||||||
|
public class PaywallOrder
|
||||||
|
{
|
||||||
|
public Guid Id { get; init; }
|
||||||
|
public Guid FileId { get; init; }
|
||||||
|
public File File { get; init; } = null!;
|
||||||
|
public PaywallService Service { get; init; }
|
||||||
|
public PaywallCurrency Currency { get; init; }
|
||||||
|
public decimal Amount { get; init; }
|
||||||
|
public PaywallOrderStatus Status { get; set; }
|
||||||
|
|
||||||
|
public PaywallOrderLightning? OrderLightning { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PaywallOrderStatus : byte
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Invoice is not paid yet
|
||||||
|
/// </summary>
|
||||||
|
Unpaid = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoice is paid
|
||||||
|
/// </summary>
|
||||||
|
Paid = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoice has expired and cant be paid
|
||||||
|
/// </summary>
|
||||||
|
Expired = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PaywallOrderLightning
|
||||||
|
{
|
||||||
|
public Guid OrderId { get; init; }
|
||||||
|
public PaywallOrder Order { get; init; } = null!;
|
||||||
|
public string Invoice { get; init; } = null!;
|
||||||
|
public DateTime Expire { get; init; }
|
||||||
|
}
|
114
VoidCat/Database/User.cs
Normal file
114
VoidCat/Database/User.cs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
namespace VoidCat.Database;
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum UserFlags
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Profile is public
|
||||||
|
/// </summary>
|
||||||
|
PublicProfile = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uploads list is public
|
||||||
|
/// </summary>
|
||||||
|
PublicUploads = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Account has email verified
|
||||||
|
/// </summary>
|
||||||
|
EmailVerified = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class User
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Unique Id of the user
|
||||||
|
/// </summary>
|
||||||
|
public Guid Id { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Users email address
|
||||||
|
/// </summary>
|
||||||
|
public string Email { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Users password (hashed)
|
||||||
|
/// </summary>
|
||||||
|
public string? Password { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When the user account was created
|
||||||
|
/// </summary>
|
||||||
|
public DateTime Created { get; init; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The last time the user logged in
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? LastLogin { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Display avatar for user profile
|
||||||
|
/// </summary>
|
||||||
|
public string? Avatar { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Display name for user profile
|
||||||
|
/// </summary>
|
||||||
|
public string DisplayName { get; set; } = "void user";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Profile flags
|
||||||
|
/// </summary>
|
||||||
|
public UserFlags Flags { get; set; } = UserFlags.PublicProfile;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Users storage system for new uploads
|
||||||
|
/// </summary>
|
||||||
|
public string Storage { get; set; } = "local-disk";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Account authentication type
|
||||||
|
/// </summary>
|
||||||
|
public UserAuthType AuthType { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Roles assigned to this user which grant them extra permissions
|
||||||
|
/// </summary>
|
||||||
|
public List<UserRole> Roles { get; init; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All files uploaded by this user
|
||||||
|
/// </summary>
|
||||||
|
public List<UserFile> UserFiles { get; init; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UserRole
|
||||||
|
{
|
||||||
|
public Guid UserId { get; init; }
|
||||||
|
public User User { get; init; }
|
||||||
|
|
||||||
|
public string Role { get; init; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum UserAuthType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Encrypted password
|
||||||
|
/// </summary>
|
||||||
|
Internal = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// PGP challenge
|
||||||
|
/// </summary>
|
||||||
|
PGP = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// OAuth2 token
|
||||||
|
/// </summary>
|
||||||
|
OAuth2 = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lightning node challenge
|
||||||
|
/// </summary>
|
||||||
|
Lightning = 3
|
||||||
|
}
|
15
VoidCat/Database/UserAuthToken.cs
Normal file
15
VoidCat/Database/UserAuthToken.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
namespace VoidCat.Database;
|
||||||
|
|
||||||
|
public class UserAuthToken
|
||||||
|
{
|
||||||
|
public Guid Id { get; init; }
|
||||||
|
public Guid UserId { get; init; }
|
||||||
|
public User User { get; init; } = null!;
|
||||||
|
public string Provider { get; init; } = null!;
|
||||||
|
public string AccessToken { get; init; } = null!;
|
||||||
|
public string TokenType { get; init; } = null!;
|
||||||
|
public DateTime Expires { get; init; }
|
||||||
|
public string RefreshToken { get; init; } = null!;
|
||||||
|
public string Scope { get; init; } = null!;
|
||||||
|
public string? IdToken { get; init; }
|
||||||
|
}
|
10
VoidCat/Database/UserFile.cs
Normal file
10
VoidCat/Database/UserFile.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace VoidCat.Database;
|
||||||
|
|
||||||
|
public class UserFile
|
||||||
|
{
|
||||||
|
public Guid FileId { get; init; }
|
||||||
|
public File File { get; init; }
|
||||||
|
|
||||||
|
public Guid UserId { get; init; }
|
||||||
|
public User User { get; init; }
|
||||||
|
}
|
12
VoidCat/Database/VirusScanResult.cs
Normal file
12
VoidCat/Database/VirusScanResult.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
namespace VoidCat.Database;
|
||||||
|
|
||||||
|
public class VirusScanResult
|
||||||
|
{
|
||||||
|
public Guid Id { get; init; } = Guid.NewGuid();
|
||||||
|
public Guid FileId { get; init; }
|
||||||
|
public File File { get; init; } = null!;
|
||||||
|
public DateTime ScanTime { get; init; }
|
||||||
|
public string Scanner { get; init; } = null!;
|
||||||
|
public decimal Score { get; init; }
|
||||||
|
public string Names { get; init; } = null!;
|
||||||
|
}
|
353
VoidCat/Migrations/20230503115108_Init.Designer.cs
generated
Normal file
353
VoidCat/Migrations/20230503115108_Init.Designer.cs
generated
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
using VoidCat.Services;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace VoidCat.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(VoidContext))]
|
||||||
|
[Migration("20230503115108_Init")]
|
||||||
|
partial class Init
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "7.0.5")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.ApiKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Expiry")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Token")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("ApiKey", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.EmailVerification", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Code")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Expires")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("EmailVerification", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.File", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Digest")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("EditSecret")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("EncryptionParams")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("Expires")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("MagnetLink")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("MimeType")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasDefaultValue("application/octet-stream");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<decimal>("Size")
|
||||||
|
.HasColumnType("numeric(20,0)");
|
||||||
|
|
||||||
|
b.Property<string>("Storage")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasDefaultValue("local-disk");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Uploaded")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Uploaded");
|
||||||
|
|
||||||
|
b.ToTable("Files", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<int>("AuthType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Avatar")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasDefaultValue("void user");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Flags")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastLogin")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Storage")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasDefaultValue("local-disk");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Email");
|
||||||
|
|
||||||
|
b.ToTable("Users", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserAuthToken", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("AccessToken")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Expires")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("IdToken")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Provider")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("RefreshToken")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Scope")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("TokenType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("UsersAuthToken", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserFile", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("FileId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "FileId");
|
||||||
|
|
||||||
|
b.HasIndex("FileId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("UserFiles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserRole", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Role")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "Role");
|
||||||
|
|
||||||
|
b.ToTable("UserRoles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.VirusScanResult", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("FileId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Names")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ScanTime")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Scanner")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<decimal>("Score")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("FileId");
|
||||||
|
|
||||||
|
b.ToTable("VirusScanResult", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.ApiKey", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.EmailVerification", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserAuthToken", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserFile", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.File", "File")
|
||||||
|
.WithOne()
|
||||||
|
.HasForeignKey("VoidCat.Database.UserFile", "FileId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("VoidCat.Database.User", "User")
|
||||||
|
.WithMany("UserFiles")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("File");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserRole", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.User", "User")
|
||||||
|
.WithMany("Roles")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.VirusScanResult", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.File", "File")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("FileId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("File");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.User", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Roles");
|
||||||
|
|
||||||
|
b.Navigation("UserFiles");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
223
VoidCat/Migrations/20230503115108_Init.cs
Normal file
223
VoidCat/Migrations/20230503115108_Init.cs
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace VoidCat.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Init : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Files",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "text", nullable: true),
|
||||||
|
Size = table.Column<decimal>(type: "numeric(20,0)", nullable: false),
|
||||||
|
Uploaded = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
Description = table.Column<string>(type: "text", nullable: true),
|
||||||
|
MimeType = table.Column<string>(type: "text", nullable: false, defaultValue: "application/octet-stream"),
|
||||||
|
Digest = table.Column<string>(type: "text", nullable: true),
|
||||||
|
EditSecret = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
Expires = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
Storage = table.Column<string>(type: "text", nullable: false, defaultValue: "local-disk"),
|
||||||
|
EncryptionParams = table.Column<string>(type: "text", nullable: true),
|
||||||
|
MagnetLink = table.Column<string>(type: "text", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Files", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Users",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
Email = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Password = table.Column<string>(type: "text", nullable: true),
|
||||||
|
Created = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
LastLogin = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||||
|
Avatar = table.Column<string>(type: "text", nullable: true),
|
||||||
|
DisplayName = table.Column<string>(type: "text", nullable: false, defaultValue: "void user"),
|
||||||
|
Flags = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Storage = table.Column<string>(type: "text", nullable: false, defaultValue: "local-disk"),
|
||||||
|
AuthType = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Users", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "VirusScanResult",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
FileId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
ScanTime = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
Scanner = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Score = table.Column<decimal>(type: "numeric", nullable: false),
|
||||||
|
Names = table.Column<string>(type: "text", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_VirusScanResult", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_VirusScanResult_Files_FileId",
|
||||||
|
column: x => x.FileId,
|
||||||
|
principalTable: "Files",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ApiKey",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
Token = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Expiry = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
Created = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ApiKey", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ApiKey_Users_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "UserFiles",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
FileId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
UserId = table.Column<Guid>(type: "uuid", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_UserFiles", x => new { x.UserId, x.FileId });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_UserFiles_Files_FileId",
|
||||||
|
column: x => x.FileId,
|
||||||
|
principalTable: "Files",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_UserFiles_Users_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "UserRoles",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
Role = table.Column<string>(type: "text", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_UserRoles", x => new { x.UserId, x.Role });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_UserRoles_Users_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "UsersAuthToken",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
Provider = table.Column<string>(type: "text", nullable: false),
|
||||||
|
AccessToken = table.Column<string>(type: "text", nullable: false),
|
||||||
|
TokenType = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Expires = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||||
|
RefreshToken = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Scope = table.Column<string>(type: "text", nullable: false),
|
||||||
|
IdToken = table.Column<string>(type: "text", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_UsersAuthToken", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_UsersAuthToken_Users_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ApiKey_UserId",
|
||||||
|
table: "ApiKey",
|
||||||
|
column: "UserId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Files_Uploaded",
|
||||||
|
table: "Files",
|
||||||
|
column: "Uploaded");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_UserFiles_FileId",
|
||||||
|
table: "UserFiles",
|
||||||
|
column: "FileId",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Users_Email",
|
||||||
|
table: "Users",
|
||||||
|
column: "Email");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_UsersAuthToken_UserId",
|
||||||
|
table: "UsersAuthToken",
|
||||||
|
column: "UserId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_VirusScanResult_FileId",
|
||||||
|
table: "VirusScanResult",
|
||||||
|
column: "FileId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "ApiKey");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "UserFiles");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "UserRoles");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "UsersAuthToken");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "VirusScanResult");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Users");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Files");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
497
VoidCat/Migrations/20230503120701_Paywall.Designer.cs
generated
Normal file
497
VoidCat/Migrations/20230503120701_Paywall.Designer.cs
generated
Normal file
@ -0,0 +1,497 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
using VoidCat.Services;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace VoidCat.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(VoidContext))]
|
||||||
|
[Migration("20230503120701_Paywall")]
|
||||||
|
partial class Paywall
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "7.0.5")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.ApiKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Expiry")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Token")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("ApiKey", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.EmailVerification", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Code")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Expires")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("EmailVerification", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.File", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Digest")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("EditSecret")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("EncryptionParams")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("Expires")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("MagnetLink")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("MimeType")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasDefaultValue("application/octet-stream");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<decimal>("Size")
|
||||||
|
.HasColumnType("numeric(20,0)");
|
||||||
|
|
||||||
|
b.Property<string>("Storage")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasDefaultValue("local-disk");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Uploaded")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Uploaded");
|
||||||
|
|
||||||
|
b.ToTable("Files", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.Paywall", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<byte>("Currency")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<bool>("Required")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("boolean")
|
||||||
|
.HasDefaultValue(true);
|
||||||
|
|
||||||
|
b.Property<int>("Service")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Payment", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallOrder", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<byte>("Currency")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<Guid>("FileId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<int>("Service")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<byte>("Status")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("FileId");
|
||||||
|
|
||||||
|
b.HasIndex("Status");
|
||||||
|
|
||||||
|
b.ToTable("PaymentOrder", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallOrderLightning", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("OrderId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Expire")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Invoice")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("OrderId");
|
||||||
|
|
||||||
|
b.ToTable("PaymentOrderLightning", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallStrike", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Handle")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("PaymentStrike", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<int>("AuthType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Avatar")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasDefaultValue("void user");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Flags")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastLogin")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Storage")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasDefaultValue("local-disk");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Email");
|
||||||
|
|
||||||
|
b.ToTable("Users", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserAuthToken", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("AccessToken")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Expires")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("IdToken")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Provider")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("RefreshToken")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Scope")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("TokenType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("UsersAuthToken", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserFile", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("FileId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "FileId");
|
||||||
|
|
||||||
|
b.HasIndex("FileId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("UserFiles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserRole", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Role")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "Role");
|
||||||
|
|
||||||
|
b.ToTable("UserRoles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.VirusScanResult", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("FileId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Names")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ScanTime")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Scanner")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<decimal>("Score")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("FileId");
|
||||||
|
|
||||||
|
b.ToTable("VirusScanResult", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.ApiKey", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.EmailVerification", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.Paywall", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.File", "File")
|
||||||
|
.WithOne("Paywall")
|
||||||
|
.HasForeignKey("VoidCat.Database.Paywall", "Id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("File");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallOrder", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.File", "File")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("FileId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("File");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallOrderLightning", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.PaywallOrder", "Order")
|
||||||
|
.WithOne("OrderLightning")
|
||||||
|
.HasForeignKey("VoidCat.Database.PaywallOrderLightning", "OrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Order");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallStrike", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.Paywall", "Paywall")
|
||||||
|
.WithOne("PaywallStrike")
|
||||||
|
.HasForeignKey("VoidCat.Database.PaywallStrike", "Id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Paywall");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserAuthToken", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserFile", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.File", "File")
|
||||||
|
.WithOne()
|
||||||
|
.HasForeignKey("VoidCat.Database.UserFile", "FileId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("VoidCat.Database.User", "User")
|
||||||
|
.WithMany("UserFiles")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("File");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserRole", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.User", "User")
|
||||||
|
.WithMany("Roles")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.VirusScanResult", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.File", "File")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("FileId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("File");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.File", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Paywall");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.Paywall", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("PaywallStrike");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallOrder", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("OrderLightning");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.User", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Roles");
|
||||||
|
|
||||||
|
b.Navigation("UserFiles");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
121
VoidCat/Migrations/20230503120701_Paywall.cs
Normal file
121
VoidCat/Migrations/20230503120701_Paywall.cs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace VoidCat.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Paywall : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Payment",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
Service = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Currency = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
|
Amount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||||
|
Required = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Payment", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Payment_Files_Id",
|
||||||
|
column: x => x.Id,
|
||||||
|
principalTable: "Files",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "PaymentOrder",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
FileId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
Service = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
Currency = table.Column<byte>(type: "smallint", nullable: false),
|
||||||
|
Amount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||||
|
Status = table.Column<byte>(type: "smallint", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_PaymentOrder", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_PaymentOrder_Files_FileId",
|
||||||
|
column: x => x.FileId,
|
||||||
|
principalTable: "Files",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "PaymentStrike",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
Handle = table.Column<string>(type: "text", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_PaymentStrike", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_PaymentStrike_Payment_Id",
|
||||||
|
column: x => x.Id,
|
||||||
|
principalTable: "Payment",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "PaymentOrderLightning",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
OrderId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
Invoice = table.Column<string>(type: "text", nullable: false),
|
||||||
|
Expire = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_PaymentOrderLightning", x => x.OrderId);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_PaymentOrderLightning_PaymentOrder_OrderId",
|
||||||
|
column: x => x.OrderId,
|
||||||
|
principalTable: "PaymentOrder",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PaymentOrder_FileId",
|
||||||
|
table: "PaymentOrder",
|
||||||
|
column: "FileId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PaymentOrder_Status",
|
||||||
|
table: "PaymentOrder",
|
||||||
|
column: "Status");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "PaymentOrderLightning");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "PaymentStrike");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "PaymentOrder");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Payment");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
501
VoidCat/Migrations/20230508205513_EmailVerificationId.Designer.cs
generated
Normal file
501
VoidCat/Migrations/20230508205513_EmailVerificationId.Designer.cs
generated
Normal file
@ -0,0 +1,501 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
using VoidCat.Services;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace VoidCat.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(VoidContext))]
|
||||||
|
[Migration("20230508205513_EmailVerificationId")]
|
||||||
|
partial class EmailVerificationId
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "7.0.5")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.ApiKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Expiry")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Token")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("ApiKey", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.EmailVerification", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("Code")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Expires")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("EmailVerification", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.File", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Digest")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("EditSecret")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("EncryptionParams")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("Expires")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("MagnetLink")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("MimeType")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasDefaultValue("application/octet-stream");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<decimal>("Size")
|
||||||
|
.HasColumnType("numeric(20,0)");
|
||||||
|
|
||||||
|
b.Property<string>("Storage")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasDefaultValue("local-disk");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Uploaded")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Uploaded");
|
||||||
|
|
||||||
|
b.ToTable("Files", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.Paywall", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<byte>("Currency")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<bool>("Required")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<int>("Service")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Payment", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallOrder", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<byte>("Currency")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<Guid>("FileId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<int>("Service")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<byte>("Status")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("FileId");
|
||||||
|
|
||||||
|
b.HasIndex("Status");
|
||||||
|
|
||||||
|
b.ToTable("PaymentOrder", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallOrderLightning", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("OrderId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Expire")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Invoice")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("OrderId");
|
||||||
|
|
||||||
|
b.ToTable("PaymentOrderLightning", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallStrike", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Handle")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("PaymentStrike", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<int>("AuthType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Avatar")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasDefaultValue("void user");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Flags")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastLogin")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Storage")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasDefaultValue("local-disk");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Email");
|
||||||
|
|
||||||
|
b.ToTable("Users", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserAuthToken", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("AccessToken")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Expires")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("IdToken")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Provider")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("RefreshToken")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Scope")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("TokenType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("UsersAuthToken", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserFile", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("FileId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "FileId");
|
||||||
|
|
||||||
|
b.HasIndex("FileId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("UserFiles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserRole", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Role")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "Role");
|
||||||
|
|
||||||
|
b.ToTable("UserRoles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.VirusScanResult", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("FileId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Names")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ScanTime")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Scanner")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<decimal>("Score")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("FileId");
|
||||||
|
|
||||||
|
b.ToTable("VirusScanResult", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.ApiKey", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.EmailVerification", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.Paywall", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.File", "File")
|
||||||
|
.WithOne("Paywall")
|
||||||
|
.HasForeignKey("VoidCat.Database.Paywall", "Id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("File");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallOrder", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.File", "File")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("FileId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("File");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallOrderLightning", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.PaywallOrder", "Order")
|
||||||
|
.WithOne("OrderLightning")
|
||||||
|
.HasForeignKey("VoidCat.Database.PaywallOrderLightning", "OrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Order");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallStrike", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.Paywall", "Paywall")
|
||||||
|
.WithOne("PaywallStrike")
|
||||||
|
.HasForeignKey("VoidCat.Database.PaywallStrike", "Id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Paywall");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserAuthToken", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserFile", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.File", "File")
|
||||||
|
.WithOne()
|
||||||
|
.HasForeignKey("VoidCat.Database.UserFile", "FileId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("VoidCat.Database.User", "User")
|
||||||
|
.WithMany("UserFiles")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("File");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserRole", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.User", "User")
|
||||||
|
.WithMany("Roles")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.VirusScanResult", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.File", "File")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("FileId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("File");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.File", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Paywall");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.Paywall", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("PaywallStrike");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallOrder", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("OrderLightning");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.User", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Roles");
|
||||||
|
|
||||||
|
b.Navigation("UserFiles");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
VoidCat/Migrations/20230508205513_EmailVerificationId.cs
Normal file
63
VoidCat/Migrations/20230508205513_EmailVerificationId.cs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace VoidCat.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class EmailVerificationId : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "EmailVerification",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
Code = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
|
Expires = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_EmailVerification_Users_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<bool>(
|
||||||
|
name: "Required",
|
||||||
|
table: "Payment",
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
oldClrType: typeof(bool),
|
||||||
|
oldType: "boolean",
|
||||||
|
oldDefaultValue: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddPrimaryKey(
|
||||||
|
name: "PK_EmailVerification",
|
||||||
|
table: "EmailVerification",
|
||||||
|
column: "Id");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable("EmailVerification");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<bool>(
|
||||||
|
name: "Required",
|
||||||
|
table: "Payment",
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: true,
|
||||||
|
oldClrType: typeof(bool),
|
||||||
|
oldType: "boolean");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
498
VoidCat/Migrations/VoidContextModelSnapshot.cs
Normal file
498
VoidCat/Migrations/VoidContextModelSnapshot.cs
Normal file
@ -0,0 +1,498 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
using VoidCat.Services;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace VoidCat.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(VoidContext))]
|
||||||
|
partial class VoidContextModelSnapshot : ModelSnapshot
|
||||||
|
{
|
||||||
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "7.0.5")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.ApiKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Expiry")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Token")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("ApiKey", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.EmailVerification", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("Code")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Expires")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("EmailVerification", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.File", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Digest")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("EditSecret")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("EncryptionParams")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("Expires")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("MagnetLink")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("MimeType")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasDefaultValue("application/octet-stream");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<decimal>("Size")
|
||||||
|
.HasColumnType("numeric(20,0)");
|
||||||
|
|
||||||
|
b.Property<string>("Storage")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasDefaultValue("local-disk");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Uploaded")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Uploaded");
|
||||||
|
|
||||||
|
b.ToTable("Files", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.Paywall", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<byte>("Currency")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<bool>("Required")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<int>("Service")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Payment", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallOrder", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.Property<byte>("Currency")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.Property<Guid>("FileId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<int>("Service")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<byte>("Status")
|
||||||
|
.HasColumnType("smallint");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("FileId");
|
||||||
|
|
||||||
|
b.HasIndex("Status");
|
||||||
|
|
||||||
|
b.ToTable("PaymentOrder", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallOrderLightning", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("OrderId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Expire")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Invoice")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("OrderId");
|
||||||
|
|
||||||
|
b.ToTable("PaymentOrderLightning", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallStrike", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Handle")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("PaymentStrike", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<int>("AuthType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("Avatar")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayName")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasDefaultValue("void user");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("Flags")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastLogin")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Storage")
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasDefaultValue("local-disk");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Email");
|
||||||
|
|
||||||
|
b.ToTable("Users", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserAuthToken", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("AccessToken")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Expires")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("IdToken")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Provider")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("RefreshToken")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Scope")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("TokenType")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("UsersAuthToken", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserFile", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("FileId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "FileId");
|
||||||
|
|
||||||
|
b.HasIndex("FileId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("UserFiles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserRole", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Role")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "Role");
|
||||||
|
|
||||||
|
b.ToTable("UserRoles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.VirusScanResult", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<Guid>("FileId")
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<string>("Names")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<DateTime>("ScanTime")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("Scanner")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<decimal>("Score")
|
||||||
|
.HasColumnType("numeric");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("FileId");
|
||||||
|
|
||||||
|
b.ToTable("VirusScanResult", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.ApiKey", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.EmailVerification", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.Paywall", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.File", "File")
|
||||||
|
.WithOne("Paywall")
|
||||||
|
.HasForeignKey("VoidCat.Database.Paywall", "Id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("File");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallOrder", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.File", "File")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("FileId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("File");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallOrderLightning", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.PaywallOrder", "Order")
|
||||||
|
.WithOne("OrderLightning")
|
||||||
|
.HasForeignKey("VoidCat.Database.PaywallOrderLightning", "OrderId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Order");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallStrike", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.Paywall", "Paywall")
|
||||||
|
.WithOne("PaywallStrike")
|
||||||
|
.HasForeignKey("VoidCat.Database.PaywallStrike", "Id")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Paywall");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserAuthToken", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserFile", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.File", "File")
|
||||||
|
.WithOne()
|
||||||
|
.HasForeignKey("VoidCat.Database.UserFile", "FileId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("VoidCat.Database.User", "User")
|
||||||
|
.WithMany("UserFiles")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("File");
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.UserRole", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.User", "User")
|
||||||
|
.WithMany("Roles")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.VirusScanResult", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("VoidCat.Database.File", "File")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("FileId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("File");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.File", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Paywall");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.Paywall", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("PaywallStrike");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.PaywallOrder", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("OrderLightning");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("VoidCat.Database.User", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Roles");
|
||||||
|
|
||||||
|
b.Navigation("UserFiles");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
VoidCat/Model/AdminApiUser.cs
Normal file
7
VoidCat/Model/AdminApiUser.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace VoidCat.Model;
|
||||||
|
|
||||||
|
public class AdminApiUser : ApiUser
|
||||||
|
{
|
||||||
|
public string Storage { get; init; } = null!;
|
||||||
|
public string Email { get; init; } = null!;
|
||||||
|
}
|
51
VoidCat/Model/ApiUser.cs
Normal file
51
VoidCat/Model/ApiUser.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using VoidCat.Database;
|
||||||
|
|
||||||
|
namespace VoidCat.Model;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A user object which can be returned via the API
|
||||||
|
/// </summary>
|
||||||
|
public class ApiUser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Unique Id of the user
|
||||||
|
/// </summary>
|
||||||
|
[JsonConverter(typeof(Base58GuidConverter))]
|
||||||
|
public Guid Id { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Display name
|
||||||
|
/// </summary>
|
||||||
|
public string? Name { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Avatar
|
||||||
|
/// </summary>
|
||||||
|
public string? Avatar { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If profile can be viewed by anyone
|
||||||
|
/// </summary>
|
||||||
|
public bool PublicProfile { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the users uploads can be viewed by anyone
|
||||||
|
/// </summary>
|
||||||
|
public bool PublicUploads { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the account is not email verified
|
||||||
|
/// </summary>
|
||||||
|
public bool? NeedsVerification { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A list of roles the user has
|
||||||
|
/// </summary>
|
||||||
|
public List<string> Roles { get; init; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When the account was created
|
||||||
|
/// </summary>
|
||||||
|
public DateTime Created { get; init; }
|
||||||
|
}
|
@ -1,9 +0,0 @@
|
|||||||
namespace VoidCat.Model;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Email verification token
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="Id"></param>
|
|
||||||
/// <param name="User"></param>
|
|
||||||
/// <param name="Expires"></param>
|
|
||||||
public sealed record EmailVerificationCode(Guid User, Guid Code, DateTime Expires);
|
|
@ -6,8 +6,9 @@ using Amazon.Runtime;
|
|||||||
using Amazon.S3;
|
using Amazon.S3;
|
||||||
using BencodeNET.Objects;
|
using BencodeNET.Objects;
|
||||||
using BencodeNET.Torrents;
|
using BencodeNET.Torrents;
|
||||||
|
using VoidCat.Database;
|
||||||
using VoidCat.Model.Exceptions;
|
using VoidCat.Model.Exceptions;
|
||||||
using VoidCat.Model.User;
|
using File = VoidCat.Database.File;
|
||||||
|
|
||||||
namespace VoidCat.Model;
|
namespace VoidCat.Model;
|
||||||
|
|
||||||
@ -82,7 +83,7 @@ public static class Extensions
|
|||||||
return !string.IsNullOrEmpty(h.Value.ToString()) ? h.Value.ToString() : default;
|
return !string.IsNullOrEmpty(h.Value.ToString()) ? h.Value.ToString() : default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool CanEdit(this SecretFileMeta file, Guid? editSecret)
|
public static bool CanEdit(this File file, Guid? editSecret)
|
||||||
{
|
{
|
||||||
return file.EditSecret == editSecret;
|
return file.EditSecret == editSecret;
|
||||||
}
|
}
|
||||||
@ -231,12 +232,15 @@ public static class Extensions
|
|||||||
/// <param name="password"></param>
|
/// <param name="password"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
/// <exception cref="InvalidOperationException"></exception>
|
/// <exception cref="InvalidOperationException"></exception>
|
||||||
public static bool CheckPassword(this InternalUser vu, string password)
|
public static bool CheckPassword(this Database.User vu, string password)
|
||||||
{
|
{
|
||||||
if (vu.AuthType != AuthType.Internal)
|
if (vu.AuthType != UserAuthType.Internal)
|
||||||
throw new InvalidOperationException("User type is not internal, cannot check password!");
|
throw new InvalidOperationException("User type is not internal, cannot check password!");
|
||||||
|
|
||||||
var hashParts = vu.Password.Split(":");
|
if (string.IsNullOrEmpty(vu.Password))
|
||||||
|
throw new InvalidOperationException("User password is not set");
|
||||||
|
|
||||||
|
var hashParts = vu.Password!.Split(":");
|
||||||
return vu.Password == password.Hash(hashParts[0], hashParts.Length == 3 ? hashParts[1] : null);
|
return vu.Password == password.Hash(hashParts[0], hashParts.Length == 3 ? hashParts[1] : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,7 +249,7 @@ public static class Extensions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="oldMeta"></param>
|
/// <param name="oldMeta"></param>
|
||||||
/// <param name="meta"></param>
|
/// <param name="meta"></param>
|
||||||
public static void Patch(this FileMeta oldMeta, FileMeta meta)
|
public static void Patch(this File oldMeta, File meta)
|
||||||
{
|
{
|
||||||
oldMeta.Description = meta.Description ?? oldMeta.Description;
|
oldMeta.Description = meta.Description ?? oldMeta.Description;
|
||||||
oldMeta.Name = meta.Name ?? oldMeta.Name;
|
oldMeta.Name = meta.Name ?? oldMeta.Name;
|
||||||
@ -276,13 +280,14 @@ public static class Extensions
|
|||||||
public static bool HasGoogle(this VoidSettings settings)
|
public static bool HasGoogle(this VoidSettings settings)
|
||||||
=> settings.Google != null;
|
=> settings.Google != null;
|
||||||
|
|
||||||
public static async Task<Torrent> MakeTorrent(this FileMeta meta, Stream fileStream, Uri baseAddress, List<string> trackers)
|
public static async Task<Torrent> MakeTorrent(this VoidFileMeta meta, Guid id, Stream fileStream, Uri baseAddress,
|
||||||
|
List<string> trackers)
|
||||||
{
|
{
|
||||||
const int pieceSize = 262_144;
|
const int pieceSize = 262_144;
|
||||||
const int pieceHashLen = 20;
|
const int pieceHashLen = 20;
|
||||||
var webSeed = new UriBuilder(baseAddress)
|
var webSeed = new UriBuilder(baseAddress)
|
||||||
{
|
{
|
||||||
Path = $"/d/{meta.Id.ToBase58()}"
|
Path = $"/d/{id.ToBase58()}"
|
||||||
};
|
};
|
||||||
|
|
||||||
async Task<byte[]> BuildPieces()
|
async Task<byte[]> BuildPieces()
|
||||||
@ -310,7 +315,7 @@ public static class Extensions
|
|||||||
FileSize = (long)meta.Size
|
FileSize = (long)meta.Size
|
||||||
},
|
},
|
||||||
Comment = meta.Name,
|
Comment = meta.Name,
|
||||||
CreationDate = meta.Uploaded.UtcDateTime,
|
CreationDate = meta.Uploaded,
|
||||||
IsPrivate = false,
|
IsPrivate = false,
|
||||||
PieceSize = pieceSize,
|
PieceSize = pieceSize,
|
||||||
Pieces = await BuildPieces(),
|
Pieces = await BuildPieces(),
|
||||||
@ -324,4 +329,72 @@ public static class Extensions
|
|||||||
|
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public static VoidFileResponse ToResponse(this File f, bool withEditSecret)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Id = f.Id,
|
||||||
|
Metadata = f.ToMeta(withEditSecret)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VoidFileMeta ToMeta(this File f, bool withEditSecret)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Name = f.Name,
|
||||||
|
Description = f.Description,
|
||||||
|
Uploaded = f.Uploaded,
|
||||||
|
MimeType = f.MimeType,
|
||||||
|
Size = f.Size,
|
||||||
|
Digest = f.Digest,
|
||||||
|
Expires = f.Expires,
|
||||||
|
EditSecret = withEditSecret ? f.EditSecret : null,
|
||||||
|
Storage = f.Storage,
|
||||||
|
EncryptionParams = f.EncryptionParams,
|
||||||
|
MagnetLink = f.MagnetLink
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ApiUser ToApiUser(this User u, bool isSelf)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Id = u.Id,
|
||||||
|
Name = u.DisplayName,
|
||||||
|
Avatar = u.Avatar,
|
||||||
|
Created = u.Created,
|
||||||
|
NeedsVerification = isSelf ? !u.Flags.HasFlag(UserFlags.EmailVerified) : null,
|
||||||
|
PublicProfile = u.Flags.HasFlag(UserFlags.PublicProfile),
|
||||||
|
PublicUploads = u.Flags.HasFlag(UserFlags.PublicUploads),
|
||||||
|
Roles = u.Roles.Select(a => a.Role).ToList()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AdminApiUser ToAdminApiUser(this User u, bool isSelf)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Id = u.Id,
|
||||||
|
Name = u.DisplayName,
|
||||||
|
Avatar = u.Avatar,
|
||||||
|
Created = u.Created,
|
||||||
|
NeedsVerification = isSelf ? !u.Flags.HasFlag(UserFlags.EmailVerified) : null,
|
||||||
|
PublicProfile = u.Flags.HasFlag(UserFlags.PublicProfile),
|
||||||
|
PublicUploads = u.Flags.HasFlag(UserFlags.PublicUploads),
|
||||||
|
Roles = u.Roles.Select(a => a.Role).ToList(),
|
||||||
|
Storage = u.Storage,
|
||||||
|
Email = u.Email
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public static VirusStatus ToVirusStatus(this VirusScanResult r)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
ScanTime = r.ScanTime,
|
||||||
|
IsVirus = r.Score > 0.7m,
|
||||||
|
Names = r.Names
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
namespace VoidCat.Model;
|
namespace VoidCat.Model;
|
||||||
|
|
||||||
public sealed record IngressPayload(Stream InStream, SecretFileMeta Meta, int Segment, int TotalSegments, bool ShouldStripMetadata)
|
public sealed record IngressPayload(Stream InStream, Database.File Meta, int Segment, int TotalSegments, bool ShouldStripMetadata)
|
||||||
{
|
{
|
||||||
public Guid Id { get; init; } = Guid.NewGuid();
|
public Guid Id { get; init; } = Guid.NewGuid();
|
||||||
public Guid? EditSecret { get; init; }
|
public Guid? EditSecret { get; init; }
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace VoidCat.Model.Payments;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Money amount for payment orders
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="Amount"></param>
|
|
||||||
/// <param name="Currency"></param>
|
|
||||||
public record PaymentMoney(decimal Amount, PaymentCurrencies Currency);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Supported payment currencies
|
|
||||||
/// </summary>
|
|
||||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
|
||||||
public enum PaymentCurrencies : byte
|
|
||||||
{
|
|
||||||
BTC = 0,
|
|
||||||
USD = 1,
|
|
||||||
EUR = 2,
|
|
||||||
GBP = 3
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
namespace VoidCat.Model.Payments;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Status of payment order
|
|
||||||
/// </summary>
|
|
||||||
public enum PaymentOrderStatus : byte
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Invoice is not paid yet
|
|
||||||
/// </summary>
|
|
||||||
Unpaid,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoice is paid
|
|
||||||
/// </summary>
|
|
||||||
Paid,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoice has expired and cant be paid
|
|
||||||
/// </summary>
|
|
||||||
Expired
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Base payment order
|
|
||||||
/// </summary>
|
|
||||||
public class PaymentOrder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Unique id of the order
|
|
||||||
/// </summary>
|
|
||||||
public Guid Id { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// File id this order is for
|
|
||||||
/// </summary>
|
|
||||||
public Guid File { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Service used to generate this order
|
|
||||||
/// </summary>
|
|
||||||
public PaymentServices Service { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The price of the order
|
|
||||||
/// </summary>
|
|
||||||
public PaymentMoney Price { get; init; } = null!;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Current status of the order
|
|
||||||
/// </summary>
|
|
||||||
public PaymentOrderStatus Status { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A payment order lightning network invoice
|
|
||||||
/// </summary>
|
|
||||||
public class LightningPaymentOrder : PaymentOrder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Lightning invoice
|
|
||||||
/// </summary>
|
|
||||||
public string Invoice { get; init; } = null!;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Expire time of the order
|
|
||||||
/// </summary>
|
|
||||||
public DateTime Expire { get; init; }
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
namespace VoidCat.Model.Payments;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Payment services supported by the system
|
|
||||||
/// </summary>
|
|
||||||
public enum PaymentServices
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// No service
|
|
||||||
/// </summary>
|
|
||||||
None,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Strike.me payment service
|
|
||||||
/// </summary>
|
|
||||||
Strike
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
namespace VoidCat.Model.Payments;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Base payment config
|
|
||||||
/// </summary>
|
|
||||||
public abstract class PaymentConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// File this config is for
|
|
||||||
/// </summary>
|
|
||||||
public Guid File { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Service used to pay the payment
|
|
||||||
/// </summary>
|
|
||||||
public PaymentServices Service { get; init; } = PaymentServices.None;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The cost for the payment to pass
|
|
||||||
/// </summary>
|
|
||||||
public PaymentMoney Cost { get; init; } = new(0m, PaymentCurrencies.BTC);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// If the payment is required
|
|
||||||
/// </summary>
|
|
||||||
public bool Required { get; init; } = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public sealed class NoPaymentConfig : PaymentConfig
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Payment config for <see cref="PaymentServices.Strike"/> service
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="Cost"></param>
|
|
||||||
public sealed class StrikePaymentConfig : PaymentConfig
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Strike username to pay to
|
|
||||||
/// </summary>
|
|
||||||
public string Handle { get; init; } = null!;
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace VoidCat.Model.User;
|
|
||||||
|
|
||||||
public sealed class ApiKey
|
|
||||||
{
|
|
||||||
[JsonConverter(typeof(Base58GuidConverter))]
|
|
||||||
public Guid Id { get; init; }
|
|
||||||
|
|
||||||
[JsonConverter(typeof(Base58GuidConverter))]
|
|
||||||
public Guid UserId { get; init; }
|
|
||||||
|
|
||||||
public string Token { get; init; }
|
|
||||||
|
|
||||||
public DateTime Expiry { get; init; }
|
|
||||||
|
|
||||||
public DateTime Created { get; init; }
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
namespace VoidCat.Model.User;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// User account authentication type
|
|
||||||
/// </summary>
|
|
||||||
public enum AuthType
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Encrypted password
|
|
||||||
/// </summary>
|
|
||||||
Internal = 0,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// PGP challenge
|
|
||||||
/// </summary>
|
|
||||||
PGP = 1,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// OAuth2 token
|
|
||||||
/// </summary>
|
|
||||||
OAuth2 = 2,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Lightning node challenge
|
|
||||||
/// </summary>
|
|
||||||
Lightning = 3
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
namespace VoidCat.Model.User;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Internal user object used by the system
|
|
||||||
/// </summary>
|
|
||||||
public sealed class InternalUser : PrivateUser
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A password hash for the user in the format <see cref="Extensions.HashPassword"/>
|
|
||||||
/// </summary>
|
|
||||||
public string Password { get; init; } = null!;
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
namespace VoidCat.Model.User;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A user object which includes the Email
|
|
||||||
/// </summary>
|
|
||||||
public class PrivateUser : User
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Users email address
|
|
||||||
/// </summary>
|
|
||||||
public string Email { get; set; } = null!;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Users storage system for new uploads
|
|
||||||
/// </summary>
|
|
||||||
public string? Storage { get; set; }
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
namespace VoidCat.Model.User;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public sealed class PublicUser : User
|
|
||||||
{
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace VoidCat.Model.User;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The base user object for the system
|
|
||||||
/// </summary>
|
|
||||||
public abstract class User
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Unique Id of the user
|
|
||||||
/// </summary>
|
|
||||||
[JsonConverter(typeof(Base58GuidConverter))]
|
|
||||||
public Guid Id { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Roles assigned to this user which grant them extra permissions
|
|
||||||
/// </summary>
|
|
||||||
public HashSet<string> Roles { get; init; } = new() {Model.Roles.User};
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// When the user account was created
|
|
||||||
/// </summary>
|
|
||||||
public DateTimeOffset Created { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The last time the user logged in
|
|
||||||
/// </summary>
|
|
||||||
public DateTimeOffset LastLogin { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Display avatar for user profile
|
|
||||||
/// </summary>
|
|
||||||
public string? Avatar { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Display name for user profile
|
|
||||||
/// </summary>
|
|
||||||
public string? DisplayName { get; set; } = "void user";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Profile flags
|
|
||||||
/// </summary>
|
|
||||||
public UserFlags Flags { get; set; } = UserFlags.PublicProfile;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Account authentication type
|
|
||||||
/// </summary>
|
|
||||||
public AuthType AuthType { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the Public object for this user
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public PublicUser ToPublic()
|
|
||||||
{
|
|
||||||
return new()
|
|
||||||
{
|
|
||||||
Id = Id,
|
|
||||||
Roles = Roles,
|
|
||||||
Created = Created,
|
|
||||||
LastLogin = LastLogin,
|
|
||||||
Avatar = Avatar,
|
|
||||||
Flags = Flags
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
namespace VoidCat.Model.User;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// OAuth2 access token
|
|
||||||
/// </summary>
|
|
||||||
public sealed class UserAuthToken
|
|
||||||
{
|
|
||||||
public Guid Id { get; init; }
|
|
||||||
|
|
||||||
public Guid User { get; init; }
|
|
||||||
|
|
||||||
public string Provider { get; init; }
|
|
||||||
|
|
||||||
public string AccessToken { get; init; }
|
|
||||||
|
|
||||||
public string TokenType { get; init; }
|
|
||||||
|
|
||||||
public DateTime Expires { get; init; }
|
|
||||||
|
|
||||||
public string RefreshToken { get; init; }
|
|
||||||
|
|
||||||
public string Scope { get; init; }
|
|
||||||
|
|
||||||
public string IdToken { get; init; }
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
namespace VoidCat.Model.User;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Account status flags
|
|
||||||
/// </summary>
|
|
||||||
[Flags]
|
|
||||||
public enum UserFlags
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Profile is public
|
|
||||||
/// </summary>
|
|
||||||
PublicProfile = 1,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Uploads list is public
|
|
||||||
/// </summary>
|
|
||||||
PublicUploads = 2,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Account has email verified
|
|
||||||
/// </summary>
|
|
||||||
EmailVerified = 4
|
|
||||||
}
|
|
@ -1,38 +1,14 @@
|
|||||||
using Newtonsoft.Json;
|
namespace VoidCat.Model;
|
||||||
|
|
||||||
namespace VoidCat.Model;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Results for virus scan of a single file
|
/// Results for virus scan of a single file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class VirusScanResult
|
public sealed class VirusStatus
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Unique Id for this scan
|
|
||||||
/// </summary>
|
|
||||||
[JsonConverter(typeof(Base58GuidConverter))]
|
|
||||||
public Guid Id { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Id of the file that was scanned
|
|
||||||
/// </summary>
|
|
||||||
[JsonConverter(typeof(Base58GuidConverter))]
|
|
||||||
public Guid File { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Time the file was scanned
|
/// Time the file was scanned
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTimeOffset ScanTime { get; init; } = DateTimeOffset.UtcNow;
|
public DateTime ScanTime { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The name of the virus scanner software
|
|
||||||
/// </summary>
|
|
||||||
public string Scanner { get; init; } = null!;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Virus detection score, this can mean different things for each scanner but the value should be between 0 and 1
|
|
||||||
/// </summary>
|
|
||||||
public decimal Score { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Detected virus names
|
/// Detected virus names
|
||||||
@ -42,5 +18,5 @@ public sealed class VirusScanResult
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// If we consider this result as a virus or not
|
/// If we consider this result as a virus or not
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsVirus => Score >= 0.75m && !string.IsNullOrEmpty(Names);
|
public bool IsVirus { get; init; }
|
||||||
}
|
}
|
@ -1,48 +1,35 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using VoidCat.Model.Payments;
|
using VoidCat.Database;
|
||||||
using VoidCat.Model.User;
|
|
||||||
|
|
||||||
namespace VoidCat.Model
|
namespace VoidCat.Model;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Primary response type for file information
|
||||||
|
/// </summary>
|
||||||
|
public class VoidFileResponse
|
||||||
{
|
{
|
||||||
public abstract record VoidFile<TMeta> where TMeta : FileMeta
|
[JsonConverter(typeof(Base58GuidConverter))]
|
||||||
{
|
public Guid Id { get; init; }
|
||||||
/// <summary>
|
public VoidFileMeta Metadata { get; init; } = null!;
|
||||||
/// Id of the file
|
public Paywall? Payment { get; init; }
|
||||||
/// </summary>
|
public ApiUser? Uploader { get; init; }
|
||||||
[JsonConverter(typeof(Base58GuidConverter))]
|
public Bandwidth? Bandwidth { get; init; }
|
||||||
public Guid Id { get; init; }
|
public VirusStatus? VirusScan { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
public class VoidFileMeta
|
||||||
/// Metadta related to the file
|
{
|
||||||
/// </summary>
|
public string? Name { get; init; }
|
||||||
public TMeta? Metadata { get; init; }
|
public ulong Size { get; init; }
|
||||||
|
public DateTime Uploaded { get; init; }
|
||||||
/// <summary>
|
public string? Description { get; init; }
|
||||||
/// Optional payment config
|
public string MimeType { get; init; }
|
||||||
/// </summary>
|
public string? Digest { get; init; }
|
||||||
public PaymentConfig? Payment { get; init; }
|
|
||||||
|
[JsonConverter(typeof(Base58GuidConverter))]
|
||||||
/// <summary>
|
public Guid? EditSecret { get; init; }
|
||||||
/// User profile that uploaded the file
|
public DateTime? Expires { get; init; }
|
||||||
/// </summary>
|
public string Storage { get; init; } = "local-disk";
|
||||||
public PublicUser? Uploader { get; init; }
|
public string? EncryptionParams { get; init; }
|
||||||
|
public string? MagnetLink { get; init; }
|
||||||
/// <summary>
|
|
||||||
/// Traffic stats for this file
|
|
||||||
/// </summary>
|
|
||||||
public Bandwidth? Bandwidth { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Virus scanner results
|
|
||||||
/// </summary>
|
|
||||||
public VirusScanResult? VirusScan { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed record PublicVoidFile : VoidFile<FileMeta>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public sealed record PrivateVoidFile : VoidFile<SecretFileMeta>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,100 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
using VoidCat.Services.Abstractions;
|
|
||||||
|
|
||||||
// ReSharper disable InconsistentNaming
|
|
||||||
|
|
||||||
namespace VoidCat.Model;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Base metadata must contain version number
|
|
||||||
/// </summary>
|
|
||||||
public interface IFileMeta
|
|
||||||
{
|
|
||||||
const int CurrentVersion = 3;
|
|
||||||
|
|
||||||
int Version { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// File metadata which is managed by <see cref="IFileMetadataStore"/>
|
|
||||||
/// </summary>
|
|
||||||
public record FileMeta : IFileMeta
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Metadata version
|
|
||||||
/// </summary>
|
|
||||||
public int Version { get; init; } = IFileMeta.CurrentVersion;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Internal Id of the file
|
|
||||||
/// </summary>
|
|
||||||
[JsonConverter(typeof(Base58GuidConverter))]
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Filename
|
|
||||||
/// </summary>
|
|
||||||
public string? Name { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Size of the file in storage
|
|
||||||
/// </summary>
|
|
||||||
public ulong Size { get; init; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Date file was uploaded
|
|
||||||
/// </summary>
|
|
||||||
public DateTimeOffset Uploaded { get; init; } = DateTimeOffset.UtcNow;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Description about the file
|
|
||||||
/// </summary>
|
|
||||||
public string? Description { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The content type of the file
|
|
||||||
/// </summary>
|
|
||||||
public string? MimeType { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SHA-256 hash of the file
|
|
||||||
/// </summary>
|
|
||||||
public string? Digest { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Url to download the file
|
|
||||||
/// </summary>
|
|
||||||
public Uri? Url { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Time when the file will expire and be deleted
|
|
||||||
/// </summary>
|
|
||||||
public DateTimeOffset? Expires { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// What storage system the file is on
|
|
||||||
/// </summary>
|
|
||||||
public string? Storage { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Encryption params as JSON string
|
|
||||||
/// </summary>
|
|
||||||
public string? EncryptionParams { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Magnet link for downloads
|
|
||||||
/// </summary>
|
|
||||||
public string? MagnetLink { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <see cref="VoidFile"/> with attached <see cref="EditSecret"/>
|
|
||||||
/// </summary>
|
|
||||||
public record SecretFileMeta : FileMeta
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A secret key used to make edits to the file after its uploaded
|
|
||||||
/// </summary>
|
|
||||||
[JsonConverter(typeof(Base58GuidConverter))]
|
|
||||||
public Guid EditSecret { get; init; }
|
|
||||||
}
|
|
@ -127,6 +127,11 @@ namespace VoidCat.Model
|
|||||||
"udp://tracker.openbittorrent.com:6969/announce",
|
"udp://tracker.openbittorrent.com:6969/announce",
|
||||||
"http://tracker.openbittorrent.com:80/announce"
|
"http://tracker.openbittorrent.com:80/announce"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lightning node configuration for LNProxy services
|
||||||
|
/// </summary>
|
||||||
|
public LndConfig? LndConfig { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class TorSettings
|
public sealed class TorSettings
|
||||||
@ -207,4 +212,12 @@ namespace VoidCat.Model
|
|||||||
public string? ClientId { get; init; }
|
public string? ClientId { get; init; }
|
||||||
public string? ClientSecret { get; init; }
|
public string? ClientSecret { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class LndConfig
|
||||||
|
{
|
||||||
|
public string Network { get; init; } = "regtest";
|
||||||
|
public Uri Endpoint { get; init; }
|
||||||
|
public string CertPath { get; init; } = null!;
|
||||||
|
public string MacaroonPath { get; init; } = null!;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
@using VoidCat.Model
|
@using VoidCat.Model
|
||||||
@using VoidCat.Services.Users
|
@using VoidCat.Services.Users
|
||||||
@model VoidCat.Model.EmailVerificationCode
|
@model VoidCat.Database.EmailVerification
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
@ -31,7 +31,7 @@
|
|||||||
<div class="page">
|
<div class="page">
|
||||||
<h1>void.cat</h1>
|
<h1>void.cat</h1>
|
||||||
<p>Your verification code is below please copy this to complete verification</p>
|
<p>Your verification code is below please copy this to complete verification</p>
|
||||||
<pre>@(Model?.Code.ToBase58() ?? "?????????????")</pre>
|
<pre>@(Model.Code.ToBase58())</pre>
|
||||||
<p>This code will expire in @BaseEmailVerification.HoursExpire hours</p>
|
<p>This code will expire in @BaseEmailVerification.HoursExpire hours</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
@ -17,12 +17,12 @@
|
|||||||
<meta property="og:site_name" content="void.cat"/>
|
<meta property="og:site_name" content="void.cat"/>
|
||||||
<meta property="og:title" content="@Model.Meta.Name"/>
|
<meta property="og:title" content="@Model.Meta.Name"/>
|
||||||
<meta property="og:description" content="@Model.Meta.Description"/>
|
<meta property="og:description" content="@Model.Meta.Description"/>
|
||||||
<meta property="og:url" content="@($"https://{Context.Request.Host}/{Model.Meta.Id.ToBase58()}")"/>
|
<meta property="og:url" content="@($"https://{Context.Request.Host}/{Model.Id.ToBase58()}")"/>
|
||||||
|
|
||||||
var mime = Model.Meta.MimeType;
|
var mime = Model.Meta.MimeType;
|
||||||
if (mime != default)
|
if (mime != default)
|
||||||
{
|
{
|
||||||
var link = $"https://{Context.Request.Host}/d/{Model.Meta.Id.ToBase58()}";
|
var link = $"https://{Context.Request.Host}/d/{Model.Id.ToBase58()}";
|
||||||
if (mime.StartsWith("image/"))
|
if (mime.StartsWith("image/"))
|
||||||
{
|
{
|
||||||
<meta property="og:image" content="@link"/>
|
<meta property="og:image" content="@link"/>
|
||||||
|
@ -1,150 +1,188 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Prometheus;
|
using Prometheus;
|
||||||
using VoidCat;
|
|
||||||
using VoidCat.Model;
|
using VoidCat.Model;
|
||||||
|
using VoidCat.Services;
|
||||||
using VoidCat.Services.Analytics;
|
using VoidCat.Services.Analytics;
|
||||||
using VoidCat.Services.Migrations;
|
using VoidCat.Services.Migrations;
|
||||||
|
|
||||||
JsonConvert.DefaultSettings = () => VoidStartup.ConfigJsonSettings(new());
|
namespace VoidCat;
|
||||||
|
|
||||||
RunModes mode = args.Length == 0 ? RunModes.All : 0;
|
static class Program
|
||||||
|
|
||||||
if (args.Contains("--run-webserver"))
|
|
||||||
{
|
{
|
||||||
mode |= RunModes.Webserver;
|
[Flags]
|
||||||
}
|
enum RunModes
|
||||||
|
|
||||||
if (args.Contains("--run-migrations"))
|
|
||||||
{
|
|
||||||
mode |= RunModes.Migrations;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.Contains("--run-background-jobs"))
|
|
||||||
{
|
|
||||||
mode |= RunModes.BackgroundJobs;
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine($"Running with modes: {mode}");
|
|
||||||
|
|
||||||
async Task RunMigrations(IServiceProvider services)
|
|
||||||
{
|
|
||||||
using var migrationScope = services.CreateScope();
|
|
||||||
var migrations = migrationScope.ServiceProvider.GetServices<IMigration>();
|
|
||||||
var logger = migrationScope.ServiceProvider.GetRequiredService<ILogger<IMigration>>();
|
|
||||||
foreach (var migration in migrations.OrderBy(a => a.Order))
|
|
||||||
{
|
{
|
||||||
logger.LogInformation("Running migration: {Migration}", migration.GetType().Name);
|
Webserver = 1,
|
||||||
var res = await migration.Migrate(args);
|
BackgroundJobs = 2,
|
||||||
logger.LogInformation("== Result: {Result}", res.ToString());
|
Migrations = 4,
|
||||||
if (res == IMigration.MigrationResult.ExitCompleted)
|
All = 255
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task Main(string[] args)
|
||||||
|
{
|
||||||
|
JsonConvert.DefaultSettings = () => VoidStartup.ConfigJsonSettings(new());
|
||||||
|
|
||||||
|
RunModes mode = args.Length == 0 ? RunModes.All : 0;
|
||||||
|
if (args.Contains("--run-webserver"))
|
||||||
{
|
{
|
||||||
return;
|
mode |= RunModes.Webserver;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode.HasFlag(RunModes.Webserver))
|
if (args.Contains("--run-migrations"))
|
||||||
{
|
{
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
mode |= RunModes.Migrations;
|
||||||
var services = builder.Services;
|
}
|
||||||
|
|
||||||
var configuration = builder.Configuration;
|
if (args.Contains("--run-background-jobs"))
|
||||||
var voidSettings = configuration.GetSection("Settings").Get<VoidSettings>();
|
{
|
||||||
services.AddSingleton(voidSettings);
|
mode |= RunModes.BackgroundJobs;
|
||||||
services.AddSingleton(voidSettings.Strike ?? new());
|
}
|
||||||
|
|
||||||
var seqSettings = configuration.GetSection("Seq");
|
Console.WriteLine($"Running with modes: {mode}");
|
||||||
builder.Logging.AddSeq(seqSettings);
|
|
||||||
|
|
||||||
services.AddBaseServices(voidSettings);
|
async Task RunMigrations(IServiceProvider services)
|
||||||
services.AddDatabaseServices(voidSettings);
|
{
|
||||||
services.AddWebServices(voidSettings);
|
using var migrationScope = services.CreateScope();
|
||||||
|
var migrations = migrationScope.ServiceProvider.GetServices<IMigration>();
|
||||||
|
var logger = migrationScope.ServiceProvider.GetRequiredService<ILogger<IMigration>>();
|
||||||
|
foreach (var migration in migrations.OrderBy(a => a.Order))
|
||||||
|
{
|
||||||
|
logger.LogInformation("Running migration: {Migration}", migration.GetType().Name);
|
||||||
|
var res = await migration.Migrate(args);
|
||||||
|
logger.LogInformation("== Result: {Result}", res.ToString());
|
||||||
|
if (res == IMigration.MigrationResult.ExitCompleted)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mode.HasFlag(RunModes.Migrations))
|
if (mode.HasFlag(RunModes.Webserver))
|
||||||
{
|
{
|
||||||
services.AddMigrations(voidSettings);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
}
|
var services = builder.Services;
|
||||||
|
|
||||||
if (mode.HasFlag(RunModes.BackgroundJobs))
|
var configuration = builder.Configuration;
|
||||||
{
|
var voidSettings = configuration.GetSection("Settings").Get<VoidSettings>();
|
||||||
services.AddBackgroundServices(voidSettings);
|
services.AddSingleton(voidSettings);
|
||||||
}
|
services.AddSingleton(voidSettings.Strike ?? new());
|
||||||
|
|
||||||
var app = builder.Build();
|
var seqSettings = configuration.GetSection("Seq");
|
||||||
|
builder.Logging.AddSeq(seqSettings);
|
||||||
|
|
||||||
if (mode.HasFlag(RunModes.Migrations))
|
ConfigureDb(services, voidSettings);
|
||||||
{
|
services.AddBaseServices(voidSettings);
|
||||||
await RunMigrations(app.Services);
|
services.AddDatabaseServices(voidSettings);
|
||||||
}
|
services.AddWebServices(voidSettings);
|
||||||
|
|
||||||
|
if (mode.HasFlag(RunModes.Migrations))
|
||||||
|
{
|
||||||
|
services.AddMigrations(voidSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode.HasFlag(RunModes.BackgroundJobs))
|
||||||
|
{
|
||||||
|
services.AddBackgroundServices(voidSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
if (mode.HasFlag(RunModes.Migrations))
|
||||||
|
{
|
||||||
|
await RunMigrations(app.Services);
|
||||||
|
}
|
||||||
|
|
||||||
#if HostSPA
|
#if HostSPA
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
app.UseHttpLogging();
|
app.UseHttpLogging();
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
app.UseCors();
|
app.UseCors();
|
||||||
app.UseSwagger();
|
app.UseSwagger();
|
||||||
app.UseSwaggerUI();
|
app.UseSwaggerUI();
|
||||||
app.UseAuthentication();
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.UseHealthChecks("/healthz");
|
app.UseHealthChecks("/healthz");
|
||||||
|
|
||||||
app.UseMiddleware<AnalyticsMiddleware>();
|
app.UseMiddleware<AnalyticsMiddleware>();
|
||||||
app.UseEndpoints(ep =>
|
app.UseEndpoints(ep =>
|
||||||
{
|
{
|
||||||
ep.MapControllers();
|
ep.MapControllers();
|
||||||
ep.MapMetrics();
|
ep.MapMetrics();
|
||||||
ep.MapRazorPages();
|
ep.MapRazorPages();
|
||||||
#if HostSPA
|
#if HostSPA
|
||||||
ep.MapFallbackToFile("index.html");
|
ep.MapFallbackToFile("index.html");
|
||||||
#endif
|
#endif
|
||||||
});
|
});
|
||||||
|
|
||||||
app.Run();
|
await app.RunAsync();
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// daemon style, dont run web server
|
|
||||||
var builder = Host.CreateDefaultBuilder(args);
|
|
||||||
builder.ConfigureServices((context, services) =>
|
|
||||||
{
|
|
||||||
var voidSettings = context.Configuration.GetSection("Settings").Get<VoidSettings>();
|
|
||||||
services.AddSingleton(voidSettings);
|
|
||||||
services.AddSingleton(voidSettings.Strike ?? new());
|
|
||||||
|
|
||||||
services.AddBaseServices(voidSettings);
|
|
||||||
services.AddDatabaseServices(voidSettings);
|
|
||||||
if (mode.HasFlag(RunModes.Migrations))
|
|
||||||
{
|
|
||||||
services.AddMigrations(voidSettings);
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (mode.HasFlag(RunModes.BackgroundJobs))
|
|
||||||
{
|
{
|
||||||
services.AddBackgroundServices(voidSettings);
|
// daemon style, dont run web server
|
||||||
}
|
var builder = Host.CreateDefaultBuilder(args);
|
||||||
});
|
builder.ConfigureServices((context, services) =>
|
||||||
builder.ConfigureLogging((context, logging) => { logging.AddSeq(context.Configuration.GetSection("Seq")); });
|
{
|
||||||
|
var voidSettings = context.Configuration.GetSection("Settings").Get<VoidSettings>();
|
||||||
|
services.AddSingleton(voidSettings);
|
||||||
|
services.AddSingleton(voidSettings.Strike ?? new());
|
||||||
|
|
||||||
var app = builder.Build();
|
ConfigureDb(services, voidSettings);
|
||||||
if (mode.HasFlag(RunModes.Migrations))
|
services.AddBaseServices(voidSettings);
|
||||||
{
|
services.AddDatabaseServices(voidSettings);
|
||||||
await RunMigrations(app.Services);
|
if (mode.HasFlag(RunModes.Migrations))
|
||||||
|
{
|
||||||
|
services.AddMigrations(voidSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode.HasFlag(RunModes.BackgroundJobs))
|
||||||
|
{
|
||||||
|
services.AddBackgroundServices(voidSettings);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.ConfigureLogging((context, logging) => { logging.AddSeq(context.Configuration.GetSection("Seq")); });
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
if (mode.HasFlag(RunModes.Migrations))
|
||||||
|
{
|
||||||
|
await RunMigrations(app.Services);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode.HasFlag(RunModes.BackgroundJobs))
|
||||||
|
{
|
||||||
|
await app.RunAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode.HasFlag(RunModes.BackgroundJobs))
|
private static void ConfigureDb(IServiceCollection services, VoidSettings settings)
|
||||||
{
|
{
|
||||||
app.Run();
|
if (settings.HasPostgres())
|
||||||
|
{
|
||||||
|
services.AddDbContext<VoidContext>(o =>
|
||||||
|
o.UseNpgsql(settings.Postgres!));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dummy method for EF core migrations
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
// ReSharper disable once UnusedMember.Global
|
||||||
|
public static IHostBuilder CreateHostBuilder(string[] args)
|
||||||
|
{
|
||||||
|
var dummyHost = Host.CreateDefaultBuilder(args);
|
||||||
|
dummyHost.ConfigureServices((ctx, svc) =>
|
||||||
|
{
|
||||||
|
var settings = ctx.Configuration.GetSection("Settings").Get<VoidSettings>();
|
||||||
|
ConfigureDb(svc, settings);
|
||||||
|
});
|
||||||
|
|
||||||
|
return dummyHost;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Flags]
|
|
||||||
internal enum RunModes
|
|
||||||
{
|
|
||||||
Webserver = 1,
|
|
||||||
BackgroundJobs = 2,
|
|
||||||
Migrations = 4,
|
|
||||||
All = 255
|
|
||||||
}
|
|
@ -6,8 +6,8 @@
|
|||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
},
|
},
|
||||||
"applicationUrl": "https://localhost:7195",
|
"applicationUrl": "http://localhost:7195",
|
||||||
"launchUrl": "https://localhost:3000",
|
"launchUrl": "http://localhost:3000",
|
||||||
"dotnetRunMessages": true
|
"dotnetRunMessages": true
|
||||||
},
|
},
|
||||||
"Docker": {
|
"Docker": {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
using VoidCat.Model.User;
|
using VoidCat.Database;
|
||||||
|
|
||||||
namespace VoidCat.Services.Abstractions;
|
namespace VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
|
@ -1,11 +1,21 @@
|
|||||||
using VoidCat.Model;
|
using VoidCat.Database;
|
||||||
using VoidCat.Model.User;
|
|
||||||
|
|
||||||
namespace VoidCat.Services.Abstractions;
|
namespace VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
public interface IEmailVerification
|
public interface IEmailVerification
|
||||||
{
|
{
|
||||||
ValueTask<EmailVerificationCode> SendNewCode(PrivateUser user);
|
/// <summary>
|
||||||
|
/// Send email verification code
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
ValueTask<EmailVerification> SendNewCode(User user);
|
||||||
|
|
||||||
ValueTask<bool> VerifyCode(PrivateUser user, Guid code);
|
/// <summary>
|
||||||
|
/// Perform account verification
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user"></param>
|
||||||
|
/// <param name="code"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
ValueTask<bool> VerifyCode(User user, Guid code);
|
||||||
}
|
}
|
@ -1,44 +1,48 @@
|
|||||||
using VoidCat.Model;
|
using VoidCat.Model;
|
||||||
|
using File = VoidCat.Database.File;
|
||||||
|
|
||||||
namespace VoidCat.Services.Abstractions;
|
namespace VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// File metadata contains all data about a file except for the file data itself
|
/// File metadata contains all data about a file except for the file data itself
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IFileMetadataStore : IPublicPrivateStore<FileMeta, SecretFileMeta>
|
public interface IFileMetadataStore
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get metadata for a single file
|
/// Get metadata for a single file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id"></param>
|
/// <param name="id"></param>
|
||||||
/// <typeparam name="TMeta"></typeparam>
|
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
ValueTask<TMeta?> Get<TMeta>(Guid id) where TMeta : FileMeta;
|
ValueTask<File?> Get(Guid id);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get metadata for multiple files
|
/// Get metadata for multiple files
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ids"></param>
|
/// <param name="ids"></param>
|
||||||
/// <typeparam name="TMeta"></typeparam>
|
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
ValueTask<IReadOnlyList<TMeta>> Get<TMeta>(Guid[] ids) where TMeta : FileMeta;
|
ValueTask<IReadOnlyList<File>> Get(Guid[] ids);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add new file metadata to this store
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="f"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
ValueTask Add(File f);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Update file metadata
|
/// Update file metadata
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id"></param>
|
/// <param name="id"></param>
|
||||||
/// <param name="meta"></param>
|
/// <param name="meta"></param>
|
||||||
/// <typeparam name="TMeta"></typeparam>
|
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
ValueTask Update<TMeta>(Guid id, TMeta meta) where TMeta : FileMeta;
|
ValueTask Update(Guid id, File meta);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// List all files in the store
|
/// List all files in the store
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request"></param>
|
/// <param name="request"></param>
|
||||||
/// <typeparam name="TMeta"></typeparam>
|
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
ValueTask<PagedResult<TMeta>> ListFiles<TMeta>(PagedRequest request) where TMeta : FileMeta;
|
ValueTask<PagedResult<File>> ListFiles(PagedRequest request);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns basic stats about the file store
|
/// Returns basic stats about the file store
|
||||||
@ -46,6 +50,13 @@ public interface IFileMetadataStore : IPublicPrivateStore<FileMeta, SecretFileMe
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
ValueTask<StoreStats> Stats();
|
ValueTask<StoreStats> Stats();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete metadata object from the store
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
ValueTask Delete(Guid id);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Simple stats of the current store
|
/// Simple stats of the current store
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using VoidCat.Model;
|
using VoidCat.Model;
|
||||||
|
using File = VoidCat.Database.File;
|
||||||
|
|
||||||
namespace VoidCat.Services.Abstractions;
|
namespace VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ public interface IFileStore
|
|||||||
/// <param name="payload"></param>
|
/// <param name="payload"></param>
|
||||||
/// <param name="cts"></param>
|
/// <param name="cts"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
ValueTask<PrivateVoidFile> Ingress(IngressPayload payload, CancellationToken cts);
|
ValueTask<File> Ingress(IngressPayload payload, CancellationToken cts);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Egress a file from the system (Download)
|
/// Egress a file from the system (Download)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
using VoidCat.Model.User;
|
using VoidCat.Database;
|
||||||
|
|
||||||
namespace VoidCat.Services.Abstractions;
|
namespace VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
@ -30,5 +30,5 @@ public interface IOAuthProvider
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="token"></param>
|
/// <param name="token"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
ValueTask<InternalUser?> GetUserDetails(UserAuthToken token);
|
ValueTask<User?> GetUserDetails(UserAuthToken token);
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
using VoidCat.Model.Payments;
|
using VoidCat.Database;
|
||||||
|
|
||||||
namespace VoidCat.Services.Abstractions;
|
namespace VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
@ -12,5 +12,5 @@ public interface IPaymentFactory
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="svc"></param>
|
/// <param name="svc"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
ValueTask<IPaymentProvider> CreateProvider(PaymentServices svc);
|
ValueTask<IPaymentProvider> CreateProvider(PaywallService svc);
|
||||||
}
|
}
|
@ -1,11 +1,11 @@
|
|||||||
using VoidCat.Model.Payments;
|
using VoidCat.Database;
|
||||||
|
|
||||||
namespace VoidCat.Services.Abstractions;
|
namespace VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Payment order store
|
/// Payment order store
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IPaymentOrderStore : IBasicStore<PaymentOrder>
|
public interface IPaymentOrderStore : IBasicStore<PaywallOrder>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Update the status of an order
|
/// Update the status of an order
|
||||||
@ -13,5 +13,5 @@ public interface IPaymentOrderStore : IBasicStore<PaymentOrder>
|
|||||||
/// <param name="order"></param>
|
/// <param name="order"></param>
|
||||||
/// <param name="status"></param>
|
/// <param name="status"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
ValueTask UpdateStatus(Guid order, PaymentOrderStatus status);
|
ValueTask UpdateStatus(Guid order, PaywallOrderStatus status);
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
using VoidCat.Model.Payments;
|
using VoidCat.Database;
|
||||||
|
|
||||||
namespace VoidCat.Services.Abstractions;
|
namespace VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
@ -12,12 +12,12 @@ public interface IPaymentProvider
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="file"></param>
|
/// <param name="file"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
ValueTask<PaymentOrder?> CreateOrder(PaymentConfig file);
|
ValueTask<PaywallOrder?> CreateOrder(Paywall file);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the status of an existing order with the provider
|
/// Get the status of an existing order with the provider
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id"></param>
|
/// <param name="id"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
ValueTask<PaymentOrder?> GetOrderStatus(Guid id);
|
ValueTask<PaywallOrder?> GetOrderStatus(Guid id);
|
||||||
}
|
}
|
@ -1,10 +1,10 @@
|
|||||||
using VoidCat.Model.Payments;
|
using VoidCat.Database;
|
||||||
|
|
||||||
namespace VoidCat.Services.Abstractions;
|
namespace VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Store for payment configs
|
/// Store for payment configs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IPaymentStore : IBasicStore<PaymentConfig>
|
public interface IPaymentStore : IBasicStore<Paywall>
|
||||||
{
|
{
|
||||||
}
|
}
|
@ -1,38 +0,0 @@
|
|||||||
namespace VoidCat.Services.Abstractions;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Store interface where there is a public and private model
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TPublic"></typeparam>
|
|
||||||
/// <typeparam name="TPrivate"></typeparam>
|
|
||||||
public interface IPublicPrivateStore<TPublic, TPrivate>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Get the public model
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
ValueTask<TPublic?> Get(Guid id);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the private model (contains sensitive data)
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
ValueTask<TPrivate?> GetPrivate(Guid id);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set the private obj in the store
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id"></param>
|
|
||||||
/// <param name="obj"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
ValueTask Set(Guid id, TPrivate obj);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Delete the object from the store
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
ValueTask Delete(Guid id);
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
using VoidCat.Model.User;
|
using VoidCat.Database;
|
||||||
|
|
||||||
namespace VoidCat.Services.Abstractions;
|
namespace VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
|
@ -1,20 +1,26 @@
|
|||||||
using VoidCat.Model;
|
using VoidCat.Database;
|
||||||
using VoidCat.Model.User;
|
using VoidCat.Model;
|
||||||
|
|
||||||
namespace VoidCat.Services.Abstractions;
|
namespace VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// User store
|
/// User store
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IUserStore : IPublicPrivateStore<User, InternalUser>
|
public interface IUserStore
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a single user
|
/// Get a single user
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id"></param>
|
/// <param name="id"></param>
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
ValueTask<T?> Get<T>(Guid id) where T : User;
|
ValueTask<User?> Get(Guid id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a new user to the store
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="u"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
ValueTask Add(User u);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Lookup a user by their email address
|
/// Lookup a user by their email address
|
||||||
@ -28,14 +34,14 @@ public interface IUserStore : IPublicPrivateStore<User, InternalUser>
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request"></param>
|
/// <param name="request"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
ValueTask<PagedResult<PrivateUser>> ListUsers(PagedRequest request);
|
ValueTask<PagedResult<User>> ListUsers(PagedRequest request);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Update a users profile
|
/// Update a users profile
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="newUser"></param>
|
/// <param name="newUser"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
ValueTask UpdateProfile(PublicUser newUser);
|
ValueTask UpdateProfile(User newUser);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the last login timestamp for the user
|
/// Updates the last login timestamp for the user
|
||||||
@ -50,5 +56,12 @@ public interface IUserStore : IPublicPrivateStore<User, InternalUser>
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="user"></param>
|
/// <param name="user"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
ValueTask AdminUpdateUser(PrivateUser user);
|
ValueTask AdminUpdateUser(User user);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete a user from the system
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
ValueTask Delete(Guid id);
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
using VoidCat.Model;
|
using VoidCat.Database;
|
||||||
|
|
||||||
namespace VoidCat.Services.Abstractions;
|
namespace VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
using VoidCat.Model;
|
using VoidCat.Database;
|
||||||
|
|
||||||
namespace VoidCat.Services.Abstractions;
|
namespace VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ public sealed class DeleteExpiredFiles : BackgroundService
|
|||||||
var fileInfoManager = scope.ServiceProvider.GetRequiredService<FileInfoManager>();
|
var fileInfoManager = scope.ServiceProvider.GetRequiredService<FileInfoManager>();
|
||||||
var fileStoreFactory = scope.ServiceProvider.GetRequiredService<FileStoreFactory>();
|
var fileStoreFactory = scope.ServiceProvider.GetRequiredService<FileStoreFactory>();
|
||||||
|
|
||||||
var files = await metadata.ListFiles<SecretFileMeta>(new(0, int.MaxValue));
|
var files = await metadata.ListFiles(new(0, int.MaxValue));
|
||||||
await foreach (var f in files.Results.WithCancellation(stoppingToken))
|
await foreach (var f in files.Results.WithCancellation(stoppingToken))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
using VoidCat.Model;
|
using VoidCat.Database;
|
||||||
using VoidCat.Model.User;
|
using VoidCat.Model;
|
||||||
using VoidCat.Services.Abstractions;
|
using VoidCat.Services.Abstractions;
|
||||||
using VoidCat.Services.Files;
|
using VoidCat.Services.Files;
|
||||||
|
|
||||||
|
@ -29,8 +29,9 @@ public class VirusScannerService : BackgroundService
|
|||||||
var page = 0;
|
var page = 0;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var files = await _fileStore.ListFiles<FileMeta>(new(page, 1_000));
|
var files = await _fileStore.ListFiles(new(page, 1_000));
|
||||||
if (files.Pages < page) break;
|
if (files.Pages < page) break;
|
||||||
|
|
||||||
page++;
|
page++;
|
||||||
|
|
||||||
await foreach (var file in files.Results.WithCancellation(stoppingToken))
|
await foreach (var file in files.Results.WithCancellation(stoppingToken))
|
||||||
@ -46,8 +47,7 @@ public class VirusScannerService : BackgroundService
|
|||||||
{
|
{
|
||||||
var result = await _scanner.ScanFile(file.Id, stoppingToken);
|
var result = await _scanner.ScanFile(file.Id, stoppingToken);
|
||||||
await _scanStore.Add(result.Id, result);
|
await _scanStore.Add(result.Id, result);
|
||||||
_logger.LogInformation("Scanned file {Id}, IsVirus = {Result}", result.File,
|
_logger.LogInformation("Scanned file {Id}, IsVirus = {Result}", result.File, result.Score);
|
||||||
result.IsVirus);
|
|
||||||
}
|
}
|
||||||
catch (RateLimitedException rx)
|
catch (RateLimitedException rx)
|
||||||
{
|
{
|
||||||
@ -66,4 +66,4 @@ public class VirusScannerService : BackgroundService
|
|||||||
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
|
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,29 +5,29 @@ namespace VoidCat.Services;
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public abstract class BasicCacheStore<TStore> : IBasicStore<TStore>
|
public abstract class BasicCacheStore<TStore> : IBasicStore<TStore>
|
||||||
{
|
{
|
||||||
protected readonly ICache _cache;
|
protected readonly ICache Cache;
|
||||||
|
|
||||||
protected BasicCacheStore(ICache cache)
|
protected BasicCacheStore(ICache cache)
|
||||||
{
|
{
|
||||||
_cache = cache;
|
Cache = cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual ValueTask<TStore?> Get(Guid id)
|
public virtual ValueTask<TStore?> Get(Guid id)
|
||||||
{
|
{
|
||||||
return _cache.Get<TStore>(MapKey(id));
|
return Cache.Get<TStore>(MapKey(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual ValueTask Add(Guid id, TStore obj)
|
public virtual ValueTask Add(Guid id, TStore obj)
|
||||||
{
|
{
|
||||||
return _cache.Set(MapKey(id), obj);
|
return Cache.Set(MapKey(id), obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual ValueTask Delete(Guid id)
|
public virtual ValueTask Delete(Guid id)
|
||||||
{
|
{
|
||||||
return _cache.Delete(MapKey(id));
|
return Cache.Delete(MapKey(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
|
using System.Collections.Immutable;
|
||||||
|
using VoidCat.Database;
|
||||||
using VoidCat.Model;
|
using VoidCat.Model;
|
||||||
using VoidCat.Model.User;
|
|
||||||
using VoidCat.Services.Abstractions;
|
using VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
namespace VoidCat.Services.Files;
|
namespace VoidCat.Services.Files;
|
||||||
@ -32,36 +33,47 @@ public sealed class FileInfoManager
|
|||||||
/// Get all metadata for a single file
|
/// Get all metadata for a single file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id"></param>
|
/// <param name="id"></param>
|
||||||
|
/// <param name="withEditSecret"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public ValueTask<PublicVoidFile?> Get(Guid id)
|
public async ValueTask<VoidFileResponse?> Get(Guid id, bool withEditSecret)
|
||||||
{
|
{
|
||||||
return Get<PublicVoidFile, FileMeta>(id);
|
var meta = await _metadataStore.Get(id);
|
||||||
}
|
if (meta == default) return default;
|
||||||
|
|
||||||
/// <summary>
|
var payment = await _paymentStore.Get(id);
|
||||||
/// Get all private metadata for a single file
|
var bandwidth = await _statsReporter.GetBandwidth(id);
|
||||||
/// </summary>
|
var virusScan = await _virusScanStore.GetByFile(id);
|
||||||
/// <param name="id"></param>
|
var uploader = await _userUploadsStore.Uploader(id);
|
||||||
/// <returns></returns>
|
|
||||||
public ValueTask<PrivateVoidFile?> GetPrivate(Guid id)
|
var user = uploader.HasValue ? await _userStore.Get(uploader.Value) : null;
|
||||||
{
|
|
||||||
return Get<PrivateVoidFile, SecretFileMeta>(id);
|
return new VoidFileResponse
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Metadata = meta.ToMeta(withEditSecret),
|
||||||
|
Payment = payment,
|
||||||
|
Bandwidth = bandwidth,
|
||||||
|
Uploader = user?.Flags.HasFlag(UserFlags.PublicProfile) == true || withEditSecret ? user?.ToApiUser(false) : null,
|
||||||
|
VirusScan = virusScan?.ToVirusStatus()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get all metadata for multiple files
|
/// Get all metadata for multiple files
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ids"></param>
|
/// <param name="ids"></param>
|
||||||
|
/// <param name="withEditSecret"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async ValueTask<IReadOnlyList<PublicVoidFile>> Get(Guid[] ids)
|
public async ValueTask<IReadOnlyList<VoidFileResponse>> Get(Guid[] ids, bool withEditSecret)
|
||||||
{
|
{
|
||||||
var ret = new List<PublicVoidFile>();
|
//todo: improve this
|
||||||
foreach (var id in ids)
|
var ret = new List<VoidFileResponse>();
|
||||||
|
foreach (var i in ids)
|
||||||
{
|
{
|
||||||
var v = await Get(id);
|
var x = await Get(i, withEditSecret);
|
||||||
if (v != default)
|
if (x != default)
|
||||||
{
|
{
|
||||||
ret.Add(v);
|
ret.Add(x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,28 +92,4 @@ public sealed class FileInfoManager
|
|||||||
await _statsReporter.Delete(id);
|
await _statsReporter.Delete(id);
|
||||||
await _virusScanStore.Delete(id);
|
await _virusScanStore.Delete(id);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private async ValueTask<TFile?> Get<TFile, TMeta>(Guid id)
|
|
||||||
where TMeta : FileMeta where TFile : VoidFile<TMeta>, new()
|
|
||||||
{
|
|
||||||
var meta = _metadataStore.Get<TMeta>(id);
|
|
||||||
var payment = _paymentStore.Get(id);
|
|
||||||
var bandwidth = _statsReporter.GetBandwidth(id);
|
|
||||||
var virusScan = _virusScanStore.GetByFile(id);
|
|
||||||
var uploader = _userUploadsStore.Uploader(id);
|
|
||||||
await Task.WhenAll(meta.AsTask(), payment.AsTask(), bandwidth.AsTask(), virusScan.AsTask(), uploader.AsTask());
|
|
||||||
|
|
||||||
if (meta.Result == default) return default;
|
|
||||||
var user = uploader.Result.HasValue ? await _userStore.Get<PublicUser>(uploader.Result.Value) : null;
|
|
||||||
|
|
||||||
return new TFile()
|
|
||||||
{
|
|
||||||
Id = id,
|
|
||||||
Metadata = meta.Result,
|
|
||||||
Payment = payment.Result,
|
|
||||||
Bandwidth = bandwidth.Result,
|
|
||||||
Uploader = user?.Flags.HasFlag(UserFlags.PublicProfile) == true ? user : null,
|
|
||||||
VirusScan = virusScan.Result
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -11,23 +11,23 @@ public static class FileStorageStartup
|
|||||||
services.AddTransient<FileInfoManager>();
|
services.AddTransient<FileInfoManager>();
|
||||||
services.AddTransient<FileStoreFactory>();
|
services.AddTransient<FileStoreFactory>();
|
||||||
services.AddTransient<CompressContent>();
|
services.AddTransient<CompressContent>();
|
||||||
|
|
||||||
if (settings.CloudStorage != default)
|
if (settings.CloudStorage != default)
|
||||||
{
|
{
|
||||||
// S3 storage
|
// S3 storage
|
||||||
foreach (var s3 in settings.CloudStorage.S3 ?? Array.Empty<S3BlobConfig>())
|
foreach (var s3 in settings.CloudStorage.S3 ?? Array.Empty<S3BlobConfig>())
|
||||||
{
|
{
|
||||||
services.AddTransient<IFileStore>((svc) =>
|
|
||||||
new S3FileStore(s3,
|
|
||||||
svc.GetRequiredService<IAggregateStatsCollector>(),
|
|
||||||
svc.GetRequiredService<FileInfoManager>(),
|
|
||||||
svc.GetRequiredService<ICache>()));
|
|
||||||
|
|
||||||
if (settings.MetadataStore == s3.Name)
|
if (settings.MetadataStore == s3.Name)
|
||||||
{
|
{
|
||||||
services.AddSingleton<IFileMetadataStore>((svc) =>
|
services.AddSingleton<IFileMetadataStore>((svc) =>
|
||||||
new S3FileMetadataStore(s3, svc.GetRequiredService<ILogger<S3FileMetadataStore>>()));
|
new S3FileMetadataStore(s3, svc.GetRequiredService<ILogger<S3FileMetadataStore>>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
services.AddTransient<IFileStore>((svc) =>
|
||||||
|
new S3FileStore(s3,
|
||||||
|
svc.GetRequiredService<IAggregateStatsCollector>(),
|
||||||
|
svc.GetRequiredService<IFileMetadataStore>(),
|
||||||
|
svc.GetRequiredService<ICache>()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ public static class FileStorageStartup
|
|||||||
services.AddTransient<IFileStore, LocalDiskFileStore>();
|
services.AddTransient<IFileStore, LocalDiskFileStore>();
|
||||||
if (settings.MetadataStore is "postgres" or "local-disk")
|
if (settings.MetadataStore is "postgres" or "local-disk")
|
||||||
{
|
{
|
||||||
services.AddSingleton<IFileMetadataStore, PostgresFileMetadataStore>();
|
services.AddTransient<IFileMetadataStore, PostgresFileMetadataStore>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -36,7 +36,7 @@ public class FileStoreFactory : IFileStore
|
|||||||
public string? Key => null;
|
public string? Key => null;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ValueTask<PrivateVoidFile> Ingress(IngressPayload payload, CancellationToken cts)
|
public ValueTask<Database.File> Ingress(IngressPayload payload, CancellationToken cts)
|
||||||
{
|
{
|
||||||
var store = GetFileStore(payload.Meta.Storage!);
|
var store = GetFileStore(payload.Meta.Storage!);
|
||||||
if (store == default)
|
if (store == default)
|
||||||
|
@ -22,18 +22,18 @@ public class LocalDiskFileMetadataStore : IFileMetadataStore
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ValueTask<TMeta?> Get<TMeta>(Guid id) where TMeta : FileMeta
|
public ValueTask<Database.File?> Get(Guid id)
|
||||||
{
|
{
|
||||||
return GetMeta<TMeta>(id);
|
return GetMeta<Database.File>(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask<IReadOnlyList<TMeta>> Get<TMeta>(Guid[] ids) where TMeta : FileMeta
|
public async ValueTask<IReadOnlyList<Database.File>> Get(Guid[] ids)
|
||||||
{
|
{
|
||||||
var ret = new List<TMeta>();
|
var ret = new List<Database.File>();
|
||||||
foreach (var id in ids)
|
foreach (var id in ids)
|
||||||
{
|
{
|
||||||
var r = await GetMeta<TMeta>(id);
|
var r = await GetMeta<Database.File>(id);
|
||||||
if (r != null)
|
if (r != null)
|
||||||
{
|
{
|
||||||
ret.Add(r);
|
ret.Add(r);
|
||||||
@ -42,11 +42,16 @@ public class LocalDiskFileMetadataStore : IFileMetadataStore
|
|||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ValueTask Add(Database.File f)
|
||||||
|
{
|
||||||
|
return Set(f.Id, f);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask Update<TMeta>(Guid id, TMeta meta) where TMeta : FileMeta
|
public async ValueTask Update(Guid id, Database.File meta)
|
||||||
{
|
{
|
||||||
var oldMeta = await Get<SecretFileMeta>(id);
|
var oldMeta = await Get(id);
|
||||||
if (oldMeta == default) return;
|
if (oldMeta == default) return;
|
||||||
|
|
||||||
oldMeta.Patch(meta);
|
oldMeta.Patch(meta);
|
||||||
@ -54,22 +59,18 @@ public class LocalDiskFileMetadataStore : IFileMetadataStore
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ValueTask<PagedResult<TMeta>> ListFiles<TMeta>(PagedRequest request) where TMeta : FileMeta
|
public ValueTask<PagedResult<Database.File>> ListFiles(PagedRequest request)
|
||||||
{
|
{
|
||||||
async IAsyncEnumerable<TMeta> EnumerateFiles()
|
async IAsyncEnumerable<Database.File> EnumerateFiles()
|
||||||
{
|
{
|
||||||
foreach (var metaFile in
|
foreach (var metaFile in
|
||||||
Directory.EnumerateFiles(Path.Join(_settings.DataDirectory, MetadataDir), "*.json"))
|
Directory.EnumerateFiles(Path.Join(_settings.DataDirectory, MetadataDir), "*.json"))
|
||||||
{
|
{
|
||||||
var json = await File.ReadAllTextAsync(metaFile);
|
var json = await File.ReadAllTextAsync(metaFile);
|
||||||
var meta = JsonConvert.DeserializeObject<TMeta>(json);
|
var meta = JsonConvert.DeserializeObject<Database.File>(json);
|
||||||
if (meta != null)
|
if (meta != null)
|
||||||
{
|
{
|
||||||
yield return meta with
|
yield return meta;
|
||||||
{
|
|
||||||
// TODO: remove after migration decay
|
|
||||||
Id = Guid.Parse(Path.GetFileNameWithoutExtension(metaFile))
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,7 +87,7 @@ public class LocalDiskFileMetadataStore : IFileMetadataStore
|
|||||||
_ => results
|
_ => results
|
||||||
};
|
};
|
||||||
|
|
||||||
return ValueTask.FromResult(new PagedResult<TMeta>
|
return ValueTask.FromResult(new PagedResult<Database.File>
|
||||||
{
|
{
|
||||||
Page = request.Page,
|
Page = request.Page,
|
||||||
PageSize = request.PageSize,
|
PageSize = request.PageSize,
|
||||||
@ -97,26 +98,14 @@ public class LocalDiskFileMetadataStore : IFileMetadataStore
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask<IFileMetadataStore.StoreStats> Stats()
|
public async ValueTask<IFileMetadataStore.StoreStats> Stats()
|
||||||
{
|
{
|
||||||
var files = await ListFiles<FileMeta>(new(0, Int32.MaxValue));
|
var files = await ListFiles(new(0, Int32.MaxValue));
|
||||||
var count = await files.Results.CountAsync();
|
var count = await files.Results.CountAsync();
|
||||||
var size = await files.Results.SumAsync(a => (long) a.Size);
|
var size = await files.Results.SumAsync(a => (long) a.Size);
|
||||||
return new(count, (ulong) size);
|
return new(count, (ulong) size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ValueTask<FileMeta?> Get(Guid id)
|
public async ValueTask Set(Guid id, Database.File meta)
|
||||||
{
|
|
||||||
return GetMeta<FileMeta>(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public ValueTask<SecretFileMeta?> GetPrivate(Guid id)
|
|
||||||
{
|
|
||||||
return GetMeta<SecretFileMeta>(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public async ValueTask Set(Guid id, SecretFileMeta meta)
|
|
||||||
{
|
{
|
||||||
var path = MapMeta(id);
|
var path = MapMeta(id);
|
||||||
var json = JsonConvert.SerializeObject(meta);
|
var json = JsonConvert.SerializeObject(meta);
|
||||||
|
@ -42,7 +42,7 @@ public class LocalDiskFileStore : StreamFileStore, IFileStore
|
|||||||
public string Key => "local-disk";
|
public string Key => "local-disk";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask<PrivateVoidFile> Ingress(IngressPayload payload, CancellationToken cts)
|
public async ValueTask<Database.File> Ingress(IngressPayload payload, CancellationToken cts)
|
||||||
{
|
{
|
||||||
var finalPath = MapPath(payload.Id);
|
var finalPath = MapPath(payload.Id);
|
||||||
await using var fsTemp = new FileStream(finalPath,
|
await using var fsTemp = new FileStream(finalPath,
|
||||||
@ -53,7 +53,7 @@ public class LocalDiskFileStore : StreamFileStore, IFileStore
|
|||||||
if (payload.ShouldStripMetadata && payload.Segment == payload.TotalSegments)
|
if (payload.ShouldStripMetadata && payload.Segment == payload.TotalSegments)
|
||||||
{
|
{
|
||||||
fsTemp.Close();
|
fsTemp.Close();
|
||||||
var ext = Path.GetExtension(vf.Metadata!.Name);
|
var ext = Path.GetExtension(vf.Name);
|
||||||
var srcPath = $"{finalPath}_orig{ext}";
|
var srcPath = $"{finalPath}_orig{ext}";
|
||||||
File.Move(finalPath, srcPath);
|
File.Move(finalPath, srcPath);
|
||||||
|
|
||||||
@ -69,12 +69,9 @@ public class LocalDiskFileStore : StreamFileStore, IFileStore
|
|||||||
var hash = await SHA256.Create().ComputeHashAsync(fInfo.OpenRead(), cts);
|
var hash = await SHA256.Create().ComputeHashAsync(fInfo.OpenRead(), cts);
|
||||||
vf = vf with
|
vf = vf with
|
||||||
{
|
{
|
||||||
Metadata = vf.Metadata! with
|
Size = (ulong)fInfo.Length,
|
||||||
{
|
Digest = hash.ToHex(),
|
||||||
Size = (ulong)fInfo.Length,
|
MimeType = res.MimeType ?? vf.MimeType
|
||||||
Digest = hash.ToHex(),
|
|
||||||
MimeType = res.MimeType ?? vf.Metadata.MimeType
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -86,7 +83,7 @@ public class LocalDiskFileStore : StreamFileStore, IFileStore
|
|||||||
|
|
||||||
if (payload.Segment == payload.TotalSegments)
|
if (payload.Segment == payload.TotalSegments)
|
||||||
{
|
{
|
||||||
var t = await vf.Metadata!.MakeTorrent(
|
var t = await vf.ToMeta(false).MakeTorrent(vf.Id,
|
||||||
new FileStream(finalPath, FileMode.Open),
|
new FileStream(finalPath, FileMode.Open),
|
||||||
_settings.SiteUrl,
|
_settings.SiteUrl,
|
||||||
_settings.TorrentTrackers);
|
_settings.TorrentTrackers);
|
||||||
@ -94,7 +91,7 @@ public class LocalDiskFileStore : StreamFileStore, IFileStore
|
|||||||
var ub = new UriBuilder(_settings.SiteUrl);
|
var ub = new UriBuilder(_settings.SiteUrl);
|
||||||
ub.Path = $"/d/{vf.Id.ToBase58()}.torrent";
|
ub.Path = $"/d/{vf.Id.ToBase58()}.torrent";
|
||||||
|
|
||||||
vf.Metadata!.MagnetLink = $"{t.GetMagnetLink()}&xs={Uri.EscapeDataString(ub.ToString())}";
|
vf.MagnetLink = $"{t.GetMagnetLink()}&xs={Uri.EscapeDataString(ub.ToString())}";
|
||||||
}
|
}
|
||||||
|
|
||||||
return vf;
|
return vf;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
using Dapper;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using VoidCat.Model;
|
using VoidCat.Model;
|
||||||
using VoidCat.Services.Abstractions;
|
using VoidCat.Services.Abstractions;
|
||||||
|
|
||||||
@ -7,119 +7,103 @@ namespace VoidCat.Services.Files;
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public class PostgresFileMetadataStore : IFileMetadataStore
|
public class PostgresFileMetadataStore : IFileMetadataStore
|
||||||
{
|
{
|
||||||
private readonly PostgresConnectionFactory _connection;
|
private readonly VoidContext _db;
|
||||||
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
|
|
||||||
public PostgresFileMetadataStore(PostgresConnectionFactory connection)
|
public PostgresFileMetadataStore(VoidContext db, IServiceScopeFactory scopeFactory)
|
||||||
{
|
{
|
||||||
_connection = connection;
|
_db = db;
|
||||||
|
_scopeFactory = scopeFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string? Key => "postgres";
|
public string? Key => "postgres";
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public ValueTask<FileMeta?> Get(Guid id)
|
|
||||||
{
|
|
||||||
return Get<FileMeta>(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ValueTask<SecretFileMeta?> GetPrivate(Guid id)
|
public async ValueTask<Database.File?> Get(Guid id)
|
||||||
{
|
{
|
||||||
return Get<SecretFileMeta>(id);
|
return await _db.Files
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(a => a.Paywall)
|
||||||
|
.SingleOrDefaultAsync(a => a.Id == id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
public async ValueTask Add(Database.File f)
|
||||||
public async ValueTask Set(Guid id, SecretFileMeta obj)
|
|
||||||
{
|
{
|
||||||
await using var conn = await _connection.Get();
|
_db.Files.Add(f);
|
||||||
await conn.ExecuteAsync(
|
await _db.SaveChangesAsync();
|
||||||
@"insert into
|
|
||||||
""Files""(""Id"", ""Name"", ""Size"", ""Uploaded"", ""Description"", ""MimeType"", ""Digest"", ""EditSecret"", ""Expires"", ""Storage"", ""EncryptionParams"", ""MagnetLink"")
|
|
||||||
values(:id, :name, :size, :uploaded, :description, :mimeType, :digest, :editSecret, :expires, :store, :encryptionParams, :magnetLink)
|
|
||||||
on conflict (""Id"") do update set
|
|
||||||
""Name"" = :name,
|
|
||||||
""Size"" = :size,
|
|
||||||
""Description"" = :description,
|
|
||||||
""MimeType"" = :mimeType,
|
|
||||||
""Expires"" = :expires,
|
|
||||||
""Storage"" = :store,
|
|
||||||
""EncryptionParams"" = :encryptionParams,
|
|
||||||
""MagnetLink"" = :magnetLink",
|
|
||||||
new
|
|
||||||
{
|
|
||||||
id,
|
|
||||||
name = obj.Name,
|
|
||||||
size = (long) obj.Size,
|
|
||||||
uploaded = obj.Uploaded.ToUniversalTime(),
|
|
||||||
description = obj.Description,
|
|
||||||
mimeType = obj.MimeType,
|
|
||||||
digest = obj.Digest,
|
|
||||||
editSecret = obj.EditSecret,
|
|
||||||
expires = obj.Expires?.ToUniversalTime(),
|
|
||||||
store = obj.Storage,
|
|
||||||
encryptionParams = obj.EncryptionParams,
|
|
||||||
magnetLink = obj.MagnetLink,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask Delete(Guid id)
|
public async ValueTask Delete(Guid id)
|
||||||
{
|
{
|
||||||
await using var conn = await _connection.Get();
|
await _db.Files
|
||||||
await conn.ExecuteAsync("delete from \"Files\" where \"Id\" = :id", new {id});
|
.Where(a => a.Id == id)
|
||||||
|
.ExecuteDeleteAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask<TMeta?> Get<TMeta>(Guid id) where TMeta : FileMeta
|
public async ValueTask<IReadOnlyList<Database.File>> Get(Guid[] ids)
|
||||||
{
|
{
|
||||||
await using var conn = await _connection.Get();
|
return await _db.Files
|
||||||
return await conn.QuerySingleOrDefaultAsync<TMeta?>(@"select * from ""Files"" where ""Id"" = :id",
|
.Include(a => a.Paywall)
|
||||||
new {id});
|
.Where(a => ids.Contains(a.Id))
|
||||||
|
.ToArrayAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask<IReadOnlyList<TMeta>> Get<TMeta>(Guid[] ids) where TMeta : FileMeta
|
public async ValueTask Update(Guid id, Database.File obj)
|
||||||
{
|
{
|
||||||
await using var conn = await _connection.Get();
|
var existing = await _db.Files.FindAsync(id);
|
||||||
var ret = await conn.QueryAsync<TMeta>("select * from \"Files\" where \"Id\" in :ids", new {ids});
|
if (existing == default)
|
||||||
return ret.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public async ValueTask Update<TMeta>(Guid id, TMeta meta) where TMeta : FileMeta
|
|
||||||
{
|
|
||||||
var oldMeta = await Get<SecretFileMeta>(id);
|
|
||||||
if (oldMeta == default) return;
|
|
||||||
|
|
||||||
oldMeta.Patch(meta);
|
|
||||||
await Set(id, oldMeta);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public async ValueTask<PagedResult<TMeta>> ListFiles<TMeta>(PagedRequest request) where TMeta : FileMeta
|
|
||||||
{
|
|
||||||
await using var conn = await _connection.Get();
|
|
||||||
var count = await conn.ExecuteScalarAsync<int>(@"select count(*) from ""Files""");
|
|
||||||
|
|
||||||
async IAsyncEnumerable<TMeta> Enumerate()
|
|
||||||
{
|
{
|
||||||
var orderBy = request.SortBy switch
|
return;
|
||||||
{
|
}
|
||||||
PagedSortBy.Date => "Uploaded",
|
|
||||||
PagedSortBy.Name => "Name",
|
|
||||||
PagedSortBy.Size => "Size",
|
|
||||||
_ => "Id"
|
|
||||||
};
|
|
||||||
await using var iconn = await _connection.Get();
|
|
||||||
var orderDirection = request.SortOrder == PageSortOrder.Asc ? "asc" : "desc";
|
|
||||||
var results = await iconn.QueryAsync<TMeta>(
|
|
||||||
$"select * from \"Files\" order by \"{orderBy}\" {orderDirection} offset @offset limit @limit",
|
|
||||||
new {offset = request.PageSize * request.Page, limit = request.PageSize});
|
|
||||||
|
|
||||||
foreach (var meta in results)
|
existing.Patch(obj);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async ValueTask<PagedResult<Database.File>> ListFiles(PagedRequest request)
|
||||||
|
{
|
||||||
|
var count = await _db.Files.CountAsync();
|
||||||
|
|
||||||
|
async IAsyncEnumerable<Database.File> Enumerate()
|
||||||
|
{
|
||||||
|
using var scope = _scopeFactory.CreateScope();
|
||||||
|
var db = scope.ServiceProvider.GetRequiredService<VoidContext>();
|
||||||
|
var q = db.Files.AsNoTracking().AsQueryable();
|
||||||
|
switch (request.SortBy, request.SortOrder)
|
||||||
{
|
{
|
||||||
yield return meta;
|
case (PagedSortBy.Id, PageSortOrder.Asc):
|
||||||
|
q = q.OrderBy(a => a.Id);
|
||||||
|
break;
|
||||||
|
case (PagedSortBy.Id, PageSortOrder.Dsc):
|
||||||
|
q = q.OrderByDescending(a => a.Id);
|
||||||
|
break;
|
||||||
|
case (PagedSortBy.Name, PageSortOrder.Asc):
|
||||||
|
q = q.OrderBy(a => a.Name);
|
||||||
|
break;
|
||||||
|
case (PagedSortBy.Name, PageSortOrder.Dsc):
|
||||||
|
q = q.OrderByDescending(a => a.Name);
|
||||||
|
break;
|
||||||
|
case (PagedSortBy.Date, PageSortOrder.Asc):
|
||||||
|
q = q.OrderBy(a => a.Uploaded);
|
||||||
|
break;
|
||||||
|
case (PagedSortBy.Date, PageSortOrder.Dsc):
|
||||||
|
q = q.OrderByDescending(a => a.Uploaded);
|
||||||
|
break;
|
||||||
|
case (PagedSortBy.Size, PageSortOrder.Asc):
|
||||||
|
q = q.OrderBy(a => a.Size);
|
||||||
|
break;
|
||||||
|
case (PagedSortBy.Size, PageSortOrder.Dsc):
|
||||||
|
q = q.OrderByDescending(a => a.Size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await foreach (var r in q.Skip(request.Page * request.PageSize).Take(request.PageSize).AsAsyncEnumerable())
|
||||||
|
{
|
||||||
|
yield return r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,9 +119,11 @@ on conflict (""Id"") do update set
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask<IFileMetadataStore.StoreStats> Stats()
|
public async ValueTask<IFileMetadataStore.StoreStats> Stats()
|
||||||
{
|
{
|
||||||
await using var conn = await _connection.Get();
|
var size = await _db.Files
|
||||||
var v = await conn.QuerySingleAsync<(long Files, long Size)>(
|
.AsNoTracking()
|
||||||
@"select count(1) ""Files"", cast(sum(""Size"") as bigint) ""Size"" from ""Files""");
|
.SumAsync(a => (long)a.Size);
|
||||||
return new(v.Files, (ulong) v.Size);
|
|
||||||
|
var count = await _db.Files.CountAsync();
|
||||||
|
return new(count, (ulong)size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,22 +19,35 @@ public class S3FileMetadataStore : IFileMetadataStore
|
|||||||
_client = _config.CreateClient();
|
_client = _config.CreateClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string? Key => _config.Name;
|
public string? Key => _config.Name;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ValueTask<TMeta?> Get<TMeta>(Guid id) where TMeta : FileMeta
|
public async ValueTask<Database.File?> Get(Guid id)
|
||||||
{
|
{
|
||||||
return GetMeta<TMeta>(id);
|
try
|
||||||
|
{
|
||||||
|
var obj = await _client.GetObjectAsync(_config.BucketName, ToKey(id));
|
||||||
|
|
||||||
|
using var sr = new StreamReader(obj.ResponseStream);
|
||||||
|
var json = await sr.ReadToEndAsync();
|
||||||
|
var ret = JsonConvert.DeserializeObject<Database.File>(json);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
catch (AmazonS3Exception aex)
|
||||||
|
{
|
||||||
|
_logger.LogError(aex, "Failed to get metadata for {Id}, {Error}", id, aex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask<IReadOnlyList<TMeta>> Get<TMeta>(Guid[] ids) where TMeta : FileMeta
|
public async ValueTask<IReadOnlyList<Database.File>> Get(Guid[] ids)
|
||||||
{
|
{
|
||||||
var ret = new List<TMeta>();
|
var ret = new List<Database.File>();
|
||||||
foreach (var id in ids)
|
foreach (var id in ids)
|
||||||
{
|
{
|
||||||
var r = await GetMeta<TMeta>(id);
|
var r = await Get(id);
|
||||||
if (r != null)
|
if (r != null)
|
||||||
{
|
{
|
||||||
ret.Add(r);
|
ret.Add(r);
|
||||||
@ -43,11 +56,15 @@ public class S3FileMetadataStore : IFileMetadataStore
|
|||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
public ValueTask Add(Database.File f)
|
||||||
|
{
|
||||||
|
return Set(f.Id, f);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask Update<TMeta>(Guid id, TMeta meta) where TMeta : FileMeta
|
public async ValueTask Update(Guid id, Database.File meta)
|
||||||
{
|
{
|
||||||
var oldMeta = await Get<SecretFileMeta>(id);
|
var oldMeta = await Get(id);
|
||||||
if (oldMeta == default) return;
|
if (oldMeta == default) return;
|
||||||
|
|
||||||
oldMeta.Patch(meta);
|
oldMeta.Patch(meta);
|
||||||
@ -55,9 +72,9 @@ public class S3FileMetadataStore : IFileMetadataStore
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ValueTask<PagedResult<TMeta>> ListFiles<TMeta>(PagedRequest request) where TMeta : FileMeta
|
public ValueTask<PagedResult<Database.File>> ListFiles(PagedRequest request)
|
||||||
{
|
{
|
||||||
async IAsyncEnumerable<TMeta> Enumerate()
|
async IAsyncEnumerable<Database.File> Enumerate()
|
||||||
{
|
{
|
||||||
var obj = await _client.ListObjectsV2Async(new()
|
var obj = await _client.ListObjectsV2Async(new()
|
||||||
{
|
{
|
||||||
@ -70,7 +87,7 @@ public class S3FileMetadataStore : IFileMetadataStore
|
|||||||
{
|
{
|
||||||
if (Guid.TryParse(file.Key.Split("metadata_")[1], out var id))
|
if (Guid.TryParse(file.Key.Split("metadata_")[1], out var id))
|
||||||
{
|
{
|
||||||
var meta = await GetMeta<TMeta>(id);
|
var meta = await Get(id);
|
||||||
if (meta != default)
|
if (meta != default)
|
||||||
{
|
{
|
||||||
yield return meta;
|
yield return meta;
|
||||||
@ -79,7 +96,7 @@ public class S3FileMetadataStore : IFileMetadataStore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ValueTask.FromResult(new PagedResult<TMeta>
|
return ValueTask.FromResult(new PagedResult<Database.File>
|
||||||
{
|
{
|
||||||
Page = request.Page,
|
Page = request.Page,
|
||||||
PageSize = request.PageSize,
|
PageSize = request.PageSize,
|
||||||
@ -90,26 +107,19 @@ public class S3FileMetadataStore : IFileMetadataStore
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask<IFileMetadataStore.StoreStats> Stats()
|
public async ValueTask<IFileMetadataStore.StoreStats> Stats()
|
||||||
{
|
{
|
||||||
var files = await ListFiles<FileMeta>(new(0, Int32.MaxValue));
|
var files = await ListFiles(new(0, Int32.MaxValue));
|
||||||
var count = await files.Results.CountAsync();
|
var count = await files.Results.CountAsync();
|
||||||
var size = await files.Results.SumAsync(a => (long) a.Size);
|
var size = await files.Results.SumAsync(a => (long) a.Size);
|
||||||
return new(count, (ulong) size);
|
return new(count, (ulong) size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ValueTask<FileMeta?> Get(Guid id)
|
public async ValueTask Delete(Guid id)
|
||||||
{
|
{
|
||||||
return GetMeta<FileMeta>(id);
|
await _client.DeleteObjectAsync(_config.BucketName, ToKey(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
private async ValueTask Set(Guid id, Database.File meta)
|
||||||
public ValueTask<SecretFileMeta?> GetPrivate(Guid id)
|
|
||||||
{
|
|
||||||
return GetMeta<SecretFileMeta>(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public async ValueTask Set(Guid id, SecretFileMeta meta)
|
|
||||||
{
|
{
|
||||||
await _client.PutObjectAsync(new()
|
await _client.PutObjectAsync(new()
|
||||||
{
|
{
|
||||||
@ -120,35 +130,5 @@ public class S3FileMetadataStore : IFileMetadataStore
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public async ValueTask Delete(Guid id)
|
|
||||||
{
|
|
||||||
await _client.DeleteObjectAsync(_config.BucketName, ToKey(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ValueTask<TMeta?> GetMeta<TMeta>(Guid id) where TMeta : FileMeta
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var obj = await _client.GetObjectAsync(_config.BucketName, ToKey(id));
|
|
||||||
|
|
||||||
using var sr = new StreamReader(obj.ResponseStream);
|
|
||||||
var json = await sr.ReadToEndAsync();
|
|
||||||
var ret = JsonConvert.DeserializeObject<TMeta>(json);
|
|
||||||
if (ret != default)
|
|
||||||
{
|
|
||||||
ret.Id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
catch (AmazonS3Exception aex)
|
|
||||||
{
|
|
||||||
_logger.LogError(aex, "Failed to get metadata for {Id}, {Error}", id, aex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ToKey(Guid id) => $"metadata_{id}";
|
private static string ToKey(Guid id) => $"metadata_{id}";
|
||||||
}
|
}
|
@ -9,13 +9,13 @@ namespace VoidCat.Services.Files;
|
|||||||
/// <inheritdoc cref="VoidCat.Services.Abstractions.IFileStore" />
|
/// <inheritdoc cref="VoidCat.Services.Abstractions.IFileStore" />
|
||||||
public class S3FileStore : StreamFileStore, IFileStore
|
public class S3FileStore : StreamFileStore, IFileStore
|
||||||
{
|
{
|
||||||
private readonly FileInfoManager _fileInfo;
|
private readonly IFileMetadataStore _fileInfo;
|
||||||
private readonly AmazonS3Client _client;
|
private readonly AmazonS3Client _client;
|
||||||
private readonly S3BlobConfig _config;
|
private readonly S3BlobConfig _config;
|
||||||
private readonly IAggregateStatsCollector _statsCollector;
|
private readonly IAggregateStatsCollector _statsCollector;
|
||||||
private readonly ICache _cache;
|
private readonly ICache _cache;
|
||||||
|
|
||||||
public S3FileStore(S3BlobConfig settings, IAggregateStatsCollector stats, FileInfoManager fileInfo, ICache cache) : base(stats)
|
public S3FileStore(S3BlobConfig settings, IAggregateStatsCollector stats, IFileMetadataStore fileInfo, ICache cache) : base(stats)
|
||||||
{
|
{
|
||||||
_fileInfo = fileInfo;
|
_fileInfo = fileInfo;
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
@ -28,7 +28,7 @@ public class S3FileStore : StreamFileStore, IFileStore
|
|||||||
public string Key => _config.Name;
|
public string Key => _config.Name;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask<PrivateVoidFile> Ingress(IngressPayload payload, CancellationToken cts)
|
public async ValueTask<Database.File> Ingress(IngressPayload payload, CancellationToken cts)
|
||||||
{
|
{
|
||||||
if (payload.IsMultipart) return await IngressMultipart(payload, cts);
|
if (payload.IsMultipart) return await IngressMultipart(payload, cts);
|
||||||
|
|
||||||
@ -75,15 +75,15 @@ public class S3FileStore : StreamFileStore, IFileStore
|
|||||||
Key = request.Id.ToString(),
|
Key = request.Id.ToString(),
|
||||||
ResponseHeaderOverrides = new()
|
ResponseHeaderOverrides = new()
|
||||||
{
|
{
|
||||||
ContentDisposition = $"inline; filename=\"{meta?.Metadata?.Name}\"",
|
ContentDisposition = $"inline; filename=\"{meta?.Name}\"",
|
||||||
ContentType = meta?.Metadata?.MimeType
|
ContentType = meta?.MimeType
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return new(new Uri(url));
|
return new(new Uri(url));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<PagedResult<PublicVoidFile>> ListFiles(PagedRequest request)
|
public async ValueTask<PagedResult<Database.File>> ListFiles(PagedRequest request)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -103,7 +103,7 @@ public class S3FileStore : StreamFileStore, IFileStore
|
|||||||
_ => objs.S3Objects.AsEnumerable()
|
_ => objs.S3Objects.AsEnumerable()
|
||||||
};
|
};
|
||||||
|
|
||||||
async IAsyncEnumerable<PublicVoidFile> EnumerateFiles(IEnumerable<S3Object> page)
|
async IAsyncEnumerable<Database.File> EnumerateFiles(IEnumerable<S3Object> page)
|
||||||
{
|
{
|
||||||
foreach (var item in page)
|
foreach (var item in page)
|
||||||
{
|
{
|
||||||
@ -133,7 +133,7 @@ public class S3FileStore : StreamFileStore, IFileStore
|
|||||||
Page = request.Page,
|
Page = request.Page,
|
||||||
PageSize = request.PageSize,
|
PageSize = request.PageSize,
|
||||||
TotalResults = 0,
|
TotalResults = 0,
|
||||||
Results = AsyncEnumerable.Empty<PublicVoidFile>()
|
Results = AsyncEnumerable.Empty<Database.File>()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,7 +163,7 @@ public class S3FileStore : StreamFileStore, IFileStore
|
|||||||
return obj.ResponseStream;
|
return obj.ResponseStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<PrivateVoidFile> IngressMultipart(IngressPayload payload, CancellationToken cts)
|
private async Task<Database.File> IngressMultipart(IngressPayload payload, CancellationToken cts)
|
||||||
{
|
{
|
||||||
string? uploadId = null;
|
string? uploadId = null;
|
||||||
var cacheKey = $"s3:{_config.Name}:multipart-upload-id:{payload.Id}";
|
var cacheKey = $"s3:{_config.Name}:multipart-upload-id:{payload.Id}";
|
||||||
|
@ -31,7 +31,7 @@ public abstract class StreamFileStore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async ValueTask<PrivateVoidFile> IngressToStream(Stream outStream, IngressPayload payload,
|
protected async ValueTask<Database.File> IngressToStream(Stream outStream, IngressPayload payload,
|
||||||
CancellationToken cts)
|
CancellationToken cts)
|
||||||
{
|
{
|
||||||
var id = payload.Id;
|
var id = payload.Id;
|
||||||
@ -48,13 +48,14 @@ public abstract class StreamFileStore
|
|||||||
return HandleCompletedUpload(payload, total);
|
return HandleCompletedUpload(payload, total);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PrivateVoidFile HandleCompletedUpload(IngressPayload payload, ulong totalSize)
|
protected Database.File HandleCompletedUpload(IngressPayload payload, ulong totalSize)
|
||||||
{
|
{
|
||||||
var meta = payload.Meta;
|
var meta = payload.Meta;
|
||||||
if (payload.IsAppend)
|
if (payload.IsAppend)
|
||||||
{
|
{
|
||||||
meta = meta with
|
meta = meta with
|
||||||
{
|
{
|
||||||
|
Id = payload.Id,
|
||||||
Size = meta.Size + totalSize
|
Size = meta.Size + totalSize
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -62,19 +63,14 @@ public abstract class StreamFileStore
|
|||||||
{
|
{
|
||||||
meta = meta with
|
meta = meta with
|
||||||
{
|
{
|
||||||
Uploaded = DateTimeOffset.UtcNow,
|
Id = payload.Id,
|
||||||
|
Uploaded = DateTime.UtcNow,
|
||||||
EditSecret = Guid.NewGuid(),
|
EditSecret = Guid.NewGuid(),
|
||||||
Size = totalSize
|
Size = totalSize
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var vf = new PrivateVoidFile()
|
return meta;
|
||||||
{
|
|
||||||
Id = payload.Id,
|
|
||||||
Metadata = meta
|
|
||||||
};
|
|
||||||
|
|
||||||
return vf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<ulong> IngressInternal(Guid id, Stream ingress, Stream outStream,
|
private async Task<ulong> IngressInternal(Guid id, Stream ingress, Stream outStream,
|
||||||
|
@ -1,100 +0,0 @@
|
|||||||
using System.Data;
|
|
||||||
using FluentMigrator;
|
|
||||||
using VoidCat.Model.User;
|
|
||||||
|
|
||||||
namespace VoidCat.Services.Migrations.Database;
|
|
||||||
|
|
||||||
[Migration(20220604_2232)]
|
|
||||||
public class Init : Migration
|
|
||||||
{
|
|
||||||
public override void Up()
|
|
||||||
{
|
|
||||||
Create.Table("Users")
|
|
||||||
.WithColumn("Id").AsGuid().PrimaryKey()
|
|
||||||
.WithColumn("Email").AsString().Indexed()
|
|
||||||
.WithColumn("Password").AsString()
|
|
||||||
.WithColumn("Created").AsDateTimeOffset().WithDefault(SystemMethods.CurrentUTCDateTime)
|
|
||||||
.WithColumn("LastLogin").AsDateTimeOffset().Nullable()
|
|
||||||
.WithColumn("Avatar").AsString().Nullable()
|
|
||||||
.WithColumn("DisplayName").AsString().WithDefaultValue("void user")
|
|
||||||
.WithColumn("Flags").AsInt32().WithDefaultValue((int) UserFlags.PublicProfile);
|
|
||||||
|
|
||||||
Create.Table("Files")
|
|
||||||
.WithColumn("Id").AsGuid().PrimaryKey()
|
|
||||||
.WithColumn("Name").AsString().Nullable()
|
|
||||||
.WithColumn("Size").AsInt64()
|
|
||||||
.WithColumn("Uploaded").AsDateTimeOffset().Indexed().WithDefault(SystemMethods.CurrentUTCDateTime)
|
|
||||||
.WithColumn("Description").AsString().Nullable()
|
|
||||||
.WithColumn("MimeType").AsString().WithDefaultValue("application/octet-stream")
|
|
||||||
.WithColumn("Digest").AsString().Nullable()
|
|
||||||
.WithColumn("EditSecret").AsGuid();
|
|
||||||
|
|
||||||
Create.Table("UserFiles")
|
|
||||||
.WithColumn("File").AsGuid().ForeignKey("Files", "Id").OnDelete(Rule.Cascade).Indexed()
|
|
||||||
.WithColumn("User").AsGuid().ForeignKey("Users", "Id").OnDelete(Rule.Cascade).Indexed();
|
|
||||||
|
|
||||||
Create.UniqueConstraint()
|
|
||||||
.OnTable("UserFiles")
|
|
||||||
.Columns("File", "User");
|
|
||||||
|
|
||||||
Create.Table("Paywall")
|
|
||||||
.WithColumn("File").AsGuid().ForeignKey("Files", "Id").OnDelete(Rule.Cascade).PrimaryKey()
|
|
||||||
.WithColumn("Service").AsInt16()
|
|
||||||
.WithColumn("Currency").AsInt16()
|
|
||||||
.WithColumn("Amount").AsDecimal();
|
|
||||||
|
|
||||||
Create.Table("PaywallStrike")
|
|
||||||
.WithColumn("File").AsGuid().ForeignKey("Paywall", "File").OnDelete(Rule.Cascade).PrimaryKey()
|
|
||||||
.WithColumn("Handle").AsString();
|
|
||||||
|
|
||||||
Create.Table("PaywallOrder")
|
|
||||||
.WithColumn("Id").AsGuid().PrimaryKey()
|
|
||||||
.WithColumn("File").AsGuid().ForeignKey("Files", "Id").OnDelete(Rule.Cascade).Indexed()
|
|
||||||
.WithColumn("Service").AsInt16()
|
|
||||||
.WithColumn("Currency").AsInt16()
|
|
||||||
.WithColumn("Amount").AsDecimal()
|
|
||||||
.WithColumn("Status").AsInt16().Indexed();
|
|
||||||
|
|
||||||
Create.Table("PaywallOrderLightning")
|
|
||||||
.WithColumn("Order").AsGuid().ForeignKey("PaywallOrder", "Id").OnDelete(Rule.Cascade).PrimaryKey()
|
|
||||||
.WithColumn("Invoice").AsString()
|
|
||||||
.WithColumn("Expire").AsDateTimeOffset();
|
|
||||||
|
|
||||||
Create.Table("UserRoles")
|
|
||||||
.WithColumn("User").AsGuid().ForeignKey("Users", "Id").OnDelete(Rule.Cascade).Indexed()
|
|
||||||
.WithColumn("Role").AsString().NotNullable();
|
|
||||||
|
|
||||||
Create.UniqueConstraint()
|
|
||||||
.OnTable("UserRoles")
|
|
||||||
.Columns("User", "Role");
|
|
||||||
|
|
||||||
Create.Table("EmailVerification")
|
|
||||||
.WithColumn("User").AsGuid().ForeignKey("Users", "Id").OnDelete(Rule.Cascade)
|
|
||||||
.WithColumn("Code").AsGuid()
|
|
||||||
.WithColumn("Expires").AsDateTimeOffset();
|
|
||||||
|
|
||||||
Create.UniqueConstraint()
|
|
||||||
.OnTable("EmailVerification")
|
|
||||||
.Columns("User", "Code");
|
|
||||||
|
|
||||||
Create.Table("VirusScanResult")
|
|
||||||
.WithColumn("Id").AsGuid().PrimaryKey()
|
|
||||||
.WithColumn("File").AsGuid().ForeignKey("Files", "Id").OnDelete(Rule.Cascade).Indexed()
|
|
||||||
.WithColumn("ScanTime").AsDateTimeOffset().WithDefault(SystemMethods.CurrentUTCDateTime)
|
|
||||||
.WithColumn("Scanner").AsString()
|
|
||||||
.WithColumn("Score").AsDecimal()
|
|
||||||
.WithColumn("Names").AsString().Nullable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Down()
|
|
||||||
{
|
|
||||||
Delete.Table("Users");
|
|
||||||
Delete.Table("Files");
|
|
||||||
Delete.Table("UsersFiles");
|
|
||||||
Delete.Table("Paywall");
|
|
||||||
Delete.Table("PaywallStrike");
|
|
||||||
Delete.Table("UserRoles");
|
|
||||||
Delete.Table("EmailVerification");
|
|
||||||
Delete.Table("VirusScanResult");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
using FluentMigrator;
|
|
||||||
|
|
||||||
namespace VoidCat.Services.Migrations.Database;
|
|
||||||
|
|
||||||
[Migration(20220615_2238)]
|
|
||||||
public class FileExpiry : Migration {
|
|
||||||
public override void Up()
|
|
||||||
{
|
|
||||||
Create.Column("Expires")
|
|
||||||
.OnTable("Files")
|
|
||||||
.AsDateTimeOffset().Nullable().Indexed();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Down()
|
|
||||||
{
|
|
||||||
Delete.Column("Expires")
|
|
||||||
.FromTable("Files");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
using System.Data;
|
|
||||||
using FluentMigrator;
|
|
||||||
|
|
||||||
namespace VoidCat.Services.Migrations.Database;
|
|
||||||
|
|
||||||
[Migration(20220725_1137)]
|
|
||||||
public class MinorVersion1 : Migration
|
|
||||||
{
|
|
||||||
public override void Up()
|
|
||||||
{
|
|
||||||
Create.Table("ApiKey")
|
|
||||||
.WithColumn("Id").AsGuid().PrimaryKey()
|
|
||||||
.WithColumn("UserId").AsGuid().ForeignKey("Users", "Id").OnDelete(Rule.Cascade).Indexed()
|
|
||||||
.WithColumn("Token").AsString()
|
|
||||||
.WithColumn("Expiry").AsDateTimeOffset()
|
|
||||||
.WithColumn("Created").AsDateTimeOffset().WithDefault(SystemMethods.CurrentUTCDateTime);
|
|
||||||
|
|
||||||
Create.Column("Storage")
|
|
||||||
.OnTable("Files")
|
|
||||||
.AsString().WithDefaultValue("local-disk");
|
|
||||||
|
|
||||||
Create.Column("Storage")
|
|
||||||
.OnTable("Users")
|
|
||||||
.AsString().WithDefaultValue("local-disk");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Down()
|
|
||||||
{
|
|
||||||
Delete.Table("ApiKey");
|
|
||||||
|
|
||||||
Delete.Column("Storage")
|
|
||||||
.FromTable("Files");
|
|
||||||
|
|
||||||
Delete.Column("Storage")
|
|
||||||
.FromTable("Users");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
using FluentMigrator;
|
|
||||||
|
|
||||||
namespace VoidCat.Services.Migrations.Database;
|
|
||||||
|
|
||||||
[Migration(20220908_1527)]
|
|
||||||
public class PaywallToPayments : Migration
|
|
||||||
{
|
|
||||||
public override void Up()
|
|
||||||
{
|
|
||||||
Rename.Table("Paywall")
|
|
||||||
.To("Payment");
|
|
||||||
|
|
||||||
Rename.Table("PaywallOrder")
|
|
||||||
.To("PaymentOrder");
|
|
||||||
|
|
||||||
Rename.Table("PaywallStrike")
|
|
||||||
.To("PaymentStrike");
|
|
||||||
|
|
||||||
Rename.Table("PaywallOrderLightning")
|
|
||||||
.To("PaymentOrderLightning");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Down()
|
|
||||||
{
|
|
||||||
// yolo
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
using FluentMigrator;
|
|
||||||
|
|
||||||
namespace VoidCat.Services.Migrations.Database;
|
|
||||||
|
|
||||||
[Migration(20220908_1602)]
|
|
||||||
public class OptionalPayments : Migration{
|
|
||||||
public override void Up()
|
|
||||||
{
|
|
||||||
Create.Column("Required")
|
|
||||||
.OnTable("Payment")
|
|
||||||
.AsBoolean()
|
|
||||||
.WithDefaultValue(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Down()
|
|
||||||
{
|
|
||||||
Delete.Column("Required")
|
|
||||||
.FromTable("Payment");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
using System.Data;
|
|
||||||
using FluentMigrator;
|
|
||||||
|
|
||||||
namespace VoidCat.Services.Migrations.Database;
|
|
||||||
|
|
||||||
[Migration(20220907_2015)]
|
|
||||||
public class AccountTypes : Migration
|
|
||||||
{
|
|
||||||
public override void Up()
|
|
||||||
{
|
|
||||||
Create.Column("AuthType")
|
|
||||||
.OnTable("Users")
|
|
||||||
.AsInt16()
|
|
||||||
.WithDefaultValue(0);
|
|
||||||
|
|
||||||
Alter.Column("Password")
|
|
||||||
.OnTable("Users")
|
|
||||||
.AsString()
|
|
||||||
.Nullable();
|
|
||||||
|
|
||||||
Create.Table("UsersAuthToken")
|
|
||||||
.WithColumn("Id").AsGuid().PrimaryKey()
|
|
||||||
.WithColumn("User").AsGuid().ForeignKey("Users", "Id").OnDelete(Rule.Cascade).Indexed()
|
|
||||||
.WithColumn("Provider").AsString()
|
|
||||||
.WithColumn("AccessToken").AsString()
|
|
||||||
.WithColumn("TokenType").AsString()
|
|
||||||
.WithColumn("Expires").AsDateTimeOffset()
|
|
||||||
.WithColumn("RefreshToken").AsString()
|
|
||||||
.WithColumn("Scope").AsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Down()
|
|
||||||
{
|
|
||||||
Delete.Column("Type")
|
|
||||||
.FromTable("Users");
|
|
||||||
|
|
||||||
Alter.Column("Password")
|
|
||||||
.OnTable("Users")
|
|
||||||
.AsString()
|
|
||||||
.NotNullable();
|
|
||||||
|
|
||||||
Delete.Table("UsersAuthToken");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
using FluentMigrator;
|
|
||||||
|
|
||||||
namespace VoidCat.Services.Migrations.Database;
|
|
||||||
|
|
||||||
[Migration(20220911_1635)]
|
|
||||||
public class EncryptionParams : Migration{
|
|
||||||
public override void Up()
|
|
||||||
{
|
|
||||||
Create.Column("EncryptionParams")
|
|
||||||
.OnTable("Files")
|
|
||||||
.AsString()
|
|
||||||
.Nullable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Down()
|
|
||||||
{
|
|
||||||
Delete.Column("EncryptionParams")
|
|
||||||
.FromTable("Files");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
using FluentMigrator;
|
|
||||||
|
|
||||||
namespace VoidCat.Services.Migrations.Database;
|
|
||||||
|
|
||||||
[Migration(20230304_1509)]
|
|
||||||
public class MagnetLink : Migration {
|
|
||||||
public override void Up()
|
|
||||||
{
|
|
||||||
Create.Column("MagnetLink")
|
|
||||||
.OnTable("Files")
|
|
||||||
.AsString()
|
|
||||||
.Nullable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Down()
|
|
||||||
{
|
|
||||||
Delete.Column("MagnetLink")
|
|
||||||
.FromTable("Files");
|
|
||||||
}
|
|
||||||
}
|
|
21
VoidCat/Services/Migrations/EFMigration.cs
Normal file
21
VoidCat/Services/Migrations/EFMigration.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace VoidCat.Services.Migrations;
|
||||||
|
|
||||||
|
public class EFMigration : IMigration
|
||||||
|
{
|
||||||
|
private readonly VoidContext _db;
|
||||||
|
|
||||||
|
public EFMigration(VoidContext db)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Order => 0;
|
||||||
|
|
||||||
|
public async ValueTask<IMigration.MigrationResult> Migrate(string[] args)
|
||||||
|
{
|
||||||
|
await _db.Database.MigrateAsync();
|
||||||
|
return IMigration.MigrationResult.Completed;
|
||||||
|
}
|
||||||
|
}
|
88
VoidCat/Services/Migrations/EFMigrationSetup.cs
Normal file
88
VoidCat/Services/Migrations/EFMigrationSetup.cs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
using System.Data.Common;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Npgsql;
|
||||||
|
using VoidCat.Model;
|
||||||
|
|
||||||
|
namespace VoidCat.Services.Migrations;
|
||||||
|
|
||||||
|
public class EFMigrationSetup : IMigration
|
||||||
|
{
|
||||||
|
private readonly VoidContext _db;
|
||||||
|
private readonly VoidSettings _settings;
|
||||||
|
|
||||||
|
public EFMigrationSetup(VoidContext db, VoidSettings settings)
|
||||||
|
{
|
||||||
|
_db = db;
|
||||||
|
_settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Order => -99;
|
||||||
|
|
||||||
|
public async ValueTask<IMigration.MigrationResult> Migrate(string[] args)
|
||||||
|
{
|
||||||
|
if (!_settings.HasPostgres()) return IMigration.MigrationResult.Skipped;
|
||||||
|
|
||||||
|
var conn = (_db.Database.GetDbConnection() as NpgsqlConnection)!;
|
||||||
|
await conn.OpenAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var cmd = new NpgsqlCommand("select max(\"Version\") from \"VersionInfo\"", conn);
|
||||||
|
|
||||||
|
var vMax = await cmd.ExecuteScalarAsync() as long?;
|
||||||
|
if (!(vMax > 0)) return IMigration.MigrationResult.Skipped;
|
||||||
|
|
||||||
|
await PrepEfMigration(conn);
|
||||||
|
}
|
||||||
|
catch (DbException dx) when (dx.SqlState is "42P01")
|
||||||
|
{
|
||||||
|
//ignored, VersionInfo does not exist
|
||||||
|
return IMigration.MigrationResult.Skipped;
|
||||||
|
}
|
||||||
|
|
||||||
|
return IMigration.MigrationResult.Completed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task PrepEfMigration(NpgsqlConnection conn)
|
||||||
|
{
|
||||||
|
await using var tx = await conn.BeginTransactionAsync();
|
||||||
|
|
||||||
|
await new NpgsqlCommand(@"
|
||||||
|
ALTER TABLE ""Files"" ALTER COLUMN ""Size"" TYPE numeric(20) USING ""Size""::numeric;
|
||||||
|
ALTER TABLE ""Payment"" RENAME COLUMN ""File"" TO ""FileId"";
|
||||||
|
ALTER TABLE ""UserFiles"" RENAME COLUMN ""File"" TO ""FileId"";
|
||||||
|
ALTER TABLE ""UserFiles"" RENAME COLUMN ""User"" TO ""UserId"";
|
||||||
|
ALTER TABLE ""UserRoles"" RENAME COLUMN ""User"" TO ""UserId"";
|
||||||
|
ALTER TABLE ""UsersAuthToken"" RENAME COLUMN ""User"" TO ""UserId"";
|
||||||
|
ALTER TABLE ""UsersAuthToken"" ADD ""IdToken"" text NULL;
|
||||||
|
ALTER TABLE ""VirusScanResult"" RENAME COLUMN ""File"" TO ""FileId"";
|
||||||
|
ALTER TABLE ""ApiKey"" RENAME CONSTRAINT ""FK_ApiKey_UserId_Users_Id"" TO ""FK_ApiKey_Users_UserId"";
|
||||||
|
ALTER TABLE ""UserFiles"" RENAME CONSTRAINT ""FK_UserFiles_File_Files_Id"" TO ""FK_UserFiles_Files_FileId"";
|
||||||
|
ALTER TABLE ""UserFiles"" RENAME CONSTRAINT ""FK_UserFiles_User_Users_Id"" TO ""FK_UserFiles_Users_UserId"";
|
||||||
|
ALTER TABLE ""UserRoles"" RENAME CONSTRAINT ""FK_UserRoles_User_Users_Id"" TO ""FK_UserRoles_Users_UserId"";
|
||||||
|
ALTER TABLE ""UsersAuthToken"" RENAME CONSTRAINT ""FK_UsersAuthToken_User_Users_Id"" TO ""FK_UsersAuthToken_Users_UserId"";
|
||||||
|
ALTER TABLE ""VirusScanResult"" RENAME CONSTRAINT ""FK_VirusScanResult_File_Files_Id"" TO ""FK_VirusScanResult_Files_FileId"";
|
||||||
|
|
||||||
|
DROP TABLE ""EmailVerification"";
|
||||||
|
DROP TABLE ""PaymentOrderLightning"";
|
||||||
|
DROP TABLE ""PaymentOrder"";
|
||||||
|
DROP TABLE ""PaymentStrike"";
|
||||||
|
DROP TABLE ""Payment"";
|
||||||
|
DROP TABLE ""VersionInfo"";
|
||||||
|
", conn, tx).ExecuteNonQueryAsync();
|
||||||
|
|
||||||
|
// manually create init migration entry for EF to skip Init migration
|
||||||
|
await new NpgsqlCommand(@"
|
||||||
|
CREATE TABLE ""__EFMigrationsHistory"" (
|
||||||
|
""MigrationId"" varchar(150) NOT NULL,
|
||||||
|
""ProductVersion"" varchar(32) NOT NULL,
|
||||||
|
CONSTRAINT ""PK___EFMigrationsHistory"" PRIMARY KEY (""MigrationId"")
|
||||||
|
)", conn, tx).ExecuteNonQueryAsync();
|
||||||
|
|
||||||
|
await new NpgsqlCommand(
|
||||||
|
"INSERT INTO \"__EFMigrationsHistory\" (\"MigrationId\", \"ProductVersion\") VALUES('20230503115108_Init', '7.0.5')", conn,
|
||||||
|
tx)
|
||||||
|
.ExecuteNonQueryAsync();
|
||||||
|
|
||||||
|
await tx.CommitAsync();
|
||||||
|
}
|
||||||
|
}
|
@ -1,52 +0,0 @@
|
|||||||
using VoidCat.Model;
|
|
||||||
using VoidCat.Services.Abstractions;
|
|
||||||
using VoidCat.Services.Files;
|
|
||||||
|
|
||||||
namespace VoidCat.Services.Migrations;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public class FixSize : IMigration
|
|
||||||
{
|
|
||||||
private readonly ILogger<FixSize> _logger;
|
|
||||||
private readonly IFileMetadataStore _fileMetadata;
|
|
||||||
private readonly FileStoreFactory _fileStore;
|
|
||||||
|
|
||||||
public FixSize(ILogger<FixSize> logger, IFileMetadataStore fileMetadata, FileStoreFactory fileStore)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_fileMetadata = fileMetadata;
|
|
||||||
_fileStore = fileStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public int Order => 2;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public async ValueTask<IMigration.MigrationResult> Migrate(string[] args)
|
|
||||||
{
|
|
||||||
var files = await _fileMetadata.ListFiles<SecretFileMeta>(new(0, int.MaxValue));
|
|
||||||
await foreach (var file in files.Results)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var fs = await _fileStore.Open(new(file.Id, Enumerable.Empty<RangeRequest>()), CancellationToken.None);
|
|
||||||
if (file.Size != (ulong)fs.Length)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Updating file size {Id} to {Size}", file.Id, fs.Length);
|
|
||||||
var newFile = file with
|
|
||||||
{
|
|
||||||
Size = (ulong)fs.Length
|
|
||||||
};
|
|
||||||
|
|
||||||
await _fileMetadata.Set(newFile.Id, newFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Failed to fix file {id}", file.Id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return IMigration.MigrationResult.Completed;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
using FluentMigrator.Runner;
|
|
||||||
|
|
||||||
namespace VoidCat.Services.Migrations;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public class FluentMigrationRunner : IMigration
|
|
||||||
{
|
|
||||||
private readonly IMigrationRunner _runner;
|
|
||||||
|
|
||||||
public FluentMigrationRunner(IMigrationRunner runner)
|
|
||||||
{
|
|
||||||
_runner = runner;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public int Order => -1;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public ValueTask<IMigration.MigrationResult> Migrate(string[] args)
|
|
||||||
{
|
|
||||||
_runner.MigrateUp();
|
|
||||||
return ValueTask.FromResult(IMigration.MigrationResult.Completed);
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user