Add 'backend/' from commit '040cfd7a75423d4e6136e58a67b40579af4ee966'
git-subtree-dir: backend git-subtree-mainline:ab911955edgit-subtree-split:040cfd7a75
This commit is contained in:
362
backend/.azure/bicep/main.bicep
Normal file
362
backend/.azure/bicep/main.bicep
Normal file
@@ -0,0 +1,362 @@
|
|||||||
|
@description('The location into which your Azure resources should be deployed.')
|
||||||
|
param location string = resourceGroup().location
|
||||||
|
|
||||||
|
@description('Select the type of environment you want to provision. Allowed values are Production, Staging, and Development.')
|
||||||
|
@allowed([
|
||||||
|
'Production'
|
||||||
|
'Staging'
|
||||||
|
'Development'
|
||||||
|
])
|
||||||
|
param environmentName string
|
||||||
|
|
||||||
|
@description('A unique suffix to add to resource names that need to be globally unique.')
|
||||||
|
@maxLength(13)
|
||||||
|
param resourceNameSuffix string = uniqueString(resourceGroup().id)
|
||||||
|
|
||||||
|
@description('The administrator login username for the SQL server.')
|
||||||
|
param sqlAdministratorUsername string
|
||||||
|
|
||||||
|
@secure()
|
||||||
|
@description('The administrator login password for the SQL server.')
|
||||||
|
param sqlAdministratorPassword string
|
||||||
|
|
||||||
|
@description('The name of the project.')
|
||||||
|
param projectName string
|
||||||
|
|
||||||
|
// Define the environment configuration map.
|
||||||
|
var environmentConfigurationMap = {
|
||||||
|
Production: {
|
||||||
|
environmentAbbreviation: 'prd'
|
||||||
|
appServicePlan: {
|
||||||
|
sku: {
|
||||||
|
name: 'S1'
|
||||||
|
capacity: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlDatabase: {
|
||||||
|
sku: {
|
||||||
|
name: 'Standard'
|
||||||
|
tier: 'Standard'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Staging: {
|
||||||
|
environmentAbbreviation: 'stg'
|
||||||
|
appServicePlan: {
|
||||||
|
sku: {
|
||||||
|
name: 'B1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlDatabase: {
|
||||||
|
sku: {
|
||||||
|
name: 'Standard'
|
||||||
|
tier: 'Standard'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Development: {
|
||||||
|
environmentAbbreviation: 'dev'
|
||||||
|
appServicePlan: {
|
||||||
|
sku: {
|
||||||
|
name: 'B1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqlDatabase: {
|
||||||
|
sku: {
|
||||||
|
name: 'Standard'
|
||||||
|
tier: 'Standard'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define the names for resources.
|
||||||
|
var environmentAbbreviation = environmentConfigurationMap[environmentName].environmentAbbreviation
|
||||||
|
var keyVaultName = 'kv-${projectName}-${environmentAbbreviation}'
|
||||||
|
var appServiceAppName = 'as-${projectName}-${resourceNameSuffix}-${environmentAbbreviation}'
|
||||||
|
var appServicePlanName = 'plan-${projectName}-${environmentAbbreviation}'
|
||||||
|
var logAnalyticsWorkspaceName = 'log-${projectName}-${environmentAbbreviation}'
|
||||||
|
var applicationInsightsName = 'appi-${projectName}-${environmentAbbreviation}'
|
||||||
|
var sqlServerName = 'sql-${projectName}-${resourceNameSuffix}-${environmentAbbreviation}'
|
||||||
|
var sqlDatabaseName = '${projectName}-${environmentAbbreviation}'
|
||||||
|
|
||||||
|
// Define the SKUs for each component based on the environment type.
|
||||||
|
var appServicePlanSku = environmentConfigurationMap[environmentName].appServicePlan.sku
|
||||||
|
var sqlDatabaseSku = environmentConfigurationMap[environmentName].sqlDatabase.sku
|
||||||
|
|
||||||
|
resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2020-10-01' = {
|
||||||
|
name: logAnalyticsWorkspaceName
|
||||||
|
location: location
|
||||||
|
properties: {
|
||||||
|
sku: {
|
||||||
|
name: 'PerGB2018'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' = {
|
||||||
|
name: keyVaultName
|
||||||
|
location: location
|
||||||
|
properties: {
|
||||||
|
enabledForTemplateDeployment: true
|
||||||
|
tenantId: subscription().tenantId
|
||||||
|
accessPolicies: []
|
||||||
|
sku: {
|
||||||
|
name: 'standard'
|
||||||
|
family: 'A'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource keyVault_ConnectionStringSecret 'Microsoft.KeyVault/vaults/secrets@2019-09-01' = {
|
||||||
|
parent: keyVault
|
||||||
|
name: 'ConnectionStrings--DefaultConnection'
|
||||||
|
properties: {
|
||||||
|
value: 'Server=tcp:${sqlServer.properties.fullyQualifiedDomainName},1433;Initial Catalog=${sqlDatabaseName};Persist Security Info=False;User ID=${sqlAdministratorUsername};Password=${sqlAdministratorPassword};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;'
|
||||||
|
}
|
||||||
|
dependsOn: [
|
||||||
|
sqlDatabase
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource keyVault_DiagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
|
||||||
|
scope: keyVault
|
||||||
|
name: 'keyVaultDiagnosticSettings'
|
||||||
|
properties: {
|
||||||
|
workspaceId: logAnalyticsWorkspace.id
|
||||||
|
logs: [
|
||||||
|
{
|
||||||
|
category: 'AuditEvent'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
metrics: [
|
||||||
|
{
|
||||||
|
category: 'AllMetrics'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource appServicePlan 'Microsoft.Web/serverfarms@2021-01-15' = {
|
||||||
|
name: appServicePlanName
|
||||||
|
location: location
|
||||||
|
sku: appServicePlanSku
|
||||||
|
}
|
||||||
|
|
||||||
|
resource appServicePlan_DiagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
|
||||||
|
scope: appServicePlan
|
||||||
|
name: 'appServicePlanDiagnosticSettings'
|
||||||
|
properties: {
|
||||||
|
workspaceId: logAnalyticsWorkspace.id
|
||||||
|
metrics: [
|
||||||
|
{
|
||||||
|
category: 'AllMetrics'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource appServiceApp 'Microsoft.Web/sites@2021-01-15' = {
|
||||||
|
name: appServiceAppName
|
||||||
|
location: location
|
||||||
|
identity: {
|
||||||
|
type: 'SystemAssigned'
|
||||||
|
}
|
||||||
|
properties: {
|
||||||
|
serverFarmId: appServicePlan.id
|
||||||
|
httpsOnly: true
|
||||||
|
siteConfig: {
|
||||||
|
healthCheckPath: '/health'
|
||||||
|
netFrameworkVersion: 'v7.0'
|
||||||
|
appSettings: [
|
||||||
|
{
|
||||||
|
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
|
||||||
|
value: applicationInsights.properties.ConnectionString
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name: 'KeyVaultUri'
|
||||||
|
value: keyVault.properties.vaultUri
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource appServiceApp_DiagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
|
||||||
|
scope: appServiceApp
|
||||||
|
name: 'appServiceAppDiagnosticSettings'
|
||||||
|
properties: {
|
||||||
|
workspaceId: logAnalyticsWorkspace.id
|
||||||
|
logs: [
|
||||||
|
{
|
||||||
|
category: 'AppServiceHTTPLogs'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
{
|
||||||
|
category: 'AppServiceConsoleLogs'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
{
|
||||||
|
category: 'AppServiceAppLogs'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
{
|
||||||
|
category: 'AppServiceAuditLogs'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
{
|
||||||
|
category: 'AppServiceIPSecAuditLogs'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
{
|
||||||
|
category: 'AppServicePlatformLogs'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
metrics: [
|
||||||
|
{
|
||||||
|
category: 'AllMetrics'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource keyVault_AccessPolicy 'Microsoft.KeyVault/vaults/accessPolicies@2022-07-01' = {
|
||||||
|
parent: keyVault
|
||||||
|
name: 'add'
|
||||||
|
properties: {
|
||||||
|
accessPolicies: [
|
||||||
|
{
|
||||||
|
tenantId: appServiceApp.identity.tenantId
|
||||||
|
objectId: appServiceApp.identity.principalId
|
||||||
|
permissions: {
|
||||||
|
keys: [
|
||||||
|
'Get'
|
||||||
|
]
|
||||||
|
secrets: [
|
||||||
|
'Get'
|
||||||
|
'List'
|
||||||
|
|
||||||
|
]
|
||||||
|
certificates: [
|
||||||
|
'Get'
|
||||||
|
'List'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = {
|
||||||
|
name: applicationInsightsName
|
||||||
|
location: location
|
||||||
|
kind: 'web'
|
||||||
|
properties: {
|
||||||
|
Application_Type: 'web'
|
||||||
|
WorkspaceResourceId: logAnalyticsWorkspace.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource sqlServer 'Microsoft.Sql/servers@2021-02-01-preview' = {
|
||||||
|
name: sqlServerName
|
||||||
|
location: location
|
||||||
|
properties: {
|
||||||
|
administratorLogin: sqlAdministratorUsername
|
||||||
|
administratorLoginPassword: sqlAdministratorPassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource sqlDatabase 'Microsoft.Sql/servers/databases@2021-02-01-preview' = {
|
||||||
|
parent: sqlServer
|
||||||
|
name: sqlDatabaseName
|
||||||
|
location: location
|
||||||
|
sku: sqlDatabaseSku
|
||||||
|
}
|
||||||
|
|
||||||
|
resource sqlServer_AuditingSettings 'Microsoft.Sql/servers/auditingSettings@2021-11-01-preview' = {
|
||||||
|
parent: sqlServer
|
||||||
|
name: 'default'
|
||||||
|
properties: {
|
||||||
|
state: 'Enabled'
|
||||||
|
isAzureMonitorTargetEnabled: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource sqlServer_FirewallRule 'Microsoft.Sql/servers/firewallRules@2021-02-01-preview' = {
|
||||||
|
parent: sqlServer
|
||||||
|
name: 'AllowAllWindowsAzureIps'
|
||||||
|
properties: {
|
||||||
|
endIpAddress: '0.0.0.0'
|
||||||
|
startIpAddress: '0.0.0.0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource sqlDatabase_DiagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
|
||||||
|
scope: sqlDatabase
|
||||||
|
name: 'sqlDatabaseDiagnosticSettings'
|
||||||
|
properties: {
|
||||||
|
workspaceId: logAnalyticsWorkspace.id
|
||||||
|
logs: [
|
||||||
|
{
|
||||||
|
category: 'SQLInsights'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
{
|
||||||
|
category: 'AutomaticTuning'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
{
|
||||||
|
category: 'QueryStoreRuntimeStatistics'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
{
|
||||||
|
category: 'QueryStoreWaitStatistics'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
{
|
||||||
|
category: 'Errors'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
{
|
||||||
|
category: 'DatabaseWaitStatistics'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
{
|
||||||
|
category: 'Timeouts'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
{
|
||||||
|
category: 'Blocks'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
{
|
||||||
|
category: 'Deadlocks'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
metrics: [
|
||||||
|
{
|
||||||
|
category: 'Basic'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
{
|
||||||
|
category: 'InstanceAndAppAdvanced'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
{
|
||||||
|
category: 'WorkloadManagement'
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output appServiceAppName string = appServiceApp.name
|
||||||
|
output appServiceAppHostName string = appServiceApp.properties.defaultHostName
|
||||||
|
output sqlServerFullyQualifiedDomainName string = sqlServer.properties.fullyQualifiedDomainName
|
||||||
|
output sqlDatabaseName string = sqlDatabase.name
|
||||||
382
backend/.editorconfig
Normal file
382
backend/.editorconfig
Normal file
@@ -0,0 +1,382 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
# All files
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
|
||||||
|
# Xml files
|
||||||
|
[*.{xml,csproj,props,targets,ruleset,nuspec,resx}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# Javascript files
|
||||||
|
[*.js]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# Json files
|
||||||
|
[*.{json,config,nswag}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
# C# files
|
||||||
|
[*.cs]
|
||||||
|
|
||||||
|
#### Core EditorConfig Options ####
|
||||||
|
|
||||||
|
# Indentation and spacing
|
||||||
|
indent_size = 4
|
||||||
|
tab_width = 4
|
||||||
|
|
||||||
|
# New line preferences
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
#### .NET Coding Conventions ####
|
||||||
|
[*.{cs,vb}]
|
||||||
|
|
||||||
|
# Organize usings
|
||||||
|
dotnet_separate_import_directive_groups = false
|
||||||
|
dotnet_sort_system_directives_first = true
|
||||||
|
file_header_template = unset
|
||||||
|
|
||||||
|
# this. and Me. preferences
|
||||||
|
dotnet_style_qualification_for_event = false:silent
|
||||||
|
dotnet_style_qualification_for_field = false:silent
|
||||||
|
dotnet_style_qualification_for_method = false:silent
|
||||||
|
dotnet_style_qualification_for_property = false:silent
|
||||||
|
|
||||||
|
# Language keywords vs BCL types preferences
|
||||||
|
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
|
||||||
|
dotnet_style_predefined_type_for_member_access = true:silent
|
||||||
|
|
||||||
|
# Parentheses preferences
|
||||||
|
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
|
||||||
|
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
|
||||||
|
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
|
||||||
|
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
|
||||||
|
|
||||||
|
# Modifier preferences
|
||||||
|
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
|
||||||
|
|
||||||
|
# Expression-level preferences
|
||||||
|
dotnet_style_coalesce_expression = true:suggestion
|
||||||
|
dotnet_style_collection_initializer = true:suggestion
|
||||||
|
dotnet_style_explicit_tuple_names = true:suggestion
|
||||||
|
dotnet_style_null_propagation = true:suggestion
|
||||||
|
dotnet_style_object_initializer = true:suggestion
|
||||||
|
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||||
|
dotnet_style_prefer_auto_properties = true:suggestion
|
||||||
|
dotnet_style_prefer_compound_assignment = true:suggestion
|
||||||
|
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
|
||||||
|
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
|
||||||
|
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||||
|
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||||
|
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||||
|
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||||
|
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||||
|
|
||||||
|
# Field preferences
|
||||||
|
dotnet_style_readonly_field = true:warning
|
||||||
|
|
||||||
|
# Parameter preferences
|
||||||
|
dotnet_code_quality_unused_parameters = all:suggestion
|
||||||
|
|
||||||
|
# Suppression preferences
|
||||||
|
dotnet_remove_unnecessary_suppression_exclusions = none
|
||||||
|
|
||||||
|
#### C# Coding Conventions ####
|
||||||
|
[*.cs]
|
||||||
|
|
||||||
|
# var preferences
|
||||||
|
csharp_style_var_elsewhere = false:silent
|
||||||
|
csharp_style_var_for_built_in_types = false:silent
|
||||||
|
csharp_style_var_when_type_is_apparent = false:silent
|
||||||
|
|
||||||
|
# Expression-bodied members
|
||||||
|
csharp_style_expression_bodied_accessors = true:silent
|
||||||
|
csharp_style_expression_bodied_constructors = false:silent
|
||||||
|
csharp_style_expression_bodied_indexers = true:silent
|
||||||
|
csharp_style_expression_bodied_lambdas = true:suggestion
|
||||||
|
csharp_style_expression_bodied_local_functions = false:silent
|
||||||
|
csharp_style_expression_bodied_methods = false:silent
|
||||||
|
csharp_style_expression_bodied_operators = false:silent
|
||||||
|
csharp_style_expression_bodied_properties = true:silent
|
||||||
|
|
||||||
|
# Pattern matching preferences
|
||||||
|
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||||
|
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||||
|
csharp_style_prefer_not_pattern = true:suggestion
|
||||||
|
csharp_style_prefer_pattern_matching = true:silent
|
||||||
|
csharp_style_prefer_switch_expression = true:suggestion
|
||||||
|
|
||||||
|
# Null-checking preferences
|
||||||
|
csharp_style_conditional_delegate_call = true:suggestion
|
||||||
|
|
||||||
|
# Modifier preferences
|
||||||
|
csharp_prefer_static_local_function = true:warning
|
||||||
|
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
|
||||||
|
|
||||||
|
# Code-block preferences
|
||||||
|
csharp_prefer_braces = true:silent
|
||||||
|
csharp_prefer_simple_using_statement = true:suggestion
|
||||||
|
|
||||||
|
# Expression-level preferences
|
||||||
|
csharp_prefer_simple_default_expression = true:suggestion
|
||||||
|
csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||||
|
csharp_style_inlined_variable_declaration = true:suggestion
|
||||||
|
csharp_style_pattern_local_over_anonymous_function = true:suggestion
|
||||||
|
csharp_style_prefer_index_operator = true:suggestion
|
||||||
|
csharp_style_prefer_range_operator = true:suggestion
|
||||||
|
csharp_style_throw_expression = true:suggestion
|
||||||
|
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
|
||||||
|
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
|
||||||
|
|
||||||
|
# 'using' directive preferences
|
||||||
|
csharp_using_directive_placement = outside_namespace:silent
|
||||||
|
|
||||||
|
#### C# Formatting Rules ####
|
||||||
|
|
||||||
|
# New line preferences
|
||||||
|
csharp_new_line_before_catch = true
|
||||||
|
csharp_new_line_before_else = true
|
||||||
|
csharp_new_line_before_finally = true
|
||||||
|
csharp_new_line_before_members_in_anonymous_types = true
|
||||||
|
csharp_new_line_before_members_in_object_initializers = true
|
||||||
|
csharp_new_line_before_open_brace = all
|
||||||
|
csharp_new_line_between_query_expression_clauses = true
|
||||||
|
|
||||||
|
# Indentation preferences
|
||||||
|
csharp_indent_block_contents = true
|
||||||
|
csharp_indent_braces = false
|
||||||
|
csharp_indent_case_contents = true
|
||||||
|
csharp_indent_case_contents_when_block = true
|
||||||
|
csharp_indent_labels = one_less_than_current
|
||||||
|
csharp_indent_switch_labels = true
|
||||||
|
|
||||||
|
# Space preferences
|
||||||
|
csharp_space_after_cast = false
|
||||||
|
csharp_space_after_colon_in_inheritance_clause = true
|
||||||
|
csharp_space_after_comma = true
|
||||||
|
csharp_space_after_dot = false
|
||||||
|
csharp_space_after_keywords_in_control_flow_statements = true
|
||||||
|
csharp_space_after_semicolon_in_for_statement = true
|
||||||
|
csharp_space_around_binary_operators = before_and_after
|
||||||
|
csharp_space_around_declaration_statements = false
|
||||||
|
csharp_space_before_colon_in_inheritance_clause = true
|
||||||
|
csharp_space_before_comma = false
|
||||||
|
csharp_space_before_dot = false
|
||||||
|
csharp_space_before_open_square_brackets = false
|
||||||
|
csharp_space_before_semicolon_in_for_statement = false
|
||||||
|
csharp_space_between_empty_square_brackets = false
|
||||||
|
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||||
|
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_declaration_name_and_open_parenthesis = false
|
||||||
|
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_parentheses = false
|
||||||
|
csharp_space_between_square_brackets = false
|
||||||
|
|
||||||
|
# Wrapping preferences
|
||||||
|
csharp_preserve_single_line_blocks = true
|
||||||
|
csharp_preserve_single_line_statements = true
|
||||||
|
csharp_style_namespace_declarations = file_scoped:silent
|
||||||
|
csharp_style_prefer_method_group_conversion = true:silent
|
||||||
|
csharp_style_prefer_top_level_statements = true:silent
|
||||||
|
csharp_style_prefer_primary_constructors = true:suggestion
|
||||||
|
csharp_style_prefer_null_check_over_type_check = true:suggestion
|
||||||
|
csharp_style_prefer_local_over_anonymous_function = true:suggestion
|
||||||
|
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
|
||||||
|
csharp_style_prefer_tuple_swap = true:suggestion
|
||||||
|
csharp_style_prefer_utf8_string_literals = true:suggestion
|
||||||
|
|
||||||
|
#### Naming styles ####
|
||||||
|
[*.{cs,vb}]
|
||||||
|
|
||||||
|
# Naming rules
|
||||||
|
|
||||||
|
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
|
||||||
|
dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces
|
||||||
|
dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters
|
||||||
|
dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods
|
||||||
|
dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties
|
||||||
|
dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.events_should_be_pascalcase.symbols = events
|
||||||
|
dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables
|
||||||
|
dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants
|
||||||
|
dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters
|
||||||
|
dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields
|
||||||
|
dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields
|
||||||
|
dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields
|
||||||
|
dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields
|
||||||
|
dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields
|
||||||
|
dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields
|
||||||
|
dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields
|
||||||
|
dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums
|
||||||
|
dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions
|
||||||
|
dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase
|
||||||
|
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members
|
||||||
|
dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase
|
||||||
|
|
||||||
|
# Symbol specifications
|
||||||
|
|
||||||
|
dotnet_naming_symbols.interfaces.applicable_kinds = interface
|
||||||
|
dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.interfaces.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.enums.applicable_kinds = enum
|
||||||
|
dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.enums.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.events.applicable_kinds = event
|
||||||
|
dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.events.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.methods.applicable_kinds = method
|
||||||
|
dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.methods.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.properties.applicable_kinds = property
|
||||||
|
dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.properties.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.public_fields.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal
|
||||||
|
dotnet_naming_symbols.public_fields.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.private_fields.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.private_fields.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.private_static_fields.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.private_static_fields.required_modifiers = static
|
||||||
|
|
||||||
|
dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum
|
||||||
|
dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.types_and_namespaces.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||||
|
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.type_parameters.applicable_kinds = namespace
|
||||||
|
dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
|
||||||
|
dotnet_naming_symbols.type_parameters.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.private_constant_fields.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.private_constant_fields.required_modifiers = const
|
||||||
|
|
||||||
|
dotnet_naming_symbols.local_variables.applicable_kinds = local
|
||||||
|
dotnet_naming_symbols.local_variables.applicable_accessibilities = local
|
||||||
|
dotnet_naming_symbols.local_variables.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.local_constants.applicable_kinds = local
|
||||||
|
dotnet_naming_symbols.local_constants.applicable_accessibilities = local
|
||||||
|
dotnet_naming_symbols.local_constants.required_modifiers = const
|
||||||
|
|
||||||
|
dotnet_naming_symbols.parameters.applicable_kinds = parameter
|
||||||
|
dotnet_naming_symbols.parameters.applicable_accessibilities = *
|
||||||
|
dotnet_naming_symbols.parameters.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.public_constant_fields.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal
|
||||||
|
dotnet_naming_symbols.public_constant_fields.required_modifiers = const
|
||||||
|
|
||||||
|
dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal
|
||||||
|
dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static
|
||||||
|
|
||||||
|
dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static
|
||||||
|
|
||||||
|
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
|
||||||
|
dotnet_naming_symbols.local_functions.applicable_accessibilities = *
|
||||||
|
dotnet_naming_symbols.local_functions.required_modifiers =
|
||||||
|
|
||||||
|
# Naming styles
|
||||||
|
|
||||||
|
dotnet_naming_style.pascalcase.required_prefix =
|
||||||
|
dotnet_naming_style.pascalcase.required_suffix =
|
||||||
|
dotnet_naming_style.pascalcase.word_separator =
|
||||||
|
dotnet_naming_style.pascalcase.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.ipascalcase.required_prefix = I
|
||||||
|
dotnet_naming_style.ipascalcase.required_suffix =
|
||||||
|
dotnet_naming_style.ipascalcase.word_separator =
|
||||||
|
dotnet_naming_style.ipascalcase.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.tpascalcase.required_prefix = T
|
||||||
|
dotnet_naming_style.tpascalcase.required_suffix =
|
||||||
|
dotnet_naming_style.tpascalcase.word_separator =
|
||||||
|
dotnet_naming_style.tpascalcase.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style._camelcase.required_prefix = _
|
||||||
|
dotnet_naming_style._camelcase.required_suffix =
|
||||||
|
dotnet_naming_style._camelcase.word_separator =
|
||||||
|
dotnet_naming_style._camelcase.capitalization = camel_case
|
||||||
|
|
||||||
|
dotnet_naming_style.camelcase.required_prefix =
|
||||||
|
dotnet_naming_style.camelcase.required_suffix =
|
||||||
|
dotnet_naming_style.camelcase.word_separator =
|
||||||
|
dotnet_naming_style.camelcase.capitalization = camel_case
|
||||||
|
|
||||||
|
dotnet_naming_style.s_camelcase.required_prefix = s_
|
||||||
|
dotnet_naming_style.s_camelcase.required_suffix =
|
||||||
|
dotnet_naming_style.s_camelcase.word_separator =
|
||||||
|
dotnet_naming_style.s_camelcase.capitalization = camel_case
|
||||||
|
|
||||||
|
dotnet_style_namespace_match_folder = true:suggestion
|
||||||
13
backend/.github/FUNDING.yml
vendored
Normal file
13
backend/.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: JasonTaylorDev # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
|
# patreon: # Replace with a single Patreon username
|
||||||
|
# open_collective: # Replace with a single Open Collective username
|
||||||
|
# ko_fi: # Replace with a single Ko-fi username
|
||||||
|
# tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
|
# community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
|
# liberapay: # Replace with a single Liberapay username
|
||||||
|
# issuehunt: # Replace with a single IssueHunt username
|
||||||
|
# otechie: # Replace with a single Otechie username
|
||||||
|
# lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||||
|
# custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||||
82
backend/.github/workflows/build.yml
vendored
Normal file
82
backend/.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
paths-ignore:
|
||||||
|
- '.scripts/**'
|
||||||
|
- .gitignore
|
||||||
|
- CODE_OF_CONDUCT.md
|
||||||
|
- LICENSE
|
||||||
|
- README.md
|
||||||
|
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
build-artifacts:
|
||||||
|
type: boolean
|
||||||
|
required: true
|
||||||
|
default: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
name: Checkout code
|
||||||
|
|
||||||
|
- name: Cache NuGet packages
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ~/.nuget/packages
|
||||||
|
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-nuget-
|
||||||
|
|
||||||
|
|
||||||
|
- name: Install .NET
|
||||||
|
uses: actions/setup-dotnet@v3
|
||||||
|
|
||||||
|
- name: Restore solution
|
||||||
|
run: dotnet restore
|
||||||
|
|
||||||
|
- name: Build solution
|
||||||
|
run: dotnet build --no-restore --configuration Release
|
||||||
|
|
||||||
|
- name: Test solution
|
||||||
|
run: dotnet test --no-build --configuration Release --filter "FullyQualifiedName!~AcceptanceTests"
|
||||||
|
|
||||||
|
- name: Publish website
|
||||||
|
if: ${{ inputs.build-artifacts == true }}
|
||||||
|
run: |
|
||||||
|
dotnet publish --configuration Release --runtime win-x86 --self-contained --output ./publish
|
||||||
|
cd publish
|
||||||
|
zip -r ./publish.zip .
|
||||||
|
working-directory: ./src/Web/
|
||||||
|
|
||||||
|
- name: Upload website artifact (website)
|
||||||
|
if: ${{ inputs.build-artifacts == true }}
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: website
|
||||||
|
path: ./src/Web/publish/publish.zip
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Create EF Core migrations bundle
|
||||||
|
if: ${{ inputs.build-artifacts == true }}
|
||||||
|
run: |
|
||||||
|
dotnet new tool-manifest
|
||||||
|
dotnet tool install dotnet-ef
|
||||||
|
dotnet ef migrations bundle --configuration Release -p ./src/Infrastructure/ -s ./src/Web/ -o efbundle.exe
|
||||||
|
zip -r ./efbundle.zip efbundle.exe
|
||||||
|
env:
|
||||||
|
SkipNSwag: True
|
||||||
|
|
||||||
|
- name: Upload EF Core migrations bundle artifact (efbundle)
|
||||||
|
if: ${{ inputs.build-artifacts == true }}
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: efbundle
|
||||||
|
path: ./efbundle.zip
|
||||||
|
if-no-files-found: error
|
||||||
42
backend/.github/workflows/cicd.yml
vendored
Normal file
42
backend/.github/workflows/cicd.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: CICD
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
paths-ignore:
|
||||||
|
- .gitignore
|
||||||
|
- CODE_OF_CONDUCT.md
|
||||||
|
- LICENSE
|
||||||
|
- README.md
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
build:
|
||||||
|
uses: ./.github/workflows/build.yml
|
||||||
|
with:
|
||||||
|
build-artifacts: true
|
||||||
|
|
||||||
|
deploy-development:
|
||||||
|
uses: ./.github/workflows/deploy.yml
|
||||||
|
secrets: inherit
|
||||||
|
needs: [ build ]
|
||||||
|
with:
|
||||||
|
environmentName: Development
|
||||||
|
|
||||||
|
deploy-staging:
|
||||||
|
uses: ./.github/workflows/deploy.yml
|
||||||
|
secrets: inherit
|
||||||
|
needs: [ deploy-development ]
|
||||||
|
with:
|
||||||
|
environmentName: Staging
|
||||||
|
|
||||||
|
deploy-production:
|
||||||
|
uses: ./.github/workflows/deploy.yml
|
||||||
|
secrets: inherit
|
||||||
|
needs: [ deploy-staging ]
|
||||||
|
with:
|
||||||
|
environmentName: Production
|
||||||
107
backend/.github/workflows/deploy.yml
vendored
Normal file
107
backend/.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
name: Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
environmentName:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
validate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: ${{ inputs.environmentName }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
name: Checkout code
|
||||||
|
|
||||||
|
- uses: azure/login@v1
|
||||||
|
name: Login to Azure
|
||||||
|
with:
|
||||||
|
client-id: ${{ vars.AZURE_CLIENT_ID }}
|
||||||
|
tenant-id: ${{ vars.AZURE_TENANT_ID }}
|
||||||
|
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
|
||||||
|
|
||||||
|
- if: inputs.environmentName == 'Development'
|
||||||
|
uses: azure/arm-deploy@v1
|
||||||
|
name: Run preflight validation
|
||||||
|
with:
|
||||||
|
deploymentName: ${{ github.run_number }}
|
||||||
|
resourceGroupName: ${{ vars.AZURE_RESOURCE_GROUP_NAME }}
|
||||||
|
template: ./.azure/bicep/main.bicep
|
||||||
|
parameters: >
|
||||||
|
environmentName=${{ inputs.environmentName }}
|
||||||
|
sqlAdministratorUsername=${{ vars.AZURE_SQL_ADMINISTRATOR_USERNAME }}
|
||||||
|
sqlAdministratorPassword=${{ secrets.AZURE_SQL_ADMINISTRATOR_PASSWORD }}
|
||||||
|
projectName=${{ vars.PROJECT_NAME }}
|
||||||
|
deploymentMode: Validate
|
||||||
|
|
||||||
|
- if: inputs.environmentName != 'Development'
|
||||||
|
uses: azure/arm-deploy@v1
|
||||||
|
name: Run what-if
|
||||||
|
with:
|
||||||
|
failOnStdErr: false
|
||||||
|
resourceGroupName: ${{ vars.AZURE_RESOURCE_GROUP_NAME }}
|
||||||
|
template: ./.azure/bicep/main.bicep
|
||||||
|
parameters: >
|
||||||
|
environmentName=${{ inputs.environmentName }}
|
||||||
|
sqlAdministratorUsername=${{ vars.AZURE_SQL_ADMINISTRATOR_USERNAME }}
|
||||||
|
sqlAdministratorPassword=${{ secrets.AZURE_SQL_ADMINISTRATOR_PASSWORD }}
|
||||||
|
projectName=${{ vars.PROJECT_NAME }}
|
||||||
|
additionalArguments: --what-if
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
needs: [ validate ]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: ${{ inputs.environmentName }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
name: Checkout code
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
name: Download artifacts
|
||||||
|
|
||||||
|
- name: Install .NET
|
||||||
|
uses: actions/setup-dotnet@v3
|
||||||
|
|
||||||
|
- uses: azure/login@v1
|
||||||
|
name: Login to Azure
|
||||||
|
with:
|
||||||
|
client-id: ${{ vars.AZURE_CLIENT_ID }}
|
||||||
|
tenant-id: ${{ vars.AZURE_TENANT_ID }}
|
||||||
|
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
|
||||||
|
|
||||||
|
- uses: azure/arm-deploy@v1
|
||||||
|
id: deploy
|
||||||
|
name: Deploy infrastructure
|
||||||
|
with:
|
||||||
|
failOnStdErr: false
|
||||||
|
deploymentName: ${{ github.run_number }}
|
||||||
|
resourceGroupName: ${{ vars.AZURE_RESOURCE_GROUP_NAME }}
|
||||||
|
template: ./.azure/bicep/main.bicep
|
||||||
|
parameters: >
|
||||||
|
environmentName=${{ inputs.environmentName }}
|
||||||
|
sqlAdministratorUsername=${{ vars.AZURE_SQL_ADMINISTRATOR_USERNAME }}
|
||||||
|
sqlAdministratorPassword=${{ secrets.AZURE_SQL_ADMINISTRATOR_PASSWORD }}
|
||||||
|
projectName=${{ vars.PROJECT_NAME }}
|
||||||
|
|
||||||
|
- name: Initialise database
|
||||||
|
run: |
|
||||||
|
unzip -o ./efbundle/efbundle.zip
|
||||||
|
echo '{ "ConnectionStrings": { "DefaultConnection": "" } }' > appsettings.json
|
||||||
|
./efbundle.exe --connection "Server=${{ steps.deploy.outputs.sqlServerFullyQualifiedDomainName }};Initial Catalog=${{ steps.deploy.outputs.sqlDatabaseName }};Persist Security Info=False;User ID=${{ vars.AZURE_SQL_ADMINISTRATOR_USERNAME }};Password=${{ secrets.AZURE_SQL_ADMINISTRATOR_PASSWORD }};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;" --verbose
|
||||||
|
|
||||||
|
- uses: azure/webapps-deploy@v2
|
||||||
|
name: Deploy website
|
||||||
|
with:
|
||||||
|
app-name: ${{ steps.deploy.outputs.appServiceAppName }}
|
||||||
|
package: website/publish.zip
|
||||||
492
backend/.gitignore
vendored
Normal file
492
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,492 @@
|
|||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from `dotnet new gitignore`
|
||||||
|
|
||||||
|
# Ignore the main configuration file
|
||||||
|
appsettings.json
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Mono auto generated files
|
||||||
|
mono_crash.*
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Ww][Ii][Nn]32/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUnit
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
nunit-*.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# Tye
|
||||||
|
.tye/
|
||||||
|
|
||||||
|
# ASP.NET Scaffolding
|
||||||
|
ScaffoldingReadMe.txt
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.tlog
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Coverlet is a free, cross platform Code Coverage Tool
|
||||||
|
coverage*.json
|
||||||
|
coverage*.xml
|
||||||
|
coverage*.info
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# NuGet Symbol Packages
|
||||||
|
*.snupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
*.appxbundle
|
||||||
|
*.appxupload
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!?*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
*- [Bb]ackup.rdl
|
||||||
|
*- [Bb]ackup ([0-9]).rdl
|
||||||
|
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||||
|
*.vbp
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||||
|
*.dsw
|
||||||
|
*.dsp
|
||||||
|
|
||||||
|
# Visual Studio 6 technical files
|
||||||
|
*.ncb
|
||||||
|
*.aps
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
.localhistory/
|
||||||
|
|
||||||
|
# Visual Studio History (VSHistory) files
|
||||||
|
.vshistory/
|
||||||
|
|
||||||
|
# BeatPulse healthcheck temp database
|
||||||
|
healthchecksdb
|
||||||
|
|
||||||
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
|
MigrationBackup/
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
.ionide/
|
||||||
|
|
||||||
|
# Fody - auto-generated XML schema
|
||||||
|
FodyWeavers.xsd
|
||||||
|
|
||||||
|
# VS Code files for those working on multiple tools
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Windows Installer files from build outputs
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
##
|
||||||
|
## Visual studio for Mac
|
||||||
|
##
|
||||||
|
|
||||||
|
|
||||||
|
# globs
|
||||||
|
Makefile.in
|
||||||
|
*.userprefs
|
||||||
|
*.usertasks
|
||||||
|
config.make
|
||||||
|
config.status
|
||||||
|
aclocal.m4
|
||||||
|
install-sh
|
||||||
|
autom4te.cache/
|
||||||
|
*.tar.gz
|
||||||
|
tarballs/
|
||||||
|
test-results/
|
||||||
|
|
||||||
|
# Mac bundle stuff
|
||||||
|
*.dmg
|
||||||
|
*.app
|
||||||
|
|
||||||
|
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
Thumbs.db
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Dump file
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
[Dd]esktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
# Vim temporary swap files
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# Other IDE files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
|
||||||
|
#AppSettings dev
|
||||||
|
*/appsettings.Development.json
|
||||||
|
/src/Web/appsettings.Development.json
|
||||||
29
backend/.scripts/checks.ps1
Normal file
29
backend/.scripts/checks.ps1
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Check if Azure CLI is installed
|
||||||
|
$azPath = (Get-Command az -ErrorAction SilentlyContinue).Source
|
||||||
|
if (-not $azPath) {
|
||||||
|
throw "Azure CLI (az) is not installed. Please install it and try again."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if Azure CLI is authenticated
|
||||||
|
az account show --output none
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "Azure CLI (az) is not authenticated. Please authenticate with Azure CLI and try again."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if GitHub CLI is installed
|
||||||
|
$ghPath = (Get-Command gh -ErrorAction SilentlyContinue).Source
|
||||||
|
if (-not $ghPath) {
|
||||||
|
throw "GitHub CLI (gh) is not installed. Please install it and try again."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if GitHub CLI is authenticated
|
||||||
|
gh auth status | Out-Null
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "GitHub CLI (gh) is not authenticated. Please authenticate with GitHub CLI and try again."
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if Git repo is initialised
|
||||||
|
git status | Out-Null
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
throw "The Git repository has not been initialised. Please create a new GitHub repository and try again."
|
||||||
|
}
|
||||||
78
backend/.scripts/cleanup.ps1
Normal file
78
backend/.scripts/cleanup.ps1
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
Param(
|
||||||
|
[String]$ProjectName
|
||||||
|
)
|
||||||
|
|
||||||
|
$scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$checksScript = Join-Path $scriptRoot "checks.ps1"
|
||||||
|
|
||||||
|
try {
|
||||||
|
. $checksScript
|
||||||
|
} catch {
|
||||||
|
Write-Host $_.Exception.Message -ForegroundColor Red
|
||||||
|
Write-Host "Setup script terminated due to the checks failure." -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$MissingParameterValues = $false
|
||||||
|
|
||||||
|
if (-not $ProjectName) {
|
||||||
|
$ProjectName = $(gh repo view --json name -q '.name' 2> $null)
|
||||||
|
if (-not $ProjectName) { $MissingParameterValues = $true }
|
||||||
|
}
|
||||||
|
|
||||||
|
$ScriptParameters = @{
|
||||||
|
"ProjectName" = $ProjectName
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host
|
||||||
|
Write-Host "This script performs cleanup operations to delete resource groups, Azure AD applications, and purge deleted key vaults associated with a specific project hosted on GitHub. It searches for resources based on the project name and performs the necessary deletion and purging actions. The script leverages the Azure CLI to interact with Azure resources. It aims to facilitate the cleanup process and remove unnecessary resources from your Azure environment."
|
||||||
|
Write-Host
|
||||||
|
Write-Host "Parameters:" -ForegroundColor Green
|
||||||
|
$ScriptParameters | Format-Table -AutoSize
|
||||||
|
Write-Host
|
||||||
|
|
||||||
|
if ($MissingParameterValues) {
|
||||||
|
Write-Host "Script execution cancelled. Missing parameter values." -ForegroundColor Red
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Warning: This script will perform cleanup operations, including deleting resource groups, Azure AD applications, and purging deleted key vaults starting with the project name '$ProjectName'. Make sure you understand the consequences and have verified the project name before proceeding." -ForegroundColor Red
|
||||||
|
Write-Host
|
||||||
|
Write-Host "Disclaimer: Use this script at your own risk. The author and contributors are not responsible for any loss of data or unintended consequences resulting from running this script." -ForegroundColor Yellow
|
||||||
|
Write-Host
|
||||||
|
|
||||||
|
$confirmation = Read-Host "Do you want to continue? (y/N)"
|
||||||
|
|
||||||
|
if ($confirmation -ne "y") {
|
||||||
|
Write-Host "Script execution cancelled." -ForegroundColor Red
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "🔍 Searching for Resource Groups..."
|
||||||
|
$resourceGroups = az group list --query "[?starts_with(name, '$ProjectName')].name" --output tsv
|
||||||
|
|
||||||
|
foreach ($rg in $resourceGroups) {
|
||||||
|
Write-Host "🔥 Deleting: $rg"
|
||||||
|
az group delete --name $rg --yes > $null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "🔍 Searching for Azure AD Applications..."
|
||||||
|
$appRegistrations = az ad app list --display-name $ProjectName --query "[].{Name:displayName, AppId:appId}" --output json | ConvertFrom-Json
|
||||||
|
|
||||||
|
foreach ($appRegistration in $appRegistrations) {
|
||||||
|
$appName = $appRegistration.Name
|
||||||
|
$appId = $appRegistration.AppId
|
||||||
|
|
||||||
|
Write-Host "🔥 Deleting: $appName"
|
||||||
|
az ad app delete --id $appId > $null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "🔍 Searching for Deleted Key Vaults..."
|
||||||
|
$deletedKeyVaults = az keyvault list-deleted --query "[?starts_with(name, 'kv-$ProjectName')].name" --output tsv
|
||||||
|
|
||||||
|
foreach ($vaultName in $deletedKeyVaults) {
|
||||||
|
Write-Host "🔥 Purging: $vaultName"
|
||||||
|
az keyvault purge --name $vaultName > $null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "✅ Done"
|
||||||
5
backend/.scripts/environments.json
Normal file
5
backend/.scripts/environments.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"Dev": "Development",
|
||||||
|
"Stg": "Staging",
|
||||||
|
"Prd": "Production"
|
||||||
|
}
|
||||||
210
backend/.scripts/setup.ps1
Normal file
210
backend/.scripts/setup.ps1
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
Param(
|
||||||
|
[String]$GitHubOrganisationName,
|
||||||
|
[String]$GitHubRepositoryName,
|
||||||
|
[String]$AzureLocation,
|
||||||
|
[String]$AzureSubscriptionId,
|
||||||
|
[String]$AzureTenantId,
|
||||||
|
[ValidateLength(4, 17)]
|
||||||
|
[String]$ProjectName,
|
||||||
|
[String]$AzureSqlLogin = "SqlAdmin"
|
||||||
|
)
|
||||||
|
|
||||||
|
$scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$checksScript = Join-Path $scriptRoot "checks.ps1"
|
||||||
|
$environmentsFile = Join-Path $scriptRoot "environments.json"
|
||||||
|
|
||||||
|
try {
|
||||||
|
. $checksScript
|
||||||
|
} catch {
|
||||||
|
Write-Host $_.Exception.Message -ForegroundColor Red
|
||||||
|
Write-Host "Setup script terminated due to the checks failure." -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$MissingParameterValues = $false
|
||||||
|
|
||||||
|
if (-not $GitHubOrganisationName) {
|
||||||
|
$ownerJson = gh repo view --json owner 2>$null | ConvertFrom-Json
|
||||||
|
if ($ownerJson -and $ownerJson.owner -and $ownerJson.owner.login) {
|
||||||
|
$GitHubOrganisationName = $ownerJson.owner.login
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$MissingParameterValues = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $GitHubRepositoryName) {
|
||||||
|
$GitHubRepositoryName = $(gh repo view --json name -q '.name' 2> $null)
|
||||||
|
if (-not $GitHubRepositoryName) { $MissingParameterValues = $true }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $AzureLocation) {
|
||||||
|
$AzureLocation = "australiaeast"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $AzureSubscriptionId) {
|
||||||
|
$AzureSubscriptionId = $(az account show --query id --output tsv 2> $null)
|
||||||
|
if (-not $AzureSubscriptionId) { $MissingParameterValues = $true }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $AzureTenantId) {
|
||||||
|
$AzureTenantId = $(az account show --query tenantId --output tsv 2> $null)
|
||||||
|
if (-not $AzureTenantId) { $MissingParameterValues = $true }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $ProjectName) {
|
||||||
|
if ($GitHubRepositoryName) {
|
||||||
|
$ProjectName = $GitHubRepositoryName
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $ProjectName) { $MissingParameterValues = $true }
|
||||||
|
}
|
||||||
|
|
||||||
|
$repoUrl = "https://github.com/$GitHubOrganisationName/$GitHubRepositoryName"
|
||||||
|
|
||||||
|
$environments = Get-Content -Raw -Path $environmentsFile | ConvertFrom-Json
|
||||||
|
|
||||||
|
$ParametersTableData = @{
|
||||||
|
"AzureLocation" = $AzureLocation
|
||||||
|
"AzureSubscriptionID" = $AzureSubscriptionId
|
||||||
|
"AzureTenantID" = $AzureTenantId
|
||||||
|
"GitHubOrganisationName" = $GitHubOrganisationName
|
||||||
|
"GitHubRepositoryName" = $GitHubRepositoryName
|
||||||
|
"ProjectName" = $ProjectName
|
||||||
|
"AzureSqlLogin" = $AzureSqlLogin
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host
|
||||||
|
Write-Host "This script automates the setup of environments, resources, and credentials for a project hosted on GitHub and deployed to Azure. It creates workload identities in Azure AD, sets up resource groups, and configures environment-specific variables and secrets in the GitHub repository. The script leverages the Azure CLI, GitHub CLI, and GitHub APIs to perform these tasks. It aims to streamline the process of setting up and configuring development, staging, and production environments for the project."
|
||||||
|
Write-Host
|
||||||
|
Write-Host "Parameters:" -ForegroundColor Green
|
||||||
|
$ParametersTableData | Format-Table -AutoSize
|
||||||
|
|
||||||
|
if ($MissingParameterValues) {
|
||||||
|
Write-Host "Script execution cancelled. Missing parameter values." -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$EnvironmentTableData = foreach ($environment in $environments.PSObject.Properties) {
|
||||||
|
[PSCustomObject]@{
|
||||||
|
Abbreviation = $environment.Name
|
||||||
|
Name = $environment.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Environments:" -ForegroundColor Green
|
||||||
|
$EnvironmentTableData | Select-Object Name, Abbreviation | Format-Table -AutoSize
|
||||||
|
Write-Host
|
||||||
|
|
||||||
|
Write-Host "Warning: Running this script will perform various operations in your GitHub repository and Azure subscription. Ensure that you have the necessary permissions and understand the consequences. " -ForegroundColor Red
|
||||||
|
Write-Host
|
||||||
|
Write-Host "Disclaimer: Use this script at your own risk. The author and contributors are not responsible for any loss of data or unintended consequences resulting from running this script." -ForegroundColor Yellow
|
||||||
|
Write-Host
|
||||||
|
|
||||||
|
$confirmation = Read-Host "Do you want to continue? (y/N)"
|
||||||
|
|
||||||
|
if ($confirmation -ne "y") {
|
||||||
|
Write-Host "Script execution cancelled." -ForegroundColor Red
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host
|
||||||
|
|
||||||
|
function CreateWorkloadIdentity {
|
||||||
|
param (
|
||||||
|
$environmentAbbr,
|
||||||
|
$environmentName
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create Azure AD Application Registration
|
||||||
|
$applicationRegistrationDetails=$(az ad app create --display-name "$ProjectName$environmentAbbr") | ConvertFrom-Json
|
||||||
|
|
||||||
|
# Create federated credentials
|
||||||
|
$credential = @{
|
||||||
|
name="$ProjectName$environmentName";
|
||||||
|
issuer="https://token.actions.githubusercontent.com";
|
||||||
|
subject="repo:${GitHubOrganisationName}/${GitHubRepositoryName}:environment:$environmentName";
|
||||||
|
audiences=@("api://AzureADTokenExchange")
|
||||||
|
} | ConvertTo-Json
|
||||||
|
|
||||||
|
$credential | az ad app federated-credential create --id $applicationRegistrationDetails.id --parameters "@-" | Out-Null
|
||||||
|
|
||||||
|
$credential = @{
|
||||||
|
name="$ProjectName";
|
||||||
|
issuer="https://token.actions.githubusercontent.com";
|
||||||
|
subject="repo:${GitHubOrganisationName}/${GitHubRepositoryName}:ref:refs/heads/main";
|
||||||
|
audiences=@("api://AzureADTokenExchange")
|
||||||
|
} | ConvertTo-Json
|
||||||
|
|
||||||
|
$credential | az ad app federated-credential create --id $applicationRegistrationDetails.id --parameters "@-" | Out-Null
|
||||||
|
|
||||||
|
return $applicationRegistrationDetails.appId
|
||||||
|
}
|
||||||
|
|
||||||
|
function CreateResourceGroup {
|
||||||
|
param (
|
||||||
|
$environmentAbbr,
|
||||||
|
$appId
|
||||||
|
)
|
||||||
|
|
||||||
|
$resourceGroupId = $(az group create --name "$ProjectName$environmentAbbr" --location $AzureLocation --query id --output tsv)
|
||||||
|
az ad sp create --id $appId
|
||||||
|
az role assignment create --assignee $appId --role Contributor --scope $resourceGroupId
|
||||||
|
}
|
||||||
|
|
||||||
|
function CreateEnvironment {
|
||||||
|
param (
|
||||||
|
$environmentName
|
||||||
|
)
|
||||||
|
|
||||||
|
$token = gh auth token
|
||||||
|
$header = @{"Authorization" = "token $token" }
|
||||||
|
$contentType = "application/json"
|
||||||
|
|
||||||
|
$uri = "https://api.github.com/repos/$GitHubOrganisationName/$GitHubRepositoryName/environments/$environmentName"
|
||||||
|
Invoke-WebRequest -Method PUT -Header $header -ContentType $contentType -Uri $uri
|
||||||
|
}
|
||||||
|
|
||||||
|
function GenerateRandomPassword {
|
||||||
|
param (
|
||||||
|
[int]$Length = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
$ValidChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#^_-+=?<>|~".ToCharArray()
|
||||||
|
$Password = -join ((Get-Random -Count $Length -InputObject $ValidChars) | Get-Random -Count $Length)
|
||||||
|
|
||||||
|
return $Password
|
||||||
|
}
|
||||||
|
|
||||||
|
function SetVariables() {
|
||||||
|
gh variable set AZURE_TENANT_ID --body $AzureTenantId --repo $repoUrl
|
||||||
|
gh variable set AZURE_SUBSCRIPTION_ID --body $AzureSubscriptionId --repo $repoUrl
|
||||||
|
gh variable set PROJECT_NAME --body $ProjectName --repo $repoUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
function SetEnvironmentVariablesAndSecrets {
|
||||||
|
param(
|
||||||
|
$environmentAbbr,
|
||||||
|
$environmentName,
|
||||||
|
$appId
|
||||||
|
)
|
||||||
|
|
||||||
|
gh variable set AZURE_CLIENT_ID --body "$appId" --env $environmentName --repo $repoUrl
|
||||||
|
gh variable set AZURE_RESOURCE_GROUP_NAME --body "$ProjectName$environmentAbbr" --env $environmentName --repo $repoUrl
|
||||||
|
gh variable set AZURE_SQL_ADMINISTRATOR_USERNAME --body "$AzureSqlLogin" --env $environmentName --repo $repoUrl
|
||||||
|
gh secret set AZURE_SQL_ADMINISTRATOR_PASSWORD --body (GenerateRandomPassword) --env $environmentName --repo $repoUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
SetVariables
|
||||||
|
|
||||||
|
foreach ($environment in $environments.PSObject.Properties) {
|
||||||
|
$environmentAbbr = $environment.Name
|
||||||
|
$environmentName = $environment.Value
|
||||||
|
|
||||||
|
CreateEnvironment $environmentName
|
||||||
|
$appId = CreateWorkloadIdentity $environmentAbbr $environmentName
|
||||||
|
CreateResourceGroup $environmentAbbr $appId
|
||||||
|
SetEnvironmentVariablesAndSecrets $environmentAbbr $environmentName $appId
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "✅ Done"
|
||||||
9
backend/Directory.Build.props
Normal file
9
backend/Directory.Build.props
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<!-- See https://aka.ms/dotnet/msbuild/customize for more details on customizing your build -->
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
1
backend/EfApplicationDbContext.ps1
Normal file
1
backend/EfApplicationDbContext.ps1
Normal file
@@ -0,0 +1 @@
|
|||||||
|
dotnet ef $args --startup-project ./src/Web/Web.csproj --project ./src/Infrastructure/Infrastructure.csproj
|
||||||
1
backend/EfContentDbContext.ps1
Normal file
1
backend/EfContentDbContext.ps1
Normal file
@@ -0,0 +1 @@
|
|||||||
|
dotnet ef $args --startup-project ./src/Web/Web.csproj --project ./src/Web/Web.csproj --context ContentDbContext
|
||||||
1
backend/EfMessagingDbContext.ps1
Normal file
1
backend/EfMessagingDbContext.ps1
Normal file
@@ -0,0 +1 @@
|
|||||||
|
dotnet ef $args --startup-project ./src/Web/Web.csproj --project ./src/Web/Web.csproj --context MessagingDbContext
|
||||||
43
backend/Hutopy.sln
Normal file
43
backend/Hutopy.sln
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.0.31903.59
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{6ED356A7-8B47-4613-AD01-C85CF28491BD}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E2DA20AA-28D1-455C-BF50-C49A8F831633}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
.editorconfig = .editorconfig
|
||||||
|
.gitignore = .gitignore
|
||||||
|
Directory.Build.props = Directory.Build.props
|
||||||
|
global.json = global.json
|
||||||
|
README.md = README.md
|
||||||
|
start-infrastructure.sh = start-infrastructure.sh
|
||||||
|
azure-pipelines.yml = azure-pipelines.yml
|
||||||
|
update-databases.sh = update-databases.sh
|
||||||
|
create-sql-scripts.sh = create-sql-scripts.sh
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{4E4EE20C-F06A-4A1B-851F-C5577796941C}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{4E4EE20C-F06A-4A1B-851F-C5577796941C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4E4EE20C-F06A-4A1B-851F-C5577796941C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4E4EE20C-F06A-4A1B-851F-C5577796941C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4E4EE20C-F06A-4A1B-851F-C5577796941C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{4E4EE20C-F06A-4A1B-851F-C5577796941C} = {6ED356A7-8B47-4613-AD01-C85CF28491BD}
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {3CB609D9-5D54-4C11-A371-DAAC8B74E430}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
2
backend/Hutopy.sln.DotSettings
Normal file
2
backend/Hutopy.sln.DotSettings
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=hutopy/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
110
backend/README.md
Normal file
110
backend/README.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# Hutopy
|
||||||
|
|
||||||
|
## Patterns / strategy used
|
||||||
|
- Clean Architecture ( with Infrastructure, Domain, Application and Web layers )
|
||||||
|
- Minimal API endpoints.
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
- Install Docker : https://www.docker.com/get-started/
|
||||||
|
- Install sql server management ( or preferred tool ) : https://learn.microsoft.com/en-us/sql/ssms/download-sql-server-management-studio-ssms?view=sql-server-ver16#download-ssms
|
||||||
|
|
||||||
|
## Database setup in docker for local dev
|
||||||
|
```
|
||||||
|
docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=P@ssword123!" -p 1433:1433 -d mcr.microsoft.com/mssql/server:2022-latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Or with a mounted volume to persist data on the computer instead ( persist data even if the container is deleted )
|
||||||
|
```
|
||||||
|
docker run -e 'ACCEPT_EULA=Y' -e 'MSSQL_SA_PASSWORD=P@ssword123!' -p 1433:1433 -v C:\dev\DockerVolumes\SqlServer-Utopy-1\data:/var/opt/mssql/data -v C:\dev\DockerVolumes\SqlServer-Utopy-1\log:/var/opt/mssql/log -v C:\dev\DockerVolumes\SqlServer-Utopy-1\secrets:/var/opt/mssql/secrets -d mcr.microsoft.com/mssql/server:2022-latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Postgres DB setup in docker for local dev
|
||||||
|
```
|
||||||
|
docker run -p 5432:5432 --name Hutopy -e POSTGRES_PASSWORD=P@ssword123! -e POSTGRES_USER=sa -d postgres
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Entity Framework
|
||||||
|
|
||||||
|
Create a new migration :
|
||||||
|
```
|
||||||
|
./Ef.ps1 migrations add NomDeLaMigration
|
||||||
|
```
|
||||||
|
|
||||||
|
Update database :
|
||||||
|
```
|
||||||
|
./Ef.ps1 database update
|
||||||
|
```
|
||||||
|
|
||||||
|
## Secret Manager tool
|
||||||
|
Go to Web project: cd src/Web
|
||||||
|
|
||||||
|
Add a user secret for local development :
|
||||||
|
```
|
||||||
|
dotnet user-secrets set "DB_PASSWORD" "12345"
|
||||||
|
```
|
||||||
|
|
||||||
|
list your stored secrets :
|
||||||
|
```
|
||||||
|
dotnet user-secrets list
|
||||||
|
```
|
||||||
|
|
||||||
|
Delete a secret :
|
||||||
|
```
|
||||||
|
dotnet user-secrets remove "DB_PASSWORD"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Run `dotnet build -tl` to build the solution.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
To run the web application:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd .\src\Web\
|
||||||
|
dotnet watch run
|
||||||
|
```
|
||||||
|
|
||||||
|
Navigate to https://localhost:5001. The application will automatically reload if you change any of the source files.
|
||||||
|
|
||||||
|
## Code Styles & Formatting
|
||||||
|
|
||||||
|
The template includes [EditorConfig](https://editorconfig.org/) support to help maintain consistent coding styles for multiple developers working on the same project across various editors and IDEs. The **.editorconfig** file defines the coding styles applicable to this solution.
|
||||||
|
|
||||||
|
## Code Scaffolding
|
||||||
|
|
||||||
|
Scaffold new commands and queries.
|
||||||
|
|
||||||
|
Start in the `.\src\Application\` folder.
|
||||||
|
|
||||||
|
Create a new command:
|
||||||
|
|
||||||
|
```
|
||||||
|
dotnet new ca-usecase --name CreateTodoList --feature-name TodoLists --usecase-type command --return-type int
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a new query:
|
||||||
|
|
||||||
|
```
|
||||||
|
dotnet new ca-usecase -n GetTodos -fn TodoLists -ut query -rt TodosVm
|
||||||
|
```
|
||||||
|
|
||||||
|
If you encounter the error *"No templates or subcommands found matching: 'ca-usecase'."*, install the template and try again:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet new install Clean.Architecture.Solution.Template::8.0.4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
|
The solution contains unit, integration, and functional tests.
|
||||||
|
|
||||||
|
- Using Moq, Nunit, Respawn, FluentAssertions
|
||||||
|
|
||||||
|
To run the tests:
|
||||||
|
```bash
|
||||||
|
dotnet test
|
||||||
|
```
|
||||||
18
backend/Start.ps1
Normal file
18
backend/Start.ps1
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
$password = $null
|
||||||
|
|
||||||
|
# Manually parse arguments for a "-p" flag
|
||||||
|
for ($i = 0; $i -lt $args.Count; $i++) {
|
||||||
|
if ($args[$i] -eq "-p" -and $args.Count -gt $i + 1) {
|
||||||
|
$password = $args[$i + 1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if we got a value for our environment variable
|
||||||
|
if ($null -ne $password) {
|
||||||
|
$Env:DB_PASSWORD = $password
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run the app in the web dir.
|
||||||
|
Set-Location -Path ./src/Web
|
||||||
|
dotnet watch run
|
||||||
34
backend/azure-pipelines.yml
Normal file
34
backend/azure-pipelines.yml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# ASP.NET Core (.NET Framework)
|
||||||
|
# Build and test ASP.NET Core projects targeting the full .NET Framework.
|
||||||
|
# Add steps that publish symbols, save build artifacts, and more:
|
||||||
|
# https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
- master
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: 'windows-latest'
|
||||||
|
|
||||||
|
variables:
|
||||||
|
solution: '**/*.sln'
|
||||||
|
buildPlatform: 'Any CPU'
|
||||||
|
buildConfiguration: 'Release'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: NuGetToolInstaller@1
|
||||||
|
|
||||||
|
- task: NuGetCommand@2
|
||||||
|
inputs:
|
||||||
|
restoreSolution: '$(solution)'
|
||||||
|
|
||||||
|
- task: VSBuild@1
|
||||||
|
inputs:
|
||||||
|
solution: '$(solution)'
|
||||||
|
msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package /p:PackageAsSingleFile=true /p:SkipInvalidConfigurations=true /p:DesktopBuildPackageLocation="$(build.artifactStagingDirectory)\WebApp.zip" /p:DeployIisAppPath="Default Web Site"'
|
||||||
|
platform: '$(buildPlatform)'
|
||||||
|
configuration: '$(buildConfiguration)'
|
||||||
|
|
||||||
|
- task: VSTest@2
|
||||||
|
inputs:
|
||||||
|
platform: '$(buildPlatform)'
|
||||||
|
configuration: '$(buildConfiguration)'
|
||||||
37
backend/create-sql-scripts.sh
Normal file
37
backend/create-sql-scripts.sh
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
dotnet ef migrations script \
|
||||||
|
--startup-project src/Web/Web.csproj \
|
||||||
|
--project src/Web/Web.csproj \
|
||||||
|
--context Hutopy.Web.Features.Users.Data.IdentityDbContext \
|
||||||
|
--configuration Debug \
|
||||||
|
--output create-identity-db.sql \
|
||||||
|
--idempotent \
|
||||||
|
--no-build
|
||||||
|
|
||||||
|
dotnet ef migrations script \
|
||||||
|
--startup-project src/Web/Web.csproj \
|
||||||
|
--project src/Web/Web.csproj \
|
||||||
|
--context Hutopy.Web.Features.Messages.Data.MessagingDbContext \
|
||||||
|
--configuration Debug \
|
||||||
|
--output create-messaging-db.sql \
|
||||||
|
--idempotent\
|
||||||
|
--no-build
|
||||||
|
|
||||||
|
dotnet ef migrations script \
|
||||||
|
--startup-project src/Web/Web.csproj \
|
||||||
|
--project src/Web/Web.csproj \
|
||||||
|
--context Hutopy.Web.Features.Contents.Data.ContentDbContext \
|
||||||
|
--configuration Debug \
|
||||||
|
--output create-content-db.sql \
|
||||||
|
--idempotent \
|
||||||
|
--no-build
|
||||||
|
|
||||||
|
dotnet ef migrations script \
|
||||||
|
--startup-project src/Web/Web.csproj \
|
||||||
|
--project src/Web/Web.csproj \
|
||||||
|
--context Hutopy.Web.Features.Memberships.Data.MembershipDbContext \
|
||||||
|
--configuration Debug \
|
||||||
|
--output create-membership-db.sql \
|
||||||
|
--idempotent \
|
||||||
|
--no-build
|
||||||
7
backend/global.json
Normal file
7
backend/global.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"sdk": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"rollForward": "latestMinor",
|
||||||
|
"allowPrerelease": false
|
||||||
|
}
|
||||||
|
}
|
||||||
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);
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user