Add 'backend/' from commit '040cfd7a75423d4e6136e58a67b40579af4ee966'
git-subtree-dir: backend git-subtree-mainline:ab911955edgit-subtree-split:040cfd7a75
This commit is contained in:
12
backend/src/Web/.config/dotnet-tools.json
Normal file
12
backend/src/Web/.config/dotnet-tools.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-ef": {
|
||||
"version": "8.0.3",
|
||||
"commands": [
|
||||
"dotnet-ef"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
147
backend/src/Web/Common/BlobStorage/AzureBlobStorage.cs
Normal file
147
backend/src/Web/Common/BlobStorage/AzureBlobStorage.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
using Azure;
|
||||
using Azure.Storage.Blobs;
|
||||
using Azure.Storage.Blobs.Models;
|
||||
|
||||
namespace Hutopy.Web.Common.BlobStorage;
|
||||
|
||||
public class AzureBlobStorage
|
||||
{
|
||||
private const long MaxUploadSize = 10 * 1024 * 1024; // 10 MB in bytes
|
||||
|
||||
private readonly BlobServiceClient _blobServiceClient;
|
||||
private readonly ILogger<AzureBlobStorage> _logger;
|
||||
|
||||
public AzureBlobStorage(IConfiguration configuration, ILogger<AzureBlobStorage> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
var connectionString = configuration.GetConnectionString("AzureBlob");
|
||||
_blobServiceClient = new BlobServiceClient(connectionString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Upload a file to microsoft azure blob storage.
|
||||
/// </summary>
|
||||
/// <param name="containerName">The name of the container where the file is stored.</param>
|
||||
/// <param name="blobName">The blob name (path within the container, include the file name).</param>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="contentType">The content type.</param>
|
||||
/// <param name="ct">The cancellation token</param>
|
||||
/// <returns></returns>
|
||||
public async Task<string> UploadFileAsync(string containerName, string blobName, Stream stream,
|
||||
string contentType, CancellationToken ct = default)
|
||||
{
|
||||
// Read the file stream into a memory stream to determine the length
|
||||
// WATCH FOR MEMORY USAGE USING THE MEMORY STREAM.
|
||||
stream.Position = 0;
|
||||
|
||||
// Check if the file size exceeds the maximum upload size
|
||||
if (stream.Length > MaxUploadSize)
|
||||
{
|
||||
_logger.LogError(
|
||||
$"Blob storage: File size exceeds the maximum allowed size of {MaxUploadSize} bytes.");
|
||||
throw new InvalidOperationException(
|
||||
$"Blob storage: File size exceeds the maximum allowed size of {MaxUploadSize} bytes.");
|
||||
}
|
||||
|
||||
// Validate content type
|
||||
if (!ContentTypes.IsAllowed(contentType, stream))
|
||||
{
|
||||
_logger.LogError(
|
||||
$"Blob storage: Unsupported file type {contentType}.");
|
||||
throw new InvalidOperationException("Unsupported file type.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Get a reference to a container
|
||||
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
|
||||
|
||||
// Create the container if it does not exist
|
||||
await containerClient.CreateIfNotExistsAsync(
|
||||
PublicAccessType.Blob,
|
||||
cancellationToken: ct);
|
||||
|
||||
// Get a reference to a blob
|
||||
var blobClient = containerClient.GetBlobClient(blobName);
|
||||
|
||||
// Define the BlobHttpHeaders to include the content type
|
||||
var blobHttpHeaders = new BlobHttpHeaders { ContentType = contentType };
|
||||
|
||||
// Upload the file
|
||||
var response = await blobClient.UploadAsync(
|
||||
stream,
|
||||
new BlobUploadOptions { HttpHeaders = blobHttpHeaders },
|
||||
ct);
|
||||
|
||||
var fileUri = blobClient.Uri.ToString();
|
||||
|
||||
_logger.LogInformation(
|
||||
"""
|
||||
Blob storage: Status [ {ResponseStatus} ]
|
||||
Uploaded [ {BlobName} ] to the container [ {ContainerName} ]
|
||||
with contentType [ {ContentType} ]
|
||||
with a length of [ {StreamLength} bytes ]
|
||||
with the uri [ {FileUri} ]
|
||||
""",
|
||||
response.GetRawResponse().Status.ToString(),
|
||||
blobName,
|
||||
containerName,
|
||||
contentType,
|
||||
stream.Length,
|
||||
fileUri
|
||||
);
|
||||
|
||||
// Return the URI of the uploaded blob
|
||||
return fileUri;
|
||||
}
|
||||
catch (RequestFailedException ex)
|
||||
{
|
||||
_logger.LogError($"Blob storage: Azure Storage request failed: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"Blob storage: An error occurred: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download a file to microsoft's azure blob storage.
|
||||
/// </summary>
|
||||
/// <param name="blobName">The blob name (path within the container).</param>
|
||||
/// <param name="containerName">The name of the container where the file is stored. (users)</param>
|
||||
/// <param name="ct">The cancellation token for the request</param>
|
||||
/// <returns></returns>
|
||||
public async Task<MemoryStream> DownloadFileAsync(string containerName, string blobName,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get a reference to a container
|
||||
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
|
||||
|
||||
// Get a reference to a blob
|
||||
var blobClient = containerClient.GetBlobClient(blobName);
|
||||
|
||||
// Download the blob to a stream
|
||||
BlobDownloadInfo download = await blobClient.DownloadAsync(ct);
|
||||
|
||||
MemoryStream memoryStream = new();
|
||||
await download.Content.CopyToAsync(memoryStream, ct);
|
||||
memoryStream.Position = 0; // Ensure the stream is at the beginning
|
||||
|
||||
return memoryStream;
|
||||
}
|
||||
catch (RequestFailedException ex)
|
||||
{
|
||||
_logger.LogError($"Azure Storage request failed: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"An error occurred: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
backend/src/Web/Common/BlobStorage/BlobStructure.txt
Normal file
33
backend/src/Web/Common/BlobStorage/BlobStructure.txt
Normal file
@@ -0,0 +1,33 @@
|
||||
users/
|
||||
│
|
||||
├── userId1/
|
||||
│ ├── profile/
|
||||
│ │ └── profilePicture.jpg
|
||||
│ │ └── data.json
|
||||
│ │
|
||||
│ ├── posts/
|
||||
│ │ ├── post1/
|
||||
│ │ │ ├── image1.jpg
|
||||
│ │ │ ├── video1.mp4
|
||||
│ │ │ └── audio1.mp3
|
||||
│ │ ├── post2/
|
||||
│ │ │ ├── image2.jpg
|
||||
│ │ │ └── video2.mp4
|
||||
│ │ └── ...
|
||||
│
|
||||
├── userId2/
|
||||
│ ├── profile/
|
||||
│ │ └── profilePicture.jpg
|
||||
│ │ └── data.json
|
||||
│ │
|
||||
│ ├── posts/
|
||||
│ │ ├── post1/
|
||||
│ │ │ ├── image1.jpg
|
||||
│ │ │ ├── video1.mp4
|
||||
│ │ │ └── audio1.mp3
|
||||
│ │ ├── post2/
|
||||
│ │ │ ├── image2.jpg
|
||||
│ │ │ └── video2.mp4
|
||||
│ │ └── ...
|
||||
│
|
||||
└── ...
|
||||
7
backend/src/Web/Common/BlobStorage/CommonFileNames.cs
Normal file
7
backend/src/Web/Common/BlobStorage/CommonFileNames.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Hutopy.Web.Common.BlobStorage;
|
||||
|
||||
public static class CommonFileNames
|
||||
{
|
||||
public static string ProfilePicture = "profilePicture";
|
||||
public static string BannerPicture = "bannerPicture";
|
||||
}
|
||||
7
backend/src/Web/Common/BlobStorage/ContainerNames.cs
Normal file
7
backend/src/Web/Common/BlobStorage/ContainerNames.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Hutopy.Web.Common.BlobStorage;
|
||||
|
||||
public static class ContainerNames
|
||||
{
|
||||
public const string Users = "users";
|
||||
public const string Creators = "creators";
|
||||
}
|
||||
49
backend/src/Web/Common/BlobStorage/ContentTypes.cs
Normal file
49
backend/src/Web/Common/BlobStorage/ContentTypes.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Hutopy.Web.Common.BlobStorage;
|
||||
|
||||
public static class ContentTypes
|
||||
{
|
||||
private const string ImagePng = "image/png";
|
||||
private const string ImageJpeg = "image/jpeg";
|
||||
private const string ImageJpg = "image/jpg";
|
||||
private const string TextHtml = "text/html";
|
||||
|
||||
private static readonly HashSet<string> AllowedContentTypes = [ImagePng, ImageJpeg, ImageJpg, TextHtml];
|
||||
|
||||
public static bool IsAllowed(
|
||||
string contentType,
|
||||
Stream fileStream)
|
||||
{
|
||||
return IsValidFileType(fileStream) && AllowedContentTypes.Contains(contentType);
|
||||
}
|
||||
|
||||
private static bool IsValidFileType(
|
||||
Stream fileStream)
|
||||
{
|
||||
byte[] buffer = new byte[512];
|
||||
_ = fileStream.Read(buffer, 0, buffer.Length);
|
||||
fileStream.Position = 0;
|
||||
|
||||
// PNG file signature: 89 50 4E 47 (in hex)
|
||||
if (buffer[0] == 0x89 && buffer[1] == 0x50 && buffer[2] == 0x4E && buffer[3] == 0x47)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// JPEG file signature: FF D8 FF (in hex)
|
||||
if (buffer[0] == 0xFF && buffer[1] == 0xD8 && buffer[2] == 0xFF)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for HTML content by looking for "<!DOCTYPE html>" or "<html>" tags
|
||||
string content = Encoding.UTF8.GetString(buffer);
|
||||
if (content.Contains("<!DOCTYPE html>"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
7
backend/src/Web/Common/BlobStorage/SubDirectoryNames.cs
Normal file
7
backend/src/Web/Common/BlobStorage/SubDirectoryNames.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Hutopy.Web.Common.BlobStorage;
|
||||
|
||||
public static class SubDirectoryNames
|
||||
{
|
||||
public static string Profile = "profile";
|
||||
public static string Contents = "contents";
|
||||
}
|
||||
94
backend/src/Web/Common/GuidExtensions.cs
Normal file
94
backend/src/Web/Common/GuidExtensions.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
namespace Hutopy.Web.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Adapted from https://raw.githubusercontent.com/uuidjs/uuid/main/src/v7.ts.
|
||||
/// to match the uuid v7 generated on the client
|
||||
/// </summary>
|
||||
public static class GuidHelper
|
||||
{
|
||||
private class V7State
|
||||
{
|
||||
public long Msecs { get; set; } = long.MinValue;
|
||||
public int Seq { get; set; }
|
||||
}
|
||||
|
||||
private static readonly V7State State = new();
|
||||
private static readonly Random Random = new();
|
||||
|
||||
public static Guid GenerateUuidV7()
|
||||
{
|
||||
byte[] randomValues = new byte[16];
|
||||
Random.NextBytes(randomValues);
|
||||
|
||||
UpdateV7State(
|
||||
State,
|
||||
DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
||||
randomValues);
|
||||
|
||||
var values = V7Bytes(randomValues, State.Msecs, State.Seq);
|
||||
|
||||
return new Guid(values);
|
||||
}
|
||||
|
||||
private static void UpdateV7State(V7State state, long now, byte[] randomBytes)
|
||||
{
|
||||
if (now > state.Msecs)
|
||||
{
|
||||
state.Seq = (randomBytes[6] << 23) | (randomBytes[7] << 16) | (randomBytes[8] << 8) | randomBytes[9];
|
||||
state.Msecs = now;
|
||||
}
|
||||
else
|
||||
{
|
||||
state.Seq = (state.Seq + 1) | 0;
|
||||
if (state.Seq == 0)
|
||||
{
|
||||
state.Msecs++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] V7Bytes(byte[] randomBytes, long? msecs = null, int? seq = null, byte[]? buf = null, int offset = 0)
|
||||
{
|
||||
if (buf == null)
|
||||
{
|
||||
buf = new byte[16];
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
// Defaults
|
||||
msecs ??= DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
seq ??= ((randomBytes[6] & 0x7f) << 24) | (randomBytes[7] << 16) | (randomBytes[8] << 8) | randomBytes[9];
|
||||
|
||||
// byte 0-5: timestamp (48 bits)
|
||||
buf[offset++] = (byte)((msecs.Value / 0x10000000000) & 0xff);
|
||||
buf[offset++] = (byte)((msecs.Value / 0x100000000) & 0xff);
|
||||
buf[offset++] = (byte)((msecs.Value / 0x1000000) & 0xff);
|
||||
buf[offset++] = (byte)((msecs.Value / 0x10000) & 0xff);
|
||||
buf[offset++] = (byte)((msecs.Value / 0x100) & 0xff);
|
||||
buf[offset++] = (byte)(msecs.Value & 0xff);
|
||||
|
||||
// byte 6: `version` (4 bits) | sequence bits 28-31 (4 bits)
|
||||
buf[offset++] = (byte)(0x70 | ((seq.Value >> 28) & 0x0f));
|
||||
|
||||
// byte 7: sequence bits 20-27 (8 bits)
|
||||
buf[offset++] = (byte)((seq.Value >> 20) & 0xff);
|
||||
|
||||
// byte 8: `variant` (2 bits) | sequence bits 14-19 (6 bits)
|
||||
buf[offset++] = (byte)(0x80 | ((seq.Value >> 14) & 0x3f));
|
||||
|
||||
// byte 9: sequence bits 6-13 (8 bits)
|
||||
buf[offset++] = (byte)((seq.Value >> 6) & 0xff);
|
||||
|
||||
// byte 10: sequence bits 0-5 (6 bits) | random (2 bits)
|
||||
buf[offset++] = (byte)(((seq.Value << 2) & 0xff) | (randomBytes[10] & 0x03));
|
||||
|
||||
// bytes 11-15: random (40 bits)
|
||||
buf[offset++] = randomBytes[11];
|
||||
buf[offset++] = randomBytes[12];
|
||||
buf[offset++] = randomBytes[13];
|
||||
buf[offset++] = randomBytes[14];
|
||||
buf[offset] = randomBytes[15];
|
||||
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
63
backend/src/Web/Common/Security/ClaimsPrincipalExtensions.cs
Normal file
63
backend/src/Web/Common/Security/ClaimsPrincipalExtensions.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Hutopy.Web.Common.Security;
|
||||
|
||||
public static class ClaimsPrincipalExtensions
|
||||
{
|
||||
public static Guid GetUserId(this ClaimsPrincipal claims)
|
||||
{
|
||||
return (Guid)claims.GetRequiredClaim<Guid>(ClaimTypes.NameIdentifier);
|
||||
}
|
||||
|
||||
public static string GetName(this ClaimsPrincipal claims)
|
||||
{
|
||||
return (string)claims.GetRequiredClaim<string>(ClaimTypes.Name);
|
||||
}
|
||||
|
||||
public static string? GetAlias(this ClaimsPrincipal claims)
|
||||
{
|
||||
return (string?)claims.GetClaim<string?>(KnownClaims.Alias);
|
||||
}
|
||||
|
||||
public static string? GetPortraitUrl(this ClaimsPrincipal claims)
|
||||
{
|
||||
return (string?)claims.GetClaim<string?>(KnownClaims.PortraitUrl);
|
||||
}
|
||||
|
||||
public static string GetFirstName(this ClaimsPrincipal claims)
|
||||
{
|
||||
return (string)claims.GetRequiredClaim<string>(ClaimTypes.GivenName);
|
||||
}
|
||||
|
||||
public static string GetLastName(this ClaimsPrincipal claims)
|
||||
{
|
||||
return (string)claims.GetRequiredClaim<string>(ClaimTypes.Surname);
|
||||
}
|
||||
|
||||
public static string GetEmail(this ClaimsPrincipal claims)
|
||||
{
|
||||
return (string)claims.GetRequiredClaim<string>(ClaimTypes.Email);
|
||||
}
|
||||
|
||||
private static object? GetClaim<TValue>(this ClaimsPrincipal claims, string key)
|
||||
{
|
||||
var claim = claims.FindFirst(key);
|
||||
|
||||
if (claim is null) return default;
|
||||
return claims.GetRequiredClaim<TValue>(key);
|
||||
}
|
||||
|
||||
private static object GetRequiredClaim<TValue>(this ClaimsPrincipal claims, string key)
|
||||
{
|
||||
var claim = claims.FindFirst(key);
|
||||
|
||||
if (claim is null) throw new MissingClaimException(key);
|
||||
|
||||
if (typeof(TValue) == typeof(Guid))
|
||||
{
|
||||
return Guid.Parse(claim.Value);
|
||||
}
|
||||
|
||||
return Convert.ChangeType(claim.Value, typeof(TValue));
|
||||
}
|
||||
}
|
||||
53
backend/src/Web/Common/Security/GenerateJwtToken.cs
Normal file
53
backend/src/Web/Common/Security/GenerateJwtToken.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace Hutopy.Web.Common.Security;
|
||||
|
||||
public static class JwtTokenHelper
|
||||
{
|
||||
public static string GenerateJwtToken(
|
||||
TimeSpan expiresIn,
|
||||
string issuer,
|
||||
string audience,
|
||||
string key,
|
||||
string userId,
|
||||
string email,
|
||||
string? alias,
|
||||
string firstname,
|
||||
string lastname,
|
||||
string? portraitUrl)
|
||||
{
|
||||
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
|
||||
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var claims = new List<Claim>(new[]
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Sub, userId),
|
||||
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
|
||||
new Claim(ClaimTypes.NameIdentifier, userId), new Claim(ClaimTypes.Email, email),
|
||||
new Claim(ClaimTypes.Name, email), new Claim(ClaimTypes.GivenName, firstname),
|
||||
new Claim(ClaimTypes.Surname, lastname)
|
||||
});
|
||||
|
||||
if (alias is not null)
|
||||
{
|
||||
claims.Add(new(KnownClaims.Alias, alias));
|
||||
}
|
||||
|
||||
if (portraitUrl is not null)
|
||||
{
|
||||
claims.Add(new(KnownClaims.PortraitUrl, portraitUrl));
|
||||
}
|
||||
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: issuer,
|
||||
audience: audience,
|
||||
claims: claims,
|
||||
expires: DateTime.Now.Add(expiresIn),
|
||||
signingCredentials: credentials);
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
}
|
||||
7
backend/src/Web/Common/Security/KnownClaims.cs
Normal file
7
backend/src/Web/Common/Security/KnownClaims.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Hutopy.Web.Common.Security;
|
||||
|
||||
public static class KnownClaims
|
||||
{
|
||||
public const string Alias = "alias";
|
||||
public const string PortraitUrl = "portraitUrl";
|
||||
}
|
||||
5
backend/src/Web/Common/Security/MissingClaimException.cs
Normal file
5
backend/src/Web/Common/Security/MissingClaimException.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace Hutopy.Web.Common.Security;
|
||||
|
||||
public class MissingClaimException(
|
||||
string claimName)
|
||||
: Exception;
|
||||
70
backend/src/Web/Common/Security/PasswordGenerator.cs
Normal file
70
backend/src/Web/Common/Security/PasswordGenerator.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Hutopy.Web.Common.Security;
|
||||
|
||||
// If we need to add special characters we can alternate between 2 pools.
|
||||
public static class PasswordGenerator
|
||||
{
|
||||
private const string LowerLetters = "abcdefghijklmnopqrstuvwxyz";
|
||||
private const string UpperLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
private const string Numbers = "0123456789";
|
||||
private const string SpecialCharacters = "!@#$%^&*()_+-=[];',./`~{}|:\"<>?";
|
||||
|
||||
private static readonly Random Random = new();
|
||||
|
||||
public static string GeneratePassword(
|
||||
int minLength,
|
||||
int maxLength,
|
||||
bool requireNumber = true,
|
||||
bool requireCapital = true,
|
||||
bool requireSpecialCharacter = true)
|
||||
{
|
||||
// Create pools based on the requirements
|
||||
var characterPool = new StringBuilder(LowerLetters);
|
||||
|
||||
if (requireCapital)
|
||||
characterPool.Append(UpperLetters);
|
||||
|
||||
if (requireNumber)
|
||||
characterPool.Append(Numbers);
|
||||
|
||||
if (requireSpecialCharacter)
|
||||
characterPool.Append(SpecialCharacters);
|
||||
|
||||
// Ensure that the length is within the specified bounds
|
||||
int length = Random.Next(minLength, maxLength + 1);
|
||||
var password = new char[length];
|
||||
|
||||
// Ensure at least one character from each required category is included
|
||||
int index = 0;
|
||||
|
||||
if (requireCapital)
|
||||
password[index++] = UpperLetters[Random.Next(UpperLetters.Length)];
|
||||
|
||||
if (requireNumber)
|
||||
password[index++] = Numbers[Random.Next(Numbers.Length)];
|
||||
|
||||
if (requireSpecialCharacter)
|
||||
password[index++] = SpecialCharacters[Random.Next(SpecialCharacters.Length)];
|
||||
|
||||
// Fill the rest of the password
|
||||
for (int i = index; i < length; i++)
|
||||
{
|
||||
password[i] = characterPool[Random.Next(characterPool.Length)];
|
||||
}
|
||||
|
||||
// Shuffle the password to randomize the placement of the required characters
|
||||
Shuffle(password);
|
||||
return new string(password);
|
||||
}
|
||||
|
||||
private static void Shuffle(
|
||||
char[] array)
|
||||
{
|
||||
for (int i = array.Length - 1; i > 0; i--)
|
||||
{
|
||||
int j = Random.Next(i + 1);
|
||||
(array[i], array[j]) = (array[j], array[i]); // Swap elements
|
||||
}
|
||||
}
|
||||
}
|
||||
60
backend/src/Web/Controllers/FacebookController.cs
Normal file
60
backend/src/Web/Controllers/FacebookController.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System.Security.Claims;
|
||||
using Hutopy.Web.Common;
|
||||
using Hutopy.Web.Common.Security;
|
||||
using Hutopy.Web.Features.Users;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.Facebook;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Hutopy.Web.Controllers;
|
||||
|
||||
public class FacebookController(
|
||||
IdentityService identityService)
|
||||
: Controller
|
||||
{
|
||||
[Microsoft.AspNetCore.Mvc.HttpGet("/api/facebook/sign-in")]
|
||||
public async Task SignIn()
|
||||
{
|
||||
await HttpContext.ChallengeAsync(FacebookDefaults.AuthenticationScheme,
|
||||
new AuthenticationProperties { RedirectUri = Url.Action("Authorize") });
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Authorize()
|
||||
{
|
||||
var authenticateResult = await HttpContext.AuthenticateAsync(FacebookDefaults.AuthenticationScheme);
|
||||
|
||||
if (!authenticateResult.Succeeded) return BadRequest();
|
||||
|
||||
var claims = authenticateResult.Principal.Claims.ToList();
|
||||
|
||||
var name = claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value ?? "";
|
||||
var email = claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value ?? "";
|
||||
var givenName = claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName)?.Value ?? "";
|
||||
var familyName = claims.FirstOrDefault(c => c.Type == ClaimTypes.Surname)?.Value ?? "";
|
||||
|
||||
var claimsIdentity = new ClaimsIdentity(
|
||||
new List<Claim>
|
||||
{
|
||||
new(ClaimTypes.Name, name),
|
||||
new(ClaimTypes.Email, email),
|
||||
new(ClaimTypes.GivenName, givenName),
|
||||
new(ClaimTypes.Surname, familyName)
|
||||
},
|
||||
CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
|
||||
if (await identityService.FindUserByEmailAsync(email) != null)
|
||||
{
|
||||
await HttpContext.SignInAsync(
|
||||
CookieAuthenticationDefaults.AuthenticationScheme,
|
||||
new ClaimsPrincipal(claimsIdentity));
|
||||
return Redirect("/");
|
||||
}
|
||||
|
||||
await identityService.CreateUserAsync(email, givenName, givenName, familyName,
|
||||
PasswordGenerator.GeneratePassword(8, 10));
|
||||
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
|
||||
new ClaimsPrincipal(claimsIdentity));
|
||||
return Redirect("/");
|
||||
}
|
||||
}
|
||||
110
backend/src/Web/DependencyInjection.cs
Normal file
110
backend/src/Web/DependencyInjection.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System.Text;
|
||||
using Azure.Identity;
|
||||
using Hutopy.Web.Features.Users.Data;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.Facebook;
|
||||
using Microsoft.AspNetCore.Authentication.Google;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace Hutopy.Web;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
public static IServiceCollection AddWebServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddDatabaseDeveloperPageExceptionFilter();
|
||||
|
||||
services.AddHttpContextAccessor();
|
||||
|
||||
services.AddHealthChecks()
|
||||
.AddDbContextCheck<IdentityDbContext>();
|
||||
|
||||
services.AddRazorPages();
|
||||
|
||||
services.AddHttpClient();
|
||||
|
||||
// Customise default API behaviour
|
||||
services.Configure<ApiBehaviorOptions>(options =>
|
||||
options.SuppressModelStateInvalidFilter = true);
|
||||
|
||||
services.AddEndpointsApiExplorer();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddKeyVaultIfConfigured(this IServiceCollection services,
|
||||
ConfigurationManager configuration)
|
||||
{
|
||||
var keyVaultUri = configuration["KeyVaultUri"];
|
||||
if (!string.IsNullOrWhiteSpace(keyVaultUri))
|
||||
{
|
||||
configuration.AddAzureKeyVault(
|
||||
new Uri(keyVaultUri),
|
||||
new DefaultAzureCredential());
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddAuthorizationAndAuthentication(this IServiceCollection services,
|
||||
ConfigurationManager configuration)
|
||||
{
|
||||
var authenticationBuilder = services
|
||||
.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddCookie("Identity.Application", options =>
|
||||
{
|
||||
options.LoginPath = "/api/Users/login";
|
||||
});
|
||||
|
||||
var authJwt = configuration.GetSection("Authentication:Jwt");
|
||||
if (authJwt.Exists())
|
||||
{
|
||||
authenticationBuilder.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, jwtBearerOptions =>
|
||||
{
|
||||
jwtBearerOptions.Authority = "https://hutopy.com";
|
||||
jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = authJwt["Issuer"],
|
||||
ValidateAudience = true,
|
||||
ValidAudience = authJwt["Audience"],
|
||||
ValidateLifetime = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authJwt["Key"] ??
|
||||
throw new ArgumentNullException("The Jwt Key is missing.")))
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
var authGoogle = configuration.GetSection("Authentication:Google");
|
||||
if (authGoogle.Exists())
|
||||
{
|
||||
authenticationBuilder.AddGoogle(GoogleDefaults.AuthenticationScheme, options =>
|
||||
{
|
||||
options.ClientId = authGoogle["ClientId"] ??
|
||||
throw new ArgumentNullException("The Google ClientId is missing.");
|
||||
options.ClientSecret = authGoogle["ClientSecret"] ??
|
||||
throw new ArgumentNullException("The Google ClientSecret is missing.");
|
||||
});
|
||||
}
|
||||
|
||||
var authFacebook = configuration.GetSection("Authentication:Facebook");
|
||||
if (authFacebook.Exists())
|
||||
{
|
||||
authenticationBuilder.AddFacebook(FacebookDefaults.AuthenticationScheme, options =>
|
||||
{
|
||||
options.ClientId = authFacebook["ClientId"] ??
|
||||
throw new ArgumentNullException("The Facebook ClientId is missing.");
|
||||
options.ClientSecret = authFacebook["ClientSecret"] ??
|
||||
throw new ArgumentNullException("The Facebook ClientSecret is missing.");
|
||||
});
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
34
backend/src/Web/Extensions/EnumExtensions.cs
Normal file
34
backend/src/Web/Extensions/EnumExtensions.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace Hutopy.Web.Extensions;
|
||||
|
||||
public static class EnumExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Converts a string to the specified enum type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The type of the enum to convert to. Must be an enum.</typeparam>
|
||||
/// <param name="value">The string value to convert.</param>
|
||||
/// <param name="ignoreCase">Specifies whether the string comparison should ignore case. Default is true.</param>
|
||||
/// <returns>
|
||||
/// The corresponding enum value if the conversion is successful; otherwise, null if the string
|
||||
/// cannot be converted to the specified enum type.
|
||||
/// </returns>
|
||||
public static TEnum? ToEnum<TEnum>(this string value, bool ignoreCase = true) where TEnum : struct
|
||||
{
|
||||
if (Enum.TryParse(value, ignoreCase, out TEnum result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts an enum value to its string representation.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEnum">The type of the enum.</typeparam>
|
||||
/// <param name="enumValue">The enum value to convert.</param>
|
||||
/// <returns>The string representation of the enum value.</returns>
|
||||
public static string FromEnum<TEnum>(this TEnum enumValue) where TEnum : struct, Enum
|
||||
{
|
||||
return enumValue.ToString();
|
||||
}
|
||||
}
|
||||
21
backend/src/Web/Features/Contents/Data/Content.cs
Normal file
21
backend/src/Web/Features/Contents/Data/Content.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Data;
|
||||
|
||||
public class Content
|
||||
{
|
||||
public Guid Id { get; init; }
|
||||
public Guid CreatedBy { get; init; }
|
||||
public Creator? Creator { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
public Guid? DeletedBy { get; set; }
|
||||
public DateTimeOffset? DeletedAt { get; set; }
|
||||
[MaxLength(128)] public required string Title { get; set; }
|
||||
|
||||
[MaxLength(512)] public string? ThumbnailUrl { get; set; } = "";
|
||||
[MaxLength(2048)] public string Description { get; set; } = "";
|
||||
[MaxLength(2048)] public string? HtmlFileUrl { get; set; } = "";
|
||||
public IList<ContentReaction> Reactions { get; set; } = new List<ContentReaction>();
|
||||
public string[]? Urls { get; init; }
|
||||
|
||||
}
|
||||
68
backend/src/Web/Features/Contents/Data/ContentDbContext.cs
Normal file
68
backend/src/Web/Features/Contents/Data/ContentDbContext.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
namespace Hutopy.Web.Features.Contents.Data;
|
||||
|
||||
public class ContentDbContext(
|
||||
DbContextOptions<ContentDbContext> options)
|
||||
: DbContext(options)
|
||||
{
|
||||
public const string SchemaName = "Content";
|
||||
|
||||
public DbSet<Content> Contents => Set<Content>();
|
||||
public DbSet<Creator> Creators => Set<Creator>();
|
||||
|
||||
protected override void OnModelCreating(
|
||||
ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.HasDefaultSchema(SchemaName);
|
||||
|
||||
modelBuilder
|
||||
.Entity<Content>()
|
||||
.Property(c => c.CreatedAt)
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
modelBuilder
|
||||
.Entity<Content>()
|
||||
.HasOne(c => c.Creator)
|
||||
.WithMany()
|
||||
.HasForeignKey(c => c.CreatedBy);
|
||||
|
||||
modelBuilder
|
||||
.Entity<Content>()
|
||||
.OwnsMany(c => c.Reactions)
|
||||
.ToTable("Reactions");
|
||||
|
||||
modelBuilder
|
||||
.Entity<Content>()
|
||||
.Property(c => c.ThumbnailUrl);
|
||||
|
||||
modelBuilder
|
||||
.Entity<Creator>()
|
||||
.Property(x => x.NormalizedName)
|
||||
.HasComputedColumnSql("LOWER( \"Content\".\"Creators\".\"Name\")", stored: true);
|
||||
|
||||
modelBuilder
|
||||
.Entity<Creator>()
|
||||
.HasIndex(x => x.NormalizedName)
|
||||
.IsUnique();
|
||||
|
||||
modelBuilder
|
||||
.Entity<Creator>()
|
||||
.OwnsOne<Socials>(x => x.Socials)
|
||||
.ToTable(nameof(Socials));
|
||||
|
||||
modelBuilder
|
||||
.Entity<Creator>()
|
||||
.OwnsOne<Colors>(x => x.Colors)
|
||||
.ToTable(nameof(Colors));
|
||||
|
||||
modelBuilder
|
||||
.Entity<Creator>()
|
||||
.OwnsOne<Images>(x => x.Images)
|
||||
.ToTable(nameof(Images));
|
||||
|
||||
modelBuilder
|
||||
.Entity<Creator>()
|
||||
.OwnsOne<PresentationInfos>(x => x.PresentationInfos)
|
||||
.ToTable(nameof(PresentationInfos));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace Hutopy.Web.Features.Contents.Data;
|
||||
|
||||
public static class InitializerExtensions
|
||||
{
|
||||
public static async Task InitialiseContentDbContextAsync(this WebApplication app)
|
||||
{
|
||||
using var scope = app.Services.CreateScope();
|
||||
|
||||
var initializer = scope.ServiceProvider.GetRequiredService<ContentDbContextInitializer>();
|
||||
|
||||
await initializer.InitialiseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public class ContentDbContextInitializer(
|
||||
ILogger<ContentDbContextInitializer> logger,
|
||||
ContentDbContext context
|
||||
)
|
||||
{
|
||||
public async Task InitialiseAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await context.Database.MigrateAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "An error occurred while initialising the content database.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
backend/src/Web/Features/Contents/Data/ContentReaction.cs
Normal file
11
backend/src/Web/Features/Contents/Data/ContentReaction.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Hutopy.Web.Features.Contents.Data.Enums;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Data;
|
||||
|
||||
public class ContentReaction
|
||||
{
|
||||
public required Reaction Reaction { get; set; }
|
||||
public required Guid UserId { get; set; }
|
||||
[MaxLength(128)] public required string UserName { get; set; }
|
||||
}
|
||||
72
backend/src/Web/Features/Contents/Data/Creator.cs
Normal file
72
backend/src/Web/Features/Contents/Data/Creator.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Data;
|
||||
|
||||
public class Creator
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid CreatedBy { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; init; }
|
||||
public bool AcceptDonation { get; set; }
|
||||
public bool Verified { get; set; }
|
||||
[MaxLength(255)] public string Name { get; set; } = null!;
|
||||
[MaxLength(255)] public string NormalizedName { get; set; } = null!;
|
||||
[MaxLength(255)] public string? Title { get; set; }
|
||||
public Socials Socials { get; set; } = new();
|
||||
public Colors Colors { get; set; } = new();
|
||||
public Images Images { get; set; } = new();
|
||||
public PresentationInfos PresentationInfos { get; set; } = new();
|
||||
}
|
||||
|
||||
public class Colors
|
||||
{
|
||||
[MaxLength(9)] public string Primary { get; set; } = null!;
|
||||
[MaxLength(9)] public string Secondary { get; set; } = null!;
|
||||
[MaxLength(9)] public string Background { get; set; } = null!;
|
||||
[MaxLength(9)] public string Surface { get; set; } = null!;
|
||||
[MaxLength(9)] public string Error { get; set; } = null!;
|
||||
[MaxLength(9)] public string OnPrimary { get; set; } = null!;
|
||||
[MaxLength(9)] public string OnSecondary { get; set; } = null!;
|
||||
[MaxLength(9)] public string OnBackground { get; set; } = null!;
|
||||
[MaxLength(9)] public string OnSurface { get; set; } = null!;
|
||||
[MaxLength(9)] public string OnError { get; set; } = null!;
|
||||
}
|
||||
|
||||
public class Socials
|
||||
{
|
||||
[MaxLength(255)] public string? FacebookUrl { get; set; }
|
||||
[MaxLength(255)] public string? InstagramUrl { get; set; }
|
||||
[MaxLength(255)] public string? XUrl { get; set; }
|
||||
[MaxLength(255)] public string? LinkedInUrl { get; set; }
|
||||
[MaxLength(255)] public string? TikTokUrl { get; set; }
|
||||
[MaxLength(255)] public string? YoutubeUrl { get; set; }
|
||||
[MaxLength(255)] public string? RedditUrl { get; set; }
|
||||
[MaxLength(255)] public string? WebsiteUrl { get; set; }
|
||||
}
|
||||
|
||||
public class Images
|
||||
{
|
||||
[MaxLength(255)] public string? Banner { get; set; }
|
||||
[MaxLength(255)] public string? Logo { get; set; }
|
||||
}
|
||||
|
||||
public class PresentationInfos
|
||||
{
|
||||
[MaxLength(255)] public string PhoneNumber { get; set; } = string.Empty;
|
||||
[MaxLength(255)] public string Email { get; set; } = string.Empty;
|
||||
[MaxLength(2000)] public string Title { get; set; } = string.Empty;
|
||||
[MaxLength(2000)] public string MainImageUrl { get; set; } = string.Empty;
|
||||
[MaxLength(10000)] public string MainImageText { get; set; } = string.Empty;
|
||||
[MaxLength(10000)] public string MainVideoText { get; set; } = string.Empty;
|
||||
[MaxLength(2000)] public string ImagesSubtitle { get; set; } = string.Empty;
|
||||
[MaxLength(2000)] public string Image1Url { get; set; } = string.Empty;
|
||||
[MaxLength(2000)] public string Image2Url { get; set; } = string.Empty;
|
||||
[MaxLength(2000)] public string Image3Url { get; set; } = string.Empty;
|
||||
[MaxLength(2000)] public string Image4Url { get; set; } = string.Empty;
|
||||
[MaxLength(10000)] public string ImagesText { get; set; } = string.Empty;
|
||||
[MaxLength(2000)] public string VideoSubtitle { get; set; } = string.Empty;
|
||||
[MaxLength(2000)] public string VideoSubtitleMain { get; set; } = string.Empty;
|
||||
[MaxLength(2000)] public string VideoUrlMain { get; set; } = string.Empty;
|
||||
[MaxLength(2000)] public string VideoUrl { get; set; } = string.Empty;
|
||||
[MaxLength(10000)] public string VideoText { get; set; } = string.Empty;
|
||||
}
|
||||
13
backend/src/Web/Features/Contents/Data/Enums/Reaction.cs
Normal file
13
backend/src/Web/Features/Contents/Data/Enums/Reaction.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Hutopy.Web.Features.Contents.Data.Enums;
|
||||
|
||||
public enum Reaction
|
||||
{
|
||||
None = 0,
|
||||
Like = 1,
|
||||
Dislike = 2,
|
||||
Love = 3,
|
||||
Haha = 4,
|
||||
Wow = 5,
|
||||
Sad = 6,
|
||||
Angry = 7
|
||||
}
|
||||
285
backend/src/Web/Features/Contents/Data/Migrations/20241020202641_Initial.Designer.cs
generated
Normal file
285
backend/src/Web/Features/Contents/Data/Migrations/20241020202641_Initial.Designer.cs
generated
Normal file
@@ -0,0 +1,285 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(ContentDbContext))]
|
||||
[Migration("20241020202641_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("Content")
|
||||
.HasAnnotation("ProductVersion", "8.0.10")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("HtmlFileUrl")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string[]>("Urls")
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedBy");
|
||||
|
||||
b.ToTable("Contents", "Content");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Creators", "Content");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatedBy")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsMany("Hutopy.Web.Features.Contents.Data.ContentReaction", "Reactions", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("ContentId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property<int>("Id"));
|
||||
|
||||
b1.Property<int>("Reaction")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b1.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("UserName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b1.HasKey("ContentId", "Id");
|
||||
|
||||
b1.ToTable("Reactions", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ContentId");
|
||||
});
|
||||
|
||||
b.Navigation("Creator");
|
||||
|
||||
b.Navigation("Reactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b =>
|
||||
{
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Colors", "Colors", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Background")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Error")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnBackground")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnError")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnPrimary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnSecondary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnSurface")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Primary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Secondary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Surface")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Colors", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Images", "Images", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Banner")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Logo")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Images", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Socials", "Socials", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("FacebookUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("InstagramUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("LinkedInUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("RedditUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("TikTokUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("WebsiteUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("XUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("YoutubeUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Socials", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.Navigation("Colors")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Images")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Socials")
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.EnsureSchema(
|
||||
name: "Content");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Creators",
|
||||
schema: "Content",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CreatedBy = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
Title = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Creators", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Colors",
|
||||
schema: "Content",
|
||||
columns: table => new
|
||||
{
|
||||
CreatorId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Primary = table.Column<string>(type: "character varying(9)", maxLength: 9, nullable: false),
|
||||
Secondary = table.Column<string>(type: "character varying(9)", maxLength: 9, nullable: false),
|
||||
Background = table.Column<string>(type: "character varying(9)", maxLength: 9, nullable: false),
|
||||
Surface = table.Column<string>(type: "character varying(9)", maxLength: 9, nullable: false),
|
||||
Error = table.Column<string>(type: "character varying(9)", maxLength: 9, nullable: false),
|
||||
OnPrimary = table.Column<string>(type: "character varying(9)", maxLength: 9, nullable: false),
|
||||
OnSecondary = table.Column<string>(type: "character varying(9)", maxLength: 9, nullable: false),
|
||||
OnBackground = table.Column<string>(type: "character varying(9)", maxLength: 9, nullable: false),
|
||||
OnSurface = table.Column<string>(type: "character varying(9)", maxLength: 9, nullable: false),
|
||||
OnError = table.Column<string>(type: "character varying(9)", maxLength: 9, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Colors", x => x.CreatorId);
|
||||
table.ForeignKey(
|
||||
name: "FK_Colors_Creators_CreatorId",
|
||||
column: x => x.CreatorId,
|
||||
principalSchema: "Content",
|
||||
principalTable: "Creators",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Contents",
|
||||
schema: "Content",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CreatedBy = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"),
|
||||
DeletedBy = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
DeletedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
Title = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
Description = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: false),
|
||||
HtmlFileUrl = table.Column<string>(type: "character varying(2048)", maxLength: 2048, nullable: true),
|
||||
Urls = table.Column<string[]>(type: "text[]", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Contents", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Contents_Creators_CreatedBy",
|
||||
column: x => x.CreatedBy,
|
||||
principalSchema: "Content",
|
||||
principalTable: "Creators",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Images",
|
||||
schema: "Content",
|
||||
columns: table => new
|
||||
{
|
||||
CreatorId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Banner = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||
Logo = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Images", x => x.CreatorId);
|
||||
table.ForeignKey(
|
||||
name: "FK_Images_Creators_CreatorId",
|
||||
column: x => x.CreatorId,
|
||||
principalSchema: "Content",
|
||||
principalTable: "Creators",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Socials",
|
||||
schema: "Content",
|
||||
columns: table => new
|
||||
{
|
||||
CreatorId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
FacebookUrl = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||
InstagramUrl = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||
XUrl = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||
LinkedInUrl = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||
TikTokUrl = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||
YoutubeUrl = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||
RedditUrl = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||
WebsiteUrl = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Socials", x => x.CreatorId);
|
||||
table.ForeignKey(
|
||||
name: "FK_Socials_Creators_CreatorId",
|
||||
column: x => x.CreatorId,
|
||||
principalSchema: "Content",
|
||||
principalTable: "Creators",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Reactions",
|
||||
schema: "Content",
|
||||
columns: table => new
|
||||
{
|
||||
ContentId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Reaction = table.Column<int>(type: "integer", nullable: false),
|
||||
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
UserName = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Reactions", x => new { x.ContentId, x.Id });
|
||||
table.ForeignKey(
|
||||
name: "FK_Reactions_Contents_ContentId",
|
||||
column: x => x.ContentId,
|
||||
principalSchema: "Content",
|
||||
principalTable: "Contents",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Contents_CreatedBy",
|
||||
schema: "Content",
|
||||
table: "Contents",
|
||||
column: "CreatedBy");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Colors",
|
||||
schema: "Content");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Images",
|
||||
schema: "Content");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Reactions",
|
||||
schema: "Content");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Socials",
|
||||
schema: "Content");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Contents",
|
||||
schema: "Content");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Creators",
|
||||
schema: "Content");
|
||||
}
|
||||
}
|
||||
}
|
||||
289
backend/src/Web/Features/Contents/Data/Migrations/20241201173048_AddThumbnailUrl.Designer.cs
generated
Normal file
289
backend/src/Web/Features/Contents/Data/Migrations/20241201173048_AddThumbnailUrl.Designer.cs
generated
Normal file
@@ -0,0 +1,289 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(ContentDbContext))]
|
||||
[Migration("20241201173048_AddThumbnailUrl")]
|
||||
partial class AddThumbnailUrl
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("Content")
|
||||
.HasAnnotation("ProductVersion", "8.0.10")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("HtmlFileUrl")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("ThumbnailUrl")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string[]>("Urls")
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedBy");
|
||||
|
||||
b.ToTable("Contents", "Content");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Creators", "Content");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatedBy")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsMany("Hutopy.Web.Features.Contents.Data.ContentReaction", "Reactions", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("ContentId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property<int>("Id"));
|
||||
|
||||
b1.Property<int>("Reaction")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b1.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("UserName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b1.HasKey("ContentId", "Id");
|
||||
|
||||
b1.ToTable("Reactions", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ContentId");
|
||||
});
|
||||
|
||||
b.Navigation("Creator");
|
||||
|
||||
b.Navigation("Reactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b =>
|
||||
{
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Colors", "Colors", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Background")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Error")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnBackground")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnError")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnPrimary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnSecondary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnSurface")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Primary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Secondary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Surface")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Colors", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Images", "Images", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Banner")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Logo")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Images", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Socials", "Socials", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("FacebookUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("InstagramUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("LinkedInUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("RedditUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("TikTokUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("WebsiteUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("XUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("YoutubeUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Socials", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.Navigation("Colors")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Images")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Socials")
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddThumbnailUrl : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "ThumbnailUrl",
|
||||
schema: "Content",
|
||||
table: "Contents",
|
||||
type: "character varying(512)",
|
||||
maxLength: 512,
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ThumbnailUrl",
|
||||
schema: "Content",
|
||||
table: "Contents");
|
||||
}
|
||||
}
|
||||
}
|
||||
390
backend/src/Web/Features/Contents/Data/Migrations/20241201182352_AddPresentationInfos.Designer.cs
generated
Normal file
390
backend/src/Web/Features/Contents/Data/Migrations/20241201182352_AddPresentationInfos.Designer.cs
generated
Normal file
@@ -0,0 +1,390 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(ContentDbContext))]
|
||||
[Migration("20241201182352_AddPresentationInfos")]
|
||||
partial class AddPresentationInfos
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("Content")
|
||||
.HasAnnotation("ProductVersion", "8.0.10")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("HtmlFileUrl")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("ThumbnailUrl")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string[]>("Urls")
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedBy");
|
||||
|
||||
b.ToTable("Contents", "Content");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Creators", "Content");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatedBy")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsMany("Hutopy.Web.Features.Contents.Data.ContentReaction", "Reactions", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("ContentId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property<int>("Id"));
|
||||
|
||||
b1.Property<int>("Reaction")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b1.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("UserName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b1.HasKey("ContentId", "Id");
|
||||
|
||||
b1.ToTable("Reactions", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ContentId");
|
||||
});
|
||||
|
||||
b.Navigation("Creator");
|
||||
|
||||
b.Navigation("Reactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b =>
|
||||
{
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Colors", "Colors", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Background")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Error")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnBackground")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnError")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnPrimary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnSecondary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnSurface")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Primary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Secondary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Surface")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Colors", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Images", "Images", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Banner")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Logo")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Images", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.PresentationInfos", "PresentationInfos", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Image1Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Image2Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Image3Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Image4Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("ImagesSubtitle")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("ImagesText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("MainImageText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("MainImageUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("MainVideoText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("PhoneNumber")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("VideoSubtitle")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("VideoSubtitleMain")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("VideoText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("VideoUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("VideoUrlMain")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("PresentationInfos", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Socials", "Socials", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("FacebookUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("InstagramUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("LinkedInUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("RedditUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("TikTokUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("WebsiteUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("XUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("YoutubeUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Socials", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.Navigation("Colors")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Images")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("PresentationInfos")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Socials")
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddPresentationInfos : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PresentationInfos",
|
||||
schema: "Content",
|
||||
columns: table => new
|
||||
{
|
||||
CreatorId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
PhoneNumber = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
Email = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
Title = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
MainImageUrl = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
MainImageText = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
MainVideoText = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
ImagesSubtitle = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
Image1Url = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
Image2Url = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
Image3Url = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
Image4Url = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
ImagesText = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
VideoSubtitle = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
VideoSubtitleMain = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
VideoUrlMain = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
VideoUrl = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
VideoText = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PresentationInfos", x => x.CreatorId);
|
||||
table.ForeignKey(
|
||||
name: "FK_PresentationInfos_Creators_CreatorId",
|
||||
column: x => x.CreatorId,
|
||||
principalSchema: "Content",
|
||||
principalTable: "Creators",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "PresentationInfos",
|
||||
schema: "Content");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(ContentDbContext))]
|
||||
[Migration("20241202131957_LongerStringPresentationInfos")]
|
||||
partial class LongerStringPresentationInfos
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("Content")
|
||||
.HasAnnotation("ProductVersion", "8.0.10")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("HtmlFileUrl")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("ThumbnailUrl")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string[]>("Urls")
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedBy");
|
||||
|
||||
b.ToTable("Contents", "Content");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Creators", "Content");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatedBy")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsMany("Hutopy.Web.Features.Contents.Data.ContentReaction", "Reactions", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("ContentId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property<int>("Id"));
|
||||
|
||||
b1.Property<int>("Reaction")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b1.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("UserName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b1.HasKey("ContentId", "Id");
|
||||
|
||||
b1.ToTable("Reactions", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ContentId");
|
||||
});
|
||||
|
||||
b.Navigation("Creator");
|
||||
|
||||
b.Navigation("Reactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b =>
|
||||
{
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Colors", "Colors", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Background")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Error")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnBackground")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnError")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnPrimary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnSecondary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnSurface")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Primary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Secondary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Surface")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Colors", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Images", "Images", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Banner")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Logo")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Images", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.PresentationInfos", "PresentationInfos", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Image1Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("Image2Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("Image3Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("Image4Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("ImagesSubtitle")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("ImagesText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10000)
|
||||
.HasColumnType("character varying(10000)");
|
||||
|
||||
b1.Property<string>("MainImageText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10000)
|
||||
.HasColumnType("character varying(10000)");
|
||||
|
||||
b1.Property<string>("MainImageUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("MainVideoText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10000)
|
||||
.HasColumnType("character varying(10000)");
|
||||
|
||||
b1.Property<string>("PhoneNumber")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("VideoSubtitle")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("VideoSubtitleMain")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("VideoText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10000)
|
||||
.HasColumnType("character varying(10000)");
|
||||
|
||||
b1.Property<string>("VideoUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("VideoUrlMain")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("PresentationInfos", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Socials", "Socials", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("FacebookUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("InstagramUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("LinkedInUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("RedditUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("TikTokUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("WebsiteUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("XUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("YoutubeUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Socials", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.Navigation("Colors")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Images")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("PresentationInfos")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Socials")
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class LongerStringPresentationInfos : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "VideoUrlMain",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(2000)",
|
||||
maxLength: 2000,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(255)",
|
||||
oldMaxLength: 255);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "VideoUrl",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(2000)",
|
||||
maxLength: 2000,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(255)",
|
||||
oldMaxLength: 255);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "VideoText",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(10000)",
|
||||
maxLength: 10000,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(255)",
|
||||
oldMaxLength: 255);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "VideoSubtitleMain",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(2000)",
|
||||
maxLength: 2000,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(255)",
|
||||
oldMaxLength: 255);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "VideoSubtitle",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(2000)",
|
||||
maxLength: 2000,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(255)",
|
||||
oldMaxLength: 255);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Title",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(2000)",
|
||||
maxLength: 2000,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(255)",
|
||||
oldMaxLength: 255);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "MainVideoText",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(10000)",
|
||||
maxLength: 10000,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(255)",
|
||||
oldMaxLength: 255);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "MainImageUrl",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(2000)",
|
||||
maxLength: 2000,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(255)",
|
||||
oldMaxLength: 255);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "MainImageText",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(10000)",
|
||||
maxLength: 10000,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(255)",
|
||||
oldMaxLength: 255);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "ImagesText",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(10000)",
|
||||
maxLength: 10000,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(255)",
|
||||
oldMaxLength: 255);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "ImagesSubtitle",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(2000)",
|
||||
maxLength: 2000,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(255)",
|
||||
oldMaxLength: 255);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Image4Url",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(2000)",
|
||||
maxLength: 2000,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(255)",
|
||||
oldMaxLength: 255);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Image3Url",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(2000)",
|
||||
maxLength: 2000,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(255)",
|
||||
oldMaxLength: 255);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Image2Url",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(2000)",
|
||||
maxLength: 2000,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(255)",
|
||||
oldMaxLength: 255);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Image1Url",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(2000)",
|
||||
maxLength: 2000,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(255)",
|
||||
oldMaxLength: 255);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "VideoUrlMain",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(255)",
|
||||
maxLength: 255,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(2000)",
|
||||
oldMaxLength: 2000);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "VideoUrl",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(255)",
|
||||
maxLength: 255,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(2000)",
|
||||
oldMaxLength: 2000);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "VideoText",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(255)",
|
||||
maxLength: 255,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(10000)",
|
||||
oldMaxLength: 10000);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "VideoSubtitleMain",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(255)",
|
||||
maxLength: 255,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(2000)",
|
||||
oldMaxLength: 2000);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "VideoSubtitle",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(255)",
|
||||
maxLength: 255,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(2000)",
|
||||
oldMaxLength: 2000);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Title",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(255)",
|
||||
maxLength: 255,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(2000)",
|
||||
oldMaxLength: 2000);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "MainVideoText",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(255)",
|
||||
maxLength: 255,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(10000)",
|
||||
oldMaxLength: 10000);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "MainImageUrl",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(255)",
|
||||
maxLength: 255,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(2000)",
|
||||
oldMaxLength: 2000);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "MainImageText",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(255)",
|
||||
maxLength: 255,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(10000)",
|
||||
oldMaxLength: 10000);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "ImagesText",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(255)",
|
||||
maxLength: 255,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(10000)",
|
||||
oldMaxLength: 10000);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "ImagesSubtitle",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(255)",
|
||||
maxLength: 255,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(2000)",
|
||||
oldMaxLength: 2000);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Image4Url",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(255)",
|
||||
maxLength: 255,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(2000)",
|
||||
oldMaxLength: 2000);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Image3Url",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(255)",
|
||||
maxLength: 255,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(2000)",
|
||||
oldMaxLength: 2000);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Image2Url",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(255)",
|
||||
maxLength: 255,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(2000)",
|
||||
oldMaxLength: 2000);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Image1Url",
|
||||
schema: "Content",
|
||||
table: "PresentationInfos",
|
||||
type: "character varying(255)",
|
||||
maxLength: 255,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(2000)",
|
||||
oldMaxLength: 2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(ContentDbContext))]
|
||||
[Migration("20250108022601_AddComputedColumnAndIndex_CreatorName")]
|
||||
partial class AddComputedColumnAndIndex_CreatorName
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("Content")
|
||||
.HasAnnotation("ProductVersion", "8.0.10")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("HtmlFileUrl")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("ThumbnailUrl")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string[]>("Urls")
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedBy");
|
||||
|
||||
b.ToTable("Contents", "Content");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)")
|
||||
.HasComputedColumnSql("LOWER( \"Content\".\"Creators\".\"Name\")", true);
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Creators", "Content");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatedBy")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsMany("Hutopy.Web.Features.Contents.Data.ContentReaction", "Reactions", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("ContentId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property<int>("Id"));
|
||||
|
||||
b1.Property<int>("Reaction")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b1.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("UserName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b1.HasKey("ContentId", "Id");
|
||||
|
||||
b1.ToTable("Reactions", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ContentId");
|
||||
});
|
||||
|
||||
b.Navigation("Creator");
|
||||
|
||||
b.Navigation("Reactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b =>
|
||||
{
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Colors", "Colors", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Background")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Error")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnBackground")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnError")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnPrimary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnSecondary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnSurface")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Primary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Secondary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Surface")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Colors", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Images", "Images", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Banner")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Logo")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Images", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.PresentationInfos", "PresentationInfos", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Image1Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("Image2Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("Image3Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("Image4Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("ImagesSubtitle")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("ImagesText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10000)
|
||||
.HasColumnType("character varying(10000)");
|
||||
|
||||
b1.Property<string>("MainImageText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10000)
|
||||
.HasColumnType("character varying(10000)");
|
||||
|
||||
b1.Property<string>("MainImageUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("MainVideoText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10000)
|
||||
.HasColumnType("character varying(10000)");
|
||||
|
||||
b1.Property<string>("PhoneNumber")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("VideoSubtitle")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("VideoSubtitleMain")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("VideoText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10000)
|
||||
.HasColumnType("character varying(10000)");
|
||||
|
||||
b1.Property<string>("VideoUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("VideoUrlMain")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("PresentationInfos", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Socials", "Socials", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("FacebookUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("InstagramUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("LinkedInUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("RedditUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("TikTokUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("WebsiteUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("XUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("YoutubeUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Socials", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.Navigation("Colors")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Images")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("PresentationInfos")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Socials")
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddComputedColumnAndIndex_CreatorName : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "NormalizedName",
|
||||
schema: "Content",
|
||||
table: "Creators",
|
||||
type: "character varying(255)",
|
||||
maxLength: 255,
|
||||
nullable: false,
|
||||
computedColumnSql: "LOWER( \"Content\".\"Creators\".\"Name\")",
|
||||
stored: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Creators_NormalizedName",
|
||||
schema: "Content",
|
||||
table: "Creators",
|
||||
column: "NormalizedName",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Creators_NormalizedName",
|
||||
schema: "Content",
|
||||
table: "Creators");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "NormalizedName",
|
||||
schema: "Content",
|
||||
table: "Creators");
|
||||
}
|
||||
}
|
||||
}
|
||||
403
backend/src/Web/Features/Contents/Data/Migrations/20250108210552_Add_Verified_Creator.Designer.cs
generated
Normal file
403
backend/src/Web/Features/Contents/Data/Migrations/20250108210552_Add_Verified_Creator.Designer.cs
generated
Normal file
@@ -0,0 +1,403 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(ContentDbContext))]
|
||||
[Migration("20250108210552_Add_Verified_Creator")]
|
||||
partial class Add_Verified_Creator
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("Content")
|
||||
.HasAnnotation("ProductVersion", "8.0.10")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("HtmlFileUrl")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("ThumbnailUrl")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string[]>("Urls")
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedBy");
|
||||
|
||||
b.ToTable("Contents", "Content");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)")
|
||||
.HasComputedColumnSql("LOWER( \"Content\".\"Creators\".\"Name\")", true);
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<bool>("Verified")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Creators", "Content");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatedBy")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsMany("Hutopy.Web.Features.Contents.Data.ContentReaction", "Reactions", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("ContentId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property<int>("Id"));
|
||||
|
||||
b1.Property<int>("Reaction")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b1.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("UserName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b1.HasKey("ContentId", "Id");
|
||||
|
||||
b1.ToTable("Reactions", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ContentId");
|
||||
});
|
||||
|
||||
b.Navigation("Creator");
|
||||
|
||||
b.Navigation("Reactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b =>
|
||||
{
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Colors", "Colors", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Background")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Error")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnBackground")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnError")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnPrimary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnSecondary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnSurface")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Primary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Secondary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Surface")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Colors", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Images", "Images", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Banner")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Logo")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Images", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.PresentationInfos", "PresentationInfos", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Image1Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("Image2Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("Image3Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("Image4Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("ImagesSubtitle")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("ImagesText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10000)
|
||||
.HasColumnType("character varying(10000)");
|
||||
|
||||
b1.Property<string>("MainImageText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10000)
|
||||
.HasColumnType("character varying(10000)");
|
||||
|
||||
b1.Property<string>("MainImageUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("MainVideoText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10000)
|
||||
.HasColumnType("character varying(10000)");
|
||||
|
||||
b1.Property<string>("PhoneNumber")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("VideoSubtitle")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("VideoSubtitleMain")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("VideoText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10000)
|
||||
.HasColumnType("character varying(10000)");
|
||||
|
||||
b1.Property<string>("VideoUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("VideoUrlMain")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("PresentationInfos", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Socials", "Socials", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("FacebookUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("InstagramUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("LinkedInUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("RedditUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("TikTokUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("WebsiteUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("XUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("YoutubeUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Socials", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.Navigation("Colors")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Images")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("PresentationInfos")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Socials")
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Add_Verified_Creator : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "Verified",
|
||||
schema: "Content",
|
||||
table: "Creators",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Verified",
|
||||
schema: "Content",
|
||||
table: "Creators");
|
||||
}
|
||||
}
|
||||
}
|
||||
406
backend/src/Web/Features/Contents/Data/Migrations/20250109015556_Adds_AcceptDonation_Creator.Designer.cs
generated
Normal file
406
backend/src/Web/Features/Contents/Data/Migrations/20250109015556_Adds_AcceptDonation_Creator.Designer.cs
generated
Normal file
@@ -0,0 +1,406 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(ContentDbContext))]
|
||||
[Migration("20250109015556_Adds_AcceptDonation_Creator")]
|
||||
partial class Adds_AcceptDonation_Creator
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("Content")
|
||||
.HasAnnotation("ProductVersion", "8.0.10")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("HtmlFileUrl")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("ThumbnailUrl")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string[]>("Urls")
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedBy");
|
||||
|
||||
b.ToTable("Contents", "Content");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("AcceptDonation")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)")
|
||||
.HasComputedColumnSql("LOWER( \"Content\".\"Creators\".\"Name\")", true);
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<bool>("Verified")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Creators", "Content");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatedBy")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsMany("Hutopy.Web.Features.Contents.Data.ContentReaction", "Reactions", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("ContentId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property<int>("Id"));
|
||||
|
||||
b1.Property<int>("Reaction")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b1.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("UserName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b1.HasKey("ContentId", "Id");
|
||||
|
||||
b1.ToTable("Reactions", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ContentId");
|
||||
});
|
||||
|
||||
b.Navigation("Creator");
|
||||
|
||||
b.Navigation("Reactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b =>
|
||||
{
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Colors", "Colors", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Background")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Error")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnBackground")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnError")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnPrimary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnSecondary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnSurface")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Primary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Secondary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Surface")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Colors", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Images", "Images", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Banner")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Logo")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Images", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.PresentationInfos", "PresentationInfos", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Image1Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("Image2Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("Image3Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("Image4Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("ImagesSubtitle")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("ImagesText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10000)
|
||||
.HasColumnType("character varying(10000)");
|
||||
|
||||
b1.Property<string>("MainImageText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10000)
|
||||
.HasColumnType("character varying(10000)");
|
||||
|
||||
b1.Property<string>("MainImageUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("MainVideoText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10000)
|
||||
.HasColumnType("character varying(10000)");
|
||||
|
||||
b1.Property<string>("PhoneNumber")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("VideoSubtitle")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("VideoSubtitleMain")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("VideoText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10000)
|
||||
.HasColumnType("character varying(10000)");
|
||||
|
||||
b1.Property<string>("VideoUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("VideoUrlMain")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("PresentationInfos", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Socials", "Socials", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("FacebookUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("InstagramUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("LinkedInUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("RedditUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("TikTokUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("WebsiteUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("XUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("YoutubeUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Socials", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.Navigation("Colors")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Images")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("PresentationInfos")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Socials")
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Adds_AcceptDonation_Creator : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "AcceptDonation",
|
||||
schema: "Content",
|
||||
table: "Creators",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AcceptDonation",
|
||||
schema: "Content",
|
||||
table: "Creators");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(ContentDbContext))]
|
||||
partial class ContentDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("Content")
|
||||
.HasAnnotation("ProductVersion", "8.0.10")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset?>("DeletedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid?>("DeletedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("HtmlFileUrl")
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.Property<string>("ThumbnailUrl")
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("character varying(512)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string[]>("Urls")
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedBy");
|
||||
|
||||
b.ToTable("Contents", "Content");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<bool>("AcceptDonation")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)")
|
||||
.HasComputedColumnSql("LOWER( \"Content\".\"Creators\".\"Name\")", true);
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<bool>("Verified")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Creators", "Content");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Content", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Contents.Data.Creator", "Creator")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatedBy")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsMany("Hutopy.Web.Features.Contents.Data.ContentReaction", "Reactions", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("ContentId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b1.Property<int>("Id"));
|
||||
|
||||
b1.Property<int>("Reaction")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b1.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("UserName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b1.HasKey("ContentId", "Id");
|
||||
|
||||
b1.ToTable("Reactions", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("ContentId");
|
||||
});
|
||||
|
||||
b.Navigation("Creator");
|
||||
|
||||
b.Navigation("Reactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Contents.Data.Creator", b =>
|
||||
{
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Colors", "Colors", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Background")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Error")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnBackground")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnError")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnPrimary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnSecondary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("OnSurface")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Primary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Secondary")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.Property<string>("Surface")
|
||||
.IsRequired()
|
||||
.HasMaxLength(9)
|
||||
.HasColumnType("character varying(9)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Colors", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Images", "Images", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Banner")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Logo")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Images", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.PresentationInfos", "PresentationInfos", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Image1Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("Image2Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("Image3Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("Image4Url")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("ImagesSubtitle")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("ImagesText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10000)
|
||||
.HasColumnType("character varying(10000)");
|
||||
|
||||
b1.Property<string>("MainImageText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10000)
|
||||
.HasColumnType("character varying(10000)");
|
||||
|
||||
b1.Property<string>("MainImageUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("MainVideoText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10000)
|
||||
.HasColumnType("character varying(10000)");
|
||||
|
||||
b1.Property<string>("PhoneNumber")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("VideoSubtitle")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("VideoSubtitleMain")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("VideoText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(10000)
|
||||
.HasColumnType("character varying(10000)");
|
||||
|
||||
b1.Property<string>("VideoUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.Property<string>("VideoUrlMain")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("PresentationInfos", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.OwnsOne("Hutopy.Web.Features.Contents.Data.Socials", "Socials", b1 =>
|
||||
{
|
||||
b1.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b1.Property<string>("FacebookUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("InstagramUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("LinkedInUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("RedditUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("TikTokUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("WebsiteUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("XUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.Property<string>("YoutubeUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b1.HasKey("CreatorId");
|
||||
|
||||
b1.ToTable("Socials", "Content");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("CreatorId");
|
||||
});
|
||||
|
||||
b.Navigation("Colors")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Images")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("PresentationInfos")
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Socials")
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
16
backend/src/Web/Features/Contents/DependencyInjection.cs
Normal file
16
backend/src/Web/Features/Contents/DependencyInjection.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
public static WebApplicationBuilder AddContentModule(
|
||||
this WebApplicationBuilder builder,
|
||||
Action<DbContextOptionsBuilder>? configureAction = null)
|
||||
{
|
||||
builder.Services.AddDbContext<ContentDbContext>(configureAction);
|
||||
builder.Services.AddScoped<ContentDbContextInitializer>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
using Hutopy.Web.Features.Memberships.Events;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.EventHandlers;
|
||||
|
||||
[UsedImplicitly]
|
||||
public class StripeAccountConfiguredHandler(
|
||||
ILogger<StripeAccountConfiguredHandler> logger,
|
||||
IServiceScopeFactory scopeFactory)
|
||||
: IEventHandler<StripeAccountConfigured>
|
||||
{
|
||||
public async Task HandleAsync(
|
||||
StripeAccountConfigured eventModel,
|
||||
CancellationToken ct)
|
||||
{
|
||||
using var scope = scopeFactory.CreateScope();
|
||||
await using var dbContext = scope.ServiceProvider.GetRequiredService<ContentDbContext>();
|
||||
|
||||
var creator = await dbContext.FindAsync<Creator>(
|
||||
[eventModel.CreatorId],
|
||||
cancellationToken: ct);
|
||||
|
||||
if (creator is null)
|
||||
{
|
||||
logger.LogError(
|
||||
"Creator with id {CreatorId} was not found.",
|
||||
eventModel.CreatorId);
|
||||
return;
|
||||
}
|
||||
|
||||
creator.AcceptDonation = true;
|
||||
|
||||
var rows = await dbContext.SaveChangesAsync(ct);
|
||||
|
||||
if (rows is 0 or > 1)
|
||||
{
|
||||
logger.LogError(
|
||||
"An error occured while updating Creator with id {CreatorId}: rows:{Rows}",
|
||||
eventModel.CreatorId,
|
||||
rows);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
backend/src/Web/Features/Contents/Handlers/AddReaction.cs
Normal file
83
backend/src/Web/Features/Contents/Handlers/AddReaction.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using Hutopy.Web.Extensions;
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
using Hutopy.Web.Features.Contents.Data.Enums;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class AddReactionRequest
|
||||
{
|
||||
public required Guid ContentId { get; set; }
|
||||
public required string Reaction { get; set; }
|
||||
public required Guid UserId { get; set; }
|
||||
public required string UserName { get; set; }
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
internal sealed class AddReactionRequestValidator
|
||||
: Validator<AddReactionRequest>
|
||||
{
|
||||
public AddReactionRequestValidator()
|
||||
{
|
||||
RuleFor(r => r.Reaction)
|
||||
.NotNull()
|
||||
.Must(BeAValidReaction)
|
||||
.WithMessage("'{PropertyValue}' is not a valid reaction.");
|
||||
}
|
||||
|
||||
private bool BeAValidReaction(string reaction)
|
||||
{
|
||||
return Enum.TryParse(typeof(Reaction), reaction, true, out _);
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public class AddReaction(
|
||||
ContentDbContext context)
|
||||
: Endpoint<AddReactionRequest>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/content/reaction");
|
||||
Options(o => o.WithTags("Contents"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
AddReactionRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var content = await context.Contents.SingleAsync(x => x.Id == req.ContentId, ct);
|
||||
var reactionEnum = req.Reaction.ToEnum<Reaction>();
|
||||
var currentReaction = content.Reactions.SingleOrDefault(x => x.UserId == req.UserId);
|
||||
|
||||
// Already reacted or reaction didn't change, do nothing
|
||||
if (currentReaction != null && currentReaction.Reaction == reactionEnum)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// User has already reacted, remove the existing reaction
|
||||
if (currentReaction != null)
|
||||
{
|
||||
content.Reactions.Remove(currentReaction);
|
||||
}
|
||||
|
||||
// If the new reaction is valid, add or update the reaction
|
||||
if (reactionEnum.HasValue)
|
||||
{
|
||||
var reaction = new ContentReaction
|
||||
{
|
||||
Reaction = reactionEnum.Value,
|
||||
UserId = req.UserId,
|
||||
UserName = req.UserName
|
||||
};
|
||||
|
||||
content.Reactions.Add(reaction);
|
||||
}
|
||||
|
||||
await context.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
60
backend/src/Web/Features/Contents/Handlers/ChangeBanner.cs
Normal file
60
backend/src/Web/Features/Contents/Handlers/ChangeBanner.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using Hutopy.Web.Common.BlobStorage;
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public record ChangeBannerRequest(
|
||||
Guid CreatorId,
|
||||
IFormFile File);
|
||||
|
||||
[PublicAPI]
|
||||
public record ChangeBannerResponse(
|
||||
string BlobUrl);
|
||||
|
||||
[PublicAPI]
|
||||
public class ChangeBannerHandler(
|
||||
ContentDbContext context,
|
||||
AzureBlobStorage blobStorage)
|
||||
: Endpoint<ChangeBannerRequest, ChangeBannerResponse>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/creators/{CreatorId}/banner");
|
||||
Options(o => o.WithTags("Creators"));
|
||||
AllowFileUploads();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
ChangeBannerRequest request,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var creator = await context
|
||||
.Creators
|
||||
.Include(c => c.Images)
|
||||
.SingleOrDefaultAsync(
|
||||
c => c.Id == request.CreatorId,
|
||||
cancellationToken: ct);
|
||||
|
||||
if (creator is null)
|
||||
{
|
||||
await SendNotFoundAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
var blobUrl = await blobStorage.UploadFileAsync(
|
||||
ContainerNames.Creators,
|
||||
$"{request.CreatorId}/{SubDirectoryNames.Profile}/{CommonFileNames.BannerPicture}",
|
||||
request.File.OpenReadStream(),
|
||||
request.File.ContentType,
|
||||
ct);
|
||||
|
||||
creator.Images.Banner = blobUrl;
|
||||
|
||||
await context.SaveChangesAsync(ct);
|
||||
|
||||
await SendOkAsync(
|
||||
new ChangeBannerResponse(blobUrl),
|
||||
ct);
|
||||
}
|
||||
}
|
||||
113
backend/src/Web/Features/Contents/Handlers/ChangeColors.cs
Normal file
113
backend/src/Web/Features/Contents/Handlers/ChangeColors.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public record ChangeColorsRequest(
|
||||
Guid CreatorId,
|
||||
string Primary,
|
||||
string Secondary,
|
||||
string Background,
|
||||
string Surface,
|
||||
string Error,
|
||||
string OnPrimary,
|
||||
string OnSecondary,
|
||||
string OnBackground,
|
||||
string OnSurface,
|
||||
string OnError);
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class ChangeColorsRequestValidator
|
||||
: Validator<ChangeColorsRequest>
|
||||
{
|
||||
public ChangeColorsRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Primary)
|
||||
.MinimumLength(4).WithMessage("The minimum value should be in the format #444")
|
||||
.MaximumLength(9).WithMessage("The maximum value should be in the format #11223344")
|
||||
.Must(x => x.StartsWith('#')).WithMessage("The format should be a valid html color and start with #");
|
||||
|
||||
RuleFor(x => x.Secondary)
|
||||
.MinimumLength(4).WithMessage("The minimum value should be in the format #444")
|
||||
.MaximumLength(9).WithMessage("The maximum value should be in the format #11223344")
|
||||
.Must(x => x.StartsWith('#')).WithMessage("The format should be a valid html color and start with #");
|
||||
|
||||
RuleFor(x => x.Background)
|
||||
.MinimumLength(4).WithMessage("The minimum value should be in the format #444")
|
||||
.MaximumLength(9).WithMessage("The maximum value should be in the format #11223344")
|
||||
.Must(x => x.StartsWith('#')).WithMessage("The format should be a valid html color and start with #");
|
||||
|
||||
RuleFor(x => x.Surface)
|
||||
.MinimumLength(4).WithMessage("The minimum value should be in the format #444")
|
||||
.MaximumLength(9).WithMessage("The maximum value should be in the format #11223344")
|
||||
.Must(x => x.StartsWith('#')).WithMessage("The format should be a valid html color and start with #");
|
||||
|
||||
RuleFor(x => x.Error)
|
||||
.MinimumLength(4).WithMessage("The minimum value should be in the format #444")
|
||||
.MaximumLength(9).WithMessage("The maximum value should be in the format #11223344")
|
||||
.Must(x => x.StartsWith('#')).WithMessage("The format should be a valid html color and start with #");
|
||||
|
||||
RuleFor(x => x.OnPrimary)
|
||||
.MinimumLength(4).WithMessage("The minimum value should be in the format #444")
|
||||
.MaximumLength(9).WithMessage("The maximum value should be in the format #11223344")
|
||||
.Must(x => x.StartsWith('#')).WithMessage("The format should be a valid html color and start with #");
|
||||
|
||||
RuleFor(x => x.OnSecondary)
|
||||
.MinimumLength(4).WithMessage("The minimum value should be in the format #444")
|
||||
.MaximumLength(9).WithMessage("The maximum value should be in the format #11223344")
|
||||
.Must(x => x.StartsWith('#')).WithMessage("The format should be a valid html color and start with #");
|
||||
|
||||
RuleFor(x => x.OnBackground)
|
||||
.MinimumLength(4).WithMessage("The minimum value should be in the format #444")
|
||||
.MaximumLength(9).WithMessage("The maximum value should be in the format #11223344")
|
||||
.Must(x => x.StartsWith('#')).WithMessage("The format should be a valid html color and start with #");
|
||||
|
||||
RuleFor(x => x.OnSurface)
|
||||
.MinimumLength(4).WithMessage("The minimum value should be in the format #444")
|
||||
.MaximumLength(9).WithMessage("The maximum value should be in the format #11223344")
|
||||
.Must(x => x.StartsWith('#')).WithMessage("The format should be a valid html color and start with #");
|
||||
|
||||
RuleFor(x => x.OnError)
|
||||
.MinimumLength(4).WithMessage("The minimum value should be in the format #444")
|
||||
.MaximumLength(9).WithMessage("The maximum value should be in the format #11223344")
|
||||
.Must(x => x.StartsWith('#')).WithMessage("The format should be a valid html color and start with #");
|
||||
}
|
||||
}
|
||||
|
||||
public class ChangeColorsHandler(
|
||||
ContentDbContext context)
|
||||
: Endpoint<ChangeColorsRequest>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/creators/{CreatorId}/colors");
|
||||
Options(o => o.WithTags("Creators"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
ChangeColorsRequest request,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var creator = await context
|
||||
.Creators
|
||||
.Include(c => c.Colors)
|
||||
.SingleAsync(
|
||||
c => c.Id == request.CreatorId,
|
||||
cancellationToken: ct);
|
||||
|
||||
creator.Colors.Primary = request.Primary;
|
||||
creator.Colors.Secondary = request.Secondary;
|
||||
creator.Colors.Background = request.Background;
|
||||
creator.Colors.Surface = request.Surface;
|
||||
creator.Colors.Error = request.Error;
|
||||
creator.Colors.OnPrimary = request.OnPrimary;
|
||||
creator.Colors.OnSecondary = request.OnSecondary;
|
||||
creator.Colors.OnBackground = request.OnBackground;
|
||||
creator.Colors.OnSurface = request.OnSurface;
|
||||
creator.Colors.OnError = request.OnError;
|
||||
|
||||
await context.SaveChangesAsync(ct);
|
||||
|
||||
await SendOkAsync(ct);
|
||||
}
|
||||
}
|
||||
70
backend/src/Web/Features/Contents/Handlers/ChangeLogo.cs
Normal file
70
backend/src/Web/Features/Contents/Handlers/ChangeLogo.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using Hutopy.Web.Common.BlobStorage;
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public record ChangeLogoRequest(
|
||||
Guid CreatorId,
|
||||
IFormFile File);
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class ChangeLogoRequestValidator : Validator<ChangeLogoRequest>
|
||||
{
|
||||
public ChangeLogoRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.CreatorId)
|
||||
.NotNull()
|
||||
.NotEmpty();
|
||||
|
||||
RuleFor(x => x.File)
|
||||
.NotNull()
|
||||
.NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public class ChangeLogoHandler(
|
||||
ContentDbContext context,
|
||||
AzureBlobStorage blobStorage)
|
||||
: Endpoint<ChangeLogoRequest>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/creators/{CreatorId}/logo");
|
||||
Options(o => o.WithTags("Creators"));
|
||||
AllowFileUploads();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
ChangeLogoRequest request,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var creator = await context
|
||||
.Creators
|
||||
.Include(c => c.Images)
|
||||
.SingleOrDefaultAsync(
|
||||
c => c.Id == request.CreatorId,
|
||||
cancellationToken: ct);
|
||||
|
||||
if (creator is null)
|
||||
{
|
||||
await SendNotFoundAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: this upload should be done to the Creators container
|
||||
var blobUrl = await blobStorage.UploadFileAsync(
|
||||
ContainerNames.Creators,
|
||||
$"{request.CreatorId}/{SubDirectoryNames.Profile}/{CommonFileNames.ProfilePicture}",
|
||||
request.File.OpenReadStream(),
|
||||
request.File.ContentType,
|
||||
ct);
|
||||
|
||||
creator.Images.Logo = blobUrl;
|
||||
|
||||
await context.SaveChangesAsync(ct);
|
||||
|
||||
await SendOkAsync(blobUrl, ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using Hutopy.Web.Common.BlobStorage;
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public record ChangePresentationInfosRequest(
|
||||
Guid CreatorId,
|
||||
string? PhoneNumber,
|
||||
string? Email,
|
||||
string? Title,
|
||||
string? MainImageText,
|
||||
string? MainVideoText,
|
||||
string? ImagesSubtitle,
|
||||
string? ImagesText,
|
||||
string? VideoSubtitle,
|
||||
string? VideoSubtitleMain,
|
||||
string? VideoUrlMain,
|
||||
string? VideoUrl,
|
||||
string? VideoText,
|
||||
string? MainImageUrl,
|
||||
string? Image1Url,
|
||||
string? Image2Url,
|
||||
string? Image3Url,
|
||||
string? Image4Url,
|
||||
IFormFile? MainImage,
|
||||
IFormFile? Image1,
|
||||
IFormFile? Image2,
|
||||
IFormFile? Image3,
|
||||
IFormFile? Image4);
|
||||
|
||||
[PublicAPI]
|
||||
public class ChangePresentationInfosHandler(
|
||||
ContentDbContext context,
|
||||
AzureBlobStorage blobStorage)
|
||||
: Endpoint<ChangePresentationInfosRequest>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/creators/{CreatorId}/presentation-infos");
|
||||
Options(o => o.WithTags("Creators"));
|
||||
AllowFileUploads();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
ChangePresentationInfosRequest request,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var creator = await context
|
||||
.Creators
|
||||
.Include(c => c.PresentationInfos)
|
||||
.SingleOrDefaultAsync(
|
||||
c => c.Id == request.CreatorId,
|
||||
cancellationToken: ct);
|
||||
|
||||
if (creator is null)
|
||||
{
|
||||
await SendNotFoundAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
async Task<string> UploadFileOrDefaultAsync(
|
||||
IFormFile? file,
|
||||
string subDirectory,
|
||||
string fileName,
|
||||
string? newUrl)
|
||||
{
|
||||
if (newUrl == "")
|
||||
return "";
|
||||
|
||||
if (file != null)
|
||||
{
|
||||
return await blobStorage.UploadFileAsync(
|
||||
ContainerNames.Creators,
|
||||
$"{request.CreatorId}/{subDirectory}/{fileName}",
|
||||
file.OpenReadStream(),
|
||||
file.ContentType,
|
||||
ct);
|
||||
}
|
||||
|
||||
return newUrl?.Trim() ?? "";
|
||||
}
|
||||
|
||||
creator.PresentationInfos.MainImageUrl = await UploadFileOrDefaultAsync(
|
||||
request.MainImage, "Profile", "MainImage", request.MainImageUrl);
|
||||
|
||||
creator.PresentationInfos.Image1Url = await UploadFileOrDefaultAsync(
|
||||
request.Image1, "Profile", "Image1", request.Image1Url);
|
||||
|
||||
creator.PresentationInfos.Image2Url = await UploadFileOrDefaultAsync(
|
||||
request.Image2, "Profile", "Image2", request.Image2Url);
|
||||
|
||||
creator.PresentationInfos.Image3Url = await UploadFileOrDefaultAsync(
|
||||
request.Image3, "Profile", "Image3", request.Image3Url);
|
||||
|
||||
creator.PresentationInfos.Image4Url = await UploadFileOrDefaultAsync(
|
||||
request.Image4, "Profile", "Image4", request.Image4Url);
|
||||
|
||||
creator.PresentationInfos.PhoneNumber = request.PhoneNumber?.Trim() ?? "";
|
||||
creator.PresentationInfos.Email = request.Email?.Trim() ?? "";
|
||||
creator.PresentationInfos.Title = request.Title?.Trim() ?? "";
|
||||
creator.PresentationInfos.MainImageText = request.MainImageText?.Trim() ?? "";
|
||||
creator.PresentationInfos.MainVideoText = request.MainVideoText?.Trim() ?? "";
|
||||
creator.PresentationInfos.ImagesSubtitle = request.ImagesSubtitle?.Trim() ?? "";
|
||||
creator.PresentationInfos.ImagesText = request.ImagesText?.Trim() ?? "";
|
||||
creator.PresentationInfos.VideoSubtitle = request.VideoSubtitle?.Trim() ?? "";
|
||||
creator.PresentationInfos.VideoSubtitleMain = request.VideoSubtitleMain?.Trim() ?? "";
|
||||
creator.PresentationInfos.VideoUrlMain = request.VideoUrlMain?.Trim() ?? "";
|
||||
creator.PresentationInfos.VideoUrl = request.VideoUrl?.Trim() ?? "";
|
||||
creator.PresentationInfos.VideoText = request.VideoText?.Trim() ?? "";
|
||||
|
||||
await context.SaveChangesAsync(ct);
|
||||
await SendOkAsync(ct);
|
||||
}
|
||||
}
|
||||
50
backend/src/Web/Features/Contents/Handlers/ChangeSocials.cs
Normal file
50
backend/src/Web/Features/Contents/Handlers/ChangeSocials.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public record ChangeSocialsRequest(
|
||||
Guid CreatorId,
|
||||
string? FacebookUrl,
|
||||
string? InstagramUrl,
|
||||
string? XUrl,
|
||||
string? LinkedInUrl,
|
||||
string? TikTokUrl,
|
||||
string? YoutubeUrl,
|
||||
string? RedditUrl,
|
||||
string? WebsiteUrl);
|
||||
|
||||
[PublicAPI]
|
||||
public class ChangeSocialsHandler(
|
||||
ContentDbContext context)
|
||||
: Endpoint<ChangeSocialsRequest>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/creators/{CreatorId}/socials");
|
||||
Options(o => o.WithTags("Creators"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(ChangeSocialsRequest request, CancellationToken ct)
|
||||
{
|
||||
var creator = await context
|
||||
.Creators
|
||||
.Include(c => c.Socials)
|
||||
.SingleAsync(
|
||||
c => c.Id == request.CreatorId,
|
||||
cancellationToken: ct);
|
||||
|
||||
creator.Socials.FacebookUrl = request.FacebookUrl;
|
||||
creator.Socials.InstagramUrl = request.InstagramUrl;
|
||||
creator.Socials.XUrl = request.XUrl;
|
||||
creator.Socials.LinkedInUrl = request.LinkedInUrl;
|
||||
creator.Socials.TikTokUrl = request.TikTokUrl;
|
||||
creator.Socials.YoutubeUrl = request.YoutubeUrl;
|
||||
creator.Socials.RedditUrl = request.RedditUrl;
|
||||
creator.Socials.WebsiteUrl = request.WebsiteUrl;
|
||||
|
||||
await context.SaveChangesAsync(ct);
|
||||
|
||||
await SendOkAsync(ct);
|
||||
}
|
||||
}
|
||||
37
backend/src/Web/Features/Contents/Handlers/ChangeTitle.cs
Normal file
37
backend/src/Web/Features/Contents/Handlers/ChangeTitle.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public record ChangeTitleRequest(
|
||||
Guid CreatorId,
|
||||
string? Title);
|
||||
|
||||
[PublicAPI]
|
||||
public class ChangeTitleHandler(
|
||||
ContentDbContext context)
|
||||
: Endpoint<ChangeTitleRequest>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/creators/{CreatorId}/title");
|
||||
Options(o => o.WithTags("Creators"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
ChangeTitleRequest request,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var creator = await context
|
||||
.Creators
|
||||
.SingleAsync(
|
||||
c => c.Id == request.CreatorId,
|
||||
cancellationToken: ct);
|
||||
|
||||
creator.Title = request.Title;
|
||||
|
||||
await context.SaveChangesAsync(ct);
|
||||
|
||||
await SendOkAsync(ct);
|
||||
}
|
||||
}
|
||||
156
backend/src/Web/Features/Contents/Handlers/CreateContent.cs
Normal file
156
backend/src/Web/Features/Contents/Handlers/CreateContent.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Hutopy.Web.Common.BlobStorage;
|
||||
using Hutopy.Web.Common.Security;
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public record PostContentRequest(
|
||||
Guid Id,
|
||||
Guid CreatorId,
|
||||
string Title,
|
||||
string Description,
|
||||
IFormFileCollection? Files,
|
||||
IFormFile? Thumbnail,
|
||||
string[]? ExternalUrls);
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class PostContentRequestValidator : Validator<PostContentRequest>
|
||||
{
|
||||
public PostContentRequestValidator()
|
||||
{
|
||||
RuleFor(r => r.Id)
|
||||
.NotNull().WithMessage("You should specify the Id")
|
||||
.NotEmpty().WithMessage("You should specify a valid/not empty Id");
|
||||
|
||||
RuleFor(r => r.CreatorId)
|
||||
.NotNull().WithMessage("You should specify the CreatorId")
|
||||
.NotEmpty().WithMessage("You should specify a valid/not empty CreatorId");
|
||||
|
||||
RuleFor(r => r.Title)
|
||||
.NotNull().WithMessage("You should specify the Title")
|
||||
.NotEmpty().WithMessage("You should specify a valid/not empty Title");
|
||||
|
||||
RuleFor(r => r.Description)
|
||||
.NotNull().WithMessage("You should specify the Description")
|
||||
.NotEmpty().WithMessage("You should specify a valid/not empty Description");
|
||||
|
||||
RuleForEach(r => r.ExternalUrls)
|
||||
.Must(url => Uri.IsWellFormedUriString(url, UriKind.Absolute) &&
|
||||
(url.StartsWith("http://") || url.StartsWith("https://")))
|
||||
.WithMessage("External URL must be a valid HTTP/HTTPS URL");
|
||||
|
||||
RuleFor(r => r.Thumbnail)
|
||||
.Must(file => file == null || file.ContentType.StartsWith("image/"))
|
||||
.WithMessage("Thumbnail must be an image");
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PostContent(
|
||||
AzureBlobStorage blobStorage,
|
||||
ContentDbContext context)
|
||||
: Endpoint<PostContentRequest>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/contents");
|
||||
Options(o => o.WithTags("Contents"));
|
||||
AllowFileUploads();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(PostContentRequest req, CancellationToken ct)
|
||||
{
|
||||
var urls = new ConcurrentBag<string>();
|
||||
string? thumbnailUrl = null;
|
||||
|
||||
await using var transaction = await context.Database.BeginTransactionAsync(ct);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
if (req.Files is not null)
|
||||
{
|
||||
await Parallel.ForEachAsync(req.Files, ct, async (file, ict) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var contentUrl = await SaveFileAsync(req.CreatorId, req.Id, file, ict);
|
||||
urls.Add(contentUrl);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Failed to upload file {FileName}: {Message}", file.FileName, ex.Message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (req.ExternalUrls is not null)
|
||||
{
|
||||
foreach (var externalUrl in req.ExternalUrls.Where(url => !string.IsNullOrWhiteSpace(url)))
|
||||
{
|
||||
urls.Add(externalUrl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (req.Thumbnail is not null)
|
||||
{
|
||||
try
|
||||
{
|
||||
thumbnailUrl = await SaveFileAsync(req.CreatorId, req.Id, req.Thumbnail, ct, isThumbnail: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Error uploading thumbnail: {Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
await context.Contents.AddAsync(new Content
|
||||
{
|
||||
Id = req.Id,
|
||||
CreatedBy = User.GetUserId(),
|
||||
Title = req.Title,
|
||||
Description = req.Description,
|
||||
Urls = urls.IsEmpty ? null : urls.ToArray(),
|
||||
ThumbnailUrl = thumbnailUrl,
|
||||
}, ct);
|
||||
|
||||
await context.SaveChangesAsync(ct);
|
||||
await transaction.CommitAsync(ct);
|
||||
|
||||
await SendOkAsync(new { Message = "Content published successfully!" }, ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await transaction.RollbackAsync(ct);
|
||||
Logger.LogError("Transaction failed: {Message}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> SaveFileAsync(
|
||||
Guid creatorId,
|
||||
Guid contentId,
|
||||
IFormFile file,
|
||||
CancellationToken ct = default,
|
||||
bool isThumbnail = false)
|
||||
{
|
||||
|
||||
var blobName = isThumbnail
|
||||
? $"{creatorId}/{SubDirectoryNames.Contents}/{contentId}/thumbnail-{file.FileName}"
|
||||
: $"{creatorId}/{SubDirectoryNames.Contents}/{contentId}/{file.FileName}";
|
||||
|
||||
|
||||
return await blobStorage.UploadFileAsync(
|
||||
ContainerNames.Creators,
|
||||
blobName,
|
||||
file.OpenReadStream(),
|
||||
file.ContentType,
|
||||
ct: ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using System.Text;
|
||||
using Hutopy.Web.Common.BlobStorage;
|
||||
using Hutopy.Web.Common.Security;
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
using Hutopy.Web.Features.Contents.Handlers.Models;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public record PostContentFromHtmlRequest(
|
||||
Guid Id,
|
||||
Guid CreatorId,
|
||||
string Title,
|
||||
string HtmlContent
|
||||
);
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class PostContentFromHtmlRequestValidator : Validator<PostContentFromHtmlRequest>
|
||||
{
|
||||
public PostContentFromHtmlRequestValidator()
|
||||
{
|
||||
RuleFor(r => r.Id)
|
||||
.NotNull().WithMessage("You should specify the Id")
|
||||
.NotEmpty().WithMessage("You should specify a valid/not empty Id");
|
||||
|
||||
RuleFor(r => r.CreatorId)
|
||||
.NotNull().WithMessage("You should specify the CreatorId")
|
||||
.NotEmpty().WithMessage("You should specify a valid/not empty CreatorId");
|
||||
|
||||
RuleFor(r => r.Title)
|
||||
.NotNull().WithMessage("You should specify the Title")
|
||||
.NotEmpty().WithMessage("You should specify a valid/not empty Title");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PostContentHtml(
|
||||
AzureBlobStorage blobStorage,
|
||||
ContentDbContext context)
|
||||
: Endpoint<PostContentFromHtmlRequest>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/contents/html");
|
||||
Options(o => o.WithTags("Contents"));
|
||||
AllowFileUploads();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
PostContentFromHtmlRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var htmlFileUrl = await SaveHtmlContentAsHtmlFileAsync(
|
||||
req.CreatorId,
|
||||
req.Id,
|
||||
req.HtmlContent,
|
||||
ct);
|
||||
|
||||
await context.Contents.AddAsync(
|
||||
new Content { Id = req.Id, CreatedBy = User.GetUserId(), Title = req.Title, HtmlFileUrl = htmlFileUrl },
|
||||
ct);
|
||||
|
||||
await context.SaveChangesAsync(ct);
|
||||
|
||||
var content = await context
|
||||
.Contents
|
||||
.Select(c => new ContentModel
|
||||
{
|
||||
Id = c.Id,
|
||||
CreatedBy = c.CreatedBy,
|
||||
CreatedByName = c.Creator!.Name,
|
||||
CreatedByPortraitUrl = c.Creator.Images.Logo,
|
||||
CreatedAt = c.CreatedAt,
|
||||
DeletedBy = c.DeletedBy,
|
||||
DeletedAt = c.DeletedAt,
|
||||
Title = c.Title,
|
||||
Description = c.Description,
|
||||
Urls = c.Urls,
|
||||
ThumbnailUrl = c.ThumbnailUrl,
|
||||
HtmlFileUrl = htmlFileUrl
|
||||
})
|
||||
.SingleOrDefaultAsync(
|
||||
c => c.Id == req.Id,
|
||||
cancellationToken: ct);
|
||||
|
||||
await SendOkAsync(content, ct);
|
||||
}
|
||||
|
||||
private async Task<string> SaveHtmlContentAsHtmlFileAsync(
|
||||
Guid creatorId,
|
||||
Guid contentId,
|
||||
string htmlContent,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var fileName = $"{contentId}.html";
|
||||
var filePath = $"{creatorId}/{SubDirectoryNames.Contents}/{fileName}";
|
||||
|
||||
// Convert the HTML string into a stream
|
||||
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(htmlContent));
|
||||
|
||||
// Upload the stream as an HTML file
|
||||
var url = await blobStorage.UploadFileAsync(
|
||||
ContainerNames.Creators,
|
||||
filePath,
|
||||
stream,
|
||||
"text/html",
|
||||
ct: ct);
|
||||
|
||||
return url;
|
||||
}
|
||||
}
|
||||
91
backend/src/Web/Features/Contents/Handlers/CreateCreator.cs
Normal file
91
backend/src/Web/Features/Contents/Handlers/CreateCreator.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System.Net;
|
||||
using FluentValidation.Results;
|
||||
using Hutopy.Web.Common.Security;
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
using Npgsql;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public record CreateCreatorRequest(
|
||||
Guid CreatorId,
|
||||
string Name);
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class CreateCreatorRequestValidator : Validator<CreateCreatorRequest>
|
||||
{
|
||||
public CreateCreatorRequestValidator()
|
||||
{
|
||||
RuleFor(r => r.CreatorId)
|
||||
.NotNull().WithMessage("You should specify the CreatorId")
|
||||
.NotEmpty().WithMessage("You should specify a valid/not empty CreatorId");
|
||||
|
||||
RuleFor(r => r.Name)
|
||||
.NotNull().WithMessage("You should specify the Name")
|
||||
.NotEmpty().WithMessage("You should specify a valid/not empty Name");
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class CreateCreatorHandler(
|
||||
ContentDbContext context)
|
||||
: Endpoint<CreateCreatorRequest>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/creators");
|
||||
Options(o => o.WithTags("Creators"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
CreateCreatorRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
await context.Creators.AddAsync(
|
||||
new Creator
|
||||
{
|
||||
Id = req.CreatorId,
|
||||
CreatedBy = User.GetUserId(),
|
||||
Name = req.Name,
|
||||
Colors =
|
||||
{
|
||||
Primary = "#6200EE",
|
||||
OnPrimary = "#FFFFFF",
|
||||
Secondary = "#03DAC6",
|
||||
OnSecondary = "#000000",
|
||||
Surface = "#FFFFFF",
|
||||
OnSurface = "#000000",
|
||||
Error = "#B00020",
|
||||
OnError = "#FFFFFF",
|
||||
Background = "#FFFFFF",
|
||||
OnBackground = "#000000",
|
||||
}
|
||||
},
|
||||
ct);
|
||||
|
||||
await context.SaveChangesAsync(ct);
|
||||
|
||||
await SendOkAsync(ct);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e.InnerException is PostgresException innerException)
|
||||
{
|
||||
if (innerException.ConstraintName == "IX_Creators_NormalizedName")
|
||||
{
|
||||
await SendResultAsync(new ProblemDetails(
|
||||
[new ValidationFailure(nameof(Creator.Name), "The name is already taken.")],
|
||||
(int)HttpStatusCode.Conflict));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await SendResultAsync(new ProblemDetails(
|
||||
[new ValidationFailure(nameof(Creator.Name), e.Message)],
|
||||
(int)HttpStatusCode.Conflict));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
backend/src/Web/Features/Contents/Handlers/DeleteContent.cs
Normal file
60
backend/src/Web/Features/Contents/Handlers/DeleteContent.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using Hutopy.Web.Common;
|
||||
using Hutopy.Web.Common.Security;
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public record DeleteContentRequest(
|
||||
Guid ContentId);
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class DeleteContentRequestValidator : Validator<DeleteContentRequest>
|
||||
{
|
||||
public DeleteContentRequestValidator()
|
||||
{
|
||||
RuleFor(r => r.ContentId)
|
||||
.NotNull().WithMessage("You should specify the ContentId")
|
||||
.NotEmpty().WithMessage("You should specify a valid/not empty ContentId");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DeleteContent(
|
||||
ContentDbContext context)
|
||||
: Endpoint<DeleteContentRequest>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Delete("/api/contents/{ContentId}");
|
||||
Options(o => o.WithTags("Contents"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
DeleteContentRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var content = await context.Contents.FindAsync(
|
||||
[req.ContentId],
|
||||
ct);
|
||||
|
||||
if (content is null)
|
||||
{
|
||||
await SendNotFoundAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
var userId = HttpContext.User.GetUserId();
|
||||
if (content.CreatedBy != userId)
|
||||
{
|
||||
await SendForbiddenAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
content.DeletedAt = DateTimeOffset.UtcNow;
|
||||
content.DeletedBy = userId;
|
||||
|
||||
await context.SaveChangesAsync(ct);
|
||||
|
||||
await SendOkAsync(ct);
|
||||
}
|
||||
}
|
||||
59
backend/src/Web/Features/Contents/Handlers/GetContent.cs
Normal file
59
backend/src/Web/Features/Contents/Handlers/GetContent.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using Hutopy.Web.Extensions;
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
using Hutopy.Web.Features.Contents.Handlers.Models;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class GetContentRequest
|
||||
{
|
||||
public Guid ContentId { get; set; }
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public class GetContent(
|
||||
ContentDbContext context)
|
||||
: Endpoint<GetContentRequest, ContentModel>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/contents/{ContentId:guid}");
|
||||
Options(o => o.WithTags("Contents"));
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
GetContentRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var content = await context
|
||||
.Contents
|
||||
.Select(c => new ContentModel
|
||||
{
|
||||
Id = c.Id,
|
||||
CreatedBy = c.CreatedBy,
|
||||
CreatedByName = c.Creator!.Name,
|
||||
CreatedByPortraitUrl = c.Creator.Images.Logo,
|
||||
CreatedAt = c.CreatedAt,
|
||||
DeletedBy = c.DeletedBy,
|
||||
DeletedAt = c.DeletedAt,
|
||||
Title = c.Title,
|
||||
Description = c.Description,
|
||||
Urls = c.Urls,
|
||||
ThumbnailUrl = c.ThumbnailUrl,
|
||||
HtmlFileUrl = c.HtmlFileUrl ?? "",
|
||||
Reactions = c.Reactions.Select(x => new ReactionModel
|
||||
{
|
||||
Reaction = x.Reaction.FromEnum(), UserId = x.UserId, UserName = x.UserName
|
||||
}).ToList()
|
||||
})
|
||||
.SingleOrDefaultAsync(
|
||||
c => c.Id == req.ContentId,
|
||||
cancellationToken: ct);
|
||||
|
||||
if (content is null)
|
||||
await SendNotFoundAsync(cancellation: ct);
|
||||
else
|
||||
await SendAsync(content, cancellation: ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using Hutopy.Web.Extensions;
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
using Hutopy.Web.Features.Contents.Handlers.Models;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class GetContentsByCreatorRequest
|
||||
{
|
||||
public Guid CreatorId { get; set; }
|
||||
[BindFrom("page_size")] public int PageSize { get; set; } = 10;
|
||||
[BindFrom("last_id")] public Guid? LastId { get; set; }
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public class GetContentsByCreatorHandler(
|
||||
ContentDbContext context)
|
||||
: Endpoint<GetContentsByCreatorRequest, List<ContentModel>>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/contents/creator/{CreatorId:guid}");
|
||||
Options(o => o.WithTags("Contents"));
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
GetContentsByCreatorRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var query = context.Contents
|
||||
.Where(c => c.CreatedBy == req.CreatorId && c.DeletedAt == null)
|
||||
.OrderByDescending(c => c.CreatedAt);
|
||||
|
||||
if (req.LastId.HasValue)
|
||||
{
|
||||
query = query.Where(c => c.Id > req.LastId.Value)
|
||||
.OrderByDescending(c => c.CreatedAt);
|
||||
}
|
||||
|
||||
var content = await query
|
||||
.Select(c => new ContentModel
|
||||
{
|
||||
Id = c.Id,
|
||||
CreatedBy = c.CreatedBy,
|
||||
CreatedByName = c.Creator!.Name,
|
||||
CreatedByPortraitUrl = c.Creator.Images.Logo,
|
||||
CreatedAt = c.CreatedAt,
|
||||
DeletedBy = c.DeletedBy,
|
||||
DeletedAt = c.DeletedAt,
|
||||
Title = c.Title,
|
||||
Description = c.Description,
|
||||
Urls = c.Urls,
|
||||
ThumbnailUrl = c.ThumbnailUrl,
|
||||
HtmlFileUrl = c.HtmlFileUrl ?? "",
|
||||
Reactions = c.Reactions.Select(x => new ReactionModel
|
||||
{
|
||||
Reaction = x.Reaction.FromEnum(),
|
||||
UserId = x.UserId,
|
||||
UserName = x.UserName
|
||||
}).ToList()
|
||||
})
|
||||
.Take(req.PageSize)
|
||||
.ToListAsync(ct);
|
||||
|
||||
await SendAsync(content, cancellation: ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
using Hutopy.Web.Features.Contents.Handlers.Models;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class GetCreatorByAliasRequest
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public record struct GetCreatorByAliasResponse(
|
||||
Guid Id,
|
||||
Guid CreatedBy,
|
||||
DateTimeOffset CreatedAt,
|
||||
bool Verified,
|
||||
bool AcceptDonation,
|
||||
string Name,
|
||||
string? Title,
|
||||
Socials Socials,
|
||||
Colors Colors,
|
||||
PresentationInfos PresentationInfos,
|
||||
Images Images);
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class GetCreatorByAliasRequestValidator
|
||||
: Validator<GetCreatorByAliasRequest>
|
||||
{
|
||||
public GetCreatorByAliasRequestValidator()
|
||||
{
|
||||
RuleFor(r => r.Name)
|
||||
.NotNull().WithMessage("You should specify the Name")
|
||||
.NotEmpty().WithMessage("You should specify a valid/not empty Name");
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public class GetCreatorByAliasHandler(
|
||||
ContentDbContext context)
|
||||
: Endpoint<GetCreatorByAliasRequest, GetCreatorByAliasResponse>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/creators/@{Name}");
|
||||
Options((o => o.WithTags("Creators")));
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
GetCreatorByAliasRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var creatorName = req.Name.ToLower();
|
||||
|
||||
var creator = await context
|
||||
.Creators
|
||||
.Where(c => EF.Functions.ILike(c.Name, creatorName))
|
||||
.FirstOrDefaultAsync(ct);
|
||||
|
||||
if (creator is null)
|
||||
{
|
||||
await SendNotFoundAsync(ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
var model = new GetCreatorByAliasResponse(
|
||||
creator.Id,
|
||||
creator.CreatedBy,
|
||||
creator.CreatedAt,
|
||||
creator.Verified,
|
||||
creator.AcceptDonation,
|
||||
creator.Name,
|
||||
creator.Title,
|
||||
creator.Socials,
|
||||
creator.Colors,
|
||||
creator.PresentationInfos,
|
||||
creator.Images);
|
||||
|
||||
await SendAsync(model, cancellation: ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
backend/src/Web/Features/Contents/Handlers/GetCreatorById.cs
Normal file
48
backend/src/Web/Features/Contents/Handlers/GetCreatorById.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class GetCreatorByIdRequest
|
||||
{
|
||||
public required Guid CreatorId { get; set; }
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class GetCreatorByIdRequestValidator
|
||||
: Validator<GetCreatorByIdRequest>
|
||||
{
|
||||
public GetCreatorByIdRequestValidator()
|
||||
{
|
||||
RuleFor(r => r.CreatorId)
|
||||
.NotNull().WithMessage("You should specify the CreatorId")
|
||||
.NotEmpty().WithMessage("You should specify a valid/not empty CreatorId");
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public class GetCreatorByIdHandler(
|
||||
ContentDbContext context)
|
||||
: Endpoint<GetCreatorByIdRequest, Creator>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/creators/{CreatorId}");
|
||||
Options((o => o.WithTags("Creators")));
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
GetCreatorByIdRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var creator = await context
|
||||
.Creators
|
||||
.FindAsync(
|
||||
[req.CreatorId],
|
||||
cancellationToken: ct);
|
||||
|
||||
if (creator is null) await SendNotFoundAsync(ct);
|
||||
else await SendAsync(creator, cancellation: ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Hutopy.Web.Common;
|
||||
using Hutopy.Web.Common.Security;
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public class GetCreatorProfileHandler(
|
||||
ContentDbContext context)
|
||||
: EndpointWithoutRequest<Creator>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/creators/profile");
|
||||
Options((o => o.WithTags("Creators")));
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
CancellationToken ct)
|
||||
{
|
||||
var creator = await context
|
||||
.Creators
|
||||
.FindAsync(
|
||||
[HttpContext.User.GetUserId()],
|
||||
cancellationToken: ct);
|
||||
|
||||
if (creator is null) await SendNotFoundAsync(ct);
|
||||
else await SendAsync(creator, cancellation: ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using Hutopy.Web.Extensions;
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
using Hutopy.Web.Features.Contents.Handlers.Models;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class GetFeaturedContentsRequest
|
||||
{
|
||||
[BindFrom("page_size")] public int PageSize { get; set; } = 10;
|
||||
[BindFrom("last_id")] public Guid? LastId { get; set; }
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public class GetFeaturedContentsHandler(
|
||||
ContentDbContext context)
|
||||
: Endpoint<GetFeaturedContentsRequest, List<ContentModel>>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/contents/featured");
|
||||
Options(o => o.WithTags("Contents"));
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
GetFeaturedContentsRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var query = context.Contents
|
||||
.Where(c => c.DeletedAt == null);
|
||||
|
||||
if (req.LastId.HasValue)
|
||||
{
|
||||
query = query.Where(c => c.Id > req.LastId.Value);
|
||||
}
|
||||
|
||||
query = query.OrderByDescending(x => x.Reactions.Count);
|
||||
|
||||
var content = await query
|
||||
.Select(c => new ContentModel
|
||||
{
|
||||
Id = c.Id,
|
||||
CreatedBy = c.CreatedBy,
|
||||
CreatedByName = c.Creator!.Name,
|
||||
CreatedByPortraitUrl = c.Creator.Images.Logo,
|
||||
CreatedAt = c.CreatedAt,
|
||||
DeletedBy = c.DeletedBy,
|
||||
DeletedAt = c.DeletedAt,
|
||||
Title = c.Title,
|
||||
Description = c.Description,
|
||||
Urls = c.Urls,
|
||||
ThumbnailUrl = c.ThumbnailUrl,
|
||||
Reactions = c.Reactions.Select(x => new ReactionModel
|
||||
{
|
||||
Reaction = x.Reaction.FromEnum(),
|
||||
UserId = x.UserId,
|
||||
UserName = x.UserName
|
||||
}).ToList()
|
||||
})
|
||||
.Take(req.PageSize)
|
||||
.ToListAsync(ct);
|
||||
|
||||
await SendAsync(content, cancellation: ct);
|
||||
}
|
||||
}
|
||||
86
backend/src/Web/Features/Contents/Handlers/InsertImage.cs
Normal file
86
backend/src/Web/Features/Contents/Handlers/InsertImage.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using Hutopy.Web.Common.BlobStorage;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public record InsertImagesRequest(
|
||||
Guid Id,
|
||||
Guid CreatorId,
|
||||
IFormFileCollection? Files
|
||||
);
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class InsertImagesRequestValidator : Validator<InsertImagesRequest>
|
||||
{
|
||||
public InsertImagesRequestValidator()
|
||||
{
|
||||
RuleFor(r => r.Id)
|
||||
.NotNull().WithMessage("You should specify the Id")
|
||||
.NotEmpty().WithMessage("You should specify a valid/not empty Id");
|
||||
|
||||
RuleFor(r => r.CreatorId)
|
||||
.NotNull().WithMessage("You should specify the CreatorId")
|
||||
.NotEmpty().WithMessage("You should specify a valid/not empty CreatorId");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class InsertImages(
|
||||
AzureBlobStorage blobStorage)
|
||||
: Endpoint<InsertImagesRequest>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/content/insert-image/");
|
||||
Options(o => o.WithTags("Contents"));
|
||||
AllowFileUploads();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
InsertImagesRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var urls = new List<string>();
|
||||
if (req.Files is not null)
|
||||
{
|
||||
await Parallel.ForEachAsync(
|
||||
req.Files,
|
||||
ct,
|
||||
async (
|
||||
file,
|
||||
ict) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var contentUrl = await SaveFileAsync(
|
||||
req.CreatorId,
|
||||
req.Id,
|
||||
file,
|
||||
ict);
|
||||
urls.Add(contentUrl);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("{ErrorMessage}", ex.Message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await SendOkAsync(urls, ct);
|
||||
}
|
||||
|
||||
private async Task<string> SaveFileAsync(
|
||||
Guid creatorId,
|
||||
Guid contentId,
|
||||
IFormFile file,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var url = await blobStorage.UploadFileAsync(
|
||||
ContainerNames.Creators,
|
||||
$"{creatorId}/{SubDirectoryNames.Contents}/{contentId}/{file.FileName}",
|
||||
file.OpenReadStream(),
|
||||
file.ContentType,
|
||||
ct: ct);
|
||||
|
||||
return url;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Hutopy.Web.Features.Contents.Handlers.Models;
|
||||
|
||||
[PublicAPI]
|
||||
public class ContentModel
|
||||
{
|
||||
public required Guid Id { get; init; }
|
||||
public required Guid CreatedBy { get; init; }
|
||||
public required string CreatedByName { get; init; }
|
||||
public required string? CreatedByPortraitUrl { get; init; }
|
||||
public required DateTimeOffset CreatedAt { get; init; }
|
||||
public Guid? DeletedBy { get; init; }
|
||||
public DateTimeOffset? DeletedAt { get; init; }
|
||||
public required string Title { get; init; }
|
||||
public required string Description { get; init; }
|
||||
public string HtmlFileUrl { get; init; } = "";
|
||||
public required string[]? Urls { get; init; }
|
||||
public string? ThumbnailUrl { get; init; }
|
||||
public IList<ReactionModel>? Reactions { get; set; } = new List<ReactionModel>();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Hutopy.Web.Features.Contents.Handlers.Models;
|
||||
|
||||
[PublicAPI]
|
||||
public record FollowModel(
|
||||
Guid CreatorId,
|
||||
string CreatorName,
|
||||
string? CreatorPortraitUrl);
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Hutopy.Web.Features.Contents.Handlers.Models;
|
||||
|
||||
public class ReactionModel
|
||||
{
|
||||
public required string Reaction { get; set; }
|
||||
public required Guid UserId { get; set; }
|
||||
public required string UserName { get; set; }
|
||||
}
|
||||
36
backend/src/Web/Features/Contents/Handlers/RemoveReaction.cs
Normal file
36
backend/src/Web/Features/Contents/Handlers/RemoveReaction.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
|
||||
namespace Hutopy.Web.Features.Contents.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class RemoveReactionRequest
|
||||
{
|
||||
public required Guid ContentId { get; set; }
|
||||
public required Guid UserId { get; set; }
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public class RemoveReaction(
|
||||
ContentDbContext context)
|
||||
: Endpoint<RemoveReactionRequest>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/content/reaction/remove");
|
||||
Options(o => o.WithTags("Contents"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
RemoveReactionRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var content = await context.Contents
|
||||
.SingleAsync(x => x.Id == req.ContentId, ct);
|
||||
|
||||
var reaction = content.Reactions.Single(x => x.UserId == req.UserId);
|
||||
|
||||
content.Reactions.Remove(reaction);
|
||||
|
||||
await context.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
9
backend/src/Web/Features/Memberships/Data/Creator.cs
Normal file
9
backend/src/Web/Features/Memberships/Data/Creator.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Hutopy.Web.Features.Memberships.Data;
|
||||
|
||||
public class Creator
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string? StripeAccountId { get; set; }
|
||||
public string PortraitUrl { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
namespace Hutopy.Web.Features.Memberships.Data;
|
||||
|
||||
public sealed class MembershipDbContext(
|
||||
DbContextOptions<MembershipDbContext> options)
|
||||
: DbContext(options)
|
||||
{
|
||||
public const string SchemaName = "Membership";
|
||||
|
||||
public DbSet<Creator> Creators => Set<Creator>();
|
||||
public DbSet<Subscription> Subscriptions => Set<Subscription>();
|
||||
public DbSet<Tier> Tiers => Set<Tier>();
|
||||
public DbSet<Tip> Tips => Set<Tip>();
|
||||
public DbSet<Transaction> Transactions => Set<Transaction>();
|
||||
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.HasDefaultSchema(SchemaName);
|
||||
|
||||
modelBuilder.Entity<Creator>();
|
||||
|
||||
modelBuilder
|
||||
.Entity<Subscription>()
|
||||
.Property(c => c.CreatedAt)
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
modelBuilder
|
||||
.Entity<Subscription>()
|
||||
.HasOne(c => c.Creator)
|
||||
.WithMany()
|
||||
.HasForeignKey(c => c.CreatorId);
|
||||
|
||||
modelBuilder
|
||||
.Entity<Tier>()
|
||||
.HasOne(c => c.Creator)
|
||||
.WithMany()
|
||||
.HasForeignKey(c => c.CreatorId);
|
||||
|
||||
modelBuilder
|
||||
.Entity<Tier>()
|
||||
.Property(c => c.CreatedAt)
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
modelBuilder
|
||||
.Entity<Tip>()
|
||||
.Property(c => c.CreatedAt)
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
modelBuilder
|
||||
.Entity<Transaction>()
|
||||
.Property(c => c.CreatedAt)
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Hutopy.Web.Features.Contents.Data;
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Data;
|
||||
|
||||
public static class InitializerExtensions
|
||||
{
|
||||
public static async Task InitialiseMembershipDbContextAsync(this WebApplication app)
|
||||
{
|
||||
using var scope = app.Services.CreateScope();
|
||||
|
||||
var initializer = scope.ServiceProvider.GetRequiredService<MembershipDbContextInitializer>();
|
||||
|
||||
await initializer.InitialiseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public class MembershipDbContextInitializer(
|
||||
ILogger<MembershipDbContextInitializer> logger,
|
||||
MembershipDbContext context
|
||||
)
|
||||
{
|
||||
public async Task InitialiseAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await context.Database.MigrateAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "An error occurred while initialising the membership database.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
288
backend/src/Web/Features/Memberships/Data/Migrations/20241022191000_Initial.Designer.cs
generated
Normal file
288
backend/src/Web/Features/Memberships/Data/Migrations/20241022191000_Initial.Designer.cs
generated
Normal file
@@ -0,0 +1,288 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Hutopy.Web.Features.Memberships.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(MembershipDbContext))]
|
||||
[Migration("20241022191000_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("Membership")
|
||||
.HasAnnotation("ProductVersion", "8.0.10")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Creator", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("StripeAccountId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Creators", "Membership");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Subscription", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset?>("EndDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTimeOffset>("StartDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("StripeSessionId")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("StripeSubscriptionId")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<Guid>("TierId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatorId");
|
||||
|
||||
b.HasIndex("TierId");
|
||||
|
||||
b.ToTable("Subscriptions", "Membership");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tier", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("CurrencyCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<decimal>("Price")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("StripePriceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("StripeProductId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatorId");
|
||||
|
||||
b.ToTable("Tiers", "Membership");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tip", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Amount")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("CreatorName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Currency")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("StripeSessionId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("TipperId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("TipperName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("TransactionId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TransactionId");
|
||||
|
||||
b.ToTable("Tips", "Membership");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Transaction", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Amount")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<string>("Currency")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("StripeInvoiceUrl")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("SubscriptionId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("Timestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SubscriptionId");
|
||||
|
||||
b.ToTable("Transactions", "Membership");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Subscription", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Memberships.Data.Creator", "Creator")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatorId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Hutopy.Web.Features.Memberships.Data.Tier", "Tier")
|
||||
.WithMany("Subscriptions")
|
||||
.HasForeignKey("TierId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Creator");
|
||||
|
||||
b.Navigation("Tier");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tier", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Memberships.Data.Creator", "Creator")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatorId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Creator");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tip", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Memberships.Data.Transaction", "Transaction")
|
||||
.WithMany()
|
||||
.HasForeignKey("TransactionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Transaction");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Transaction", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Memberships.Data.Subscription", null)
|
||||
.WithMany("Transactions")
|
||||
.HasForeignKey("SubscriptionId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Subscription", b =>
|
||||
{
|
||||
b.Navigation("Transactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tier", b =>
|
||||
{
|
||||
b.Navigation("Subscriptions");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.EnsureSchema(
|
||||
name: "Membership");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Creators",
|
||||
schema: "Membership",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Name = table.Column<string>(type: "text", nullable: false),
|
||||
StripeAccountId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Creators", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Tiers",
|
||||
schema: "Membership",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"),
|
||||
CreatorId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
Description = table.Column<string>(type: "character varying(4096)", maxLength: 4096, nullable: false),
|
||||
Price = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
CurrencyCode = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
StripeProductId = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
StripePriceId = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Tiers", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Tiers_Creators_CreatorId",
|
||||
column: x => x.CreatorId,
|
||||
principalSchema: "Membership",
|
||||
principalTable: "Creators",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Subscriptions",
|
||||
schema: "Membership",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"),
|
||||
UserId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CreatorId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
TierId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
StartDate = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
|
||||
EndDate = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
StripeSessionId = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||
StripeSubscriptionId = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Subscriptions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Subscriptions_Creators_CreatorId",
|
||||
column: x => x.CreatorId,
|
||||
principalSchema: "Membership",
|
||||
principalTable: "Creators",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Subscriptions_Tiers_TierId",
|
||||
column: x => x.TierId,
|
||||
principalSchema: "Membership",
|
||||
principalTable: "Tiers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Transactions",
|
||||
schema: "Membership",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"),
|
||||
Amount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
Currency = table.Column<string>(type: "text", nullable: false),
|
||||
Type = table.Column<string>(type: "text", nullable: false),
|
||||
Timestamp = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
StripeInvoiceUrl = table.Column<string>(type: "text", nullable: true),
|
||||
SubscriptionId = table.Column<Guid>(type: "uuid", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Transactions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Transactions_Subscriptions_SubscriptionId",
|
||||
column: x => x.SubscriptionId,
|
||||
principalSchema: "Membership",
|
||||
principalTable: "Subscriptions",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Tips",
|
||||
schema: "Membership",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"),
|
||||
StripeSessionId = table.Column<string>(type: "text", nullable: false),
|
||||
TipperId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
TipperName = table.Column<string>(type: "text", nullable: false),
|
||||
CreatorId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CreatorName = table.Column<string>(type: "text", nullable: false),
|
||||
Amount = table.Column<decimal>(type: "numeric", nullable: false),
|
||||
Currency = table.Column<string>(type: "text", nullable: false),
|
||||
Message = table.Column<string>(type: "text", nullable: false),
|
||||
TransactionId = table.Column<Guid>(type: "uuid", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Tips", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Tips_Transactions_TransactionId",
|
||||
column: x => x.TransactionId,
|
||||
principalSchema: "Membership",
|
||||
principalTable: "Transactions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Subscriptions_CreatorId",
|
||||
schema: "Membership",
|
||||
table: "Subscriptions",
|
||||
column: "CreatorId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Subscriptions_TierId",
|
||||
schema: "Membership",
|
||||
table: "Subscriptions",
|
||||
column: "TierId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Tiers_CreatorId",
|
||||
schema: "Membership",
|
||||
table: "Tiers",
|
||||
column: "CreatorId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Tips_TransactionId",
|
||||
schema: "Membership",
|
||||
table: "Tips",
|
||||
column: "TransactionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Transactions_SubscriptionId",
|
||||
schema: "Membership",
|
||||
table: "Transactions",
|
||||
column: "SubscriptionId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Tips",
|
||||
schema: "Membership");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Transactions",
|
||||
schema: "Membership");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Subscriptions",
|
||||
schema: "Membership");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Tiers",
|
||||
schema: "Membership");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Creators",
|
||||
schema: "Membership");
|
||||
}
|
||||
}
|
||||
}
|
||||
292
backend/src/Web/Features/Memberships/Data/Migrations/20241022203207_PortraitUrlToCreator.Designer.cs
generated
Normal file
292
backend/src/Web/Features/Memberships/Data/Migrations/20241022203207_PortraitUrlToCreator.Designer.cs
generated
Normal file
@@ -0,0 +1,292 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Hutopy.Web.Features.Memberships.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(MembershipDbContext))]
|
||||
[Migration("20241022203207_PortraitUrlToCreator")]
|
||||
partial class PortraitUrlToCreator
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("Membership")
|
||||
.HasAnnotation("ProductVersion", "8.0.10")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Creator", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PortraitUrl")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("StripeAccountId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Creators", "Membership");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Subscription", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset?>("EndDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTimeOffset>("StartDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("StripeSessionId")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("StripeSubscriptionId")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<Guid>("TierId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatorId");
|
||||
|
||||
b.HasIndex("TierId");
|
||||
|
||||
b.ToTable("Subscriptions", "Membership");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tier", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("CurrencyCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<decimal>("Price")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("StripePriceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("StripeProductId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatorId");
|
||||
|
||||
b.ToTable("Tiers", "Membership");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tip", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Amount")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("CreatorName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Currency")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("StripeSessionId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("TipperId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("TipperName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("TransactionId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TransactionId");
|
||||
|
||||
b.ToTable("Tips", "Membership");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Transaction", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Amount")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<string>("Currency")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("StripeInvoiceUrl")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("SubscriptionId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("Timestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SubscriptionId");
|
||||
|
||||
b.ToTable("Transactions", "Membership");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Subscription", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Memberships.Data.Creator", "Creator")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatorId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Hutopy.Web.Features.Memberships.Data.Tier", "Tier")
|
||||
.WithMany("Subscriptions")
|
||||
.HasForeignKey("TierId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Creator");
|
||||
|
||||
b.Navigation("Tier");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tier", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Memberships.Data.Creator", "Creator")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatorId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Creator");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tip", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Memberships.Data.Transaction", "Transaction")
|
||||
.WithMany()
|
||||
.HasForeignKey("TransactionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Transaction");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Transaction", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Memberships.Data.Subscription", null)
|
||||
.WithMany("Transactions")
|
||||
.HasForeignKey("SubscriptionId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Subscription", b =>
|
||||
{
|
||||
b.Navigation("Transactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tier", b =>
|
||||
{
|
||||
b.Navigation("Subscriptions");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class PortraitUrlToCreator : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "PortraitUrl",
|
||||
schema: "Membership",
|
||||
table: "Creators",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PortraitUrl",
|
||||
schema: "Membership",
|
||||
table: "Creators");
|
||||
}
|
||||
}
|
||||
}
|
||||
292
backend/src/Web/Features/Memberships/Data/Migrations/20241216215210_UpdateSeedData.Designer.cs
generated
Normal file
292
backend/src/Web/Features/Memberships/Data/Migrations/20241216215210_UpdateSeedData.Designer.cs
generated
Normal file
@@ -0,0 +1,292 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Hutopy.Web.Features.Memberships.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(MembershipDbContext))]
|
||||
[Migration("20241216215210_UpdateSeedData")]
|
||||
partial class UpdateSeedData
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("Membership")
|
||||
.HasAnnotation("ProductVersion", "8.0.10")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Creator", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PortraitUrl")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("StripeAccountId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Creators", "Membership");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Subscription", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset?>("EndDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTimeOffset>("StartDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("StripeSessionId")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("StripeSubscriptionId")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<Guid>("TierId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatorId");
|
||||
|
||||
b.HasIndex("TierId");
|
||||
|
||||
b.ToTable("Subscriptions", "Membership");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tier", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("CurrencyCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<decimal>("Price")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("StripePriceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("StripeProductId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatorId");
|
||||
|
||||
b.ToTable("Tiers", "Membership");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tip", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Amount")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("CreatorName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Currency")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("StripeSessionId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("TipperId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("TipperName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("TransactionId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TransactionId");
|
||||
|
||||
b.ToTable("Tips", "Membership");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Transaction", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Amount")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<string>("Currency")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("StripeInvoiceUrl")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("SubscriptionId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("Timestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SubscriptionId");
|
||||
|
||||
b.ToTable("Transactions", "Membership");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Subscription", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Memberships.Data.Creator", "Creator")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatorId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Hutopy.Web.Features.Memberships.Data.Tier", "Tier")
|
||||
.WithMany("Subscriptions")
|
||||
.HasForeignKey("TierId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Creator");
|
||||
|
||||
b.Navigation("Tier");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tier", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Memberships.Data.Creator", "Creator")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatorId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Creator");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tip", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Memberships.Data.Transaction", "Transaction")
|
||||
.WithMany()
|
||||
.HasForeignKey("TransactionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Transaction");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Transaction", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Memberships.Data.Subscription", null)
|
||||
.WithMany("Transactions")
|
||||
.HasForeignKey("SubscriptionId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Subscription", b =>
|
||||
{
|
||||
b.Navigation("Transactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tier", b =>
|
||||
{
|
||||
b.Navigation("Subscriptions");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class UpdateSeedData : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Hutopy.Web.Features.Memberships.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(MembershipDbContext))]
|
||||
partial class MembershipDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("Membership")
|
||||
.HasAnnotation("ProductVersion", "8.0.10")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Creator", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PortraitUrl")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("StripeAccountId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Creators", "Membership");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Subscription", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset?>("EndDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTimeOffset>("StartDate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("StripeSessionId")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("StripeSubscriptionId")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<Guid>("TierId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatorId");
|
||||
|
||||
b.HasIndex("TierId");
|
||||
|
||||
b.ToTable("Subscriptions", "Membership");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tier", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("CurrencyCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4096)
|
||||
.HasColumnType("character varying(4096)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<decimal>("Price")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<string>("StripePriceId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("StripeProductId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatorId");
|
||||
|
||||
b.ToTable("Tiers", "Membership");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tip", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Amount")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatorId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("CreatorName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Currency")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Message")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("StripeSessionId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("TipperId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("TipperName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("TransactionId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TransactionId");
|
||||
|
||||
b.ToTable("Tips", "Membership");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Transaction", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("Amount")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<string>("Currency")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("StripeInvoiceUrl")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid?>("SubscriptionId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime>("Timestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SubscriptionId");
|
||||
|
||||
b.ToTable("Transactions", "Membership");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Subscription", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Memberships.Data.Creator", "Creator")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatorId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Hutopy.Web.Features.Memberships.Data.Tier", "Tier")
|
||||
.WithMany("Subscriptions")
|
||||
.HasForeignKey("TierId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Creator");
|
||||
|
||||
b.Navigation("Tier");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tier", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Memberships.Data.Creator", "Creator")
|
||||
.WithMany()
|
||||
.HasForeignKey("CreatorId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Creator");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tip", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Memberships.Data.Transaction", "Transaction")
|
||||
.WithMany()
|
||||
.HasForeignKey("TransactionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Transaction");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Transaction", b =>
|
||||
{
|
||||
b.HasOne("Hutopy.Web.Features.Memberships.Data.Subscription", null)
|
||||
.WithMany("Transactions")
|
||||
.HasForeignKey("SubscriptionId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Subscription", b =>
|
||||
{
|
||||
b.Navigation("Transactions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Memberships.Data.Tier", b =>
|
||||
{
|
||||
b.Navigation("Subscriptions");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
21
backend/src/Web/Features/Memberships/Data/Subscription.cs
Normal file
21
backend/src/Web/Features/Memberships/Data/Subscription.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Data;
|
||||
|
||||
public class Subscription
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public Guid CreatorId { get; set; }
|
||||
public Creator Creator { get; set; }
|
||||
public Guid TierId { get; set; }
|
||||
public Tier Tier { get; set; }
|
||||
public DateTimeOffset StartDate { get; set; }
|
||||
public DateTimeOffset? EndDate { get; set; }
|
||||
public bool IsActive => EndDate == null || EndDate > DateTimeOffset.UtcNow;
|
||||
[MaxLength(255)]public string? StripeSessionId { get; set; }
|
||||
[MaxLength(255)]public string? StripeSubscriptionId { get; set; }
|
||||
|
||||
public ICollection<Transaction> Transactions { get; set; } = [];
|
||||
}
|
||||
19
backend/src/Web/Features/Memberships/Data/Tier.cs
Normal file
19
backend/src/Web/Features/Memberships/Data/Tier.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Data;
|
||||
|
||||
public class Tier
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public Guid CreatorId { get; set; }
|
||||
public Creator Creator { get; set; } = null!;
|
||||
[MaxLength(128)] public string Name { get; set; } = null!;
|
||||
[MaxLength(4096)] public string Description { get; set; } = null!;
|
||||
public decimal Price { get; set; }
|
||||
[MaxLength(128)] public string CurrencyCode { get; set; } = null!;
|
||||
[MaxLength(128)] public string StripeProductId { get; set; } = null!;
|
||||
[MaxLength(128)] public string StripePriceId { get; set; } = null!;
|
||||
|
||||
public ICollection<Subscription> Subscriptions { get; set; } = [];
|
||||
}
|
||||
18
backend/src/Web/Features/Memberships/Data/Tip.cs
Normal file
18
backend/src/Web/Features/Memberships/Data/Tip.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace Hutopy.Web.Features.Memberships.Data;
|
||||
|
||||
public class Tip
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public string StripeSessionId { get; set; }
|
||||
public Guid TipperId { get; set; }
|
||||
public string TipperName { get; set; }
|
||||
public Guid CreatorId { get; set; }
|
||||
public string CreatorName { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public string Currency { get; set; }
|
||||
public string Message { get; set; }
|
||||
|
||||
public Guid TransactionId { get; set; }
|
||||
public Transaction Transaction { get; set; }
|
||||
}
|
||||
12
backend/src/Web/Features/Memberships/Data/Transaction.cs
Normal file
12
backend/src/Web/Features/Memberships/Data/Transaction.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Hutopy.Web.Features.Memberships.Data;
|
||||
|
||||
public class Transaction
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public decimal Amount { get; set; }
|
||||
public string Currency { get; set; }
|
||||
public string Type { get; set; } // Subscription, Tip
|
||||
public DateTime Timestamp { get; set; }
|
||||
public string? StripeInvoiceUrl { get; set; }
|
||||
}
|
||||
27
backend/src/Web/Features/Memberships/DependencyInjection.cs
Normal file
27
backend/src/Web/Features/Memberships/DependencyInjection.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Hutopy.Web.Features.Memberships.Data;
|
||||
using Hutopy.Web.Features.Memberships.Infrastructure;
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
public static WebApplicationBuilder AddMembershipModule(
|
||||
this WebApplicationBuilder builder,
|
||||
Action<DbContextOptionsBuilder>? configureAction = null)
|
||||
{
|
||||
builder.Services.AddSingleton<PushNotificationService>();
|
||||
|
||||
builder.Services.AddDbContext<MembershipDbContext>(configureAction);
|
||||
builder.Services.AddScoped<MembershipDbContextInitializer>();
|
||||
|
||||
builder.Services.AddScoped<StripeService>();
|
||||
|
||||
builder.Services
|
||||
.AddOptions<StripeOptions>()
|
||||
.Bind(builder.Configuration.GetSection("Stripe"))
|
||||
.ValidateDataAnnotations()
|
||||
.ValidateOnStart();
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Hutopy.Web.Features.Memberships.Events;
|
||||
|
||||
public record StripeAccountConfigured(
|
||||
Guid CreatorId,
|
||||
string StripeAccountId);
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Hutopy.Web.Features.Memberships.Events;
|
||||
|
||||
public record struct SubscriptionPaid(
|
||||
Guid CreatorId,
|
||||
string CreatorName,
|
||||
string Tier,
|
||||
DateTimeOffset Since);
|
||||
8
backend/src/Web/Features/Memberships/Events/TipPaid.cs
Normal file
8
backend/src/Web/Features/Memberships/Events/TipPaid.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Hutopy.Web.Features.Memberships.Events;
|
||||
|
||||
public record struct TipPaid(
|
||||
Guid CreatorId,
|
||||
string CreatorName,
|
||||
decimal Amount,
|
||||
string Currency,
|
||||
string Message);
|
||||
@@ -0,0 +1,48 @@
|
||||
using Hutopy.Web.Features.Memberships.Data;
|
||||
using Hutopy.Web.Features.Memberships.Infrastructure;
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public class CancelSubscriptionRequest
|
||||
{
|
||||
public Guid SubscriptionId { get; set; }
|
||||
}
|
||||
|
||||
public class CancelSubscriptionHandler(
|
||||
MembershipDbContext dbContext,
|
||||
StripeService stripeService)
|
||||
: Endpoint<CancelSubscriptionRequest>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/membership/unsubscribe");
|
||||
Options(o => o.WithTags("Memberships"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
CancelSubscriptionRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var subscription = await dbContext
|
||||
.Subscriptions
|
||||
.FindAsync(
|
||||
[req.SubscriptionId],
|
||||
cancellationToken: ct);
|
||||
|
||||
if (subscription is not { EndDate: null })
|
||||
{
|
||||
await SendNotFoundAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel Stripe subscription
|
||||
await stripeService.CancelSubscription(subscription.Id);
|
||||
|
||||
// Update subscription in the system
|
||||
subscription.EndDate = DateTime.UtcNow;
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
|
||||
await SendOkAsync(subscription.Id, ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using Hutopy.Web.Common.Security;
|
||||
using Hutopy.Web.Features.Memberships.Data;
|
||||
using Hutopy.Web.Features.Memberships.Events;
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public record struct ChangeStripeIdRequest(
|
||||
string StripeAccountId);
|
||||
|
||||
public class ChangeStripeIdHandler(
|
||||
MembershipDbContext dbContext)
|
||||
: Endpoint<ChangeStripeIdRequest>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/membership/stripe-account");
|
||||
Options(o => o.WithTags("Memberships"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
ChangeStripeIdRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var creatorId = HttpContext.User.GetUserId();
|
||||
|
||||
var creator = await dbContext
|
||||
.Creators
|
||||
.FindAsync(
|
||||
[creatorId],
|
||||
cancellationToken: ct);
|
||||
|
||||
if (creator is null)
|
||||
{
|
||||
creator = new Creator
|
||||
{
|
||||
Id = creatorId,
|
||||
Name = HttpContext.User.GetAlias() ?? creatorId.ToString(),
|
||||
PortraitUrl = HttpContext.User.GetPortraitUrl() ?? string.Empty
|
||||
};
|
||||
|
||||
await dbContext.AddAsync(creator, ct);
|
||||
}
|
||||
|
||||
creator.StripeAccountId = req.StripeAccountId;
|
||||
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
|
||||
await PublishAsync(
|
||||
new StripeAccountConfigured(creator.Id, creator.StripeAccountId),
|
||||
cancellation: ct);
|
||||
|
||||
await SendOkAsync(creator.Id, ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Hutopy.Web.Features.Memberships.Data;
|
||||
using Hutopy.Web.Features.Memberships.Infrastructure;
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public record struct CreateMembershipTierRequest(
|
||||
Guid CreatorId,
|
||||
string Name,
|
||||
string Description,
|
||||
decimal Price,
|
||||
string Currency = "CAD");
|
||||
|
||||
[PublicAPI]
|
||||
public class CreateMembershipTierEndpoint(
|
||||
MembershipDbContext dbContext,
|
||||
StripeService stripe)
|
||||
: Endpoint<CreateMembershipTierRequest>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/membership/tiers");
|
||||
Options(o => o.WithTags("Memberships"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
CreateMembershipTierRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var tierId = Guid.NewGuid();
|
||||
|
||||
var productId = await stripe.CreateProductAsync(
|
||||
req.CreatorId,
|
||||
tierId,
|
||||
req.Name,
|
||||
req.Currency,
|
||||
req.Price);
|
||||
|
||||
// Record the new Tier
|
||||
var tier = new Tier
|
||||
{
|
||||
Id = tierId,
|
||||
CreatorId = req.CreatorId,
|
||||
Price = req.Price,
|
||||
Name = req.Name,
|
||||
Description = req.Description,
|
||||
StripeProductId = productId,
|
||||
};
|
||||
|
||||
dbContext.Tiers.Add(tier);
|
||||
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
|
||||
await SendOkAsync(tier, ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Hutopy.Web.Common.Security;
|
||||
using Hutopy.Web.Features.Memberships.Data;
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public record struct GetActiveSubscriptionsResponse(
|
||||
Guid Id,
|
||||
Guid CreatorId,
|
||||
string CreatorName,
|
||||
string CreatorPortraitUrl,
|
||||
DateTimeOffset StartDate,
|
||||
DateTimeOffset? EndDate);
|
||||
|
||||
[PublicAPI]
|
||||
public class GetActiveSubscriptionsHandler(
|
||||
MembershipDbContext dbContext)
|
||||
: EndpointWithoutRequest<List<GetActiveSubscriptionsResponse>>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/membership/active");
|
||||
Options(o => o.WithTags("Memberships"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
CancellationToken ct)
|
||||
{
|
||||
var subscriptions = await dbContext
|
||||
.Subscriptions
|
||||
.Where(subscription => subscription.UserId == User.GetUserId())
|
||||
.Where(subscription => subscription.EndDate == null || subscription.EndDate > DateTimeOffset.UtcNow)
|
||||
.Select(subscription => new GetActiveSubscriptionsResponse(
|
||||
subscription.Id,
|
||||
subscription.Creator.Id,
|
||||
subscription.Creator.Name,
|
||||
subscription.Creator.PortraitUrl,
|
||||
subscription.StartDate,
|
||||
subscription.EndDate))
|
||||
.ToListAsync(ct);
|
||||
|
||||
await SendOkAsync(subscriptions, ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using Hutopy.Web.Features.Memberships.Data;
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public record GetMembershipTierRequest
|
||||
{
|
||||
public Guid CreatorId { get; set; }
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public record struct TierModel(
|
||||
Guid Id,
|
||||
DateTime CreatedAt,
|
||||
string Name,
|
||||
string Description,
|
||||
decimal Price,
|
||||
string CurrencyCode,
|
||||
string StripeProductId);
|
||||
|
||||
[PublicAPI]
|
||||
public class GetMembershipTierEndpoint(
|
||||
MembershipDbContext dbContext)
|
||||
: Endpoint<GetMembershipTierRequest, List<TierModel>>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/membership/tiers/{CreatorId:guid}");
|
||||
Options(o => o.WithTags("Memberships"));
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
GetMembershipTierRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var tiers = await dbContext
|
||||
.Tiers
|
||||
.Where(tier => tier.CreatorId == req.CreatorId)
|
||||
.Select(tier => new TierModel(
|
||||
tier.Id,
|
||||
tier.CreatedAt,
|
||||
tier.Name,
|
||||
tier.Description,
|
||||
tier.Price,
|
||||
tier.CurrencyCode,
|
||||
tier.StripeProductId))
|
||||
.ToListAsync(ct);
|
||||
|
||||
await SendOkAsync(tiers, ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using Hutopy.Web.Common;
|
||||
using Hutopy.Web.Common.Security;
|
||||
using Hutopy.Web.Features.Memberships.Data;
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public record struct TipReceivedModel(
|
||||
Guid Id,
|
||||
DateTimeOffset CreatedAt,
|
||||
Guid TipperId,
|
||||
string TipperName,
|
||||
decimal Amount,
|
||||
string Currency,
|
||||
string Message);
|
||||
|
||||
[PublicAPI]
|
||||
public class GetReceivedTipsHandler(
|
||||
MembershipDbContext dbContext)
|
||||
: EndpointWithoutRequest<List<TipReceivedModel>>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/tips/received");
|
||||
Options(o => o.WithTags("Memberships"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
CancellationToken ct)
|
||||
{
|
||||
var tipsReceived = await dbContext
|
||||
.Tips
|
||||
.Where(tip => tip.CreatorId == User.GetUserId())
|
||||
.Select(tip => new TipReceivedModel(
|
||||
tip.Id,
|
||||
tip.CreatedAt,
|
||||
tip.TipperId,
|
||||
tip.TipperName,
|
||||
tip.Amount,
|
||||
tip.Currency,
|
||||
tip.Message))
|
||||
.ToListAsync(ct);
|
||||
|
||||
await SendOkAsync(tipsReceived, ct);
|
||||
}
|
||||
}
|
||||
46
backend/src/Web/Features/Memberships/Handlers/GetSentTips.cs
Normal file
46
backend/src/Web/Features/Memberships/Handlers/GetSentTips.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using Hutopy.Web.Common;
|
||||
using Hutopy.Web.Common.Security;
|
||||
using Hutopy.Web.Features.Memberships.Data;
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public record struct TipSentModel(
|
||||
Guid Id,
|
||||
DateTimeOffset CreatedAt,
|
||||
Guid CreatorId,
|
||||
string CreatorName,
|
||||
decimal Amount,
|
||||
string Currency,
|
||||
string Message);
|
||||
|
||||
[PublicAPI]
|
||||
public class GetSentTipsHandler(
|
||||
MembershipDbContext dbContext)
|
||||
: EndpointWithoutRequest<List<TipSentModel>>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Get("/api/tips/sent");
|
||||
Options(o => o.WithTags("Memberships"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
CancellationToken ct)
|
||||
{
|
||||
var tips = await dbContext
|
||||
.Tips
|
||||
.Where(t => t.TipperId == User.GetUserId())
|
||||
.Select(tip => new TipSentModel(
|
||||
tip.Id,
|
||||
tip.CreatedAt,
|
||||
tip.CreatorId,
|
||||
tip.CreatorName,
|
||||
tip.Amount,
|
||||
tip.Currency,
|
||||
tip.Message))
|
||||
.ToListAsync(ct);
|
||||
|
||||
await SendOkAsync(tips, ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Hutopy.Web.Features.Memberships.Infrastructure;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Stripe;
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Handlers;
|
||||
|
||||
public class StripeWebhookEndpoint(
|
||||
StripeService stripeService,
|
||||
IOptions<StripeOptions> options)
|
||||
: EndpointWithoutRequest
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/stripe");
|
||||
AllowAnonymous();
|
||||
Options(o => o.WithTags("Memberships"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CancellationToken ct)
|
||||
{
|
||||
using var streamReader = new StreamReader(HttpContext.Request.Body);
|
||||
var json = await streamReader.ReadToEndAsync(ct);
|
||||
|
||||
var signatureHeader = HttpContext.Request.Headers["Stripe-Signature"];
|
||||
var stripeEvent = EventUtility.ConstructEvent(json, signatureHeader, options.Value.WebhookSecret);
|
||||
|
||||
switch (stripeEvent.Type)
|
||||
{
|
||||
case "checkout.session.completed":
|
||||
await stripeService.HandleCheckoutSessionCompleted(stripeEvent, ct);
|
||||
break;
|
||||
case "invoice.payment_succeeded":
|
||||
await stripeService.HandleInvoicePaymentSucceeded(stripeEvent, ct);
|
||||
break;
|
||||
case "invoice.payment_failed":
|
||||
await stripeService.HandleInvoicePaymentFailed(stripeEvent, ct);
|
||||
break;
|
||||
case "customer.subscription.created":
|
||||
await stripeService.HandleCustomerSubscriptionCreated(stripeEvent, ct);
|
||||
break;
|
||||
case "customer.subscription.updated":
|
||||
await stripeService.HandleCustomerSubscriptionUpdated(stripeEvent, ct);
|
||||
break;
|
||||
case "customer.subscription.deleted":
|
||||
await stripeService.HandleCustomerSubscriptionDeleted(stripeEvent, ct);
|
||||
break;
|
||||
}
|
||||
|
||||
await SendOkAsync(ct);
|
||||
}
|
||||
}
|
||||
87
backend/src/Web/Features/Memberships/Handlers/SendTip.cs
Normal file
87
backend/src/Web/Features/Memberships/Handlers/SendTip.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using Hutopy.Web.Common.Security;
|
||||
using Hutopy.Web.Features.Memberships.Data;
|
||||
using Hutopy.Web.Features.Memberships.Infrastructure;
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public record SendTipRequest(
|
||||
Guid CreatorId,
|
||||
decimal Amount,
|
||||
string Currency,
|
||||
string Message,
|
||||
string CheckoutSuccessUrl,
|
||||
string CheckoutCancelledUrl);
|
||||
|
||||
[PublicAPI]
|
||||
public record SendTipResponse(
|
||||
string Status,
|
||||
string StripeCheckoutUrl);
|
||||
|
||||
[PublicAPI]
|
||||
public class SendTipRequestValidator : Validator<SendTipRequest>
|
||||
{
|
||||
public SendTipRequestValidator()
|
||||
{
|
||||
RuleFor(x => x.Amount)
|
||||
.GreaterThan(0)
|
||||
.WithMessage("Tip amount must be greater than 0");
|
||||
|
||||
RuleFor(x => x.CreatorId)
|
||||
.NotEmpty()
|
||||
.WithMessage("Creator ID is required");
|
||||
|
||||
RuleFor(x => x.CheckoutSuccessUrl)
|
||||
.NotEmpty()
|
||||
.WithMessage("CheckoutSuccessUrl is required");
|
||||
|
||||
RuleFor(x => x.CheckoutCancelledUrl)
|
||||
.NotEmpty()
|
||||
.WithMessage("CheckoutCancelledUrl is required");
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public class SendTipHandler(
|
||||
MembershipDbContext dbContext,
|
||||
StripeService stripeService)
|
||||
: Endpoint<SendTipRequest, SendTipResponse>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/tips");
|
||||
Options(o => o.WithTags("Memberships"));
|
||||
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
SendTipRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var creator = await dbContext.Creators.FindAsync(
|
||||
[req.CreatorId],
|
||||
cancellationToken: ct);
|
||||
|
||||
if (creator == null)
|
||||
{
|
||||
await SendNotFoundAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
var checkoutSession = await stripeService.CreateTipCheckoutSessionAsync(
|
||||
creator.Id,
|
||||
creator.Name,
|
||||
req.Amount,
|
||||
req.Currency,
|
||||
req.Message,
|
||||
creator.StripeAccountId,
|
||||
req.CheckoutSuccessUrl,
|
||||
req.CheckoutCancelledUrl
|
||||
);
|
||||
|
||||
await SendAsync(
|
||||
new SendTipResponse("Pending", checkoutSession.Url),
|
||||
cancellation: ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using Hutopy.Web.Common.Security;
|
||||
using Hutopy.Web.Features.Memberships.Data;
|
||||
using Hutopy.Web.Features.Memberships.Infrastructure;
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Handlers;
|
||||
|
||||
[PublicAPI]
|
||||
public class SubscribeRequest
|
||||
{
|
||||
public Guid CreatorId { get; set; }
|
||||
public Guid TierId { get; set; }
|
||||
public required string CheckoutSuccessUrl { get; init; }
|
||||
public required string CheckoutCancelledUrl { get; init; }
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public record struct SubscriptionResponse(
|
||||
string StripeCheckoutUrl);
|
||||
|
||||
[PublicAPI]
|
||||
public class SubscribeValidator : Validator<SubscribeRequest>
|
||||
{
|
||||
public SubscribeValidator()
|
||||
{
|
||||
RuleFor(x => x.TierId).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public class SubscribeHandler(
|
||||
MembershipDbContext dbContext,
|
||||
StripeService stripeService)
|
||||
: Endpoint<SubscribeRequest, SubscriptionResponse>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/membership/subscribe");
|
||||
Options(o => o.WithTags("Memberships"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
SubscribeRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var tier = await dbContext
|
||||
.Tiers
|
||||
.Include(tier => tier.Creator) // Include the related table
|
||||
.Where(tier => tier.Id == req.TierId)
|
||||
.FirstOrDefaultAsync(ct);
|
||||
|
||||
if (tier == null)
|
||||
{
|
||||
await SendNotFoundAsync(ct);
|
||||
return;
|
||||
}
|
||||
|
||||
// Process Stripe subscription
|
||||
var checkoutSession = await stripeService.CreateSubscriptionCheckoutSession(
|
||||
User.GetUserId(),
|
||||
tier.Creator.Id,
|
||||
tier.Creator.Name,
|
||||
tier.Creator.StripeAccountId,
|
||||
tier.Id,
|
||||
tier.StripePriceId,
|
||||
req.CheckoutSuccessUrl,
|
||||
req.CheckoutCancelledUrl);
|
||||
|
||||
await SendOkAsync(
|
||||
new SubscriptionResponse { StripeCheckoutUrl = checkoutSession.Url },
|
||||
cancellation: ct);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Hutopy.Web.Features.Memberships.Infrastructure;
|
||||
|
||||
public sealed class PushNotificationService(
|
||||
ILogger<PushNotificationService> logger)
|
||||
{
|
||||
public void NotifyCreator<TEvent>(
|
||||
Guid tipCreatorId,
|
||||
TEvent notification)
|
||||
where TEvent : struct
|
||||
{
|
||||
logger.LogInformation("Notifying creator {CreatorId}, {Notification}", tipCreatorId, notification);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Hutopy.Web.Features.Memberships.Data;
|
||||
using Hutopy.Web.Features.Memberships.Events;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Stripe;
|
||||
using Stripe.Checkout;
|
||||
using Subscription = Stripe.Subscription;
|
||||
|
||||
namespace Hutopy.Web.Features.Memberships.Infrastructure;
|
||||
|
||||
public class StripeOptions
|
||||
{
|
||||
[Required] public required string SecretKey { get; init; }
|
||||
|
||||
[Required] public required string WebhookSecret { get; init; }
|
||||
|
||||
[Required] [Range(0, 1)] public required decimal HutopyRate { get; init; }
|
||||
}
|
||||
|
||||
public sealed class StripeService(
|
||||
IOptions<StripeOptions> paymentOptions,
|
||||
MembershipDbContext dbContext,
|
||||
PushNotificationService notificationService)
|
||||
{
|
||||
public async Task<string> CreateProductAsync(
|
||||
Guid creatorId,
|
||||
Guid tierId,
|
||||
string productName,
|
||||
string currencyCode,
|
||||
decimal amount)
|
||||
{
|
||||
StripeConfiguration.ApiKey = paymentOptions.Value.SecretKey;
|
||||
|
||||
// Create the product
|
||||
var productService = new ProductService();
|
||||
var product = await productService.CreateAsync(
|
||||
new ProductCreateOptions
|
||||
{
|
||||
Name = productName,
|
||||
Metadata = { { "creatorId", creatorId.ToString() }, { "tierId", tierId.ToString() } }
|
||||
});
|
||||
|
||||
// Create the price for the product
|
||||
var priceService = new PriceService();
|
||||
await priceService.CreateAsync(
|
||||
new PriceCreateOptions
|
||||
{
|
||||
Product = product.Id,
|
||||
UnitAmountDecimal = amount * 100, // Convert amount to cents
|
||||
Currency = currencyCode,
|
||||
Recurring = new PriceRecurringOptions { Interval = "month" }
|
||||
});
|
||||
|
||||
return product.Id;
|
||||
}
|
||||
|
||||
public async Task<Session> CreateTipCheckoutSessionAsync(
|
||||
Guid creatorId,
|
||||
string creatorName,
|
||||
decimal amount,
|
||||
string currencyCode,
|
||||
string message,
|
||||
string creatorAccountId,
|
||||
string successUrl,
|
||||
string cancelUrl,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
StripeConfiguration.ApiKey = paymentOptions.Value.SecretKey;
|
||||
|
||||
// Create Stripe customer for the user if not already created
|
||||
var customerService = new CustomerService();
|
||||
var customer = await customerService.CreateAsync(
|
||||
new CustomerCreateOptions{},
|
||||
cancellationToken: ct);
|
||||
|
||||
// Create paymentIntent for the user
|
||||
var sessionService = new SessionService();
|
||||
return await sessionService.CreateAsync(
|
||||
new SessionCreateOptions
|
||||
{
|
||||
Customer = customer.Id,
|
||||
PaymentMethodTypes = ["card"],
|
||||
LineItems =
|
||||
[
|
||||
new SessionLineItemOptions
|
||||
{
|
||||
PriceData = new SessionLineItemPriceDataOptions
|
||||
{
|
||||
Currency = currencyCode,
|
||||
UnitAmountDecimal = amount, // Amount in cents
|
||||
ProductData = new SessionLineItemPriceDataProductDataOptions
|
||||
{
|
||||
Name = $"Tip for {creatorName}", // or any descriptive name for the tip
|
||||
Metadata = new Dictionary<string, string> { { "creatorId", creatorId.ToString() } }
|
||||
}
|
||||
},
|
||||
Quantity = 1
|
||||
}
|
||||
],
|
||||
Mode = "payment",
|
||||
PaymentIntentData = new SessionPaymentIntentDataOptions
|
||||
{
|
||||
ApplicationFeeAmount =
|
||||
Convert.ToInt64(amount * 100 * paymentOptions.Value.HutopyRate), // Platform fee
|
||||
TransferData = new SessionPaymentIntentDataTransferDataOptions
|
||||
{
|
||||
Destination = creatorAccountId // Creator's Stripe account ID
|
||||
}
|
||||
},
|
||||
SuccessUrl = successUrl, // Redirect after successful payment
|
||||
CancelUrl = cancelUrl, // Redirect after canceled payment
|
||||
Metadata = new Dictionary<string, string>
|
||||
{
|
||||
{ "creatorId", creatorId.ToString() },
|
||||
{ "creatorName", creatorName },
|
||||
{ "message", message },
|
||||
}
|
||||
},
|
||||
cancellationToken: ct);
|
||||
}
|
||||
|
||||
public async Task<Session> CreateSubscriptionCheckoutSession(
|
||||
Guid userId,
|
||||
Guid creatorId,
|
||||
string creatorName,
|
||||
string creatorAccountId,
|
||||
Guid tierId,
|
||||
string priceId,
|
||||
string successUrl,
|
||||
string cancelUrl)
|
||||
{
|
||||
StripeConfiguration.ApiKey = paymentOptions.Value.SecretKey;
|
||||
|
||||
// Create Stripe customer for the user if not already created
|
||||
var customerService = new CustomerService();
|
||||
var customer = await customerService.CreateAsync(
|
||||
new CustomerCreateOptions
|
||||
{
|
||||
Metadata = new Dictionary<string, string> { { "userId", userId.ToString() } }
|
||||
});
|
||||
|
||||
// Create Checkout Session for the subscription
|
||||
var sessionService = new SessionService();
|
||||
return await sessionService.CreateAsync(
|
||||
new SessionCreateOptions
|
||||
{
|
||||
Customer = customer.Id,
|
||||
PaymentMethodTypes = ["card"],
|
||||
LineItems =
|
||||
[
|
||||
new SessionLineItemOptions { Price = priceId, Quantity = 1 }
|
||||
],
|
||||
Mode = "subscription",
|
||||
SubscriptionData = new SessionSubscriptionDataOptions
|
||||
{
|
||||
ApplicationFeePercent = paymentOptions.Value.HutopyRate,
|
||||
TransferData = new SessionSubscriptionDataTransferDataOptions { Destination = creatorAccountId }
|
||||
},
|
||||
SuccessUrl = successUrl, // Redirect after successful payment
|
||||
CancelUrl = cancelUrl, // Redirect after canceled payment
|
||||
Metadata = new Dictionary<string, string>
|
||||
{
|
||||
{ "userId", userId.ToString() },
|
||||
{ "creatorId", creatorId.ToString() },
|
||||
{ "creatorName", creatorName },
|
||||
{ "tierId", tierId.ToString() }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task CancelSubscription(
|
||||
Guid subscriptionId)
|
||||
{
|
||||
var subscriptionService = new SubscriptionService();
|
||||
await subscriptionService.CancelAsync(subscriptionId.ToString());
|
||||
}
|
||||
|
||||
public async Task HandleInvoicePaymentSucceeded(
|
||||
Event stripeEvent,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
// Ensure we have an invoice related to a Subscription
|
||||
if (stripeEvent.Data.Object is not Invoice { Subscription: not null } invoice)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var subscription = await dbContext
|
||||
.Subscriptions
|
||||
.FirstOrDefaultAsync(
|
||||
subscription => subscription.StripeSubscriptionId == invoice.Subscription.Id,
|
||||
cancellationToken: ct);
|
||||
|
||||
if (subscription == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Record the Transaction
|
||||
var transaction = new Transaction
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
Amount = invoice.AmountPaid / 100m, // Convert amount from cents to dollars
|
||||
Currency = invoice.Currency,
|
||||
Type = "Subscription",
|
||||
Timestamp = DateTime.UtcNow,
|
||||
StripeInvoiceUrl = invoice.HostedInvoiceUrl
|
||||
};
|
||||
|
||||
dbContext.Transactions.Add(transaction);
|
||||
|
||||
// Link the Transaction to the Subscription
|
||||
subscription.Transactions.Add(transaction);
|
||||
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
public async Task HandleInvoicePaymentFailed(
|
||||
Event stripeEvent,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (stripeEvent.Data.Object is not Invoice { Subscription: not null } invoice)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var subscription = await dbContext
|
||||
.Subscriptions
|
||||
.SingleOrDefaultAsync(
|
||||
subscription => subscription.StripeSubscriptionId == invoice.SubscriptionId,
|
||||
cancellationToken: ct);
|
||||
|
||||
if (subscription != null)
|
||||
{
|
||||
subscription.EndDate = DateTimeOffset.UtcNow; // Mark as expired or failed
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleTipPayment(
|
||||
Session session,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Record the Tip
|
||||
var tip = new Tip
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
StripeSessionId = session.Id,
|
||||
TipperId = Guid.Parse(session.Metadata["tipperId"]),
|
||||
TipperName = session.Metadata["tipperName"],
|
||||
CreatorId = Guid.Parse(session.Metadata["creatorId"]),
|
||||
CreatorName = session.Metadata["creatorName"],
|
||||
Amount = session.AmountTotal!.Value / 100m,
|
||||
Currency = session.Currency,
|
||||
Message = session.Metadata["message"]
|
||||
};
|
||||
|
||||
dbContext.Tips.Add(tip);
|
||||
|
||||
// Record the Transaction
|
||||
var transaction = new Transaction
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
Amount = tip.Amount,
|
||||
Currency = tip.Currency,
|
||||
Type = "Tip",
|
||||
Timestamp = DateTime.UtcNow,
|
||||
// TODO: __StripeInvoiceUrl = session.Invoice.HostedInvoiceUrl__ How come nor Invoice or InvoiceId are set.
|
||||
};
|
||||
|
||||
dbContext.Transactions.Add(transaction);
|
||||
|
||||
// Link the Transaction to the Tip
|
||||
tip.TransactionId = transaction.Id;
|
||||
|
||||
// Save the changes
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
|
||||
// Notify the Creator
|
||||
notificationService.NotifyCreator(
|
||||
tip.CreatorId,
|
||||
new TipPaid(
|
||||
tip.CreatorId,
|
||||
tip.CreatorName,
|
||||
tip.Amount,
|
||||
tip.Currency,
|
||||
tip.Message)
|
||||
);
|
||||
}
|
||||
|
||||
private async Task HandleSubscriptionPayment(
|
||||
Session session,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// Record the Subscription
|
||||
var subscription = new Data.Subscription
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
UserId = Guid.Parse(session.Metadata["userId"]),
|
||||
CreatorId = Guid.Parse(session.Metadata["creatorId"]),
|
||||
TierId = Guid.Parse(session.Metadata["tierId"]),
|
||||
StartDate = DateTimeOffset.UtcNow,
|
||||
StripeSessionId = session.Id,
|
||||
StripeSubscriptionId = session.SubscriptionId
|
||||
};
|
||||
|
||||
dbContext.Subscriptions.Add(subscription);
|
||||
|
||||
// Record the Transaction
|
||||
var transaction = new Transaction
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
Amount = session.AmountTotal!.Value / 100m, // Convert amount from cents to dollars
|
||||
Currency = session.Currency,
|
||||
Type = "Subscription",
|
||||
Timestamp = DateTime.UtcNow,
|
||||
// TODO: __StripeInvoiceUrl = session.Invoice.HostedInvoiceUrl__ How come nor Invoice or InvoiceId are set.
|
||||
};
|
||||
|
||||
dbContext.Transactions.Add(transaction);
|
||||
|
||||
// Link the Transaction to the Subscription
|
||||
subscription.Transactions.Add(transaction);
|
||||
|
||||
// Save the changes
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
|
||||
// Notify the Creator
|
||||
notificationService.NotifyCreator(
|
||||
subscription.CreatorId,
|
||||
new SubscriptionPaid(
|
||||
subscription.CreatorId,
|
||||
session.Metadata["creatorName"],
|
||||
subscription.TierId.ToString(),
|
||||
subscription.StartDate)
|
||||
);
|
||||
}
|
||||
|
||||
public async Task HandleCheckoutSessionCompleted(
|
||||
Event stripeEvent,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (stripeEvent.Data.Object is not Session session)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (session.Mode)
|
||||
{
|
||||
// Check if this is a one-time tip
|
||||
case "payment" when session.PaymentIntentId != null:
|
||||
await HandleTipPayment(session, ct);
|
||||
break;
|
||||
// Check if this is a subscription
|
||||
case "subscription" when session.SubscriptionId != null:
|
||||
await HandleSubscriptionPayment(session, ct);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task HandleCustomerSubscriptionCreated(
|
||||
Event stripeEvent,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (stripeEvent.Data.Object is not Subscription stripeSubscription)
|
||||
return;
|
||||
|
||||
var subscription = await dbContext
|
||||
.Subscriptions
|
||||
.SingleOrDefaultAsync(
|
||||
subscription => subscription.StripeSubscriptionId == stripeSubscription.Id,
|
||||
cancellationToken: ct);
|
||||
|
||||
if (subscription != null)
|
||||
{
|
||||
subscription.StartDate = stripeSubscription.CurrentPeriodStart;
|
||||
subscription.EndDate = null; // Active subscription
|
||||
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task HandleCustomerSubscriptionUpdated(
|
||||
Event stripeEvent,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (stripeEvent.Data.Object is Subscription stripeSubscription)
|
||||
{
|
||||
var subscription = await dbContext
|
||||
.Subscriptions
|
||||
.SingleOrDefaultAsync(
|
||||
s => s.StripeSubscriptionId == stripeSubscription.Id,
|
||||
cancellationToken: ct);
|
||||
|
||||
if (subscription != null)
|
||||
{
|
||||
subscription.StartDate = stripeSubscription.CurrentPeriodStart;
|
||||
subscription.EndDate = null; // Active subscription
|
||||
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task HandleCustomerSubscriptionDeleted(
|
||||
Event stripeEvent,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var subscription = stripeEvent.Data.Object as Subscription;
|
||||
var existingSubscription = await dbContext
|
||||
.Subscriptions
|
||||
.FirstOrDefaultAsync(x => x.StripeSubscriptionId == subscription!.Id, ct);
|
||||
|
||||
if (existingSubscription != null)
|
||||
{
|
||||
var today = DateTime.Today;
|
||||
int lastDay = DateTime.DaysInMonth(today.Year, today.Month);
|
||||
var lastDayOfMonth = new DateTime(today.Year, today.Month, lastDay);
|
||||
existingSubscription.EndDate = new DateTimeOffset(lastDayOfMonth);
|
||||
await dbContext.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
backend/src/Web/Features/Messages/Data/Message.cs
Normal file
15
backend/src/Web/Features/Messages/Data/Message.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Hutopy.Web.Features.Messages.Data;
|
||||
|
||||
public class Message
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid SubjectId { get; set; }
|
||||
public Guid CreatedBy { get; set; }
|
||||
[MaxLength(255)] public required string CreatedByName { get; set; }
|
||||
[MaxLength(255)] public string? CreatedByPortraitUrl { get; set; }
|
||||
public DateTimeOffset CreatedAt { get; set; }
|
||||
public Guid? ParentId { get; set; }
|
||||
[MaxLength(2048)] public required string Value { get; set; }
|
||||
}
|
||||
76
backend/src/Web/Features/Messages/Data/MessagingDbContext.cs
Normal file
76
backend/src/Web/Features/Messages/Data/MessagingDbContext.cs
Normal file
@@ -0,0 +1,76 @@
|
||||
using Hutopy.Web.Features.Messages.Handlers.Models;
|
||||
|
||||
namespace Hutopy.Web.Features.Messages.Data;
|
||||
|
||||
public class MessagingDbContext(
|
||||
DbContextOptions<MessagingDbContext> options)
|
||||
: DbContext(options)
|
||||
{
|
||||
public const string SchemaName = "Messaging";
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.HasDefaultSchema(SchemaName);
|
||||
|
||||
modelBuilder
|
||||
.Entity<Message>()
|
||||
.Property(c => c.CreatedAt)
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
}
|
||||
|
||||
public DbSet<Message> Messages { get; set; }
|
||||
|
||||
public async Task<List<MessageDto>> GetMessagesAsync(
|
||||
Guid subjectId,
|
||||
Guid? parentId,
|
||||
Guid? lastId,
|
||||
int pageSize,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var query = Messages
|
||||
.Where(c => c.SubjectId == subjectId)
|
||||
.Where(c => c.ParentId == parentId);
|
||||
|
||||
if (lastId.HasValue)
|
||||
{
|
||||
var lastMessage = await Messages
|
||||
.Where(c => c.Id == lastId.Value)
|
||||
.Select(c => new { c.CreatedAt, c.Id })
|
||||
.FirstOrDefaultAsync(cancellationToken: ct);
|
||||
|
||||
if (lastMessage != null)
|
||||
{
|
||||
query = query
|
||||
.Where(c => c.CreatedAt < lastMessage.CreatedAt
|
||||
|| (c.CreatedAt == lastMessage.CreatedAt && c.Id < lastMessage.Id));
|
||||
}
|
||||
}
|
||||
|
||||
var messages = await query
|
||||
.OrderByDescending(c => c.CreatedAt)
|
||||
.ThenByDescending(c => c.Id)
|
||||
.Take(pageSize)
|
||||
.Select(message => message.ToDto())
|
||||
.ToListAsync(cancellationToken: ct);
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
public async Task<int> GetMessageCountAsync(
|
||||
Guid subjectId,
|
||||
Guid? parentId,
|
||||
int pageSize,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var query = Messages
|
||||
.Where(c => c.SubjectId == subjectId)
|
||||
.Where(c => c.ParentId == parentId);
|
||||
|
||||
var messageCount = await query
|
||||
.Take(pageSize)
|
||||
.CountAsync(ct);
|
||||
|
||||
return messageCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace Hutopy.Web.Features.Messages.Data;
|
||||
|
||||
public static class InitializerExtensions
|
||||
{
|
||||
public static async Task InitialiseMessagingDbContextAsync(this WebApplication app)
|
||||
{
|
||||
using var scope = app.Services.CreateScope();
|
||||
|
||||
var initializer = scope.ServiceProvider.GetRequiredService<MessagingDbContextInitializer>();
|
||||
|
||||
await initializer.InitialiseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public class MessagingDbContextInitializer(
|
||||
ILogger<MessagingDbContextInitializer> logger,
|
||||
MessagingDbContext context
|
||||
)
|
||||
{
|
||||
public async Task InitialiseAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await context.Database.MigrateAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "An error occurred while initialising the messaging database.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
69
backend/src/Web/Features/Messages/Data/Migrations/20240805012343_Initial.Designer.cs
generated
Normal file
69
backend/src/Web/Features/Messages/Data/Migrations/20240805012343_Initial.Designer.cs
generated
Normal file
@@ -0,0 +1,69 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Hutopy.Web.Features.Messages.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Messages.Migrations
|
||||
{
|
||||
[DbContext(typeof(MessagingDbContext))]
|
||||
[Migration("20240805012343_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("Messaging")
|
||||
.HasAnnotation("ProductVersion", "8.0.4")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Messages.Data.Message", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("CreatedByName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("CreatedByPortraitUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<Guid?>("ParentId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("SubjectId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Messages", "Messaging");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Messages.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.EnsureSchema(
|
||||
name: "Messaging");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Messages",
|
||||
schema: "Messaging",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
SubjectId = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CreatedBy = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
CreatedByName = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: false),
|
||||
CreatedByPortraitUrl = table.Column<string>(type: "character varying(255)", maxLength: 255, nullable: true),
|
||||
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"),
|
||||
ParentId = table.Column<Guid>(type: "uuid", nullable: true),
|
||||
Value = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Messages", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Messages",
|
||||
schema: "Messaging");
|
||||
}
|
||||
}
|
||||
}
|
||||
70
backend/src/Web/Features/Messages/Data/Migrations/20241217225954_ChangeStripeId.Designer.cs
generated
Normal file
70
backend/src/Web/Features/Messages/Data/Migrations/20241217225954_ChangeStripeId.Designer.cs
generated
Normal file
@@ -0,0 +1,70 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Hutopy.Web.Features.Messages.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Messages.Migrations
|
||||
{
|
||||
[DbContext(typeof(MessagingDbContext))]
|
||||
[Migration("20241217225954_ChangeStripeId")]
|
||||
partial class ChangeStripeId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("Messaging")
|
||||
.HasAnnotation("ProductVersion", "8.0.10")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Messages.Data.Message", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("CreatedByName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("CreatedByPortraitUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<Guid?>("ParentId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("SubjectId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Messages", "Messaging");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Messages.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ChangeStripeId : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Value",
|
||||
schema: "Messaging",
|
||||
table: "Messages",
|
||||
type: "character varying(2048)",
|
||||
maxLength: 2048,
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "text");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Value",
|
||||
schema: "Messaging",
|
||||
table: "Messages",
|
||||
type: "text",
|
||||
nullable: false,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "character varying(2048)",
|
||||
oldMaxLength: 2048);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Hutopy.Web.Features.Messages.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Hutopy.Web.Features.Messages.Migrations
|
||||
{
|
||||
[DbContext(typeof(MessagingDbContext))]
|
||||
partial class MessagingDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasDefaultSchema("Messaging")
|
||||
.HasAnnotation("ProductVersion", "8.0.10")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Hutopy.Web.Features.Messages.Data.Message", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTimeOffset>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<Guid>("CreatedBy")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("CreatedByName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<string>("CreatedByPortraitUrl")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("character varying(255)");
|
||||
|
||||
b.Property<Guid?>("ParentId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("SubjectId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("character varying(2048)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Messages", "Messaging");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
16
backend/src/Web/Features/Messages/DependencyInjection.cs
Normal file
16
backend/src/Web/Features/Messages/DependencyInjection.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Hutopy.Web.Features.Messages.Data;
|
||||
|
||||
namespace Hutopy.Web.Features.Messages;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
public static WebApplicationBuilder AddMessagingModule(
|
||||
this WebApplicationBuilder builder,
|
||||
Action<DbContextOptionsBuilder>? configureAction = null)
|
||||
{
|
||||
builder.Services.AddDbContext<MessagingDbContext>(configureAction);
|
||||
builder.Services.AddScoped<MessagingDbContextInitializer>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user