Compare commits
No commits in common. "main" and "v0.2" have entirely different histories.
@ -1,28 +0,0 @@
|
||||
**/.classpath
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/azds.yaml
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
||||
**/appsettings.*.json
|
||||
**/data
|
||||
**/build
|
||||
**/dist
|
21
.drone.yml
21
.drone.yml
@ -1,21 +0,0 @@
|
||||
---
|
||||
kind: pipeline
|
||||
type: kubernetes
|
||||
name: default
|
||||
|
||||
metadata:
|
||||
namespace: git
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: docker
|
||||
privileged: true
|
||||
environment:
|
||||
TOKEN:
|
||||
from_secret: gitea
|
||||
commands:
|
||||
- dockerd &
|
||||
- docker login -u kieran -p $TOKEN git.v0l.io
|
||||
- docker buildx create --name mybuilder --bootstrap --use
|
||||
- docker buildx build -t git.v0l.io/kieran/void-cat:latest --platform linux/amd64 --push .
|
||||
- kill $(cat /var/run/docker.pid)
|
72
.github/workflows/codeql-analysis.yml
vendored
72
.github/workflows/codeql-analysis.yml
vendored
@ -1,72 +0,0 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ v4-re ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ v4-re ]
|
||||
schedule:
|
||||
- cron: '36 14 * * 0'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'csharp', 'javascript' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
15
.gitignore
vendored
15
.gitignore
vendored
@ -1,20 +1,7 @@
|
||||
.vs/
|
||||
bin/
|
||||
obj/
|
||||
data/
|
||||
*.csproj.user
|
||||
*.vcxproj.user
|
||||
*.min.*
|
||||
dist/
|
||||
.vscode/
|
||||
Debug/
|
||||
Release/
|
||||
node_modules/
|
||||
package-lock.json
|
||||
out/
|
||||
sw.js
|
||||
.DS_Store
|
||||
.idea/
|
||||
appsettings.*.json
|
||||
VoidCat.xml
|
||||
build/
|
||||
.vscode/
|
32
Dockerfile
32
Dockerfile
@ -1,32 +0,0 @@
|
||||
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
|
||||
WORKDIR /app
|
||||
|
||||
#install npm
|
||||
ENV NODE_MAJOR=20
|
||||
RUN apt update && \
|
||||
apt install -y ca-certificates curl gnupg && \
|
||||
mkdir -p /etc/apt/keyrings && \
|
||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
|
||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list && \
|
||||
apt update && \
|
||||
apt install nodejs -y
|
||||
|
||||
#run yarn install
|
||||
COPY . .
|
||||
RUN git config --global --add safe.directory /app \
|
||||
&& cd VoidCat/spa \
|
||||
&& npx yarn \
|
||||
&& npx yarn build
|
||||
|
||||
RUN rm -rf VoidCat/appsettings.*.json \
|
||||
&& 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 \
|
||||
&& apt clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
COPY --from=build-env /app/out .
|
||||
ENTRYPOINT ["dotnet", "VoidCat.dll"]
|
110
README.md
110
README.md
@ -1,88 +1,36 @@
|
||||
## Void.cat
|
||||
Free, simple file hosting
|
||||
|
||||
Setup
|
||||
===
|
||||
|
||||
### Features
|
||||
- Profiles
|
||||
- File bandwidth statistics
|
||||
- Administration features
|
||||
- File download paywall
|
||||
|
||||
### Running
|
||||
|
||||
Use the docker image to run void.cat:
|
||||
|
||||
`docker run --rm -it -p 8080:80 git.v0l.io/kieran/void-cat:latest`
|
||||
|
||||
Then open your browser at http://localhost:8080.
|
||||
|
||||
**The first registration will be set as admin,
|
||||
so make sure to create your own account**
|
||||
|
||||
### Deploying
|
||||
Docker compose is the best option for most as this sets up postgres / redis / clamav.
|
||||
|
||||
Run the following commands to get going:
|
||||
```bash
|
||||
git clone https://git.v0l.io/Kieran/void.cat
|
||||
cd void.cat/
|
||||
docker compose up -d
|
||||
* Nginx
|
||||
* php-fpm
|
||||
* php-redis
|
||||
* php-curl
|
||||
* php-gmp
|
||||
* Redis
|
||||
|
||||
Nginx handler
|
||||
====
|
||||
```
|
||||
|
||||
You should now be able to access void.cat on `http://localhost`.
|
||||
|
||||
If you already have something running on port `80` you may have problems, you can modify the `docker-compose.yml`
|
||||
file to change the port.
|
||||
|
||||
You can modify the site config in `./VoidCat/appsettings.compose.json`, this is recommended for anything other
|
||||
than a simple test
|
||||
|
||||
### Usage
|
||||
|
||||
Simply drag and drop your files into the dropzone,
|
||||
or paste your screenshots or files into the browser window.
|
||||
|
||||
From cli you can upload with `curl`:
|
||||
```bash
|
||||
export FILE=memes.jpg
|
||||
curl -X POST \
|
||||
-H "V-Content-Type: $(file --mime-type -b $FILE)" \
|
||||
-H "V-Full-Digest: $(sha256sum -bz $FILE | cut -d' ' -f1)" \
|
||||
-H "V-Filename: $FILE" \
|
||||
--data-binary @$FILE \
|
||||
"https://void.cat/upload?cli=true"
|
||||
```
|
||||
|
||||
Or you can create an alias function in `~/bash_aliases` like so:
|
||||
```bash
|
||||
vcu() {
|
||||
echo "Uploading $1"
|
||||
curl -X POST \
|
||||
-H "V-Content-Type: $(file --mime-type -b $1)" \
|
||||
-H "V-Full-Digest: $(sha256sum -bz $1 | cut -d' ' -f1)" \
|
||||
-H "V-Filename: $1" \
|
||||
--data-binary @$1 \
|
||||
"https://void.cat/upload?cli=true"
|
||||
echo -e ""
|
||||
location ~* "^\/([0-9a-z]{27})$" {
|
||||
try_files $uri /src/php/handler.php?h=download&id=$1;
|
||||
}
|
||||
```
|
||||
|
||||
Uploading from cli will simply become `vcu memes.jpg`
|
||||
Void Binary File Format (VBF)
|
||||
===
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| version | uint8_t | Binary file format version |
|
||||
| hash | SHA256 hash | The hash of the unencrypted file |
|
||||
| uploaded | uint32_t | Timestamp of when the upload started |
|
||||
| payload | >EOF | The encrypted payload |
|
||||
|
||||
You can also upload files to your user account by specifying an API key in the curl command:
|
||||
```bash
|
||||
-H "Authorization: Bearer MY_API_KEY"
|
||||
```
|
||||
---
|
||||
VBF Payload Format
|
||||
|
||||
This command will return the direct download URL only.
|
||||
To get the json output simply remove the `?cli=true` from the url.
|
||||
|
||||
### Development
|
||||
To run postgres in local use:
|
||||
```
|
||||
docker run --rm -it -p 5432:5432 -e POSTGRES_DB=void -e POSTGRES_PASSWORD=postgres postgres -d postgres
|
||||
```
|
||||
|
||||
To run MinIO in local use:
|
||||
```
|
||||
docker run --rm -it -p 9000:9000 -p 9001:9001 minio/minio -- server /data --console-address ":9001"
|
||||
```
|
||||
| Name | Type | Description |
|
||||
|---|---|---|
|
||||
| header_length | uint16_t | Length of the header section |
|
||||
| header | string | The header json |
|
||||
| file | >EOF | The file |
|
||||
|
32
VoidCat.sln
32
VoidCat.sln
@ -1,32 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.32014.148
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VoidCat", "VoidCat\VoidCat.csproj", "{4BF9EA98-D462-4665-8276-6D3B4CD50C3A}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{40BC550E-CAD3-4E85-8BC9-539B53F87126}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.gitignore = .gitignore
|
||||
docker-compose.dev.yml = docker-compose.dev.yml
|
||||
prometheus.yml = prometheus.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{4BF9EA98-D462-4665-8276-6D3B4CD50C3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4BF9EA98-D462-4665-8276-6D3B4CD50C3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4BF9EA98-D462-4665-8276-6D3B4CD50C3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4BF9EA98-D462-4665-8276-6D3B4CD50C3A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {B1192B04-DFA3-4A9D-A6A3-E4602EF06B96}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
@ -1,123 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services.Abstractions;
|
||||
using VoidCat.Services.Files;
|
||||
|
||||
namespace VoidCat.Controllers.Admin;
|
||||
|
||||
[Route("admin")]
|
||||
[Authorize(Policy = Policies.RequireAdmin)]
|
||||
public class AdminController : Controller
|
||||
{
|
||||
private readonly FileStoreFactory _fileStore;
|
||||
private readonly IFileMetadataStore _fileMetadata;
|
||||
private readonly FileInfoManager _fileInfo;
|
||||
private readonly IUserStore _userStore;
|
||||
private readonly IUserUploadsStore _userUploads;
|
||||
|
||||
public AdminController(FileStoreFactory fileStore, IUserStore userStore, FileInfoManager fileInfo,
|
||||
IFileMetadataStore fileMetadata, IUserUploadsStore userUploads)
|
||||
{
|
||||
_fileStore = fileStore;
|
||||
_userStore = userStore;
|
||||
_fileInfo = fileInfo;
|
||||
_fileMetadata = fileMetadata;
|
||||
_userUploads = userUploads;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List all files in the system
|
||||
/// </summary>
|
||||
/// <param name="request">Page request</param>
|
||||
/// <returns></returns>
|
||||
[HttpOptions]
|
||||
[HttpPost]
|
||||
[Route("file")]
|
||||
public async Task<RenderedResults<VoidFileResponse>> ListFiles([FromBody] PagedRequest request)
|
||||
{
|
||||
var files = await _fileMetadata.ListFiles(request);
|
||||
|
||||
return new()
|
||||
{
|
||||
Page = files.Page,
|
||||
PageSize = files.PageSize,
|
||||
TotalResults = files.TotalResults,
|
||||
Results = (await files.Data.SelectAwait(a => _fileInfo.Get(a.Id, false)).ToListAsync())!
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete a file from the system
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the file to delete</param>
|
||||
[HttpOptions]
|
||||
[HttpDelete]
|
||||
[Route("file/{id}")]
|
||||
public async Task DeleteFile([FromRoute] string id)
|
||||
{
|
||||
var gid = id.FromBase58Guid();
|
||||
await _fileStore.DeleteFile(gid);
|
||||
await _fileInfo.Delete(gid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List all users in the system
|
||||
/// </summary>
|
||||
/// <param name="request">Page request</param>
|
||||
/// <returns></returns>
|
||||
[HttpOptions]
|
||||
[HttpPost]
|
||||
[Route("users")]
|
||||
public async Task<RenderedResults<AdminListedUser>> ListUsers([FromBody] PagedRequest request)
|
||||
{
|
||||
var result = await _userStore.ListUsers(request);
|
||||
|
||||
var ret = await result.Data.SelectAwait(async a =>
|
||||
{
|
||||
var uploads = await _userUploads.ListFiles(a.Id, new(0, int.MaxValue));
|
||||
return new AdminListedUser(a.ToAdminApiUser(true), uploads.TotalResults);
|
||||
}).ToListAsync();
|
||||
|
||||
return new()
|
||||
{
|
||||
PageSize = request.PageSize,
|
||||
Page = request.Page,
|
||||
TotalResults = result.TotalResults,
|
||||
Results = ret
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Admin update user account
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
[HttpOptions]
|
||||
[HttpPost]
|
||||
[Route("update-user")]
|
||||
public async Task<IActionResult> UpdateUser([FromBody] AdminUpdateUser user)
|
||||
{
|
||||
var oldUser = await _userStore.Get(user.Id);
|
||||
if (oldUser == default) return BadRequest();
|
||||
|
||||
oldUser.Storage = user.Storage;
|
||||
oldUser.Email = user.Email;
|
||||
|
||||
await _userStore.AdminUpdateUser(oldUser);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
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!;
|
||||
}
|
||||
}
|
@ -1,216 +0,0 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using VoidCat.Database;
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services.Abstractions;
|
||||
using VoidCat.Services.Users;
|
||||
|
||||
namespace VoidCat.Controllers;
|
||||
|
||||
[Route("auth")]
|
||||
public class AuthController : Controller
|
||||
{
|
||||
private readonly UserManager _manager;
|
||||
private readonly VoidSettings _settings;
|
||||
private readonly ICaptchaVerifier _captchaVerifier;
|
||||
private readonly IApiKeyStore _apiKeyStore;
|
||||
private readonly IUserStore _userStore;
|
||||
|
||||
public AuthController(UserManager userManager, VoidSettings settings, ICaptchaVerifier captchaVerifier,
|
||||
IApiKeyStore apiKeyStore,
|
||||
IUserStore userStore)
|
||||
{
|
||||
_manager = userManager;
|
||||
_settings = settings;
|
||||
_captchaVerifier = captchaVerifier;
|
||||
_apiKeyStore = apiKeyStore;
|
||||
_userStore = userStore;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Login to a user account
|
||||
/// </summary>
|
||||
/// <param name="req"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("login")]
|
||||
public async Task<LoginResponse> Login([FromBody] LoginRequest req)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!TryValidateModel(req))
|
||||
{
|
||||
var error = ControllerContext.ModelState.FirstOrDefault().Value?.Errors.FirstOrDefault()?.ErrorMessage;
|
||||
return new(null, error);
|
||||
}
|
||||
|
||||
// check captcha
|
||||
if (!await _captchaVerifier.Verify(req.Captcha))
|
||||
{
|
||||
return new(null, "Captcha verification failed");
|
||||
}
|
||||
|
||||
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.ToApiUser(true));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new(null, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a new account
|
||||
/// </summary>
|
||||
/// <param name="req"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("register")]
|
||||
public async Task<LoginResponse> Register([FromBody] LoginRequest req)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!TryValidateModel(req))
|
||||
{
|
||||
var error = ControllerContext.ModelState.FirstOrDefault().Value?.Errors.FirstOrDefault()?.ErrorMessage;
|
||||
return new(null, error);
|
||||
}
|
||||
|
||||
// check captcha
|
||||
if (!await _captchaVerifier.Verify(req.Captcha))
|
||||
{
|
||||
return new(null, "Captcha verification failed");
|
||||
}
|
||||
|
||||
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.ToApiUser(true));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return new(null, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start OAuth2 authorize flow
|
||||
/// </summary>
|
||||
/// <param name="provider">OAuth provider</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
[Route("{provider}")]
|
||||
public IActionResult Authorize([FromRoute] string provider)
|
||||
{
|
||||
return Redirect(_manager.Authorize(provider).ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authorize user from OAuth2 code grant
|
||||
/// </summary>
|
||||
/// <param name="code">Code used to generate access token</param>
|
||||
/// <param name="provider">OAuth provider</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
[Route("{provider}/token")]
|
||||
public async Task<IActionResult> Token([FromRoute] string provider, [FromQuery] string code)
|
||||
{
|
||||
var newUser = await _manager.LoginOrRegister(code, provider);
|
||||
var token = CreateToken(newUser, DateTime.UtcNow.AddHours(12));
|
||||
var tokenWriter = new JwtSecurityTokenHandler();
|
||||
|
||||
return Redirect($"/login#{tokenWriter.WriteToken(token)}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List api keys for the user
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
[Route("api-key")]
|
||||
public async Task<IActionResult> ListApiKeys()
|
||||
{
|
||||
var uid = HttpContext.GetUserId();
|
||||
if (uid == default) return Unauthorized();
|
||||
|
||||
return Json(await _apiKeyStore.ListKeys(uid.Value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new API key for the logged in user
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("api-key")]
|
||||
public async Task<IActionResult> CreateApiKey([FromBody] CreateApiKeyRequest request)
|
||||
{
|
||||
var uid = HttpContext.GetUserId();
|
||||
if (uid == default) return Unauthorized();
|
||||
|
||||
var user = await _userStore.Get(uid.Value);
|
||||
if (user == default) return Unauthorized();
|
||||
|
||||
var expiry = DateTime.SpecifyKind(request.Expiry, DateTimeKind.Utc);
|
||||
if (expiry > DateTime.UtcNow.AddYears(1))
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
var key = new ApiKey()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = user.Id,
|
||||
Token = new JwtSecurityTokenHandler().WriteToken(CreateToken(user, expiry)),
|
||||
Expiry = expiry
|
||||
};
|
||||
|
||||
await _apiKeyStore.Add(key.Id, key);
|
||||
return Json(key);
|
||||
}
|
||||
|
||||
private JwtSecurityToken CreateToken(User user, DateTime expiry)
|
||||
{
|
||||
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_settings.JwtSettings.Key));
|
||||
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var claims = new List<Claim>()
|
||||
{
|
||||
new(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||
new(JwtRegisteredClaimNames.Exp, new DateTimeOffset(expiry).ToUnixTimeSeconds().ToString()),
|
||||
new(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString())
|
||||
};
|
||||
|
||||
claims.AddRange(user.Roles.Select(a => new Claim(ClaimTypes.Role, a.Role)));
|
||||
|
||||
return new JwtSecurityToken(_settings.JwtSettings.Issuer, claims: claims,
|
||||
signingCredentials: credentials);
|
||||
}
|
||||
|
||||
public sealed class LoginRequest
|
||||
{
|
||||
public LoginRequest(string username, string password)
|
||||
{
|
||||
Username = username;
|
||||
Password = password;
|
||||
}
|
||||
|
||||
[Required] [EmailAddress] public string Username { get; }
|
||||
|
||||
[Required] [MinLength(6)] public string Password { get; }
|
||||
|
||||
public string? Captcha { get; init; }
|
||||
}
|
||||
|
||||
public sealed record LoginResponse(string? Jwt, string? Error = null, ApiUser? Profile = null);
|
||||
|
||||
public sealed record CreateApiKeyRequest(DateTime Expiry);
|
||||
}
|
@ -1,223 +0,0 @@
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using VoidCat.Database;
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services.Abstractions;
|
||||
using VoidCat.Services.Files;
|
||||
|
||||
namespace VoidCat.Controllers;
|
||||
|
||||
public abstract class BaseDownloadController : Controller
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly VoidSettings _settings;
|
||||
private readonly FileInfoManager _fileInfo;
|
||||
private readonly IPaymentOrderStore _paymentOrders;
|
||||
private readonly IPaymentFactory _paymentFactory;
|
||||
private readonly FileStoreFactory _storage;
|
||||
|
||||
protected BaseDownloadController(VoidSettings settings, FileInfoManager fileInfo, IPaymentOrderStore paymentOrders,
|
||||
IPaymentFactory paymentFactory, ILogger logger, FileStoreFactory storage)
|
||||
{
|
||||
_settings = settings;
|
||||
_fileInfo = fileInfo;
|
||||
_paymentOrders = paymentOrders;
|
||||
_paymentFactory = paymentFactory;
|
||||
_logger = logger;
|
||||
_storage = storage;
|
||||
}
|
||||
|
||||
protected async Task SendResponse(string id, VoidFileResponse voidFile)
|
||||
{
|
||||
var gid = voidFile.Id;
|
||||
|
||||
if (id.EndsWith(".torrent"))
|
||||
{
|
||||
var t = await voidFile.Metadata.MakeTorrent(voidFile.Id,
|
||||
await _storage.Open(new(gid, Enumerable.Empty<RangeRequest>()), CancellationToken.None),
|
||||
_settings.SiteUrl, _settings.TorrentTrackers);
|
||||
|
||||
Response.Headers.ContentDisposition = $"inline; filename=\"{id}\"";
|
||||
Response.ContentType = "application/x-bittorent";
|
||||
await t.EncodeToAsync(Response.Body);
|
||||
return;
|
||||
}
|
||||
|
||||
var egressReq = new EgressRequest(gid, GetRanges(Request, (long)voidFile!.Metadata!.Size));
|
||||
if (egressReq.Ranges.Count() > 1)
|
||||
{
|
||||
_logger.LogWarning("Multi-range request not supported!");
|
||||
// downgrade to full send
|
||||
egressReq = egressReq with
|
||||
{
|
||||
Ranges = Enumerable.Empty<RangeRequest>()
|
||||
};
|
||||
}
|
||||
else if (egressReq.Ranges.Count() == 1)
|
||||
{
|
||||
Response.StatusCode = (int)HttpStatusCode.PartialContent;
|
||||
if (egressReq.Ranges.Sum(a => a.Size) == 0)
|
||||
{
|
||||
Response.StatusCode = (int)HttpStatusCode.RequestedRangeNotSatisfiable;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Response.Headers.AcceptRanges = "bytes";
|
||||
}
|
||||
|
||||
foreach (var range in egressReq.Ranges)
|
||||
{
|
||||
Response.Headers.Add("content-range", range.ToContentRange());
|
||||
Response.ContentLength = range.Size;
|
||||
}
|
||||
|
||||
var preResult = await _storage.StartEgress(egressReq);
|
||||
if (preResult.Redirect != null)
|
||||
{
|
||||
Response.StatusCode = (int)HttpStatusCode.Redirect;
|
||||
Response.Headers.Location = preResult.Redirect.ToString();
|
||||
Response.ContentLength = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
var cts = HttpContext.RequestAborted;
|
||||
await Response.StartAsync(cts);
|
||||
await _storage.Egress(egressReq, Response.Body, cts);
|
||||
await Response.CompleteAsync();
|
||||
}
|
||||
|
||||
protected async Task<VoidFileResponse?> SetupDownload(Guid id)
|
||||
{
|
||||
var meta = await _fileInfo.Get(id, false);
|
||||
if (meta == null)
|
||||
{
|
||||
Response.StatusCode = 404;
|
||||
return default;
|
||||
}
|
||||
|
||||
return await CheckDownload(meta);
|
||||
}
|
||||
|
||||
private async Task<VoidFileResponse?> CheckDownload(VoidFileResponse meta)
|
||||
{
|
||||
var origin = Request.Headers.Referer.FirstOrDefault() ?? Request.Headers.Origin.FirstOrDefault();
|
||||
if (!string.IsNullOrEmpty(origin) && Uri.TryCreate(origin, UriKind.RelativeOrAbsolute, out var u))
|
||||
{
|
||||
if (_settings.BlockedOrigins.Any(a => string.Equals(a, u.DnsSafeHost, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
// check payment order
|
||||
if (meta.Payment != default && meta.Payment.Service != PaywallService.None && meta.Payment.Required)
|
||||
{
|
||||
var h402 = Request.Headers.FirstOrDefault(a => a.Key.Equals("Authorization", StringComparison.InvariantCultureIgnoreCase))
|
||||
.Value.FirstOrDefault(a => a?.StartsWith("L402") ?? false);
|
||||
|
||||
var orderId = Request.Headers.GetHeader("V-OrderId") ?? h402 ?? Request.Query["orderId"];
|
||||
if (!await IsOrderPaid(orderId!))
|
||||
{
|
||||
Response.Headers.CacheControl = "no-cache";
|
||||
Response.StatusCode = (int)HttpStatusCode.PaymentRequired;
|
||||
if (meta.Payment.Service is PaywallService.Strike or PaywallService.LnProxy)
|
||||
{
|
||||
var accept = Request.Headers.GetHeader("accept");
|
||||
if (accept == "L402")
|
||||
{
|
||||
var provider = await _paymentFactory.CreateProvider(meta.Payment.Service);
|
||||
var order = await provider.CreateOrder(meta.Payment!);
|
||||
if (order != default)
|
||||
{
|
||||
Response.Headers.Add("access-control-expose-headers", "www-authenticate");
|
||||
Response.Headers.Add("www-authenticate",
|
||||
$"L402 macaroon=\"{Convert.ToBase64String(order.Id.ToByteArray())}\", invoice=\"{order!.OrderLightning!.Invoice}\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
// prevent hot-linking viruses
|
||||
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;
|
||||
|
||||
if (meta.VirusScan?.IsVirus == true && !hasCorrectReferer)
|
||||
{
|
||||
Response.StatusCode = (int)HttpStatusCode.Redirect;
|
||||
Response.Headers.Location = $"/{meta.Id.ToBase58()}";
|
||||
return default;
|
||||
}
|
||||
|
||||
if (meta.IsNostr)
|
||||
{
|
||||
Response.StatusCode = (int)HttpStatusCode.Redirect;
|
||||
Response.Headers.Location = $"https://nostr.download/{meta.Metadata.Digest}";
|
||||
return default;
|
||||
}
|
||||
|
||||
Response.Headers.XFrameOptions = "SAMEORIGIN";
|
||||
Response.Headers.ContentDisposition = $"inline; filename=\"{meta?.Metadata?.Name}\"";
|
||||
Response.ContentType = meta?.Metadata?.MimeType ?? "application/octet-stream";
|
||||
Response.ContentLength = (long?)meta?.Metadata?.Size;
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
private async ValueTask<bool> IsOrderPaid(string? orderId)
|
||||
{
|
||||
if (orderId?.StartsWith("L402") ?? false)
|
||||
{
|
||||
orderId = new Guid(Convert.FromBase64String(orderId.Substring(5).Split(":")[0])).ToString();
|
||||
}
|
||||
|
||||
if (Guid.TryParse(orderId, out var oid))
|
||||
{
|
||||
var order = await _paymentOrders.Get(oid);
|
||||
if (order?.Status == PaywallOrderStatus.Paid)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (order?.Status is PaywallOrderStatus.Unpaid)
|
||||
{
|
||||
// check status
|
||||
var svc = await _paymentFactory.CreateProvider(order.Service);
|
||||
var status = await svc.GetOrderStatus(order.Id);
|
||||
if (status != default && status.Status != order.Status)
|
||||
{
|
||||
await _paymentOrders.UpdateStatus(order.Id, status.Status);
|
||||
}
|
||||
|
||||
if (status?.Status == PaywallOrderStatus.Paid)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private IEnumerable<RangeRequest> GetRanges(HttpRequest request, long totalSize)
|
||||
{
|
||||
foreach (var rangeHeader in request.Headers.Range)
|
||||
{
|
||||
if (string.IsNullOrEmpty(rangeHeader))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var h in RangeRequest.Parse(rangeHeader, totalSize))
|
||||
{
|
||||
yield return h;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services.Abstractions;
|
||||
using VoidCat.Services.Files;
|
||||
|
||||
namespace VoidCat.Controllers;
|
||||
|
||||
[Route("d")]
|
||||
public class DownloadController : BaseDownloadController
|
||||
{
|
||||
public DownloadController(FileStoreFactory storage, ILogger<DownloadController> logger, FileInfoManager fileInfo,
|
||||
IPaymentOrderStore paymentOrderStore, VoidSettings settings, IPaymentFactory paymentFactory)
|
||||
: base(settings, fileInfo, paymentOrderStore, paymentFactory, logger, storage)
|
||||
{
|
||||
}
|
||||
|
||||
[HttpHead]
|
||||
[HttpOptions]
|
||||
[Route("{id}")]
|
||||
[EnableCors("*")]
|
||||
public Task DownloadFileOptions([FromRoute] string id)
|
||||
{
|
||||
var gid = id.FromBase58Guid();
|
||||
return SetupDownload(gid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download a specific file by Id
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
[ResponseCache(Location = ResponseCacheLocation.Any, Duration = 86400)]
|
||||
[HttpGet]
|
||||
[Route("{id}")]
|
||||
[EnableCors("*")]
|
||||
public async Task DownloadFile([FromRoute] string id)
|
||||
{
|
||||
var gid = id.FromBase58Guid();
|
||||
var voidFile = await SetupDownload(gid);
|
||||
if (voidFile == default) return;
|
||||
|
||||
await SendResponse(id, voidFile);
|
||||
}
|
||||
}
|
@ -1,139 +0,0 @@
|
||||
using AngleSharp;
|
||||
using AngleSharp.Dom;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services.Abstractions;
|
||||
|
||||
namespace VoidCat.Controllers;
|
||||
|
||||
public class IndexController : Controller
|
||||
{
|
||||
private readonly IWebHostEnvironment _webHost;
|
||||
private readonly IFileMetadataStore _fileMetadata;
|
||||
private readonly VoidSettings _settings;
|
||||
|
||||
public IndexController(IFileMetadataStore fileMetadata, IWebHostEnvironment webHost, VoidSettings settings)
|
||||
{
|
||||
_fileMetadata = fileMetadata;
|
||||
_webHost = webHost;
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return html content with tags for file preview
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[Route("{id}")]
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> FilePreview(string id)
|
||||
{
|
||||
id.TryFromBase58Guid(out var gid);
|
||||
|
||||
var ubDownload = new UriBuilder(_settings.SiteUrl)
|
||||
{
|
||||
Path = $"/d/{gid.ToBase58()}"
|
||||
};
|
||||
|
||||
var ubView = new UriBuilder(_settings.SiteUrl)
|
||||
{
|
||||
Path = $"/{gid.ToBase58()}"
|
||||
};
|
||||
|
||||
var indexPath = Path.Combine(_webHost.WebRootPath, "index.html");
|
||||
|
||||
var indexContent = System.IO.File.Exists(indexPath) ?
|
||||
await System.IO.File.ReadAllTextAsync(indexPath)
|
||||
: string.Empty;
|
||||
|
||||
var meta = (await _fileMetadata.Get(gid))?.ToMeta(false);
|
||||
var tags = new List<KeyValuePair<string, string>>()
|
||||
{
|
||||
new("site_name", "void.cat"),
|
||||
new("title", meta?.Name ?? ""),
|
||||
new("description", meta?.Description ?? ""),
|
||||
new("url", ubView.Uri.ToString()),
|
||||
};
|
||||
|
||||
var mime = meta?.MimeType;
|
||||
if (mime?.StartsWith("image/") ?? false)
|
||||
{
|
||||
tags.Add(new("type", "image"));
|
||||
tags.Add(new("image", ubDownload.Uri.ToString()));
|
||||
tags.Add(new("image:type", mime));
|
||||
}
|
||||
else if (mime?.StartsWith("video/") ?? false)
|
||||
{
|
||||
tags.Add(new("type", "video.other"));
|
||||
tags.Add(new("image", ""));
|
||||
tags.Add(new("video", ubDownload.Uri.ToString()));
|
||||
tags.Add(new("video:url", ubDownload.Uri.ToString()));
|
||||
tags.Add(new("video:secure_url", ubDownload.Uri.ToString()));
|
||||
tags.Add(new("video:type", mime));
|
||||
}
|
||||
else if (mime?.StartsWith("audio/") ?? false)
|
||||
{
|
||||
tags.Add(new("type", "audio.other"));
|
||||
tags.Add(new("audio", ubDownload.Uri.ToString()));
|
||||
tags.Add(new("audio:type", mime));
|
||||
}
|
||||
else
|
||||
{
|
||||
tags.Add(new("type", "website"));
|
||||
}
|
||||
|
||||
var injectedHtml = await InjectTags(indexContent, tags);
|
||||
return Content(injectedHtml?.ToHtml() ?? indexContent, "text/html");
|
||||
}
|
||||
|
||||
public class IndexModel
|
||||
{
|
||||
public Guid Id { get; init; }
|
||||
public VoidFileMeta? Meta { get; init; }
|
||||
|
||||
public AssetManifest Manifest { get; init; }
|
||||
}
|
||||
|
||||
public class AssetManifest
|
||||
{
|
||||
public Dictionary<string, string> Files { get; init; }
|
||||
public List<string> Entrypoints { get; init; }
|
||||
}
|
||||
|
||||
private async Task<IDocument?> InjectTags(string html, List<KeyValuePair<string, string>> tags)
|
||||
{
|
||||
var config = Configuration.Default;
|
||||
var context = BrowsingContext.New(config);
|
||||
var doc = await context.OpenAsync(c => c.Content(html));
|
||||
|
||||
foreach (var tag in tags)
|
||||
{
|
||||
var ogTag = doc.CreateElement("meta");
|
||||
ogTag.SetAttribute("property", $"og:{tag.Key}");
|
||||
ogTag.SetAttribute("content", tag.Value);
|
||||
doc.Head?.AppendChild(ogTag);
|
||||
switch (tag.Key.ToLower())
|
||||
{
|
||||
case "title":
|
||||
{
|
||||
var titleTag = doc.Head?.QuerySelector("title");
|
||||
if (titleTag != default)
|
||||
{
|
||||
titleTag.TextContent = tag.Value;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "description":
|
||||
{
|
||||
var descriptionTag = doc.Head?.QuerySelector("meta[name='description']");
|
||||
descriptionTag?.SetAttribute("content", tag.Value);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services.Abstractions;
|
||||
|
||||
namespace VoidCat.Controllers;
|
||||
|
||||
[Route("info")]
|
||||
public class InfoController : Controller
|
||||
{
|
||||
private readonly IStatsReporter _statsReporter;
|
||||
private readonly IFileMetadataStore _fileMetadata;
|
||||
private readonly VoidSettings _settings;
|
||||
private readonly ITimeSeriesStatsReporter _timeSeriesStats;
|
||||
private readonly IEnumerable<string?> _fileStores;
|
||||
private readonly IEnumerable<string> _oAuthProviders;
|
||||
|
||||
public InfoController(IStatsReporter statsReporter, IFileMetadataStore fileMetadata, VoidSettings settings,
|
||||
ITimeSeriesStatsReporter stats, IEnumerable<IFileStore> fileStores, IEnumerable<IOAuthProvider> oAuthProviders)
|
||||
{
|
||||
_statsReporter = statsReporter;
|
||||
_fileMetadata = fileMetadata;
|
||||
_settings = settings;
|
||||
_timeSeriesStats = stats;
|
||||
_fileStores = fileStores.Select(a => a.Key);
|
||||
_oAuthProviders = oAuthProviders.Select(a => a.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return system info
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
[ResponseCache(Location = ResponseCacheLocation.Any, Duration = 60)]
|
||||
public async Task<GlobalInfo> GetGlobalStats()
|
||||
{
|
||||
var bw = await _statsReporter.GetBandwidth();
|
||||
var storeStats = await _fileMetadata.Stats();
|
||||
|
||||
return new()
|
||||
{
|
||||
Bandwidth = bw,
|
||||
TotalBytes = storeStats.Size,
|
||||
Count = storeStats.Files,
|
||||
BuildInfo = BuildInfo.GetBuildInfo(),
|
||||
CaptchaSiteKey = _settings.CaptchaSettings?.SiteKey,
|
||||
TimeSeriesMetrics = await _timeSeriesStats.GetBandwidth(DateTime.UtcNow.AddDays(-30), DateTime.UtcNow),
|
||||
FileStores = _fileStores,
|
||||
UploadSegmentSize = _settings.UploadSegmentSize,
|
||||
OAuthProviders = _oAuthProviders
|
||||
};
|
||||
}
|
||||
|
||||
public sealed class GlobalInfo
|
||||
{
|
||||
public Bandwidth Bandwidth { get; init; }
|
||||
public ulong TotalBytes { get; init; }
|
||||
public long Count { get; init; }
|
||||
public BuildInfo BuildInfo { get; init; }
|
||||
public string? CaptchaSiteKey { get; init; }
|
||||
public IEnumerable<BandwidthPoint> TimeSeriesMetrics { get; init; }
|
||||
public IEnumerable<string?> FileStores { get; init; }
|
||||
public ulong? UploadSegmentSize { get; init; }
|
||||
public IEnumerable<string> OAuthProviders { get; init; }
|
||||
}
|
||||
}
|
@ -1,242 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services;
|
||||
using VoidCat.Services.Abstractions;
|
||||
using VoidCat.Services.Files;
|
||||
using VoidCat.Services.Users;
|
||||
using File = VoidCat.Database.File;
|
||||
|
||||
namespace VoidCat.Controllers;
|
||||
|
||||
[Route("nostr")]
|
||||
public class NostrController : BaseDownloadController
|
||||
{
|
||||
private readonly VoidSettings _settings;
|
||||
private readonly UserManager _userManager;
|
||||
private readonly FileStoreFactory _storeFactory;
|
||||
private readonly IFileMetadataStore _fileMetadata;
|
||||
private readonly IUserUploadsStore _userUploads;
|
||||
private readonly FileInfoManager _fileInfo;
|
||||
|
||||
public NostrController(VoidSettings settings, UserManager userManager, FileStoreFactory storeFactory, IFileMetadataStore fileMetadata,
|
||||
IUserUploadsStore userUploads, FileInfoManager fileInfo, IPaymentOrderStore paymentOrderStore, IPaymentFactory paymentFactory,
|
||||
ILogger<NostrController> logger)
|
||||
: base(settings, fileInfo, paymentOrderStore, paymentFactory, logger, storeFactory)
|
||||
{
|
||||
_settings = settings;
|
||||
_userManager = userManager;
|
||||
_storeFactory = storeFactory;
|
||||
_fileMetadata = fileMetadata;
|
||||
_userUploads = userUploads;
|
||||
_fileInfo = fileInfo;
|
||||
}
|
||||
|
||||
[HttpGet("/.well-known/nostr/nip96.json")]
|
||||
public IActionResult GetInfo()
|
||||
{
|
||||
var info = new Nip96Info
|
||||
{
|
||||
ApiUri = new Uri(_settings.SiteUrl, "/nostr"),
|
||||
Plans = new()
|
||||
{
|
||||
{
|
||||
"free", new Nip96Plan
|
||||
{
|
||||
Name = "Default",
|
||||
MaxUploadSize = (long?)_settings.UploadSegmentSize
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return Json(info);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[DisableRequestSizeLimit]
|
||||
[DisableFormValueModelBinding]
|
||||
[Authorize(AuthenticationSchemes = NostrAuth.Scheme, Policy = Policies.RequireNostr)]
|
||||
public async Task<IActionResult> Upload()
|
||||
{
|
||||
var pubkey = HttpContext.GetPubKey();
|
||||
if (string.IsNullOrEmpty(pubkey))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
try
|
||||
{
|
||||
if (_settings.MaintenanceMode)
|
||||
{
|
||||
throw new InvalidOperationException("Site is in maintenance mode");
|
||||
}
|
||||
var nostrUser = await _userManager.LoginOrRegister(pubkey);
|
||||
var file = Request.Form.Files.First();
|
||||
|
||||
var meta = new File
|
||||
{
|
||||
MimeType = file.ContentType,
|
||||
Name = file.FileName,
|
||||
Description = Request.Form.TryGetValue("alt", out var sd) ? sd.First() : default,
|
||||
Size = (ulong)file.Length,
|
||||
Storage = nostrUser.Storage
|
||||
};
|
||||
|
||||
var vf = await _storeFactory.Ingress(new(file.OpenReadStream(), meta, 1, 1, true), HttpContext.RequestAborted);
|
||||
|
||||
// save metadata
|
||||
await _fileMetadata.Add(vf);
|
||||
await _userUploads.AddFile(nostrUser.Id, vf.Id);
|
||||
|
||||
List<List<string>> tags = new()
|
||||
{
|
||||
new() {"url", new Uri(_settings.SiteUrl, $"/nostr/{vf.OriginalDigest}{Path.GetExtension(vf.Name)}").ToString()},
|
||||
new() {"ox", vf.OriginalDigest ?? "", _settings.SiteUrl.ToString()},
|
||||
new() {"x", vf.Digest ?? ""},
|
||||
new() {"m", vf.MimeType}
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(vf.MediaDimensions))
|
||||
{
|
||||
tags.Add(new() {"dim", vf.MediaDimensions});
|
||||
}
|
||||
|
||||
var ret = new Nip96UploadResult
|
||||
{
|
||||
FileHeader = new()
|
||||
{
|
||||
Tags = tags
|
||||
}
|
||||
};
|
||||
|
||||
return Json(ret);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Json(new Nip96UploadResult()
|
||||
{
|
||||
Status = "error",
|
||||
Message = ex.Message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task GetFile([FromRoute] string id)
|
||||
{
|
||||
var digest = Path.GetFileNameWithoutExtension(id);
|
||||
var file = await _fileMetadata.GetHash(digest);
|
||||
if (file == default)
|
||||
{
|
||||
Response.StatusCode = 404;
|
||||
return;
|
||||
}
|
||||
|
||||
var meta = await SetupDownload(file.Id);
|
||||
if (meta == default) return;
|
||||
|
||||
await SendResponse(id, meta);
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize(AuthenticationSchemes = NostrAuth.Scheme, Policy = Policies.RequireNostr)]
|
||||
public async Task<IActionResult> DeleteFile([FromRoute] string id)
|
||||
{
|
||||
var digest = Path.GetFileNameWithoutExtension(id);
|
||||
var file = await _fileMetadata.GetHash(digest);
|
||||
if (file == default)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var pubkey = HttpContext.GetPubKey();
|
||||
if (string.IsNullOrEmpty(pubkey))
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var nostrUser = await _userManager.LoginOrRegister(pubkey);
|
||||
var uploader = await _userUploads.Uploader(file.Id);
|
||||
if (uploader == default || uploader != nostrUser.Id)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
await _fileInfo.Delete(file.Id);
|
||||
|
||||
return Json(new Nip96UploadResult());
|
||||
}
|
||||
}
|
||||
|
||||
public class Nip96Info
|
||||
{
|
||||
[JsonProperty("api_url")]
|
||||
public Uri ApiUri { get; init; } = null!;
|
||||
|
||||
[JsonProperty("download_url")]
|
||||
public Uri? DownloadUrl { get; init; }
|
||||
|
||||
[JsonProperty("delegated_to_url")]
|
||||
public Uri? DelegatedTo { get; init; }
|
||||
|
||||
[JsonProperty("supported_nips")]
|
||||
public List<int>? SupportedNips { get; init; }
|
||||
|
||||
[JsonProperty("tos_url")]
|
||||
public Uri? Tos { get; init; }
|
||||
|
||||
[JsonProperty("content_types")]
|
||||
public List<string>? ContentTypes { get; init; }
|
||||
|
||||
[JsonProperty("plans")]
|
||||
public Dictionary<string, Nip96Plan>? Plans { get; init; }
|
||||
}
|
||||
|
||||
public class Nip96Plan
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; init; } = null!;
|
||||
|
||||
[JsonProperty("is_nip98_required")]
|
||||
public bool Nip98Required { get; init; } = true;
|
||||
|
||||
[JsonProperty("url")]
|
||||
public Uri? LandingPage { get; init; }
|
||||
|
||||
[JsonProperty("max_byte_size")]
|
||||
public long? MaxUploadSize { get; init; }
|
||||
|
||||
[JsonProperty("file_expiration")]
|
||||
public int[] FileExpiration { get; init; } = {0, 0};
|
||||
|
||||
[JsonProperty("media_transformations")]
|
||||
public Nip96MediaTransformations? MediaTransformations { get; init; }
|
||||
}
|
||||
|
||||
public class Nip96MediaTransformations
|
||||
{
|
||||
[JsonProperty("image")]
|
||||
public List<string>? Image { get; init; }
|
||||
}
|
||||
|
||||
public class Nip96UploadResult
|
||||
{
|
||||
[JsonProperty("status")]
|
||||
public string Status { get; init; } = "success";
|
||||
|
||||
[JsonProperty("message")]
|
||||
public string? Message { get; init; }
|
||||
|
||||
[JsonProperty("processing_url")]
|
||||
public Uri? ProcessingUrl { get; init; }
|
||||
|
||||
[JsonProperty("nip94_event")]
|
||||
public Nip94Info FileHeader { get; init; } = null!;
|
||||
}
|
||||
|
||||
public class Nip94Info
|
||||
{
|
||||
[JsonProperty("tags")]
|
||||
public List<List<string>> Tags { get; init; } = new();
|
||||
}
|
@ -1,420 +0,0 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
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.Services.Abstractions;
|
||||
using VoidCat.Services.Files;
|
||||
using VoidCat.Services.Users;
|
||||
using File = VoidCat.Database.File;
|
||||
|
||||
namespace VoidCat.Controllers
|
||||
{
|
||||
[Route("upload")]
|
||||
public class UploadController : Controller
|
||||
{
|
||||
private readonly FileStoreFactory _storage;
|
||||
private readonly IFileMetadataStore _metadata;
|
||||
private readonly IPaymentStore _paymentStore;
|
||||
private readonly IPaymentFactory _paymentFactory;
|
||||
private readonly FileInfoManager _fileInfo;
|
||||
private readonly IUserUploadsStore _userUploads;
|
||||
private readonly IUserStore _userStore;
|
||||
private readonly UserManager _userManager;
|
||||
private readonly ITimeSeriesStatsReporter _timeSeriesStats;
|
||||
private readonly VoidSettings _settings;
|
||||
|
||||
public UploadController(FileStoreFactory storage, IFileMetadataStore metadata, IPaymentStore payment,
|
||||
IPaymentFactory paymentFactory, FileInfoManager fileInfo, IUserUploadsStore userUploads,
|
||||
ITimeSeriesStatsReporter timeSeriesStats, IUserStore userStore, VoidSettings settings, UserManager userManager)
|
||||
{
|
||||
_storage = storage;
|
||||
_metadata = metadata;
|
||||
_paymentStore = payment;
|
||||
_paymentFactory = paymentFactory;
|
||||
_fileInfo = fileInfo;
|
||||
_userUploads = userUploads;
|
||||
_timeSeriesStats = timeSeriesStats;
|
||||
_userStore = userStore;
|
||||
_settings = settings;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Primary upload endpoint
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Additional optional headers can be included to provide details about the file being uploaded:
|
||||
///
|
||||
/// `V-Content-Type` - Sets the `mimeType` of the file and is used on the preview page to display the file.
|
||||
/// `V-Filename` - Sets the filename of the file.
|
||||
/// `V-Description` - Sets the description of the file.
|
||||
/// `V-Full-Digest` - Include a SHA256 hash of the entire file for verification purposes.
|
||||
/// </remarks>
|
||||
/// <param name="cli">True if you want to return only the url of the file in the response</param>
|
||||
/// <returns>Returns <see cref="UploadResult"/></returns>
|
||||
[HttpPost]
|
||||
[HttpOptions]
|
||||
[HttpHead]
|
||||
[DisableRequestSizeLimit]
|
||||
[DisableFormValueModelBinding]
|
||||
[Authorize(AuthenticationSchemes = "Bearer,Nostr", Policy = Policies.RequireNostr)]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> UploadFile([FromQuery] bool cli = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
var mime = Request.Headers.GetHeader("V-Content-Type");
|
||||
var stripMetadata = (mime?.StartsWith("image/") ?? false) || (Request.Headers.GetHeader("V-Strip-Metadata")
|
||||
?.Equals("true", StringComparison.InvariantCultureIgnoreCase) ?? false);
|
||||
|
||||
if (_settings.MaintenanceMode)
|
||||
{
|
||||
throw new InvalidOperationException("Site is in maintenance mode");
|
||||
}
|
||||
|
||||
var bodyLength = Request.ContentLength ?? 0;
|
||||
if ((ulong)bodyLength >= (_settings.MaxFileSize ?? ulong.MaxValue))
|
||||
{
|
||||
throw new InvalidOperationException("File size too large");
|
||||
}
|
||||
|
||||
var uid = HttpContext.GetUserId();
|
||||
var pubkey = HttpContext.GetPubKey();
|
||||
if (uid == default && !string.IsNullOrEmpty(pubkey))
|
||||
{
|
||||
var nostrUser = await _userManager.LoginOrRegister(pubkey);
|
||||
uid = nostrUser.Id;
|
||||
}
|
||||
|
||||
var filename = Request.Headers.GetHeader("V-Filename");
|
||||
|
||||
if (string.IsNullOrEmpty(mime) && !string.IsNullOrEmpty(filename))
|
||||
{
|
||||
if (new FileExtensionContentTypeProvider().TryGetContentType(filename, out var contentType))
|
||||
{
|
||||
mime = contentType;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(mime))
|
||||
{
|
||||
mime = "application/octet-stream";
|
||||
}
|
||||
|
||||
// detect store for ingress
|
||||
var store = _settings.DefaultFileStore;
|
||||
if (uid.HasValue)
|
||||
{
|
||||
var user = await _userStore.Get(uid.Value);
|
||||
if (user?.Storage != default)
|
||||
{
|
||||
store = user.Storage!;
|
||||
}
|
||||
}
|
||||
|
||||
var meta = new File
|
||||
{
|
||||
MimeType = mime,
|
||||
Name = filename,
|
||||
Description = Request.Headers.GetHeader("V-Description"),
|
||||
Digest = Request.Headers.GetHeader("V-Full-Digest"),
|
||||
Size = (ulong?)Request.ContentLength ?? 0UL,
|
||||
Storage = store,
|
||||
EncryptionParams = Request.Headers.GetHeader("V-EncryptionParams")
|
||||
};
|
||||
|
||||
var (segment, totalSegments) = ParseSegmentsHeader();
|
||||
var vf = await _storage.Ingress(new(Request.Body, meta, segment, totalSegments, stripMetadata),
|
||||
HttpContext.RequestAborted);
|
||||
|
||||
// save metadata
|
||||
await _metadata.Add(vf);
|
||||
|
||||
// attach file upload to user
|
||||
if (uid.HasValue)
|
||||
{
|
||||
await _userUploads.AddFile(uid.Value, vf.Id);
|
||||
}
|
||||
|
||||
if (cli)
|
||||
{
|
||||
var urlBuilder = new UriBuilder(_settings.SiteUrl)
|
||||
{
|
||||
Path = $"/d/{vf.Id.ToBase58()}"
|
||||
};
|
||||
|
||||
return Content(urlBuilder.Uri.ToString(), "text/plain");
|
||||
}
|
||||
|
||||
return Json(UploadResult.Success(vf.ToResponse(true)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Json(UploadResult.Error(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Append data onto a file
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This endpoint is mainly used to bypass file upload limits enforced by CloudFlare.
|
||||
/// Clients should split their uploads into segments, upload the first segment to the regular
|
||||
/// upload endpoint, use the `editSecret` to append data to the file.
|
||||
///
|
||||
/// Set the edit secret in the header `V-EditSecret` otherwise you will not be able to append data.
|
||||
/// </remarks>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[DisableRequestSizeLimit]
|
||||
[DisableFormValueModelBinding]
|
||||
[Route("{id}")]
|
||||
public async Task<UploadResult> UploadFileAppend([FromRoute] string id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var stripMetadata = Request.Headers.GetHeader("V-Strip-Metadata")
|
||||
?.Equals("true", StringComparison.InvariantCultureIgnoreCase) ?? false;
|
||||
|
||||
if (_settings.MaintenanceMode && !stripMetadata)
|
||||
{
|
||||
throw new InvalidOperationException("Site is in maintenance mode");
|
||||
}
|
||||
|
||||
var gid = id.FromBase58Guid();
|
||||
var meta = await _metadata.Get(gid);
|
||||
if (meta == default) return UploadResult.Error("File not found");
|
||||
|
||||
// Parse V-Segment header
|
||||
var (segment, totalSegments) = ParseSegmentsHeader();
|
||||
|
||||
// sanity check for append operations
|
||||
if (segment <= 1 || totalSegments <= 1)
|
||||
{
|
||||
return UploadResult.Error("Malformed request, segment must be > 1 for append");
|
||||
}
|
||||
|
||||
var editSecret = Request.Headers.GetHeader("V-EditSecret");
|
||||
var vf = await _storage.Ingress(new(Request.Body, meta, segment, totalSegments, stripMetadata)
|
||||
{
|
||||
EditSecret = editSecret?.FromBase58Guid() ?? Guid.Empty,
|
||||
Id = gid
|
||||
}, HttpContext.RequestAborted);
|
||||
|
||||
// update file size
|
||||
await _metadata.Update(vf.Id, vf);
|
||||
return UploadResult.Success(vf.ToResponse(true));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return UploadResult.Error(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return information about a specific file
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
[Route("{id}")]
|
||||
public async Task<IActionResult> GetInfo([FromRoute] string id)
|
||||
{
|
||||
if (!id.TryFromBase58Guid(out var fid)) return StatusCode(404);
|
||||
|
||||
var uid = HttpContext.GetUserId();
|
||||
var isOwner = uid.HasValue && await _userUploads.Uploader(fid) == uid;
|
||||
|
||||
var info = await _fileInfo.Get(fid, isOwner || HttpContext.IsRole(Roles.Admin));
|
||||
if (info == default) return StatusCode(404);
|
||||
|
||||
return Json(info);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return information about a specific file
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
[Route("{id}/metrics")]
|
||||
public async Task<IActionResult> Metrics([FromRoute] string id)
|
||||
{
|
||||
if (!id.TryFromBase58Guid(out var fid)) return StatusCode(404);
|
||||
|
||||
var stats = await _timeSeriesStats.GetBandwidth(fid, DateTime.UtcNow.Subtract(TimeSpan.FromDays(30)),
|
||||
DateTime.UtcNow);
|
||||
|
||||
return Json(stats);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a payment order to pay
|
||||
/// </summary>
|
||||
/// <param name="id">File id</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
[Route("{id}/payment")]
|
||||
public async ValueTask<PaywallOrder?> CreateOrder([FromRoute] string id)
|
||||
{
|
||||
var gid = id.FromBase58Guid();
|
||||
var file = await _fileInfo.Get(gid, false);
|
||||
var config = await _paymentStore.Get(gid);
|
||||
|
||||
var provider = await _paymentFactory.CreateProvider(config!.Service);
|
||||
return await provider.CreateOrder(file!.Payment!);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the status of an order
|
||||
/// </summary>
|
||||
/// <param name="id">File id</param>
|
||||
/// <param name="order">Order id</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
[Route("{id}/payment/{order:guid}")]
|
||||
public async ValueTask<PaywallOrder?> GetOrderStatus([FromRoute] string id, [FromRoute] Guid order)
|
||||
{
|
||||
var gid = id.FromBase58Guid();
|
||||
var config = await _paymentStore.Get(gid);
|
||||
|
||||
var provider = await _paymentFactory.CreateProvider(config!.Service);
|
||||
return await provider.GetOrderStatus(order);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the payment config
|
||||
/// </summary>
|
||||
/// <param name="id">File id</param>
|
||||
/// <param name="req">Requested config to set on the file</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("{id}/payment")]
|
||||
public async Task<IActionResult> SetPaymentConfig([FromRoute] string id, [FromBody] SetPaymentConfigRequest req)
|
||||
{
|
||||
var gid = id.FromBase58Guid();
|
||||
var meta = await _metadata.Get(gid);
|
||||
if (meta == default) return NotFound();
|
||||
if (!meta.CanEdit(req.EditSecret)) return Unauthorized();
|
||||
|
||||
if (req.StrikeHandle != default)
|
||||
{
|
||||
if (meta.Paywall?.Id != default)
|
||||
{
|
||||
await _paymentStore.Delete(meta.Paywall.Id);
|
||||
}
|
||||
|
||||
await _paymentStore.Add(gid, new Paywall
|
||||
{
|
||||
FileId = meta.Id,
|
||||
Service = PaywallService.Strike,
|
||||
Upstream = req.StrikeHandle,
|
||||
Amount = req.Amount,
|
||||
Currency = Enum.Parse<PaywallCurrency>(req.Currency),
|
||||
Required = req.Required
|
||||
});
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
// if none set, delete existing config
|
||||
if (meta.Paywall?.Id != default)
|
||||
{
|
||||
await _paymentStore.Delete(meta.Paywall.Id);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update metadata about file
|
||||
/// </summary>
|
||||
/// <param name="id">Id of file to edit</param>
|
||||
/// <param name="fileMeta">New metadata to update</param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// You can only change `Name`, `Description` and `MimeType`
|
||||
/// </remarks>
|
||||
[HttpPost]
|
||||
[Route("{id}/meta")]
|
||||
public async Task<IActionResult> UpdateFileMeta([FromRoute] string id, [FromBody] VoidFileMeta fileMeta)
|
||||
{
|
||||
var gid = id.FromBase58Guid();
|
||||
var meta = await _metadata.Get(gid);
|
||||
if (meta == default) return NotFound();
|
||||
if (!(meta.CanEdit(fileMeta.EditSecret) || HttpContext.IsRole(Roles.Admin))) return Unauthorized();
|
||||
|
||||
await _metadata.Update(gid, new()
|
||||
{
|
||||
Name = fileMeta.Name,
|
||||
Description = fileMeta.Description,
|
||||
Expires = fileMeta.Expires,
|
||||
MimeType = fileMeta.MimeType
|
||||
});
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
private (int Segment, int TotalSegments) ParseSegmentsHeader()
|
||||
{
|
||||
// Parse V-Segment header
|
||||
int segment = 1, totalSegments = 1;
|
||||
var segmentHeader = Request.Headers.GetHeader("V-Segment");
|
||||
if (!string.IsNullOrEmpty(segmentHeader))
|
||||
{
|
||||
var split = segmentHeader.Split("/");
|
||||
if (split.Length == 2 && int.TryParse(split[0], out var a) && int.TryParse(split[1], out var b))
|
||||
{
|
||||
segment = a;
|
||||
totalSegments = b;
|
||||
}
|
||||
}
|
||||
|
||||
return (segment, totalSegments);
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
||||
public class DisableFormValueModelBindingAttribute : Attribute, IResourceFilter
|
||||
{
|
||||
public void OnResourceExecuting(ResourceExecutingContext context)
|
||||
{
|
||||
var factories = context.ValueProviderFactories;
|
||||
factories.RemoveType<FormValueProviderFactory>();
|
||||
factories.RemoveType<FormFileValueProviderFactory>();
|
||||
factories.RemoveType<JQueryFormValueProviderFactory>();
|
||||
}
|
||||
|
||||
public void OnResourceExecuted(ResourceExecutedContext context)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public record UploadResult(bool Ok, VoidFileResponse? File, string? ErrorMessage)
|
||||
{
|
||||
public static UploadResult Success(VoidFileResponse vf)
|
||||
=> new(true, vf, null);
|
||||
|
||||
public static UploadResult Error(string message)
|
||||
=> new(false, null, message);
|
||||
}
|
||||
|
||||
public record SetPaymentConfigRequest
|
||||
{
|
||||
[JsonConverter(typeof(Base58GuidConverter))]
|
||||
public Guid EditSecret { get; init; }
|
||||
|
||||
public decimal Amount { get; init; }
|
||||
|
||||
public string Currency { get; init; } = null!;
|
||||
|
||||
public string? StrikeHandle { get; init; }
|
||||
|
||||
public bool Required { get; init; }
|
||||
}
|
||||
}
|
@ -1,177 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using VoidCat.Database;
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services.Abstractions;
|
||||
using VoidCat.Services.Files;
|
||||
using UserFlags = VoidCat.Database.UserFlags;
|
||||
|
||||
namespace VoidCat.Controllers;
|
||||
|
||||
[Route("user/{id}")]
|
||||
public class UserController : Controller
|
||||
{
|
||||
private readonly IUserStore _store;
|
||||
private readonly IUserUploadsStore _userUploads;
|
||||
private readonly IEmailVerification _emailVerification;
|
||||
private readonly FileInfoManager _fileInfoManager;
|
||||
|
||||
public UserController(IUserStore store, IUserUploadsStore userUploads, IEmailVerification emailVerification,
|
||||
FileInfoManager fileInfoManager)
|
||||
{
|
||||
_store = store;
|
||||
_userUploads = userUploads;
|
||||
_emailVerification = emailVerification;
|
||||
_fileInfoManager = fileInfoManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return user profile
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You do not need to be logged in to return a user profile if their profile is set to public.
|
||||
///
|
||||
/// You may also use `me` as the `id` to get the logged in users profile.
|
||||
/// </remarks>
|
||||
/// <param name="id">User id to load</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetUser([FromRoute] string id)
|
||||
{
|
||||
var loggedUser = HttpContext.GetUserId();
|
||||
var isMe = id.Equals("me", StringComparison.InvariantCultureIgnoreCase);
|
||||
if (isMe && !loggedUser.HasValue) return Unauthorized();
|
||||
|
||||
var requestedId = isMe ? loggedUser!.Value : id.FromBase58Guid();
|
||||
var user = await _store.Get(requestedId);
|
||||
if (user == default) return NotFound();
|
||||
if (loggedUser != requestedId && !user.Flags.HasFlag(UserFlags.PublicProfile) && !HttpContext.IsRole(Roles.Admin))
|
||||
return NotFound();
|
||||
|
||||
var isMyProfile = requestedId == user.Id;
|
||||
return Json(user!.ToApiUser(isMyProfile));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update profile settings
|
||||
/// </summary>
|
||||
///
|
||||
/// <param name="id">User id</param>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> 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();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a list of files which the user has uploaded
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will return files if the profile has public uploads set on their profile.
|
||||
/// Otherwise you can return your own uploaded files if you are logged in.
|
||||
/// </remarks>
|
||||
/// <param name="id">User id</param>
|
||||
/// <param name="request">Page request</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("files")]
|
||||
public async Task<IActionResult> ListUserFiles([FromRoute] string id,
|
||||
[FromBody] PagedRequest request)
|
||||
{
|
||||
var loggedUser = HttpContext.GetUserId();
|
||||
var isAdmin = HttpContext.IsRole(Roles.Admin);
|
||||
|
||||
var user = await GetRequestedUser(id);
|
||||
if (user == default) return NotFound();
|
||||
|
||||
// not logged in user files, check public flag
|
||||
var canViewUploads = loggedUser == user.Id || isAdmin;
|
||||
if (!canViewUploads &&
|
||||
!user.Flags.HasFlag(UserFlags.PublicUploads)) return Forbid();
|
||||
|
||||
var results = await _userUploads.ListFiles(id.FromBase58Guid(), request);
|
||||
var files = await results.Data.ToListAsync();
|
||||
var fileInfo = await _fileInfoManager.Get(files.ToArray(), false);
|
||||
return Json(new RenderedResults<VoidFileResponse>()
|
||||
{
|
||||
PageSize = results.PageSize,
|
||||
Page = results.Page,
|
||||
TotalResults = results.TotalResults,
|
||||
Results = fileInfo.ToList()
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a verification code for a specific user
|
||||
/// </summary>
|
||||
/// <param name="id">User id to send code for</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
[Route("verify")]
|
||||
public async Task<IActionResult> SendVerificationCode([FromRoute] string id)
|
||||
{
|
||||
var user = await GetAuthorizedUser(id);
|
||||
if (user == default) return Unauthorized();
|
||||
|
||||
var isEmailVerified = (user?.Flags.HasFlag(UserFlags.EmailVerified) ?? false);
|
||||
if (isEmailVerified) return UnprocessableEntity();
|
||||
|
||||
await _emailVerification.SendNewCode(user!);
|
||||
return Accepted();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Confirm email verification code
|
||||
/// </summary>
|
||||
/// <param name="id">User id to verify</param>
|
||||
/// <param name="req">Verification code to check</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Route("verify")]
|
||||
public async Task<IActionResult> VerifyCode([FromRoute] string id, [FromBody] VerifyCodeRequest req)
|
||||
{
|
||||
var user = await GetAuthorizedUser(id);
|
||||
if (user == default) return Unauthorized();
|
||||
|
||||
if (!await _emailVerification.VerifyCode(user, req.Code)) return BadRequest();
|
||||
|
||||
user.Flags |= UserFlags.EmailVerified;
|
||||
await _store.UpdateProfile(user);
|
||||
return Accepted();
|
||||
}
|
||||
|
||||
private async Task<User?> GetAuthorizedUser(string id)
|
||||
{
|
||||
var loggedUser = HttpContext.GetUserId();
|
||||
var gid = id.FromBase58Guid();
|
||||
var user = await _store.Get(gid);
|
||||
return user?.Id != loggedUser ? default : user;
|
||||
}
|
||||
|
||||
private async Task<User?> GetRequestedUser(string id)
|
||||
{
|
||||
var gid = id.FromBase58Guid();
|
||||
return await _store.Get(gid);
|
||||
}
|
||||
|
||||
public class VerifyCodeRequest
|
||||
{
|
||||
[JsonProperty("code")]
|
||||
[JsonConverter(typeof(Base58GuidConverter))]
|
||||
public Guid Code { get; init; }
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
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; }
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
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.Property(a => a.OriginalDigest);
|
||||
|
||||
builder.Property(a => a.MediaDimensions);
|
||||
|
||||
builder.HasIndex(a => a.Uploaded);
|
||||
|
||||
builder.HasIndex(a => a.Digest);
|
||||
builder.HasIndex(a => a.OriginalDigest);
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
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>(a => a.FileId);
|
||||
|
||||
builder.Property(a => a.Upstream);
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
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; }
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
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 string? OriginalDigest { get; init; }
|
||||
public string? MediaDimensions { get; init; }
|
||||
|
||||
public Paywall? Paywall { get; init; }
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
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 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 bool Required { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Upstream identifier, handle or lnurl
|
||||
/// </summary>
|
||||
public string? Upstream { get; init; }
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
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; }
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
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,
|
||||
|
||||
/// <summary>
|
||||
/// Nostr login
|
||||
/// </summary>
|
||||
Nostr = 4,
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
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; }
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
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; }
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
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
353
VoidCat/Migrations/20230503115108_Init.Designer.cs
generated
@ -1,353 +0,0 @@
|
||||
// <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
|
||||
}
|
||||
}
|
||||
}
|
@ -1,223 +0,0 @@
|
||||
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
497
VoidCat/Migrations/20230503120701_Paywall.Designer.cs
generated
@ -1,497 +0,0 @@
|
||||
// <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
|
||||
}
|
||||
}
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,501 +0,0 @@
|
||||
// <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
|
||||
}
|
||||
}
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,474 +0,0 @@
|
||||
// <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("20230529211008_SimplifyPaymentOrder")]
|
||||
partial class SimplifyPaymentOrder
|
||||
{
|
||||
/// <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.Property<string>("Upstream")
|
||||
.HasColumnType("text");
|
||||
|
||||
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.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.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.PaywallOrder", b =>
|
||||
{
|
||||
b.Navigation("OrderLightning");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("VoidCat.Database.User", b =>
|
||||
{
|
||||
b.Navigation("Roles");
|
||||
|
||||
b.Navigation("UserFiles");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace VoidCat.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class SimplifyPaymentOrder : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "PaymentStrike");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Upstream",
|
||||
table: "Payment",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Upstream",
|
||||
table: "Payment");
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
481
VoidCat/Migrations/20230529211453_AddFileId.Designer.cs
generated
481
VoidCat/Migrations/20230529211453_AddFileId.Designer.cs
generated
@ -1,481 +0,0 @@
|
||||
// <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("20230529211453_AddFileId")]
|
||||
partial class AddFileId
|
||||
{
|
||||
/// <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")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Amount")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<byte>("Currency")
|
||||
.HasColumnType("smallint");
|
||||
|
||||
b.Property<Guid>("FileId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("Required")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("Service")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Upstream")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FileId")
|
||||
.IsUnique();
|
||||
|
||||
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.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", "FileId")
|
||||
.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.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.PaywallOrder", b =>
|
||||
{
|
||||
b.Navigation("OrderLightning");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("VoidCat.Database.User", b =>
|
||||
{
|
||||
b.Navigation("Roles");
|
||||
|
||||
b.Navigation("UserFiles");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace VoidCat.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddFileId : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Payment_Files_Id",
|
||||
table: "Payment");
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "FileId",
|
||||
table: "Payment",
|
||||
type: "uuid",
|
||||
nullable: false,
|
||||
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"));
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Payment_FileId",
|
||||
table: "Payment",
|
||||
column: "FileId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Payment_Files_FileId",
|
||||
table: "Payment",
|
||||
column: "FileId",
|
||||
principalTable: "Files",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Payment_Files_FileId",
|
||||
table: "Payment");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Payment_FileId",
|
||||
table: "Payment");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FileId",
|
||||
table: "Payment");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Payment_Files_Id",
|
||||
table: "Payment",
|
||||
column: "Id",
|
||||
principalTable: "Files",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
491
VoidCat/Migrations/20231120132852_Nip96.Designer.cs
generated
491
VoidCat/Migrations/20231120132852_Nip96.Designer.cs
generated
@ -1,491 +0,0 @@
|
||||
// <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("20231120132852_Nip96")]
|
||||
partial class Nip96
|
||||
{
|
||||
/// <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>("MediaDimensions")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MimeType")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text")
|
||||
.HasDefaultValue("application/octet-stream");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("OriginalDigest")
|
||||
.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("Digest");
|
||||
|
||||
b.HasIndex("OriginalDigest");
|
||||
|
||||
b.HasIndex("Uploaded");
|
||||
|
||||
b.ToTable("Files", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("VoidCat.Database.Paywall", 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<bool>("Required")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("Service")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Upstream")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FileId")
|
||||
.IsUnique();
|
||||
|
||||
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.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", "FileId")
|
||||
.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.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.PaywallOrder", b =>
|
||||
{
|
||||
b.Navigation("OrderLightning");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("VoidCat.Database.User", b =>
|
||||
{
|
||||
b.Navigation("Roles");
|
||||
|
||||
b.Navigation("UserFiles");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace VoidCat.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Nip96 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "MediaDimensions",
|
||||
table: "Files",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "OriginalDigest",
|
||||
table: "Files",
|
||||
type: "text",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Files_Digest",
|
||||
table: "Files",
|
||||
column: "Digest");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Files_OriginalDigest",
|
||||
table: "Files",
|
||||
column: "OriginalDigest");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Files_Digest",
|
||||
table: "Files");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Files_OriginalDigest",
|
||||
table: "Files");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MediaDimensions",
|
||||
table: "Files");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "OriginalDigest",
|
||||
table: "Files");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,488 +0,0 @@
|
||||
// <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>("MediaDimensions")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MimeType")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text")
|
||||
.HasDefaultValue("application/octet-stream");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("OriginalDigest")
|
||||
.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("Digest");
|
||||
|
||||
b.HasIndex("OriginalDigest");
|
||||
|
||||
b.HasIndex("Uploaded");
|
||||
|
||||
b.ToTable("Files", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("VoidCat.Database.Paywall", 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<bool>("Required")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("Service")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Upstream")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FileId")
|
||||
.IsUnique();
|
||||
|
||||
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.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", "FileId")
|
||||
.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.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.PaywallOrder", b =>
|
||||
{
|
||||
b.Navigation("OrderLightning");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("VoidCat.Database.User", b =>
|
||||
{
|
||||
b.Navigation("Roles");
|
||||
|
||||
b.Navigation("UserFiles");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
namespace VoidCat.Model;
|
||||
|
||||
public class AdminApiUser : ApiUser
|
||||
{
|
||||
public string Storage { get; init; } = null!;
|
||||
public string Email { get; init; } = null!;
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
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,16 +0,0 @@
|
||||
namespace VoidCat.Model;
|
||||
|
||||
/// <summary>
|
||||
/// I/O bandwidth model
|
||||
/// </summary>
|
||||
/// <param name="Ingress"></param>
|
||||
/// <param name="Egress"></param>
|
||||
public sealed record Bandwidth(ulong Ingress, ulong Egress);
|
||||
|
||||
/// <summary>
|
||||
/// I/O bandwidth model at a specific time
|
||||
/// </summary>
|
||||
/// <param name="Time"></param>
|
||||
/// <param name="Ingress"></param>
|
||||
/// <param name="Egress"></param>
|
||||
public sealed record BandwidthPoint(DateTime Time, ulong Ingress, ulong Egress);
|
@ -1,27 +0,0 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace VoidCat.Model;
|
||||
|
||||
public class Base58GuidConverter : JsonConverter<Guid>
|
||||
{
|
||||
public override void WriteJson(JsonWriter writer, Guid value, JsonSerializer serializer)
|
||||
{
|
||||
writer.WriteValue(value.ToBase58());
|
||||
}
|
||||
|
||||
public override Guid ReadJson(JsonReader reader, Type objectType, Guid existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType == JsonToken.String && existingValue == Guid.Empty)
|
||||
{
|
||||
var str = reader.Value as string;
|
||||
if ((str?.Contains('-') ?? false) && Guid.TryParse(str, out var g))
|
||||
{
|
||||
return g;
|
||||
}
|
||||
return str?.FromBase58Guid() ?? existingValue;
|
||||
}
|
||||
|
||||
return existingValue;
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace VoidCat.Model;
|
||||
|
||||
public class BuildInfo
|
||||
{
|
||||
public string? Version { get; init; }
|
||||
public string? GitHash { get; init; }
|
||||
public DateTime BuildTime { get; init; }
|
||||
|
||||
public static BuildInfo GetBuildInfo()
|
||||
{
|
||||
var asm = Assembly.GetEntryAssembly();
|
||||
var version = asm.GetName().Version;
|
||||
|
||||
var gitHash = asm
|
||||
.GetCustomAttributes<AssemblyMetadataAttribute>()
|
||||
.FirstOrDefault(attr => attr.Key == "GitHash")?.Value;
|
||||
|
||||
var buildTime = asm
|
||||
.GetCustomAttributes<AssemblyMetadataAttribute>()
|
||||
.FirstOrDefault(attr => attr.Key == "BuildTime");
|
||||
|
||||
return new()
|
||||
{
|
||||
Version = $"{version.Major}.{version.Minor}.{version.Build}",
|
||||
GitHash = gitHash,
|
||||
BuildTime = DateTime.FromBinary(long.Parse(buildTime?.Value ?? "0"))
|
||||
};
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
namespace VoidCat.Model;
|
||||
|
||||
public sealed record EgressRequest(Guid Id, IEnumerable<RangeRequest> Ranges);
|
||||
|
||||
public sealed record EgressResult(Uri? Redirect = null);
|
@ -1,13 +0,0 @@
|
||||
namespace VoidCat.Model.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// Specified file was not found
|
||||
/// </summary>
|
||||
public class VoidFileNotFoundException : Exception
|
||||
{
|
||||
public VoidFileNotFoundException(Guid id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
public Guid Id { get; }
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
namespace VoidCat.Model.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// Specified id was not in the correct format
|
||||
/// </summary>
|
||||
public class VoidInvalidIdException : Exception
|
||||
{
|
||||
public VoidInvalidIdException(string id)
|
||||
{
|
||||
Id = id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The id in question
|
||||
/// </summary>
|
||||
public string Id { get; }
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
namespace VoidCat.Model.Exceptions;
|
||||
|
||||
/// <summary>
|
||||
/// Operation is not allowed
|
||||
/// </summary>
|
||||
public class VoidNotAllowedException : Exception
|
||||
{
|
||||
public VoidNotAllowedException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
}
|
@ -1,407 +0,0 @@
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Amazon;
|
||||
using Amazon.Runtime;
|
||||
using Amazon.S3;
|
||||
using BencodeNET.Objects;
|
||||
using BencodeNET.Torrents;
|
||||
using VoidCat.Database;
|
||||
using VoidCat.Model.Exceptions;
|
||||
using File = VoidCat.Database.File;
|
||||
|
||||
namespace VoidCat.Model;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public static AmazonS3Client CreateClient(this S3BlobConfig c)
|
||||
{
|
||||
AWSConfigsS3.UseSignatureVersion4 = true;
|
||||
return new AmazonS3Client(new BasicAWSCredentials(c.AccessKey, c.SecretKey),
|
||||
new AmazonS3Config
|
||||
{
|
||||
RegionEndpoint = !string.IsNullOrEmpty(c.Region) ? RegionEndpoint.GetBySystemName(c.Region) : null,
|
||||
ServiceURL = c.ServiceUrl?.ToString(),
|
||||
UseHttp = c.ServiceUrl?.Scheme == "http",
|
||||
ForcePathStyle = true
|
||||
});
|
||||
}
|
||||
|
||||
public static Guid? GetUserId(this HttpContext context)
|
||||
{
|
||||
var claimSub = context.User.Claims.FirstOrDefault(a => a.Type == ClaimTypes.NameIdentifier)?.Value;
|
||||
return Guid.TryParse(claimSub, out var g) ? g : null;
|
||||
}
|
||||
|
||||
public static string? GetPubKey(this HttpContext context)
|
||||
{
|
||||
var claim = context.User.Claims.FirstOrDefault(a => a.Type == ClaimTypes.Name);
|
||||
return claim?.Value;
|
||||
}
|
||||
|
||||
public static IEnumerable<string>? GetUserRoles(this HttpContext context)
|
||||
{
|
||||
return context?.User?.Claims?.Where(a => a.Type == ClaimTypes.Role)
|
||||
?.Select(a => a?.Value!);
|
||||
}
|
||||
|
||||
public static bool IsRole(this HttpContext context, string role)
|
||||
{
|
||||
return GetUserRoles(context)?.Contains(role) ?? false;
|
||||
}
|
||||
|
||||
public static Guid FromBase58Guid(this string base58)
|
||||
{
|
||||
var enc = new NBitcoin.DataEncoders.Base58Encoder();
|
||||
base58 = Path.GetFileNameWithoutExtension(base58);
|
||||
var guidBytes = enc.DecodeData(base58);
|
||||
if (guidBytes.Length != 16) throw new VoidInvalidIdException(base58);
|
||||
|
||||
return new Guid(guidBytes);
|
||||
}
|
||||
|
||||
public static bool TryFromBase58Guid(this string base58, out Guid v)
|
||||
{
|
||||
try
|
||||
{
|
||||
v = base58.FromBase58Guid();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
v = Guid.Empty;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string ToBase58(this Guid id)
|
||||
{
|
||||
var enc = new NBitcoin.DataEncoders.Base58Encoder();
|
||||
return enc.EncodeData(id.ToByteArray());
|
||||
}
|
||||
|
||||
public static string? GetHeader(this IHeaderDictionary headers, string key)
|
||||
{
|
||||
var h = headers
|
||||
.FirstOrDefault(a => a.Key.Equals(key, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
return !string.IsNullOrEmpty(h.Value.ToString()) ? h.Value.ToString() : default;
|
||||
}
|
||||
|
||||
public static bool CanEdit(this File file, Guid? editSecret)
|
||||
{
|
||||
return file.EditSecret == editSecret;
|
||||
}
|
||||
|
||||
public static string ToHex(this byte[] data)
|
||||
{
|
||||
return BitConverter.ToString(data).Replace("-", string.Empty).ToLower();
|
||||
}
|
||||
|
||||
private static int HexToInt(char c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '0':
|
||||
return 0;
|
||||
case '1':
|
||||
return 1;
|
||||
case '2':
|
||||
return 2;
|
||||
case '3':
|
||||
return 3;
|
||||
case '4':
|
||||
return 4;
|
||||
case '5':
|
||||
return 5;
|
||||
case '6':
|
||||
return 6;
|
||||
case '7':
|
||||
return 7;
|
||||
case '8':
|
||||
return 8;
|
||||
case '9':
|
||||
return 9;
|
||||
case 'a':
|
||||
case 'A':
|
||||
return 10;
|
||||
case 'b':
|
||||
case 'B':
|
||||
return 11;
|
||||
case 'c':
|
||||
case 'C':
|
||||
return 12;
|
||||
case 'd':
|
||||
case 'D':
|
||||
return 13;
|
||||
case 'e':
|
||||
case 'E':
|
||||
return 14;
|
||||
case 'f':
|
||||
case 'F':
|
||||
return 15;
|
||||
default:
|
||||
throw new FormatException("Unrecognized hex char " + c);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly byte[,] ByteLookup = new byte[,]
|
||||
{
|
||||
// low nibble
|
||||
{
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
|
||||
},
|
||||
// high nibble
|
||||
{
|
||||
0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70,
|
||||
0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0
|
||||
}
|
||||
};
|
||||
|
||||
public static byte[] FromHex(this string input)
|
||||
{
|
||||
var result = new byte[(input.Length + 1) >> 1];
|
||||
var lastCell = result.Length - 1;
|
||||
var lastChar = input.Length - 1;
|
||||
for (var i = 0; i < input.Length; i++)
|
||||
{
|
||||
result[lastCell - (i >> 1)] |= ByteLookup[i & 1, HexToInt(input[lastChar - i])];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string HashPassword(this string password)
|
||||
{
|
||||
return password.Hash("pbkdf2");
|
||||
}
|
||||
|
||||
public static string Hash(this string password, string algo, string? saltHex = null)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(password);
|
||||
return Hash(bytes, algo, saltHex);
|
||||
}
|
||||
|
||||
public static string Hash(this byte[] bytes, string algo, string? saltHex = null)
|
||||
{
|
||||
switch (algo)
|
||||
{
|
||||
case "md5":
|
||||
{
|
||||
var hash = MD5.Create().ComputeHash(bytes);
|
||||
return $"md5:{hash.ToHex()}";
|
||||
}
|
||||
case "sha1":
|
||||
{
|
||||
var hash = SHA1.Create().ComputeHash(bytes);
|
||||
return $"sha1:{hash.ToHex()}";
|
||||
}
|
||||
case "sha256":
|
||||
{
|
||||
var hash = SHA256.Create().ComputeHash(bytes);
|
||||
return $"sha256:{hash.ToHex()}";
|
||||
}
|
||||
case "sha512":
|
||||
{
|
||||
var hash = SHA512.Create().ComputeHash(bytes);
|
||||
return $"sha512:{hash.ToHex()}";
|
||||
}
|
||||
case "pbkdf2":
|
||||
{
|
||||
const int saltSize = 32;
|
||||
const int iterations = 310_000;
|
||||
|
||||
var salt = new byte[saltSize];
|
||||
if (saltHex == default)
|
||||
{
|
||||
RandomNumberGenerator.Fill(salt);
|
||||
}
|
||||
else
|
||||
{
|
||||
salt = saltHex.FromHex();
|
||||
}
|
||||
|
||||
var pbkdf2 = new Rfc2898DeriveBytes(bytes, salt, iterations);
|
||||
return $"pbkdf2:{salt.ToHex()}:{pbkdf2.GetBytes(salt.Length).ToHex()}";
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentException("Unknown algo", nameof(algo));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate password matches hashed password
|
||||
/// </summary>
|
||||
/// <param name="vu"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public static bool CheckPassword(this Database.User vu, string password)
|
||||
{
|
||||
if (vu.AuthType != UserAuthType.Internal)
|
||||
throw new InvalidOperationException("User type is not internal, cannot check password!");
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Patch metadata
|
||||
/// </summary>
|
||||
/// <param name="oldMeta"></param>
|
||||
/// <param name="meta"></param>
|
||||
public static void Patch(this File oldMeta, File meta)
|
||||
{
|
||||
oldMeta.Description = meta.Description ?? oldMeta.Description;
|
||||
oldMeta.Name = meta.Name ?? oldMeta.Name;
|
||||
oldMeta.MimeType = meta.MimeType ?? oldMeta.MimeType;
|
||||
oldMeta.Storage = meta.Storage ?? oldMeta.Storage;
|
||||
oldMeta.Expires = meta.Expires;
|
||||
oldMeta.EncryptionParams = meta.EncryptionParams ?? oldMeta.EncryptionParams;
|
||||
}
|
||||
|
||||
public static bool HasPostgres(this VoidSettings settings)
|
||||
=> !string.IsNullOrEmpty(settings.Postgres);
|
||||
|
||||
public static bool HasRedis(this VoidSettings settings)
|
||||
=> !string.IsNullOrEmpty(settings.Redis);
|
||||
|
||||
public static bool HasPrometheus(this VoidSettings settings)
|
||||
=> settings.Prometheus?.Url != null;
|
||||
|
||||
public static bool HasVirusScanner(this VoidSettings settings)
|
||||
=> settings.VirusScanner?.ClamAV != default || settings.VirusScanner?.VirusTotal != default;
|
||||
|
||||
public static bool HasPlausible(this VoidSettings settings)
|
||||
=> settings.PlausibleAnalytics?.Endpoint != null;
|
||||
|
||||
public static bool HasDiscord(this VoidSettings settings)
|
||||
=> settings.Discord != null;
|
||||
|
||||
public static bool HasGoogle(this VoidSettings settings)
|
||||
=> settings.Google != null;
|
||||
|
||||
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 pieceHashLen = 20;
|
||||
var webSeed = new UriBuilder(baseAddress)
|
||||
{
|
||||
Path = $"/d/{id.ToBase58()}"
|
||||
};
|
||||
|
||||
async Task<byte[]> BuildPieces()
|
||||
{
|
||||
fileStream.Seek(0, SeekOrigin.Begin);
|
||||
var chunks = (int)Math.Ceiling(meta.Size / (decimal)pieceSize);
|
||||
var hashes = new byte[pieceHashLen * chunks];
|
||||
var chunk = new byte[pieceSize];
|
||||
for (var x = 0; x < chunks; x++)
|
||||
{
|
||||
var rLen = await fileStream.ReadAsync(chunk, 0, chunk.Length);
|
||||
var hash = SHA1.HashData(chunk.AsSpan(0, rLen));
|
||||
Buffer.BlockCopy(hash, 0, hashes, x * pieceHashLen, pieceHashLen);
|
||||
}
|
||||
|
||||
return hashes;
|
||||
}
|
||||
|
||||
// build magnet link
|
||||
var t = new Torrent()
|
||||
{
|
||||
File = new()
|
||||
{
|
||||
FileName = meta.Name,
|
||||
FileSize = (long)meta.Size
|
||||
},
|
||||
Comment = meta.Name,
|
||||
CreationDate = meta.Uploaded,
|
||||
IsPrivate = false,
|
||||
PieceSize = pieceSize,
|
||||
Pieces = await BuildPieces(),
|
||||
// ReSharper disable once CoVariantArrayConversion
|
||||
Trackers = trackers.Select(a => new[] {a}).ToArray(),
|
||||
ExtraFields = new BDictionary
|
||||
{
|
||||
{"url-list", webSeed.ToString()}
|
||||
}
|
||||
};
|
||||
|
||||
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,
|
||||
MediaDimensions = f.MediaDimensions
|
||||
};
|
||||
}
|
||||
|
||||
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,11 +0,0 @@
|
||||
namespace VoidCat.Model;
|
||||
|
||||
public sealed record IngressPayload(Stream InStream, Database.File Meta, int Segment, int TotalSegments, bool ShouldStripMetadata)
|
||||
{
|
||||
public Guid Id { get; init; } = Guid.NewGuid();
|
||||
public Guid? EditSecret { get; init; }
|
||||
|
||||
public bool IsAppend => Segment > 1 && IsMultipart;
|
||||
|
||||
public bool IsMultipart => TotalSegments > 1;
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
namespace VoidCat.Model;
|
||||
|
||||
public abstract class PagedResult
|
||||
{
|
||||
public int Page { get; init; }
|
||||
public int PageSize { get; init; }
|
||||
public int Pages => TotalResults / PageSize;
|
||||
public int TotalResults { get; init; }
|
||||
|
||||
public int Results { get; init; }
|
||||
}
|
||||
|
||||
public sealed class PagedResult<T> : PagedResult
|
||||
{
|
||||
public IAsyncEnumerable<T> Data { get; init; } = null!;
|
||||
|
||||
public async Task<RenderedResults<T>> GetResults()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Page = Page,
|
||||
PageSize = PageSize,
|
||||
TotalResults = TotalResults,
|
||||
Results = await Data.ToListAsync()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class RenderedResults<T> : PagedResult
|
||||
{
|
||||
public IList<T> Results { get; init; }
|
||||
}
|
||||
|
||||
public sealed record PagedRequest(int Page, int PageSize, PagedSortBy SortBy = PagedSortBy.Name, PageSortOrder SortOrder = PageSortOrder.Asc);
|
||||
|
||||
public enum PagedSortBy : byte
|
||||
{
|
||||
Name,
|
||||
Date,
|
||||
Size,
|
||||
Id
|
||||
}
|
||||
|
||||
public enum PageSortOrder : byte
|
||||
{
|
||||
Asc,
|
||||
Dsc
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
namespace VoidCat.Model;
|
||||
|
||||
public sealed record RangeRequest(long? TotalSize, long? Start, long? End)
|
||||
{
|
||||
private const long DefaultBufferSize = 1024L * 512L;
|
||||
|
||||
public string OriginalString { get; private init; }
|
||||
|
||||
public long? Size
|
||||
=> (Start.HasValue ? (End ?? Math.Min(TotalSize!.Value - 1, Start.Value + DefaultBufferSize)) - Start.Value : End) + 1L;
|
||||
|
||||
/// <summary>
|
||||
/// Return Content-Range header content for this range
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string ToContentRange()
|
||||
=> $"bytes {Start}-{End ?? (Start + Size - 1L)}/{TotalSize?.ToString() ?? "*"}";
|
||||
|
||||
public static IEnumerable<RangeRequest> Parse(string header, long totalSize)
|
||||
{
|
||||
var ranges = header.Replace("bytes=", string.Empty).Split(",");
|
||||
foreach (var range in ranges)
|
||||
{
|
||||
var rangeValues = range.Split("-");
|
||||
|
||||
long? endByte = null, startByte = 0;
|
||||
if (long.TryParse(rangeValues[1], out var endParsed))
|
||||
endByte = endParsed;
|
||||
|
||||
if (long.TryParse(rangeValues[0], out var startParsed))
|
||||
startByte = startParsed;
|
||||
|
||||
yield return new(totalSize, startByte, endByte)
|
||||
{
|
||||
OriginalString = range
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
namespace VoidCat.Model;
|
||||
|
||||
public static class Roles
|
||||
{
|
||||
public const string User = "User";
|
||||
public const string Admin = "Admin";
|
||||
}
|
||||
|
||||
public static class Policies
|
||||
{
|
||||
public const string RequireAdmin = "RequireAdmin";
|
||||
public const string RequireNostr = "RequireNostr";
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
namespace VoidCat.Model;
|
||||
|
||||
/// <summary>
|
||||
/// Results for virus scan of a single file
|
||||
/// </summary>
|
||||
public sealed class VirusStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Time the file was scanned
|
||||
/// </summary>
|
||||
public DateTime ScanTime { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Detected virus names
|
||||
/// </summary>
|
||||
public string? Names { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// If we consider this result as a virus or not
|
||||
/// </summary>
|
||||
public bool IsVirus { get; init; }
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using VoidCat.Database;
|
||||
|
||||
namespace VoidCat.Model;
|
||||
|
||||
/// <summary>
|
||||
/// Primary response type for file information
|
||||
/// </summary>
|
||||
public class VoidFileResponse
|
||||
{
|
||||
[JsonConverter(typeof(Base58GuidConverter))]
|
||||
public Guid Id { get; init; }
|
||||
public VoidFileMeta Metadata { get; init; } = null!;
|
||||
public Paywall? Payment { get; init; }
|
||||
public ApiUser? Uploader { get; set; }
|
||||
public Bandwidth? Bandwidth { get; init; }
|
||||
public VirusStatus? VirusScan { get; init; }
|
||||
public bool IsNostr { get; init; }
|
||||
}
|
||||
|
||||
public class VoidFileMeta
|
||||
{
|
||||
public string? Name { get; init; }
|
||||
public ulong Size { get; init; }
|
||||
public DateTime Uploaded { get; init; }
|
||||
public string? Description { get; init; }
|
||||
public string MimeType { get; init; }
|
||||
public string? Digest { get; init; }
|
||||
|
||||
[JsonConverter(typeof(Base58GuidConverter))]
|
||||
public Guid? EditSecret { get; init; }
|
||||
public DateTime? Expires { get; init; }
|
||||
public string Storage { get; init; } = "local-disk";
|
||||
public string? EncryptionParams { get; init; }
|
||||
public string? MagnetLink { get; init; }
|
||||
public string? MediaDimensions { get; init; }
|
||||
}
|
@ -1,233 +0,0 @@
|
||||
using VoidCat.Services.Strike;
|
||||
|
||||
namespace VoidCat.Model
|
||||
{
|
||||
/// <summary>
|
||||
/// System settings
|
||||
/// </summary>
|
||||
public class VoidSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Maintenance flag
|
||||
/// </summary>
|
||||
public bool MaintenanceMode { get; init; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Base site url, used for redirect urls
|
||||
/// </summary>
|
||||
public Uri SiteUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Data directory to store files in
|
||||
/// </summary>
|
||||
public string DataDirectory { get; init; } = "./data";
|
||||
|
||||
/// <summary>
|
||||
/// Size in bytes to split uploads into chunks
|
||||
/// </summary>
|
||||
public ulong? UploadSegmentSize { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Max file size for upload
|
||||
/// </summary>
|
||||
public ulong? MaxFileSize { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Tor configuration
|
||||
/// </summary>
|
||||
public TorSettings? TorSettings { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// JWT settings for login token signing
|
||||
/// </summary>
|
||||
public JwtSettings JwtSettings { get; init; } = new()
|
||||
{
|
||||
Issuer = "void_cat_internal",
|
||||
Key = "default_key_void_cat_host"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Redis database connection string
|
||||
/// </summary>
|
||||
public string? Redis { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Strike payment service api settings
|
||||
/// </summary>
|
||||
public StrikeApiSettings? Strike { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Email server settings
|
||||
/// </summary>
|
||||
public SmtpSettings? Smtp { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// CORS origins
|
||||
/// </summary>
|
||||
public List<string> CorsOrigins { get; init; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Cloud file storage settings
|
||||
/// </summary>
|
||||
public CloudStorageSettings? CloudStorage { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Virus scanner settings
|
||||
/// </summary>
|
||||
public VirusScannerSettings? VirusScanner { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Request header to unmask in the logs, otherwise all are masked
|
||||
/// </summary>
|
||||
public IEnumerable<string>? RequestHeadersLog { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// hCaptcha settings
|
||||
/// </summary>
|
||||
public CaptchaSettings? CaptchaSettings { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Postgres database connection string
|
||||
/// </summary>
|
||||
public string? Postgres { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Prometheus server for querying metrics
|
||||
/// </summary>
|
||||
public PrometheusSettings? Prometheus { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Select where to store metadata, if not set "local-disk" will be used
|
||||
/// </summary>
|
||||
public string MetadataStore { get; init; } = "local-disk";
|
||||
|
||||
/// <summary>
|
||||
/// Select which store to use for files storage, if not set "local-disk" will be used
|
||||
/// </summary>
|
||||
public string DefaultFileStore { get; init; } = "local-disk";
|
||||
|
||||
/// <summary>
|
||||
/// Plausible Analytics endpoint url
|
||||
/// </summary>
|
||||
public PlausibleSettings? PlausibleAnalytics { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Discord application settings
|
||||
/// </summary>
|
||||
public OAuthDetails? Discord { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Google application settings
|
||||
/// </summary>
|
||||
public OAuthDetails? Google { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of trackers to attach to torrent files
|
||||
/// </summary>
|
||||
public List<string> TorrentTrackers { get; init; } = new()
|
||||
{
|
||||
"wss://tracker.btorrent.xyz",
|
||||
"wss://tracker.openwebtorrent.com",
|
||||
"udp://tracker.opentrackr.org:1337/announce",
|
||||
"udp://tracker.openbittorrent.com:6969/announce",
|
||||
"http://tracker.openbittorrent.com:80/announce"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Lightning node configuration for LNProxy services
|
||||
/// </summary>
|
||||
public LndConfig? LndConfig { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Blocked origin hostnames
|
||||
/// </summary>
|
||||
public List<string> BlockedOrigins { get; init; } = new();
|
||||
}
|
||||
|
||||
public sealed class TorSettings
|
||||
{
|
||||
public Uri TorControl { get; init; }
|
||||
public string PrivateKey { get; init; }
|
||||
public string ControlPassword { get; init; }
|
||||
}
|
||||
|
||||
public sealed class JwtSettings
|
||||
{
|
||||
public string Issuer { get; init; }
|
||||
public string Key { get; init; }
|
||||
}
|
||||
|
||||
public sealed class SmtpSettings
|
||||
{
|
||||
public Uri? Server { get; init; }
|
||||
public string? Username { get; init; }
|
||||
public string? Password { get; init; }
|
||||
}
|
||||
|
||||
public sealed class CloudStorageSettings
|
||||
{
|
||||
public S3BlobConfig[]? S3 { get; init; }
|
||||
}
|
||||
|
||||
public sealed class S3BlobConfig
|
||||
{
|
||||
public string Name { get; init; } = null!;
|
||||
public string? AccessKey { get; init; }
|
||||
public string? SecretKey { get; init; }
|
||||
public Uri? ServiceUrl { get; init; }
|
||||
public string? Region { get; init; }
|
||||
public string? BucketName { get; init; } = "void-cat";
|
||||
public bool Direct { get; init; }
|
||||
public bool SendChecksum { get; init; } = true;
|
||||
public bool DisablePayloadSigning { get; init; }
|
||||
}
|
||||
|
||||
public sealed class VirusScannerSettings
|
||||
{
|
||||
public ClamAVSettings? ClamAV { get; init; }
|
||||
public VirusTotalConfig? VirusTotal { get; init; }
|
||||
}
|
||||
|
||||
public sealed class ClamAVSettings
|
||||
{
|
||||
public Uri? Endpoint { get; init; }
|
||||
public long? MaxStreamSize { get; init; }
|
||||
}
|
||||
|
||||
public sealed class VirusTotalConfig
|
||||
{
|
||||
public string? ApiKey { get; init; }
|
||||
}
|
||||
|
||||
public sealed class CaptchaSettings
|
||||
{
|
||||
public string? SiteKey { get; init; }
|
||||
public string? Secret { get; init; }
|
||||
}
|
||||
|
||||
public sealed class PrometheusSettings
|
||||
{
|
||||
public Uri? Url { get; init; }
|
||||
public string? EgressQuery { get; init; }
|
||||
}
|
||||
|
||||
public sealed class PlausibleSettings
|
||||
{
|
||||
public Uri? Endpoint { get; init; }
|
||||
public string? Domain { get; init; }
|
||||
}
|
||||
|
||||
public sealed class OAuthDetails
|
||||
{
|
||||
public string? ClientId { 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,38 +0,0 @@
|
||||
@using VoidCat.Model
|
||||
@using VoidCat.Services.Users
|
||||
@model VoidCat.Database.EmailVerification
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>void.cat - Email Verification Code</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: black;
|
||||
color: white;
|
||||
font-family: 'Source Code Pro', monospace;
|
||||
}
|
||||
.page {
|
||||
width: 720px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
pre {
|
||||
padding: 10px;
|
||||
font-size: 24px;
|
||||
background-color: #eee;
|
||||
width: fit-content;
|
||||
color: black;
|
||||
user-select: all;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<h1>void.cat</h1>
|
||||
<p>Your verification code is below please copy this to complete verification</p>
|
||||
<pre>@(Model.Code.ToBase58())</pre>
|
||||
<p>This code will expire in @BaseEmailVerification.HoursExpire hours</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,184 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json;
|
||||
using Prometheus;
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services;
|
||||
using VoidCat.Services.Analytics;
|
||||
using VoidCat.Services.Migrations;
|
||||
|
||||
namespace VoidCat;
|
||||
|
||||
static class Program
|
||||
{
|
||||
[Flags]
|
||||
enum RunModes
|
||||
{
|
||||
Webserver = 1,
|
||||
BackgroundJobs = 2,
|
||||
Migrations = 4,
|
||||
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"))
|
||||
{
|
||||
mode |= RunModes.Webserver;
|
||||
}
|
||||
|
||||
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);
|
||||
var res = await migration.Migrate(args);
|
||||
logger.LogInformation("== Result: {Result}", res.ToString());
|
||||
if (res == IMigration.MigrationResult.ExitCompleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mode.HasFlag(RunModes.Webserver))
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
var services = builder.Services;
|
||||
|
||||
var configuration = builder.Configuration;
|
||||
var voidSettings = configuration.GetSection("Settings").Get<VoidSettings>();
|
||||
services.AddSingleton(voidSettings);
|
||||
services.AddSingleton(voidSettings.Strike ?? new());
|
||||
|
||||
var seqSettings = configuration.GetSection("Seq");
|
||||
builder.Logging.AddSeq(seqSettings);
|
||||
|
||||
ConfigureDb(services, voidSettings);
|
||||
services.AddBaseServices(voidSettings);
|
||||
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);
|
||||
}
|
||||
|
||||
app.UseStaticFiles();
|
||||
app.UseHttpLogging();
|
||||
app.UseRouting();
|
||||
app.UseHttpMetrics();
|
||||
app.UseCors();
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseHealthChecks("/healthz");
|
||||
|
||||
app.UseMiddleware<AnalyticsMiddleware>();
|
||||
app.UseEndpoints(ep =>
|
||||
{
|
||||
ep.MapControllers();
|
||||
ep.MapMetrics();
|
||||
ep.MapRazorPages();
|
||||
ep.MapFallbackToFile("index.html");
|
||||
});
|
||||
|
||||
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());
|
||||
|
||||
ConfigureDb(services, voidSettings);
|
||||
services.AddBaseServices(voidSettings);
|
||||
services.AddDatabaseServices(voidSettings);
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ConfigureDb(IServiceCollection services, VoidSettings settings)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
{
|
||||
"profiles": {
|
||||
"VoidCat": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": false,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "http://localhost:7195",
|
||||
"launchUrl": "http://localhost:3000",
|
||||
"dotnetRunMessages": true
|
||||
},
|
||||
"Docker": {
|
||||
"commandName": "Docker",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
|
||||
"publishAllPorts": true,
|
||||
"useSSL": true
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
public interface IAggregateStatsCollector : IStatsCollector
|
||||
{
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
using VoidCat.Database;
|
||||
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Api key store
|
||||
/// </summary>
|
||||
public interface IApiKeyStore : IBasicStore<ApiKey>
|
||||
{
|
||||
/// <summary>
|
||||
/// Return a list of Api keys for a given user
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<IReadOnlyList<ApiKey>> ListKeys(Guid id);
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Simple CRUD interface for data stores
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public interface IBasicStore<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Get a single item from the store
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<T?> Get(Guid id);
|
||||
|
||||
/// <summary>
|
||||
/// Add an item to the store
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="obj"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask Add(Guid id, T obj);
|
||||
|
||||
/// <summary>
|
||||
/// Delete an item from the store
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask Delete(Guid id);
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Basic KV cache interface
|
||||
/// </summary>
|
||||
public interface ICache
|
||||
{
|
||||
/// <summary>
|
||||
/// Get a single object from cache by its key
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
ValueTask<T?> Get<T>(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Set the the value of a key in the cache
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="expire"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
ValueTask Set<T>(string key, T value, TimeSpan? expire = null);
|
||||
|
||||
/// <summary>
|
||||
/// Delete an object from the cache
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask Delete(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Return a list of items at the specified key
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<string[]> GetList(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Add an item to the list at the specified key
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask AddToList(string key, string value);
|
||||
|
||||
/// <summary>
|
||||
/// Remove an item from the list at a the specified key
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask RemoveFromList(string key, string value);
|
||||
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
public interface ICaptchaVerifier
|
||||
{
|
||||
/// <summary>
|
||||
/// Verify captcha token is valid
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<bool> Verify(string? token);
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
using VoidCat.Database;
|
||||
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
public interface IEmailVerification
|
||||
{
|
||||
/// <summary>
|
||||
/// Send email verification code
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<EmailVerification> SendNewCode(User user);
|
||||
|
||||
/// <summary>
|
||||
/// Perform account verification
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="code"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<bool> VerifyCode(User user, Guid code);
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
using VoidCat.Model;
|
||||
using File = VoidCat.Database.File;
|
||||
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// File metadata contains all data about a file except for the file data itself
|
||||
/// </summary>
|
||||
public interface IFileMetadataStore
|
||||
{
|
||||
/// <summary>
|
||||
/// Get metadata for a single file
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<File?> Get(Guid id);
|
||||
|
||||
/// <summary>
|
||||
/// Get metadata for a single file by its hash
|
||||
/// </summary>
|
||||
/// <param name="digest"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<File?> GetHash(string digest);
|
||||
|
||||
/// <summary>
|
||||
/// Get metadata for multiple files
|
||||
/// </summary>
|
||||
/// <param name="ids"></param>
|
||||
/// <returns></returns>
|
||||
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>
|
||||
/// Update file metadata
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="meta"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask Update(Guid id, File meta);
|
||||
|
||||
/// <summary>
|
||||
/// List all files in the store
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<PagedResult<File>> ListFiles(PagedRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Returns basic stats about the file store
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
ValueTask<StoreStats> Stats();
|
||||
|
||||
/// <summary>
|
||||
/// Delete metadata object from the store
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask Delete(Guid id);
|
||||
|
||||
/// <summary>
|
||||
/// Simple stats of the current store
|
||||
/// </summary>
|
||||
/// <param name="Files"></param>
|
||||
/// <param name="Size"></param>
|
||||
public sealed record StoreStats(long Files, ulong Size);
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
using VoidCat.Model;
|
||||
using File = VoidCat.Database.File;
|
||||
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// File binary data store
|
||||
/// </summary>
|
||||
public interface IFileStore
|
||||
{
|
||||
/// <summary>
|
||||
/// Return key for named instance
|
||||
/// </summary>
|
||||
string? Key { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Check if a file exists in the store
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<bool> Exists(Guid id);
|
||||
|
||||
/// <summary>
|
||||
/// Ingress a file into the system (Upload)
|
||||
/// </summary>
|
||||
/// <param name="payload"></param>
|
||||
/// <param name="cts"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<File> Ingress(IngressPayload payload, CancellationToken cts);
|
||||
|
||||
/// <summary>
|
||||
/// Egress a file from the system (Download)
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="outStream"></param>
|
||||
/// <param name="cts"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask Egress(EgressRequest request, Stream outStream, CancellationToken cts);
|
||||
|
||||
/// <summary>
|
||||
/// Pre-Egress checks
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<EgressResult> StartEgress(EgressRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes file data only, metadata must be deleted with <see cref="IFileInfoManager.Delete"/>
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask DeleteFile(Guid id);
|
||||
|
||||
/// <summary>
|
||||
/// Open a filestream for a file on the system
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cts"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<Stream> Open(EgressRequest request, CancellationToken cts);
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
using VoidCat.Database;
|
||||
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// OAuth2 code grant provider
|
||||
/// </summary>
|
||||
public interface IOAuthProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Id of this provider
|
||||
/// </summary>
|
||||
string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Generate authorization code grant uri
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Uri Authorize();
|
||||
|
||||
/// <summary>
|
||||
/// Get access token from auth code
|
||||
/// </summary>
|
||||
/// <param name="code"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<UserAuthToken> GetToken(string code);
|
||||
|
||||
/// <summary>
|
||||
/// Get a user object which represents this external account authorization
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<User?> GetUserDetails(UserAuthToken token);
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
using VoidCat.Database;
|
||||
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Factory class to access service provider implementations
|
||||
/// </summary>
|
||||
public interface IPaymentFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Create provider handler for specified service type
|
||||
/// </summary>
|
||||
/// <param name="svc"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<IPaymentProvider> CreateProvider(PaywallService svc);
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
using VoidCat.Database;
|
||||
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Payment order store
|
||||
/// </summary>
|
||||
public interface IPaymentOrderStore : IBasicStore<PaywallOrder>
|
||||
{
|
||||
/// <summary>
|
||||
/// Update the status of an order
|
||||
/// </summary>
|
||||
/// <param name="order"></param>
|
||||
/// <param name="status"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask UpdateStatus(Guid order, PaywallOrderStatus status);
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
using VoidCat.Database;
|
||||
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Provider to generate orders for a specific config
|
||||
/// </summary>
|
||||
public interface IPaymentProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Create an order with the provider
|
||||
/// </summary>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<PaywallOrder?> CreateOrder(Paywall file);
|
||||
|
||||
/// <summary>
|
||||
/// Get the status of an existing order with the provider
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<PaywallOrder?> GetOrderStatus(Guid id);
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
using VoidCat.Database;
|
||||
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Store for payment configs
|
||||
/// </summary>
|
||||
public interface IPaymentStore : IBasicStore<Paywall>
|
||||
{
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
public interface IStatsCollector
|
||||
{
|
||||
ValueTask TrackIngress(Guid id, ulong amount);
|
||||
ValueTask TrackEgress(Guid id, ulong amount);
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
using VoidCat.Model;
|
||||
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Get metrics from the system
|
||||
/// </summary>
|
||||
public interface IStatsReporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Get global total bandwidth
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
ValueTask<Bandwidth> GetBandwidth();
|
||||
|
||||
/// <summary>
|
||||
/// Get global bandwidth for a single file
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<Bandwidth> GetBandwidth(Guid id);
|
||||
|
||||
/// <summary>
|
||||
/// Delete bandwidth data for a single file
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask Delete(Guid id);
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
using VoidCat.Model;
|
||||
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
public interface ITimeSeriesStatsReporter
|
||||
{
|
||||
ValueTask<IReadOnlyList<BandwidthPoint>> GetBandwidth(DateTime start, DateTime end);
|
||||
ValueTask<IReadOnlyList<BandwidthPoint>> GetBandwidth(Guid id, DateTime start, DateTime end);
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
using VoidCat.Database;
|
||||
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// User access token store
|
||||
/// </summary>
|
||||
public interface IUserAuthTokenStore : IBasicStore<UserAuthToken>
|
||||
{
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
using VoidCat.Database;
|
||||
using VoidCat.Model;
|
||||
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// User store
|
||||
/// </summary>
|
||||
public interface IUserStore
|
||||
{
|
||||
/// <summary>
|
||||
/// Get a single user
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
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>
|
||||
/// Lookup a user by their email address
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<Guid?> LookupUser(string email);
|
||||
|
||||
/// <summary>
|
||||
/// List all users in the system
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<PagedResult<User>> ListUsers(PagedRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Update a users profile
|
||||
/// </summary>
|
||||
/// <param name="newUser"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask UpdateProfile(User newUser);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the last login timestamp for the user
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="timestamp"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask UpdateLastLogin(Guid id, DateTime timestamp);
|
||||
|
||||
/// <summary>
|
||||
/// Update user account for admin
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask AdminUpdateUser(User user);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a user from the system
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask Delete(Guid id);
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
using VoidCat.Model;
|
||||
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Mapping store to associate files to users
|
||||
/// </summary>
|
||||
public interface IUserUploadsStore
|
||||
{
|
||||
/// <summary>
|
||||
/// List all files for the user
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<PagedResult<Guid>> ListFiles(Guid user, PagedRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Assign a file upload to a user
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask AddFile(Guid user, Guid file);
|
||||
|
||||
/// <summary>
|
||||
/// Get the uploader of a single file
|
||||
/// </summary>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<Guid?> Uploader(Guid file);
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
using VoidCat.Database;
|
||||
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// Store for virus scan results
|
||||
/// </summary>
|
||||
public interface IVirusScanStore : IBasicStore<VirusScanResult>
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the latest scan result by file id
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<VirusScanResult?> GetByFile(Guid id);
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
using VoidCat.Database;
|
||||
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
/// <summary>
|
||||
/// File virus scanning interface
|
||||
/// </summary>
|
||||
public interface IVirusScanner
|
||||
{
|
||||
/// <summary>
|
||||
/// Scan a single file
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="cts"></param>
|
||||
/// <returns></returns>
|
||||
ValueTask<VirusScanResult> ScanFile(Guid id, CancellationToken cts);
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
namespace VoidCat.Services.Abstractions;
|
||||
|
||||
public interface IWebAnalyticsCollector
|
||||
{
|
||||
Task TrackPageView(HttpContext context);
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
using VoidCat.Services.Abstractions;
|
||||
|
||||
namespace VoidCat.Services.Analytics;
|
||||
|
||||
public class AnalyticsMiddleware : IMiddleware
|
||||
{
|
||||
private readonly ILogger<AnalyticsMiddleware> _logger;
|
||||
private readonly IEnumerable<IWebAnalyticsCollector> _collectors;
|
||||
|
||||
public AnalyticsMiddleware(IEnumerable<IWebAnalyticsCollector> collectors, ILogger<AnalyticsMiddleware> logger)
|
||||
{
|
||||
_collectors = collectors;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||
{
|
||||
foreach (var collector in _collectors)
|
||||
{
|
||||
try
|
||||
{
|
||||
await collector.TrackPageView(context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to track page view");
|
||||
}
|
||||
}
|
||||
|
||||
await next(context);
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services.Abstractions;
|
||||
|
||||
namespace VoidCat.Services.Analytics;
|
||||
|
||||
public static class AnalyticsStartup
|
||||
{
|
||||
/// <summary>
|
||||
/// Add services needed to collect analytics
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="settings"></param>
|
||||
public static void AddAnalytics(this IServiceCollection services, VoidSettings settings)
|
||||
{
|
||||
services.AddTransient<AnalyticsMiddleware>();
|
||||
if (settings.HasPlausible())
|
||||
{
|
||||
services.AddTransient<IWebAnalyticsCollector, PlausibleAnalytics>();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services.Abstractions;
|
||||
|
||||
namespace VoidCat.Services.Analytics;
|
||||
|
||||
public class PlausibleAnalytics : IWebAnalyticsCollector
|
||||
{
|
||||
private readonly HttpClient _client;
|
||||
private readonly ILogger<PlausibleAnalytics> _logger;
|
||||
private readonly string _domain;
|
||||
private readonly Uri _siteUrl;
|
||||
|
||||
public PlausibleAnalytics(HttpClient client, VoidSettings settings, ILogger<PlausibleAnalytics> logger)
|
||||
{
|
||||
_client = client;
|
||||
_logger = logger;
|
||||
_client.BaseAddress = settings.PlausibleAnalytics!.Endpoint!;
|
||||
_client.Timeout = TimeSpan.FromSeconds(1);
|
||||
_domain = settings.PlausibleAnalytics!.Domain!;
|
||||
_siteUrl = settings.SiteUrl;
|
||||
}
|
||||
|
||||
public async Task TrackPageView(HttpContext context)
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, "/api/event");
|
||||
request.Headers.UserAgent.ParseAdd(context.Request.Headers.UserAgent);
|
||||
if (context.Request.Headers.TryGetValue("x-forwarded-for", out var xff))
|
||||
{
|
||||
foreach (var xf in xff)
|
||||
{
|
||||
request.Headers.Add("x-forwarded-for", xf);
|
||||
}
|
||||
}
|
||||
|
||||
var ub = new UriBuilder(_siteUrl)
|
||||
{
|
||||
Path = context.Request.Path,
|
||||
Query = context.Request.QueryString.ToUriComponent()
|
||||
};
|
||||
|
||||
var ev = new EventObj(_domain, ub.Uri)
|
||||
{
|
||||
Referrer =
|
||||
context.Request.Headers.Referer.Any()
|
||||
? new Uri(context.Request.Headers.Referer.ToString())
|
||||
: null
|
||||
};
|
||||
|
||||
var json = JsonConvert.SerializeObject(ev, new JsonSerializerSettings()
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
});
|
||||
|
||||
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
_logger.LogDebug("Sending pageview {request} {json}", request.ToString(), json);
|
||||
var rsp = await _client.SendAsync(request);
|
||||
if (!rsp.IsSuccessStatusCode)
|
||||
{
|
||||
throw new Exception(
|
||||
$"Invalid plausible analytics response {rsp.StatusCode} {await rsp.Content.ReadAsStringAsync()}");
|
||||
}
|
||||
}
|
||||
|
||||
internal class EventObj
|
||||
{
|
||||
public EventObj(string domain, Uri url)
|
||||
{
|
||||
Domain = domain;
|
||||
Url = url;
|
||||
}
|
||||
|
||||
[JsonProperty("name")]
|
||||
public string Name { get; init; } = "pageview";
|
||||
|
||||
[JsonProperty("domain")]
|
||||
public string Domain { get; init; }
|
||||
|
||||
[JsonProperty("url")]
|
||||
public Uri Url { get; init; }
|
||||
|
||||
[JsonProperty("screen_width")]
|
||||
public int? ScreenWidth { get; init; }
|
||||
|
||||
[JsonProperty("referrer")]
|
||||
public Uri? Referrer { get; init; }
|
||||
|
||||
[JsonProperty("props")]
|
||||
public object? Props { get; init; }
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services.Abstractions;
|
||||
using VoidCat.Services.Files;
|
||||
|
||||
namespace VoidCat.Services.Background;
|
||||
|
||||
/// <summary>
|
||||
/// Delete expired files
|
||||
/// </summary>
|
||||
public sealed class DeleteExpiredFiles : BackgroundService
|
||||
{
|
||||
private readonly ILogger<DeleteExpiredFiles> _logger;
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
|
||||
public DeleteExpiredFiles(ILogger<DeleteExpiredFiles> logger, IServiceScopeFactory scopeFactory)
|
||||
{
|
||||
_logger = logger;
|
||||
_scopeFactory = scopeFactory;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var metadata = scope.ServiceProvider.GetRequiredService<IFileMetadataStore>();
|
||||
var fileInfoManager = scope.ServiceProvider.GetRequiredService<FileInfoManager>();
|
||||
var fileStoreFactory = scope.ServiceProvider.GetRequiredService<FileStoreFactory>();
|
||||
|
||||
var files = await metadata.ListFiles(new(0, int.MaxValue));
|
||||
await foreach (var f in files.Data.WithCancellation(stoppingToken))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (f.Expires < DateTime.Now)
|
||||
{
|
||||
await fileStoreFactory.DeleteFile(f.Id);
|
||||
await fileInfoManager.Delete(f.Id);
|
||||
|
||||
_logger.LogInformation("Deleted file: {Id}", f.Id);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to delete file: {Id}", f.Id);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to run delete expired file services");
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
using VoidCat.Database;
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services.Abstractions;
|
||||
using VoidCat.Services.Files;
|
||||
|
||||
namespace VoidCat.Services.Background;
|
||||
|
||||
public class DeleteUnverifiedAccounts : BackgroundService
|
||||
{
|
||||
private readonly ILogger<DeleteUnverifiedAccounts> _logger;
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
|
||||
public DeleteUnverifiedAccounts(ILogger<DeleteUnverifiedAccounts> logger, IServiceScopeFactory scopeFactory)
|
||||
{
|
||||
_logger = logger;
|
||||
_scopeFactory = scopeFactory;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var userStore = scope.ServiceProvider.GetRequiredService<IUserStore>();
|
||||
var userUploads = scope.ServiceProvider.GetRequiredService<IUserUploadsStore>();
|
||||
var fileStore = scope.ServiceProvider.GetRequiredService<FileStoreFactory>();
|
||||
var fileInfoManager = scope.ServiceProvider.GetRequiredService<FileInfoManager>();
|
||||
|
||||
var accounts = await userStore.ListUsers(new(0, Int32.MaxValue));
|
||||
|
||||
await foreach (var account in accounts.Data.WithCancellation(stoppingToken))
|
||||
{
|
||||
if (!account.Flags.HasFlag(UserFlags.EmailVerified) &&
|
||||
account.Created.AddDays(7) < DateTimeOffset.UtcNow)
|
||||
{
|
||||
_logger.LogInformation("Deleting un-verified account: {Id}", account.Id.ToBase58());
|
||||
await userStore.Delete(account.Id);
|
||||
|
||||
var files = await userUploads.ListFiles(account.Id, new(0, Int32.MinValue));
|
||||
// ReSharper disable once UseCancellationTokenForIAsyncEnumerable
|
||||
await foreach (var file in files.Data)
|
||||
{
|
||||
await fileStore.DeleteFile(file);
|
||||
await fileInfoManager.Delete(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to delete unverified accounts");
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services.Abstractions;
|
||||
using VoidCat.Services.VirusScanner.Exceptions;
|
||||
|
||||
namespace VoidCat.Services.Background;
|
||||
|
||||
public class VirusScannerService : BackgroundService
|
||||
{
|
||||
private readonly ILogger<VirusScannerService> _logger;
|
||||
private readonly IVirusScanner _scanner;
|
||||
private readonly IFileMetadataStore _fileStore;
|
||||
private readonly IVirusScanStore _scanStore;
|
||||
|
||||
public VirusScannerService(ILogger<VirusScannerService> logger, IVirusScanner scanner, IVirusScanStore scanStore,
|
||||
IFileMetadataStore fileStore)
|
||||
{
|
||||
_scanner = scanner;
|
||||
_logger = logger;
|
||||
_scanStore = scanStore;
|
||||
_fileStore = fileStore;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
_logger.LogInformation("Virus scanner background service starting..");
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
var page = 0;
|
||||
while (true)
|
||||
{
|
||||
var files = await _fileStore.ListFiles(new(page++, 1_000));
|
||||
if (files.Results == 0) break;
|
||||
|
||||
await foreach (var file in files.Data.WithCancellation(stoppingToken))
|
||||
{
|
||||
// file is too large, cant scan
|
||||
if (file.Size > 4_000_000) continue;
|
||||
|
||||
// check for scans
|
||||
var scan = await _scanStore.GetByFile(file.Id);
|
||||
if (scan == default || scan.ScanTime < DateTime.UtcNow.Subtract(TimeSpan.FromDays(30)))
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _scanner.ScanFile(file.Id, stoppingToken);
|
||||
await _scanStore.Add(result.Id, result);
|
||||
_logger.LogInformation("Scanned file {Id}, IsVirus = {Result}", result.File, result.Score);
|
||||
}
|
||||
catch (RateLimitedException rx)
|
||||
{
|
||||
var sleep = rx.RetryAfter ?? DateTimeOffset.UtcNow.AddMinutes(10);
|
||||
_logger.LogWarning("VirusScanner was rate limited, sleeping until {Time}", sleep);
|
||||
await Task.Delay(sleep - DateTimeOffset.UtcNow, stoppingToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to scan file {Id} error={Message}", file.Id, ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
using VoidCat.Services.Abstractions;
|
||||
|
||||
namespace VoidCat.Services;
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract class BasicCacheStore<TStore> : IBasicStore<TStore>
|
||||
{
|
||||
protected readonly ICache Cache;
|
||||
|
||||
protected BasicCacheStore(ICache cache)
|
||||
{
|
||||
Cache = cache;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual ValueTask<TStore?> Get(Guid id)
|
||||
{
|
||||
return Cache.Get<TStore>(MapKey(id));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual ValueTask Add(Guid id, TStore obj)
|
||||
{
|
||||
return Cache.Set(MapKey(id), obj);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual ValueTask Delete(Guid id)
|
||||
{
|
||||
return Cache.Delete(MapKey(id));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Map an id to a key in the KV store
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
protected abstract string MapKey(Guid id);
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
using VoidCat.Model;
|
||||
using VoidCat.Services.Abstractions;
|
||||
|
||||
namespace VoidCat.Services.Captcha;
|
||||
|
||||
public static class CaptchaStartup
|
||||
{
|
||||
public static void AddCaptcha(this IServiceCollection services, VoidSettings settings)
|
||||
{
|
||||
if (settings.CaptchaSettings != default)
|
||||
{
|
||||
services.AddTransient<ICaptchaVerifier, hCaptchaVerifier>();
|
||||
}
|
||||
else
|
||||
{
|
||||
services.AddTransient<ICaptchaVerifier, NoOpVerifier>();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
using VoidCat.Services.Abstractions;
|
||||
|
||||
namespace VoidCat.Services.Captcha;
|
||||
|
||||
/// <summary>
|
||||
/// No captcha system is configured
|
||||
/// </summary>
|
||||
public class NoOpVerifier : ICaptchaVerifier
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ValueTask<bool> Verify(string? token)
|
||||
{
|
||||
return ValueTask.FromResult(true);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user