diff --git a/.drone.yml b/.drone.yml
new file mode 100644
index 0000000..4df3b8e
--- /dev/null
+++ b/.drone.yml
@@ -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
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 276e16d..bd32e17 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,23 +1,26 @@
-# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
WORKDIR /app
#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 yarn install
-COPY VoidCat/spa/package.json VoidCat/spa/yarn.lock spa/
-RUN cd spa && npx yarn install
-
-# Copy everything else and build
COPY . .
-RUN rm -rf VoidCat/appsettings.*.json
-RUN dotnet publish -c Release -o out VoidCat/VoidCat.csproj
+RUN cd VoidCat/spa \
+ && 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
FROM mcr.microsoft.com/dotnet/aspnet:6.0
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 .
ENTRYPOINT ["dotnet", "VoidCat.dll"]
\ No newline at end of file
diff --git a/VoidCat/Controllers/Admin/AdminController.cs b/VoidCat/Controllers/Admin/AdminController.cs
index ca53d58..01a97ea 100644
--- a/VoidCat/Controllers/Admin/AdminController.cs
+++ b/VoidCat/Controllers/Admin/AdminController.cs
@@ -1,7 +1,7 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
+using Newtonsoft.Json;
using VoidCat.Model;
-using VoidCat.Model.User;
using VoidCat.Services.Abstractions;
using VoidCat.Services.Files;
@@ -34,16 +34,16 @@ public class AdminController : Controller
///
[HttpPost]
[Route("file")]
- public async Task> ListFiles([FromBody] PagedRequest request)
+ public async Task> ListFiles([FromBody] PagedRequest request)
{
- var files = await _fileMetadata.ListFiles(request);
+ var files = await _fileMetadata.ListFiles(request);
return new()
{
Page = files.Page,
PageSize = files.PageSize,
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 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();
return new()
@@ -93,14 +93,27 @@ public class AdminController : Controller
///
[HttpPost]
[Route("update-user")]
- public async Task UpdateUser([FromBody] PrivateUser user)
+ public async Task UpdateUser([FromBody] AdminUpdateUser user)
{
var oldUser = await _userStore.Get(user.Id);
if (oldUser == default) return BadRequest();
- await _userStore.AdminUpdateUser(user);
+ oldUser.Storage = user.Storage;
+ oldUser.Email = user.Email;
+
+ await _userStore.AdminUpdateUser(oldUser);
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!;
+ }
}
diff --git a/VoidCat/Controllers/AuthController.cs b/VoidCat/Controllers/AuthController.cs
index dcceda0..a2e9650 100644
--- a/VoidCat/Controllers/AuthController.cs
+++ b/VoidCat/Controllers/AuthController.cs
@@ -4,8 +4,8 @@ using System.Security.Claims;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
+using VoidCat.Database;
using VoidCat.Model;
-using VoidCat.Model.User;
using VoidCat.Services.Abstractions;
using VoidCat.Services.Users;
@@ -57,7 +57,7 @@ public class AuthController : Controller
var user = await _manager.Login(req.Username, req.Password);
var token = CreateToken(user, DateTime.UtcNow.AddHours(12));
var tokenWriter = new JwtSecurityTokenHandler();
- return new(tokenWriter.WriteToken(token), Profile: user.ToPublic());
+ return new(tokenWriter.WriteToken(token), Profile: user.ToApiUser(true));
}
catch (Exception ex)
{
@@ -91,7 +91,7 @@ public class AuthController : Controller
var newUser = await _manager.Register(req.Username, req.Password);
var token = CreateToken(newUser, DateTime.UtcNow.AddHours(12));
var tokenWriter = new JwtSecurityTokenHandler();
- return new(tokenWriter.WriteToken(token), Profile: newUser.ToPublic());
+ return new(tokenWriter.WriteToken(token), Profile: newUser.ToApiUser(true));
}
catch (Exception ex)
{
@@ -189,7 +189,7 @@ public class AuthController : Controller
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,
signingCredentials: credentials);
@@ -210,7 +210,7 @@ public class AuthController : Controller
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);
}
\ No newline at end of file
diff --git a/VoidCat/Controllers/DownloadController.cs b/VoidCat/Controllers/DownloadController.cs
index 31d5809..7c40045 100644
--- a/VoidCat/Controllers/DownloadController.cs
+++ b/VoidCat/Controllers/DownloadController.cs
@@ -1,8 +1,8 @@
using System.Net;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
+using VoidCat.Database;
using VoidCat.Model;
-using VoidCat.Model.Payments;
using VoidCat.Services.Abstractions;
using VoidCat.Services.Files;
@@ -52,7 +52,7 @@ public class DownloadController : Controller
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()), CancellationToken.None),
_settings.SiteUrl, _settings.TorrentTrackers);
@@ -107,9 +107,9 @@ public class DownloadController : Controller
await Response.CompleteAsync();
}
- private async Task SetupDownload(Guid id)
+ private async Task SetupDownload(Guid id)
{
- var meta = await _fileInfo.Get(id);
+ var meta = await _fileInfo.Get(id, false);
if (meta == null)
{
Response.StatusCode = 404;
@@ -117,10 +117,10 @@ public class DownloadController : Controller
}
// 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"];
- if (!await IsOrderPaid(orderId))
+ if (!await IsOrderPaid(orderId!))
{
Response.StatusCode = (int)HttpStatusCode.PaymentRequired;
return default;
@@ -128,7 +128,7 @@ public class DownloadController : Controller
}
// 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) ??
false;
@@ -151,7 +151,7 @@ public class DownloadController : Controller
if (Guid.TryParse(orderId, out var oid))
{
var order = await _paymentOrders.Get(oid);
- if (order?.Status == PaymentOrderStatus.Paid)
+ if (order?.Status == PaywallOrderStatus.Paid)
{
return true;
}
diff --git a/VoidCat/Controllers/IndexController.cs b/VoidCat/Controllers/IndexController.cs
index e642e84..e2e6431 100644
--- a/VoidCat/Controllers/IndexController.cs
+++ b/VoidCat/Controllers/IndexController.cs
@@ -40,14 +40,16 @@ public class IndexController : Controller
var jsonManifest = await System.IO.File.ReadAllTextAsync(manifestPath);
return View("~/Pages/Index.cshtml", new IndexModel
{
- Meta = await _fileMetadata.Get(gid),
+ Id = gid,
+ Meta = (await _fileMetadata.Get(gid))?.ToMeta(false),
Manifest = JsonConvert.DeserializeObject(jsonManifest)!
});
}
public class IndexModel
{
- public FileMeta? Meta { get; init; }
+ public Guid Id { get; init; }
+ public VoidFileMeta? Meta { get; init; }
public AssetManifest Manifest { get; init; }
}
diff --git a/VoidCat/Controllers/UploadController.cs b/VoidCat/Controllers/UploadController.cs
index cd6b5fc..9f58a71 100644
--- a/VoidCat/Controllers/UploadController.cs
+++ b/VoidCat/Controllers/UploadController.cs
@@ -3,11 +3,11 @@ using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.StaticFiles;
using Newtonsoft.Json;
+using VoidCat.Database;
using VoidCat.Model;
-using VoidCat.Model.Payments;
-using VoidCat.Model.User;
using VoidCat.Services.Abstractions;
using VoidCat.Services.Files;
+using File = VoidCat.Database.File;
namespace VoidCat.Controllers
{
@@ -63,6 +63,7 @@ namespace VoidCat.Controllers
{
throw new InvalidOperationException("Site is in maintenance mode");
}
+
var uid = HttpContext.GetUserId();
var mime = Request.Headers.GetHeader("V-Content-Type");
var filename = Request.Headers.GetHeader("V-Filename");
@@ -86,14 +87,14 @@ namespace VoidCat.Controllers
var store = _settings.DefaultFileStore;
if (uid.HasValue)
{
- var user = await _userStore.Get(uid.Value);
+ var user = await _userStore.Get(uid.Value);
if (user?.Storage != default)
{
store = user.Storage!;
}
}
- var meta = new SecretFileMeta
+ var meta = new File
{
MimeType = mime,
Name = filename,
@@ -109,7 +110,7 @@ namespace VoidCat.Controllers
HttpContext.RequestAborted);
// save metadata
- await _metadata.Set(vf.Id, vf.Metadata!);
+ await _metadata.Add(vf);
// attach file upload to user
if (uid.HasValue)
@@ -126,7 +127,7 @@ namespace VoidCat.Controllers
return Content(urlBuilder.Uri.ToString(), "text/plain");
}
- return Json(UploadResult.Success(vf));
+ return Json(UploadResult.Success(vf.ToResponse(true)));
}
catch (Exception ex)
{
@@ -158,8 +159,9 @@ namespace VoidCat.Controllers
{
throw new InvalidOperationException("Site is in maintenance mode");
}
+
var gid = id.FromBase58Guid();
- var meta = await _metadata.Get(gid);
+ var meta = await _metadata.Get(gid);
if (meta == default) return UploadResult.Error("File not found");
// Parse V-Segment header
@@ -182,8 +184,8 @@ namespace VoidCat.Controllers
}, HttpContext.RequestAborted);
// update file size
- await _metadata.Set(vf.Id, vf.Metadata!);
- return UploadResult.Success(vf);
+ await _metadata.Update(vf.Id, vf);
+ return UploadResult.Success(vf.ToResponse(true));
}
catch (Exception ex)
{
@@ -205,7 +207,10 @@ namespace VoidCat.Controllers
var uid = HttpContext.GetUserId();
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);
}
///
@@ -232,10 +237,10 @@ namespace VoidCat.Controllers
///
[HttpGet]
[Route("{id}/payment")]
- public async ValueTask CreateOrder([FromRoute] string id)
+ public async ValueTask CreateOrder([FromRoute] string id)
{
var gid = id.FromBase58Guid();
- var file = await _fileInfo.Get(gid);
+ var file = await _fileInfo.Get(gid, false);
var config = await _paymentStore.Get(gid);
var provider = await _paymentFactory.CreateProvider(config!.Service);
@@ -250,7 +255,7 @@ namespace VoidCat.Controllers
///
[HttpGet]
[Route("{id}/payment/{order:guid}")]
- public async ValueTask GetOrderStatus([FromRoute] string id, [FromRoute] Guid order)
+ public async ValueTask GetOrderStatus([FromRoute] string id, [FromRoute] Guid order)
{
var gid = id.FromBase58Guid();
var config = await _paymentStore.Get(gid);
@@ -270,17 +275,23 @@ namespace VoidCat.Controllers
public async Task SetPaymentConfig([FromRoute] string id, [FromBody] SetPaymentConfigRequest req)
{
var gid = id.FromBase58Guid();
- var meta = await _metadata.Get(gid);
+ var meta = await _metadata.Get(gid);
if (meta == default) return NotFound();
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,
- Handle = req.Strike.Handle,
- Cost = req.Strike.Cost,
+ File = meta,
+ Service = PaywallService.Strike,
+ PaywallStrike = new()
+ {
+ Handle = req.StrikeHandle
+ },
+ Amount = req.Amount,
+ Currency = Enum.Parse(req.Currency),
Required = req.Required
});
@@ -303,14 +314,21 @@ namespace VoidCat.Controllers
///
[HttpPost]
[Route("{id}/meta")]
- public async Task UpdateFileMeta([FromRoute] string id, [FromBody] SecretFileMeta fileMeta)
+ public async Task UpdateFileMeta([FromRoute] string id, [FromBody] VoidFileMeta fileMeta)
{
var gid = id.FromBase58Guid();
- var meta = await _metadata.Get(gid);
+ var meta = await _metadata.Get(gid);
if (meta == default) return NotFound();
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();
}
@@ -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);
public static UploadResult Error(string message)
@@ -363,7 +381,11 @@ namespace VoidCat.Controllers
[JsonConverter(typeof(Base58GuidConverter))]
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; }
}
diff --git a/VoidCat/Controllers/UserController.cs b/VoidCat/Controllers/UserController.cs
index af2dfbf..fd5a34d 100644
--- a/VoidCat/Controllers/UserController.cs
+++ b/VoidCat/Controllers/UserController.cs
@@ -1,8 +1,9 @@
using Microsoft.AspNetCore.Mvc;
+using VoidCat.Database;
using VoidCat.Model;
-using VoidCat.Model.User;
using VoidCat.Services.Abstractions;
using VoidCat.Services.Files;
+using UserFlags = VoidCat.Database.UserFlags;
namespace VoidCat.Controllers;
@@ -41,18 +42,11 @@ public class UserController : Controller
if (isMe && !loggedUser.HasValue) return Unauthorized();
var requestedId = isMe ? loggedUser!.Value : id.FromBase58Guid();
- if (loggedUser == requestedId)
- {
- var pUser = await _store.Get(requestedId);
- if (pUser == default) return NotFound();
+ var user = await _store.Get(requestedId);
+ if (loggedUser != requestedId && !(user?.Flags.HasFlag(UserFlags.PublicProfile) ?? false))
+ return NotFound();
- return Json(pUser);
- }
-
- var user = await _store.Get(requestedId);
- if (!(user?.Flags.HasFlag(UserFlags.PublicProfile) ?? false)) return NotFound();
-
- return Json(user);
+ return Json(user!.ToApiUser(isMe));
}
///
@@ -63,14 +57,20 @@ public class UserController : Controller
///
///
[HttpPost]
- public async Task UpdateUser([FromRoute] string id, [FromBody] PublicUser user)
+ public async Task UpdateUser([FromRoute] string id, [FromBody] ApiUser user)
{
var loggedUser = await GetAuthorizedUser(id);
if (loggedUser == default) return Unauthorized();
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();
}
@@ -102,13 +102,13 @@ public class UserController : Controller
var results = await _userUploads.ListFiles(id.FromBase58Guid(), request);
var files = await results.Results.ToListAsync();
- var fileInfo = await Task.WhenAll(files.Select(a => _fileInfoManager.Get(a).AsTask()));
- return Json(new RenderedResults()
+ var fileInfo = await _fileInfoManager.Get(files.ToArray(), false);
+ return Json(new RenderedResults()
{
PageSize = results.PageSize,
Page = results.Page,
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();
user.Flags |= UserFlags.EmailVerified;
- await _store.UpdateProfile(user.ToPublic());
+ await _store.UpdateProfile(user);
return Accepted();
}
- private async Task GetAuthorizedUser(string id)
+ private async Task GetAuthorizedUser(string id)
{
var loggedUser = HttpContext.GetUserId();
var gid = id.FromBase58Guid();
- var user = await _store.Get(gid);
+ var user = await _store.Get(gid);
return user?.Id != loggedUser ? default : user;
}
- private async Task GetRequestedUser(string id)
+ private async Task GetRequestedUser(string id)
{
var gid = id.FromBase58Guid();
- return await _store.Get(gid);
+ return await _store.Get(gid);
}
}
diff --git a/VoidCat/Database/ApiKey.cs b/VoidCat/Database/ApiKey.cs
new file mode 100644
index 0000000..e5dfdba
--- /dev/null
+++ b/VoidCat/Database/ApiKey.cs
@@ -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; }
+}
diff --git a/VoidCat/Database/Configurations/ApiKeyConfiguration.cs b/VoidCat/Database/Configurations/ApiKeyConfiguration.cs
new file mode 100644
index 0000000..0603684
--- /dev/null
+++ b/VoidCat/Database/Configurations/ApiKeyConfiguration.cs
@@ -0,0 +1,25 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace VoidCat.Database.Configurations;
+
+public class ApiKeyConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder 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);
+ }
+}
diff --git a/VoidCat/Database/Configurations/EmailVerficationConfiguration.cs b/VoidCat/Database/Configurations/EmailVerficationConfiguration.cs
new file mode 100644
index 0000000..b92b5ca
--- /dev/null
+++ b/VoidCat/Database/Configurations/EmailVerficationConfiguration.cs
@@ -0,0 +1,23 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace VoidCat.Database.Configurations;
+
+public class EmailVerficationConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder 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);
+ }
+}
diff --git a/VoidCat/Database/Configurations/FileConfiguration.cs b/VoidCat/Database/Configurations/FileConfiguration.cs
new file mode 100644
index 0000000..92bfdea
--- /dev/null
+++ b/VoidCat/Database/Configurations/FileConfiguration.cs
@@ -0,0 +1,39 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace VoidCat.Database.Configurations;
+
+public class FileConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder 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);
+ }
+}
diff --git a/VoidCat/Database/Configurations/PaywallConfiguration.cs b/VoidCat/Database/Configurations/PaywallConfiguration.cs
new file mode 100644
index 0000000..4aeae84
--- /dev/null
+++ b/VoidCat/Database/Configurations/PaywallConfiguration.cs
@@ -0,0 +1,28 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace VoidCat.Database.Configurations;
+
+public class PaywallConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder 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();
+ }
+}
diff --git a/VoidCat/Database/Configurations/PaywallOrderConfiguration.cs b/VoidCat/Database/Configurations/PaywallOrderConfiguration.cs
new file mode 100644
index 0000000..6ff6d3b
--- /dev/null
+++ b/VoidCat/Database/Configurations/PaywallOrderConfiguration.cs
@@ -0,0 +1,30 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace VoidCat.Database.Configurations;
+
+public class PaywallOrderConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder 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);
+ }
+}
diff --git a/VoidCat/Database/Configurations/PaywallOrderLightningConfiguration.cs b/VoidCat/Database/Configurations/PaywallOrderLightningConfiguration.cs
new file mode 100644
index 0000000..9217d90
--- /dev/null
+++ b/VoidCat/Database/Configurations/PaywallOrderLightningConfiguration.cs
@@ -0,0 +1,22 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace VoidCat.Database.Configurations;
+
+public class PaywallOrderLightningConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder 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(a => a.OrderId);
+ }
+}
diff --git a/VoidCat/Database/Configurations/PaywallStrikeConfiguration.cs b/VoidCat/Database/Configurations/PaywallStrikeConfiguration.cs
new file mode 100644
index 0000000..4ec465f
--- /dev/null
+++ b/VoidCat/Database/Configurations/PaywallStrikeConfiguration.cs
@@ -0,0 +1,19 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace VoidCat.Database.Configurations;
+
+public class PaywallStrikeConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder 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();
+ }
+}
diff --git a/VoidCat/Database/Configurations/UserAuthTokenConfiguration.cs b/VoidCat/Database/Configurations/UserAuthTokenConfiguration.cs
new file mode 100644
index 0000000..9a0548e
--- /dev/null
+++ b/VoidCat/Database/Configurations/UserAuthTokenConfiguration.cs
@@ -0,0 +1,36 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace VoidCat.Database.Configurations;
+
+public class UserAuthTokenConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder 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);
+ }
+}
diff --git a/VoidCat/Database/Configurations/UserConfiguration.cs b/VoidCat/Database/Configurations/UserConfiguration.cs
new file mode 100644
index 0000000..a1dc7ae
--- /dev/null
+++ b/VoidCat/Database/Configurations/UserConfiguration.cs
@@ -0,0 +1,38 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace VoidCat.Database.Configurations;
+
+public class UserConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder 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);
+ }
+}
diff --git a/VoidCat/Database/Configurations/UserFileConfiguration.cs b/VoidCat/Database/Configurations/UserFileConfiguration.cs
new file mode 100644
index 0000000..daa617f
--- /dev/null
+++ b/VoidCat/Database/Configurations/UserFileConfiguration.cs
@@ -0,0 +1,21 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace VoidCat.Database.Configurations;
+
+public class UserFileConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder 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(a => a.FileId);
+ }
+}
diff --git a/VoidCat/Database/Configurations/UserRolesConfiguration.cs b/VoidCat/Database/Configurations/UserRolesConfiguration.cs
new file mode 100644
index 0000000..df0628d
--- /dev/null
+++ b/VoidCat/Database/Configurations/UserRolesConfiguration.cs
@@ -0,0 +1,20 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace VoidCat.Database.Configurations;
+
+public class UserRolesConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder 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);
+ }
+}
diff --git a/VoidCat/Database/Configurations/VirusScanResultConfiguration.cs b/VoidCat/Database/Configurations/VirusScanResultConfiguration.cs
new file mode 100644
index 0000000..7dd2119
--- /dev/null
+++ b/VoidCat/Database/Configurations/VirusScanResultConfiguration.cs
@@ -0,0 +1,24 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+
+namespace VoidCat.Database.Configurations;
+
+public class VirusScanResultConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder 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);
+ }
+}
diff --git a/VoidCat/Database/EmailVerification.cs b/VoidCat/Database/EmailVerification.cs
new file mode 100644
index 0000000..5813015
--- /dev/null
+++ b/VoidCat/Database/EmailVerification.cs
@@ -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; }
+}
diff --git a/VoidCat/Database/File.cs b/VoidCat/Database/File.cs
new file mode 100644
index 0000000..7052c6d
--- /dev/null
+++ b/VoidCat/Database/File.cs
@@ -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; }
+}
diff --git a/VoidCat/Database/Paywall.cs b/VoidCat/Database/Paywall.cs
new file mode 100644
index 0000000..b696acf
--- /dev/null
+++ b/VoidCat/Database/Paywall.cs
@@ -0,0 +1,47 @@
+namespace VoidCat.Database;
+
+public enum PaywallCurrency : byte
+{
+ BTC = 0,
+ USD = 1,
+ EUR = 2,
+ GBP = 3
+}
+
+public enum PaywallService
+{
+ ///
+ /// No service
+ ///
+ None,
+
+ ///
+ /// Strike.me payment service
+ ///
+ Strike,
+
+ ///
+ /// LNProxy payment
+ ///
+ 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!;
+}
\ No newline at end of file
diff --git a/VoidCat/Database/PaywallOrder.cs b/VoidCat/Database/PaywallOrder.cs
new file mode 100644
index 0000000..fcdf5a0
--- /dev/null
+++ b/VoidCat/Database/PaywallOrder.cs
@@ -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
+{
+ ///
+ /// Invoice is not paid yet
+ ///
+ Unpaid = 0,
+
+ ///
+ /// Invoice is paid
+ ///
+ Paid = 1,
+
+ ///
+ /// Invoice has expired and cant be paid
+ ///
+ 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; }
+}
\ No newline at end of file
diff --git a/VoidCat/Database/User.cs b/VoidCat/Database/User.cs
new file mode 100644
index 0000000..76afbd4
--- /dev/null
+++ b/VoidCat/Database/User.cs
@@ -0,0 +1,114 @@
+namespace VoidCat.Database;
+
+[Flags]
+public enum UserFlags
+{
+ ///
+ /// Profile is public
+ ///
+ PublicProfile = 1,
+
+ ///
+ /// Uploads list is public
+ ///
+ PublicUploads = 2,
+
+ ///
+ /// Account has email verified
+ ///
+ EmailVerified = 4
+}
+
+public sealed class User
+{
+ ///
+ /// Unique Id of the user
+ ///
+ public Guid Id { get; init; }
+
+ ///
+ /// Users email address
+ ///
+ public string Email { get; set; } = null!;
+
+ ///
+ /// Users password (hashed)
+ ///
+ public string? Password { get; init; }
+
+ ///
+ /// When the user account was created
+ ///
+ public DateTime Created { get; init; } = DateTime.UtcNow;
+
+ ///
+ /// The last time the user logged in
+ ///
+ public DateTime? LastLogin { get; set; }
+
+ ///
+ /// Display avatar for user profile
+ ///
+ public string? Avatar { get; set; }
+
+ ///
+ /// Display name for user profile
+ ///
+ public string DisplayName { get; set; } = "void user";
+
+ ///
+ /// Profile flags
+ ///
+ public UserFlags Flags { get; set; } = UserFlags.PublicProfile;
+
+ ///
+ /// Users storage system for new uploads
+ ///
+ public string Storage { get; set; } = "local-disk";
+
+ ///
+ /// Account authentication type
+ ///
+ public UserAuthType AuthType { get; init; }
+
+ ///
+ /// Roles assigned to this user which grant them extra permissions
+ ///
+ public List Roles { get; init; } = new();
+
+ ///
+ /// All files uploaded by this user
+ ///
+ public List 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
+{
+ ///
+ /// Encrypted password
+ ///
+ Internal = 0,
+
+ ///
+ /// PGP challenge
+ ///
+ PGP = 1,
+
+ ///
+ /// OAuth2 token
+ ///
+ OAuth2 = 2,
+
+ ///
+ /// Lightning node challenge
+ ///
+ Lightning = 3
+}
diff --git a/VoidCat/Database/UserAuthToken.cs b/VoidCat/Database/UserAuthToken.cs
new file mode 100644
index 0000000..433faa1
--- /dev/null
+++ b/VoidCat/Database/UserAuthToken.cs
@@ -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; }
+}
diff --git a/VoidCat/Database/UserFile.cs b/VoidCat/Database/UserFile.cs
new file mode 100644
index 0000000..4cfd9d6
--- /dev/null
+++ b/VoidCat/Database/UserFile.cs
@@ -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; }
+}
diff --git a/VoidCat/Database/VirusScanResult.cs b/VoidCat/Database/VirusScanResult.cs
new file mode 100644
index 0000000..d21eacb
--- /dev/null
+++ b/VoidCat/Database/VirusScanResult.cs
@@ -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!;
+}
diff --git a/VoidCat/Migrations/20230503115108_Init.Designer.cs b/VoidCat/Migrations/20230503115108_Init.Designer.cs
new file mode 100644
index 0000000..bf7a7e9
--- /dev/null
+++ b/VoidCat/Migrations/20230503115108_Init.Designer.cs
@@ -0,0 +1,353 @@
+//
+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
+ {
+ ///
+ 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("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Created")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Expiry")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Token")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("ApiKey", (string)null);
+ });
+
+ modelBuilder.Entity("VoidCat.Database.EmailVerification", b =>
+ {
+ b.Property("Code")
+ .HasColumnType("uuid");
+
+ b.Property("Expires")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("EmailVerification", (string)null);
+ });
+
+ modelBuilder.Entity("VoidCat.Database.File", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("Digest")
+ .HasColumnType("text");
+
+ b.Property("EditSecret")
+ .HasColumnType("uuid");
+
+ b.Property("EncryptionParams")
+ .HasColumnType("text");
+
+ b.Property("Expires")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("MagnetLink")
+ .HasColumnType("text");
+
+ b.Property("MimeType")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("text")
+ .HasDefaultValue("application/octet-stream");
+
+ b.Property("Name")
+ .HasColumnType("text");
+
+ b.Property("Size")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("Storage")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("text")
+ .HasDefaultValue("local-disk");
+
+ b.Property("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("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AuthType")
+ .HasColumnType("integer");
+
+ b.Property("Avatar")
+ .HasColumnType("text");
+
+ b.Property("Created")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DisplayName")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("text")
+ .HasDefaultValue("void user");
+
+ b.Property("Email")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Flags")
+ .HasColumnType("integer");
+
+ b.Property("LastLogin")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Password")
+ .HasColumnType("text");
+
+ b.Property("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("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AccessToken")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Expires")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IdToken")
+ .HasColumnType("text");
+
+ b.Property("Provider")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("RefreshToken")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Scope")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("TokenType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("UsersAuthToken", (string)null);
+ });
+
+ modelBuilder.Entity("VoidCat.Database.UserFile", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.Property("FileId")
+ .HasColumnType("uuid");
+
+ b.HasKey("UserId", "FileId");
+
+ b.HasIndex("FileId")
+ .IsUnique();
+
+ b.ToTable("UserFiles", (string)null);
+ });
+
+ modelBuilder.Entity("VoidCat.Database.UserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.Property("Role")
+ .HasColumnType("text");
+
+ b.HasKey("UserId", "Role");
+
+ b.ToTable("UserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("VoidCat.Database.VirusScanResult", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("FileId")
+ .HasColumnType("uuid");
+
+ b.Property("Names")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ScanTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Scanner")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("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
+ }
+ }
+}
diff --git a/VoidCat/Migrations/20230503115108_Init.cs b/VoidCat/Migrations/20230503115108_Init.cs
new file mode 100644
index 0000000..d5d32c8
--- /dev/null
+++ b/VoidCat/Migrations/20230503115108_Init.cs
@@ -0,0 +1,223 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace VoidCat.Migrations
+{
+ ///
+ public partial class Init : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "Files",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ Name = table.Column(type: "text", nullable: true),
+ Size = table.Column(type: "numeric(20,0)", nullable: false),
+ Uploaded = table.Column(type: "timestamp with time zone", nullable: false),
+ Description = table.Column(type: "text", nullable: true),
+ MimeType = table.Column(type: "text", nullable: false, defaultValue: "application/octet-stream"),
+ Digest = table.Column(type: "text", nullable: true),
+ EditSecret = table.Column(type: "uuid", nullable: false),
+ Expires = table.Column(type: "timestamp with time zone", nullable: true),
+ Storage = table.Column(type: "text", nullable: false, defaultValue: "local-disk"),
+ EncryptionParams = table.Column(type: "text", nullable: true),
+ MagnetLink = table.Column(type: "text", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Files", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Users",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ Email = table.Column(type: "text", nullable: false),
+ Password = table.Column(type: "text", nullable: true),
+ Created = table.Column(type: "timestamp with time zone", nullable: false),
+ LastLogin = table.Column(type: "timestamp with time zone", nullable: true),
+ Avatar = table.Column(type: "text", nullable: true),
+ DisplayName = table.Column(type: "text", nullable: false, defaultValue: "void user"),
+ Flags = table.Column(type: "integer", nullable: false),
+ Storage = table.Column(type: "text", nullable: false, defaultValue: "local-disk"),
+ AuthType = table.Column(type: "integer", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Users", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "VirusScanResult",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ FileId = table.Column(type: "uuid", nullable: false),
+ ScanTime = table.Column(type: "timestamp with time zone", nullable: false),
+ Scanner = table.Column(type: "text", nullable: false),
+ Score = table.Column(type: "numeric", nullable: false),
+ Names = table.Column(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(type: "uuid", nullable: false),
+ UserId = table.Column(type: "uuid", nullable: false),
+ Token = table.Column(type: "text", nullable: false),
+ Expiry = table.Column(type: "timestamp with time zone", nullable: false),
+ Created = table.Column(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(type: "uuid", nullable: false),
+ UserId = table.Column(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(type: "uuid", nullable: false),
+ Role = table.Column(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(type: "uuid", nullable: false),
+ UserId = table.Column(type: "uuid", nullable: false),
+ Provider = table.Column(type: "text", nullable: false),
+ AccessToken = table.Column(type: "text", nullable: false),
+ TokenType = table.Column(type: "text", nullable: false),
+ Expires = table.Column(type: "timestamp with time zone", nullable: false),
+ RefreshToken = table.Column(type: "text", nullable: false),
+ Scope = table.Column(type: "text", nullable: false),
+ IdToken = table.Column(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");
+ }
+
+ ///
+ 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");
+ }
+ }
+}
diff --git a/VoidCat/Migrations/20230503120701_Paywall.Designer.cs b/VoidCat/Migrations/20230503120701_Paywall.Designer.cs
new file mode 100644
index 0000000..e45736d
--- /dev/null
+++ b/VoidCat/Migrations/20230503120701_Paywall.Designer.cs
@@ -0,0 +1,497 @@
+//
+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
+ {
+ ///
+ 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("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Created")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Expiry")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Token")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("ApiKey", (string)null);
+ });
+
+ modelBuilder.Entity("VoidCat.Database.EmailVerification", b =>
+ {
+ b.Property("Code")
+ .HasColumnType("uuid");
+
+ b.Property("Expires")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("EmailVerification", (string)null);
+ });
+
+ modelBuilder.Entity("VoidCat.Database.File", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("Digest")
+ .HasColumnType("text");
+
+ b.Property("EditSecret")
+ .HasColumnType("uuid");
+
+ b.Property("EncryptionParams")
+ .HasColumnType("text");
+
+ b.Property("Expires")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("MagnetLink")
+ .HasColumnType("text");
+
+ b.Property("MimeType")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("text")
+ .HasDefaultValue("application/octet-stream");
+
+ b.Property("Name")
+ .HasColumnType("text");
+
+ b.Property("Size")
+ .HasColumnType("numeric(20,0)");
+
+ b.Property("Storage")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("text")
+ .HasDefaultValue("local-disk");
+
+ b.Property("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("Id")
+ .HasColumnType("uuid");
+
+ b.Property("Amount")
+ .HasColumnType("numeric");
+
+ b.Property("Currency")
+ .HasColumnType("smallint");
+
+ b.Property("Required")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("boolean")
+ .HasDefaultValue(true);
+
+ b.Property("Service")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.ToTable("Payment", (string)null);
+ });
+
+ modelBuilder.Entity("VoidCat.Database.PaywallOrder", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Amount")
+ .HasColumnType("numeric");
+
+ b.Property("Currency")
+ .HasColumnType("smallint");
+
+ b.Property("FileId")
+ .HasColumnType("uuid");
+
+ b.Property("Service")
+ .HasColumnType("integer");
+
+ b.Property("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("OrderId")
+ .HasColumnType("uuid");
+
+ b.Property("Expire")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Invoice")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("OrderId");
+
+ b.ToTable("PaymentOrderLightning", (string)null);
+ });
+
+ modelBuilder.Entity("VoidCat.Database.PaywallStrike", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("uuid");
+
+ b.Property("Handle")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.ToTable("PaymentStrike", (string)null);
+ });
+
+ modelBuilder.Entity("VoidCat.Database.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AuthType")
+ .HasColumnType("integer");
+
+ b.Property("Avatar")
+ .HasColumnType("text");
+
+ b.Property("Created")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("DisplayName")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("text")
+ .HasDefaultValue("void user");
+
+ b.Property("Email")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Flags")
+ .HasColumnType("integer");
+
+ b.Property("LastLogin")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Password")
+ .HasColumnType("text");
+
+ b.Property("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("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("AccessToken")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Expires")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("IdToken")
+ .HasColumnType("text");
+
+ b.Property("Provider")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("RefreshToken")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("Scope")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("TokenType")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("UsersAuthToken", (string)null);
+ });
+
+ modelBuilder.Entity("VoidCat.Database.UserFile", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.Property("FileId")
+ .HasColumnType("uuid");
+
+ b.HasKey("UserId", "FileId");
+
+ b.HasIndex("FileId")
+ .IsUnique();
+
+ b.ToTable("UserFiles", (string)null);
+ });
+
+ modelBuilder.Entity("VoidCat.Database.UserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.Property("Role")
+ .HasColumnType("text");
+
+ b.HasKey("UserId", "Role");
+
+ b.ToTable("UserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("VoidCat.Database.VirusScanResult", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("FileId")
+ .HasColumnType("uuid");
+
+ b.Property("Names")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("ScanTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Scanner")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("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
+ }
+ }
+}
diff --git a/VoidCat/Migrations/20230503120701_Paywall.cs b/VoidCat/Migrations/20230503120701_Paywall.cs
new file mode 100644
index 0000000..bad719e
--- /dev/null
+++ b/VoidCat/Migrations/20230503120701_Paywall.cs
@@ -0,0 +1,121 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace VoidCat.Migrations
+{
+ ///
+ public partial class Paywall : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "Payment",
+ columns: table => new
+ {
+ Id = table.Column(type: "uuid", nullable: false),
+ Service = table.Column(type: "integer", nullable: false),
+ Currency = table.Column(type: "smallint", nullable: false),
+ Amount = table.Column(type: "numeric", nullable: false),
+ Required = table.Column(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(type: "uuid", nullable: false),
+ FileId = table.Column(type: "uuid", nullable: false),
+ Service = table.Column(type: "integer", nullable: false),
+ Currency = table.Column(type: "smallint", nullable: false),
+ Amount = table.Column(type: "numeric", nullable: false),
+ Status = table.Column(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(type: "uuid", nullable: false),
+ Handle = table.Column(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(type: "uuid", nullable: false),
+ Invoice = table.Column(type: "text", nullable: false),
+ Expire = table.Column(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");
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "PaymentOrderLightning");
+
+ migrationBuilder.DropTable(
+ name: "PaymentStrike");
+
+ migrationBuilder.DropTable(
+ name: "PaymentOrder");
+
+ migrationBuilder.DropTable(
+ name: "Payment");
+ }
+ }
+}
diff --git a/VoidCat/Migrations/20230508205513_EmailVerificationId.Designer.cs b/VoidCat/Migrations/20230508205513_EmailVerificationId.Designer.cs
new file mode 100644
index 0000000..ff8ff4d
--- /dev/null
+++ b/VoidCat/Migrations/20230508205513_EmailVerificationId.Designer.cs
@@ -0,0 +1,501 @@
+//
+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
+ {
+ ///
+ 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("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Created")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Expiry")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Token")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("ApiKey", (string)null);
+ });
+
+ modelBuilder.Entity("VoidCat.Database.EmailVerification", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Code")
+ .HasColumnType("uuid");
+
+ b.Property("Expires")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserId")
+ .HasColumnType("uuid");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("EmailVerification", (string)null);
+ });
+
+ modelBuilder.Entity("VoidCat.Database.File", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("uuid");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("Digest")
+ .HasColumnType("text");
+
+ b.Property("EditSecret")
+ .HasColumnType("uuid");
+
+ b.Property