diff --git a/LittleShop/apply-migration.sql b/LittleShop/apply-migration.sql new file mode 100644 index 0000000..bed7c5b --- /dev/null +++ b/LittleShop/apply-migration.sql @@ -0,0 +1,520 @@ +CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" ( + "MigrationId" TEXT NOT NULL CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY, + "ProductVersion" TEXT NOT NULL +); + +BEGIN TRANSACTION; +CREATE TABLE "Bots" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_Bots" PRIMARY KEY, + "BotKey" TEXT NOT NULL, + "Name" TEXT NOT NULL, + "Description" TEXT NOT NULL, + "Type" INTEGER NOT NULL, + "Status" INTEGER NOT NULL, + "Settings" TEXT NOT NULL, + "CreatedAt" TEXT NOT NULL, + "LastSeenAt" TEXT NULL, + "LastConfigSyncAt" TEXT NULL, + "IsActive" INTEGER NOT NULL, + "Version" TEXT NOT NULL, + "IpAddress" TEXT NOT NULL, + "PlatformUsername" TEXT NOT NULL, + "PlatformDisplayName" TEXT NOT NULL, + "PlatformId" TEXT NOT NULL, + "PersonalityName" TEXT NOT NULL +); + +CREATE TABLE "Categories" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_Categories" PRIMARY KEY, + "Name" TEXT NOT NULL, + "Description" TEXT NULL, + "CreatedAt" TEXT NOT NULL, + "UpdatedAt" TEXT NOT NULL, + "IsActive" INTEGER NOT NULL +); + +CREATE TABLE "Customers" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_Customers" PRIMARY KEY, + "TelegramUserId" INTEGER NOT NULL, + "TelegramUsername" TEXT NOT NULL, + "TelegramDisplayName" TEXT NOT NULL, + "TelegramFirstName" TEXT NOT NULL, + "TelegramLastName" TEXT NOT NULL, + "Email" TEXT NULL, + "PhoneNumber" TEXT NULL, + "AllowMarketing" INTEGER NOT NULL, + "AllowOrderUpdates" INTEGER NOT NULL, + "Language" TEXT NOT NULL, + "Timezone" TEXT NOT NULL, + "TotalOrders" INTEGER NOT NULL, + "TotalSpent" decimal(18,2) NOT NULL, + "AverageOrderValue" decimal(18,2) NOT NULL, + "FirstOrderDate" TEXT NOT NULL, + "LastOrderDate" TEXT NOT NULL, + "CustomerNotes" TEXT NULL, + "IsBlocked" INTEGER NOT NULL, + "BlockReason" TEXT NULL, + "RiskScore" INTEGER NOT NULL, + "SuccessfulOrders" INTEGER NOT NULL, + "CancelledOrders" INTEGER NOT NULL, + "DisputedOrders" INTEGER NOT NULL, + "CreatedAt" TEXT NOT NULL, + "UpdatedAt" TEXT NOT NULL, + "LastActiveAt" TEXT NOT NULL, + "DataRetentionDate" TEXT NULL, + "IsActive" INTEGER NOT NULL +); + +CREATE TABLE "ShippingRates" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_ShippingRates" PRIMARY KEY, + "Name" TEXT NOT NULL, + "Description" TEXT NULL, + "Country" TEXT NOT NULL, + "MinWeight" decimal(18,2) NOT NULL, + "MaxWeight" decimal(18,2) NOT NULL, + "Price" decimal(18,2) NOT NULL, + "MinDeliveryDays" INTEGER NOT NULL, + "MaxDeliveryDays" INTEGER NOT NULL, + "IsActive" INTEGER NOT NULL, + "CreatedAt" TEXT NOT NULL, + "UpdatedAt" TEXT NOT NULL +); + +CREATE TABLE "Users" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_Users" PRIMARY KEY, + "Username" TEXT NOT NULL, + "PasswordHash" TEXT NOT NULL, + "Email" TEXT NULL, + "Role" TEXT NOT NULL, + "CreatedAt" TEXT NOT NULL, + "IsActive" INTEGER NOT NULL +); + +CREATE TABLE "BotMetrics" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_BotMetrics" PRIMARY KEY, + "BotId" TEXT NOT NULL, + "MetricType" INTEGER NOT NULL, + "Value" TEXT NOT NULL, + "Metadata" TEXT NOT NULL, + "RecordedAt" TEXT NOT NULL, + "Category" TEXT NOT NULL, + "Description" TEXT NOT NULL, + CONSTRAINT "FK_BotMetrics_Bots_BotId" FOREIGN KEY ("BotId") REFERENCES "Bots" ("Id") ON DELETE CASCADE +); + +CREATE TABLE "BotSessions" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_BotSessions" PRIMARY KEY, + "BotId" TEXT NOT NULL, + "SessionIdentifier" TEXT NOT NULL, + "Platform" TEXT NOT NULL, + "StartedAt" TEXT NOT NULL, + "LastActivityAt" TEXT NOT NULL, + "EndedAt" TEXT NULL, + "OrderCount" INTEGER NOT NULL, + "MessageCount" INTEGER NOT NULL, + "TotalSpent" TEXT NOT NULL, + "Language" TEXT NOT NULL, + "Country" TEXT NOT NULL, + "IsAnonymous" INTEGER NOT NULL, + "Metadata" TEXT NOT NULL, + CONSTRAINT "FK_BotSessions_Bots_BotId" FOREIGN KEY ("BotId") REFERENCES "Bots" ("Id") ON DELETE CASCADE +); + +CREATE TABLE "Products" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_Products" PRIMARY KEY, + "Name" TEXT NOT NULL, + "Description" TEXT NOT NULL, + "Price" decimal(18,2) NOT NULL, + "Weight" decimal(18,4) NOT NULL, + "WeightUnit" INTEGER NOT NULL, + "StockQuantity" INTEGER NOT NULL, + "CategoryId" TEXT NOT NULL, + "IsActive" INTEGER NOT NULL, + "CreatedAt" TEXT NOT NULL, + "UpdatedAt" TEXT NOT NULL, + CONSTRAINT "FK_Products_Categories_CategoryId" FOREIGN KEY ("CategoryId") REFERENCES "Categories" ("Id") ON DELETE RESTRICT +); + +CREATE TABLE "BotContacts" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_BotContacts" PRIMARY KEY, + "BotId" TEXT NOT NULL, + "TelegramUserId" INTEGER NOT NULL, + "TelegramUsername" TEXT NOT NULL, + "DisplayName" TEXT NOT NULL, + "FirstName" TEXT NOT NULL, + "LastName" TEXT NOT NULL, + "FirstContactDate" TEXT NOT NULL, + "LastContactDate" TEXT NOT NULL, + "TotalInteractions" INTEGER NOT NULL, + "LastKnownLanguage" TEXT NOT NULL, + "Status" INTEGER NOT NULL, + "StatusReason" TEXT NULL, + "CustomerId" TEXT NULL, + "IsRecovered" INTEGER NOT NULL, + "RecoveredFromBotId" TEXT NULL, + "RecoveredAt" TEXT NULL, + "CreatedAt" TEXT NOT NULL, + "UpdatedAt" TEXT NOT NULL, + "IsActive" INTEGER NOT NULL, + "EncryptedContactData" TEXT NULL, + "Preferences" TEXT NULL, + "Notes" TEXT NULL, + CONSTRAINT "FK_BotContacts_Bots_BotId" FOREIGN KEY ("BotId") REFERENCES "Bots" ("Id") ON DELETE CASCADE, + CONSTRAINT "FK_BotContacts_Customers_CustomerId" FOREIGN KEY ("CustomerId") REFERENCES "Customers" ("Id") +); + +CREATE TABLE "Orders" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_Orders" PRIMARY KEY, + "CustomerId" TEXT NULL, + "IdentityReference" TEXT NULL, + "Status" INTEGER NOT NULL, + "TotalAmount" decimal(18,2) NOT NULL, + "Currency" TEXT NOT NULL, + "ShippingName" TEXT NOT NULL, + "ShippingAddress" TEXT NOT NULL, + "ShippingCity" TEXT NOT NULL, + "ShippingPostCode" TEXT NOT NULL, + "ShippingCountry" TEXT NOT NULL, + "Notes" TEXT NULL, + "TrackingNumber" TEXT NULL, + "CreatedAt" TEXT NOT NULL, + "UpdatedAt" TEXT NOT NULL, + "PaidAt" TEXT NULL, + "AcceptedAt" TEXT NULL, + "PackingStartedAt" TEXT NULL, + "DispatchedAt" TEXT NULL, + "ExpectedDeliveryDate" TEXT NULL, + "ActualDeliveryDate" TEXT NULL, + "OnHoldAt" TEXT NULL, + "AcceptedByUser" TEXT NULL, + "PackedByUser" TEXT NULL, + "DispatchedByUser" TEXT NULL, + "OnHoldReason" TEXT NULL, + "ShippedAt" TEXT NULL, + CONSTRAINT "FK_Orders_Customers_CustomerId" FOREIGN KEY ("CustomerId") REFERENCES "Customers" ("Id") ON DELETE RESTRICT +); + +CREATE TABLE "PushSubscriptions" ( + "Id" INTEGER NOT NULL CONSTRAINT "PK_PushSubscriptions" PRIMARY KEY AUTOINCREMENT, + "Endpoint" TEXT NOT NULL, + "P256DH" TEXT NOT NULL, + "Auth" TEXT NOT NULL, + "UserId" TEXT NULL, + "CustomerId" TEXT NULL, + "SubscribedAt" TEXT NOT NULL, + "LastUsedAt" TEXT NULL, + "IsActive" INTEGER NOT NULL, + "UserAgent" TEXT NULL, + "IpAddress" TEXT NULL, + CONSTRAINT "FK_PushSubscriptions_Customers_CustomerId" FOREIGN KEY ("CustomerId") REFERENCES "Customers" ("Id") ON DELETE CASCADE, + CONSTRAINT "FK_PushSubscriptions_Users_UserId" FOREIGN KEY ("UserId") REFERENCES "Users" ("Id") ON DELETE CASCADE +); + +CREATE TABLE "ProductMultiBuys" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_ProductMultiBuys" PRIMARY KEY, + "ProductId" TEXT NOT NULL, + "Name" TEXT NOT NULL, + "Description" TEXT NOT NULL, + "Quantity" INTEGER NOT NULL, + "Price" decimal(18,2) NOT NULL, + "PricePerUnit" decimal(18,2) NOT NULL, + "SortOrder" INTEGER NOT NULL, + "IsActive" INTEGER NOT NULL, + "CreatedAt" TEXT NOT NULL, + "UpdatedAt" TEXT NOT NULL, + CONSTRAINT "FK_ProductMultiBuys_Products_ProductId" FOREIGN KEY ("ProductId") REFERENCES "Products" ("Id") ON DELETE CASCADE +); + +CREATE TABLE "ProductPhotos" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_ProductPhotos" PRIMARY KEY, + "ProductId" TEXT NOT NULL, + "FileName" TEXT NOT NULL, + "FilePath" TEXT NOT NULL, + "AltText" TEXT NULL, + "SortOrder" INTEGER NOT NULL, + "CreatedAt" TEXT NOT NULL, + CONSTRAINT "FK_ProductPhotos_Products_ProductId" FOREIGN KEY ("ProductId") REFERENCES "Products" ("Id") ON DELETE CASCADE +); + +CREATE TABLE "ProductVariants" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_ProductVariants" PRIMARY KEY, + "ProductId" TEXT NOT NULL, + "Name" TEXT NOT NULL, + "VariantType" TEXT NOT NULL, + "SortOrder" INTEGER NOT NULL, + "IsActive" INTEGER NOT NULL, + "StockLevel" INTEGER NOT NULL, + "CreatedAt" TEXT NOT NULL, + "UpdatedAt" TEXT NOT NULL, + CONSTRAINT "FK_ProductVariants_Products_ProductId" FOREIGN KEY ("ProductId") REFERENCES "Products" ("Id") ON DELETE CASCADE +); + +CREATE TABLE "BotActivities" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_BotActivities" PRIMARY KEY, + "BotId" TEXT NOT NULL, + "SessionIdentifier" TEXT NOT NULL, + "UserDisplayName" TEXT NOT NULL, + "ActivityType" TEXT NOT NULL, + "ActivityDescription" TEXT NOT NULL, + "ProductId" TEXT NULL, + "ProductName" TEXT NOT NULL, + "OrderId" TEXT NULL, + "CategoryName" TEXT NOT NULL, + "Value" TEXT NULL, + "Quantity" INTEGER NULL, + "Platform" TEXT NOT NULL, + "DeviceType" TEXT NOT NULL, + "Location" TEXT NOT NULL, + "Timestamp" TEXT NOT NULL, + "Metadata" TEXT NOT NULL, + CONSTRAINT "FK_BotActivities_Bots_BotId" FOREIGN KEY ("BotId") REFERENCES "Bots" ("Id") ON DELETE CASCADE, + CONSTRAINT "FK_BotActivities_Orders_OrderId" FOREIGN KEY ("OrderId") REFERENCES "Orders" ("Id") ON DELETE SET NULL, + CONSTRAINT "FK_BotActivities_Products_ProductId" FOREIGN KEY ("ProductId") REFERENCES "Products" ("Id") ON DELETE SET NULL +); + +CREATE TABLE "CryptoPayments" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_CryptoPayments" PRIMARY KEY, + "OrderId" TEXT NOT NULL, + "Currency" INTEGER NOT NULL, + "WalletAddress" TEXT NOT NULL, + "RequiredAmount" decimal(18,8) NOT NULL, + "PaidAmount" decimal(18,8) NOT NULL, + "Status" INTEGER NOT NULL, + "BTCPayInvoiceId" TEXT NULL, + "SilverPayOrderId" TEXT NULL, + "TransactionHash" TEXT NULL, + "CreatedAt" TEXT NOT NULL, + "PaidAt" TEXT NULL, + "ExpiresAt" TEXT NOT NULL, + CONSTRAINT "FK_CryptoPayments_Orders_OrderId" FOREIGN KEY ("OrderId") REFERENCES "Orders" ("Id") ON DELETE CASCADE +); + +CREATE TABLE "CustomerMessages" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_CustomerMessages" PRIMARY KEY, + "CustomerId" TEXT NOT NULL, + "OrderId" TEXT NULL, + "AdminUserId" TEXT NULL, + "Direction" INTEGER NOT NULL, + "Type" INTEGER NOT NULL, + "Subject" TEXT NOT NULL, + "Content" TEXT NOT NULL, + "Status" INTEGER NOT NULL, + "CreatedAt" TEXT NOT NULL, + "SentAt" TEXT NULL, + "DeliveredAt" TEXT NULL, + "ReadAt" TEXT NULL, + "FailedAt" TEXT NULL, + "FailureReason" TEXT NULL, + "RetryCount" INTEGER NOT NULL, + "NextRetryAt" TEXT NULL, + "ParentMessageId" TEXT NULL, + "ThreadId" TEXT NULL, + "Platform" TEXT NOT NULL, + "PlatformMessageId" TEXT NULL, + "Priority" INTEGER NOT NULL, + "ScheduledFor" TEXT NULL, + "ExpiresAt" TEXT NULL, + "RequiresResponse" INTEGER NOT NULL, + "IsUrgent" INTEGER NOT NULL, + "IsMarketing" INTEGER NOT NULL, + "IsAutoGenerated" INTEGER NOT NULL, + "AutoGenerationTrigger" TEXT NULL, + "IsArchived" INTEGER NOT NULL, + "ArchivedAt" TEXT NULL, + CONSTRAINT "FK_CustomerMessages_CustomerMessages_ParentMessageId" FOREIGN KEY ("ParentMessageId") REFERENCES "CustomerMessages" ("Id") ON DELETE RESTRICT, + CONSTRAINT "FK_CustomerMessages_Customers_CustomerId" FOREIGN KEY ("CustomerId") REFERENCES "Customers" ("Id") ON DELETE CASCADE, + CONSTRAINT "FK_CustomerMessages_Orders_OrderId" FOREIGN KEY ("OrderId") REFERENCES "Orders" ("Id") ON DELETE SET NULL, + CONSTRAINT "FK_CustomerMessages_Users_AdminUserId" FOREIGN KEY ("AdminUserId") REFERENCES "Users" ("Id") ON DELETE SET NULL +); + +CREATE TABLE "Reviews" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_Reviews" PRIMARY KEY, + "ProductId" TEXT NOT NULL, + "CustomerId" TEXT NOT NULL, + "OrderId" TEXT NOT NULL, + "Rating" INTEGER NOT NULL, + "Title" TEXT NULL, + "Comment" TEXT NULL, + "IsVerifiedPurchase" INTEGER NOT NULL, + "IsApproved" INTEGER NOT NULL, + "IsActive" INTEGER NOT NULL, + "CreatedAt" TEXT NOT NULL, + "UpdatedAt" TEXT NOT NULL, + "ApprovedAt" TEXT NULL, + "ApprovedByUserId" TEXT NULL, + CONSTRAINT "FK_Reviews_Customers_CustomerId" FOREIGN KEY ("CustomerId") REFERENCES "Customers" ("Id") ON DELETE CASCADE, + CONSTRAINT "FK_Reviews_Orders_OrderId" FOREIGN KEY ("OrderId") REFERENCES "Orders" ("Id") ON DELETE RESTRICT, + CONSTRAINT "FK_Reviews_Products_ProductId" FOREIGN KEY ("ProductId") REFERENCES "Products" ("Id") ON DELETE CASCADE, + CONSTRAINT "FK_Reviews_Users_ApprovedByUserId" FOREIGN KEY ("ApprovedByUserId") REFERENCES "Users" ("Id") ON DELETE SET NULL +); + +CREATE TABLE "OrderItems" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_OrderItems" PRIMARY KEY, + "OrderId" TEXT NOT NULL, + "ProductId" TEXT NOT NULL, + "ProductMultiBuyId" TEXT NULL, + "SelectedVariant" TEXT NULL, + "Quantity" INTEGER NOT NULL, + "UnitPrice" decimal(18,2) NOT NULL, + "TotalPrice" decimal(18,2) NOT NULL, + CONSTRAINT "FK_OrderItems_Orders_OrderId" FOREIGN KEY ("OrderId") REFERENCES "Orders" ("Id") ON DELETE CASCADE, + CONSTRAINT "FK_OrderItems_ProductMultiBuys_ProductMultiBuyId" FOREIGN KEY ("ProductMultiBuyId") REFERENCES "ProductMultiBuys" ("Id") ON DELETE RESTRICT, + CONSTRAINT "FK_OrderItems_Products_ProductId" FOREIGN KEY ("ProductId") REFERENCES "Products" ("Id") ON DELETE RESTRICT +); + +CREATE INDEX "IX_BotActivities_ActivityType" ON "BotActivities" ("ActivityType"); + +CREATE INDEX "IX_BotActivities_BotId_Timestamp" ON "BotActivities" ("BotId", "Timestamp"); + +CREATE INDEX "IX_BotActivities_OrderId" ON "BotActivities" ("OrderId"); + +CREATE INDEX "IX_BotActivities_ProductId" ON "BotActivities" ("ProductId"); + +CREATE INDEX "IX_BotActivities_SessionIdentifier" ON "BotActivities" ("SessionIdentifier"); + +CREATE INDEX "IX_BotActivities_Timestamp" ON "BotActivities" ("Timestamp"); + +CREATE INDEX "IX_BotContacts_BotId" ON "BotContacts" ("BotId"); + +CREATE INDEX "IX_BotContacts_CustomerId" ON "BotContacts" ("CustomerId"); + +CREATE INDEX "IX_BotMetrics_BotId_RecordedAt" ON "BotMetrics" ("BotId", "RecordedAt"); + +CREATE INDEX "IX_BotMetrics_MetricType" ON "BotMetrics" ("MetricType"); + +CREATE UNIQUE INDEX "IX_Bots_BotKey" ON "Bots" ("BotKey"); + +CREATE INDEX "IX_Bots_Name" ON "Bots" ("Name"); + +CREATE INDEX "IX_Bots_Status" ON "Bots" ("Status"); + +CREATE INDEX "IX_BotSessions_BotId_SessionIdentifier" ON "BotSessions" ("BotId", "SessionIdentifier"); + +CREATE INDEX "IX_BotSessions_LastActivityAt" ON "BotSessions" ("LastActivityAt"); + +CREATE INDEX "IX_BotSessions_StartedAt" ON "BotSessions" ("StartedAt"); + +CREATE INDEX "IX_CryptoPayments_BTCPayInvoiceId" ON "CryptoPayments" ("BTCPayInvoiceId"); + +CREATE INDEX "IX_CryptoPayments_OrderId" ON "CryptoPayments" ("OrderId"); + +CREATE INDEX "IX_CryptoPayments_WalletAddress" ON "CryptoPayments" ("WalletAddress"); + +CREATE INDEX "IX_CustomerMessages_AdminUserId" ON "CustomerMessages" ("AdminUserId"); + +CREATE INDEX "IX_CustomerMessages_CustomerId_CreatedAt" ON "CustomerMessages" ("CustomerId", "CreatedAt"); + +CREATE INDEX "IX_CustomerMessages_Direction" ON "CustomerMessages" ("Direction"); + +CREATE INDEX "IX_CustomerMessages_OrderId" ON "CustomerMessages" ("OrderId"); + +CREATE INDEX "IX_CustomerMessages_ParentMessageId" ON "CustomerMessages" ("ParentMessageId"); + +CREATE INDEX "IX_CustomerMessages_ScheduledFor" ON "CustomerMessages" ("ScheduledFor"); + +CREATE INDEX "IX_CustomerMessages_Status" ON "CustomerMessages" ("Status"); + +CREATE INDEX "IX_CustomerMessages_ThreadId" ON "CustomerMessages" ("ThreadId"); + +CREATE INDEX "IX_CustomerMessages_Type" ON "CustomerMessages" ("Type"); + +CREATE INDEX "IX_Customers_CreatedAt" ON "Customers" ("CreatedAt"); + +CREATE INDEX "IX_Customers_DataRetentionDate" ON "Customers" ("DataRetentionDate"); + +CREATE INDEX "IX_Customers_Email" ON "Customers" ("Email"); + +CREATE INDEX "IX_Customers_LastActiveAt" ON "Customers" ("LastActiveAt"); + +CREATE UNIQUE INDEX "IX_Customers_TelegramUserId" ON "Customers" ("TelegramUserId"); + +CREATE INDEX "IX_Customers_TelegramUsername" ON "Customers" ("TelegramUsername"); + +CREATE INDEX "IX_OrderItems_OrderId" ON "OrderItems" ("OrderId"); + +CREATE INDEX "IX_OrderItems_ProductId" ON "OrderItems" ("ProductId"); + +CREATE INDEX "IX_OrderItems_ProductMultiBuyId" ON "OrderItems" ("ProductMultiBuyId"); + +CREATE INDEX "IX_Orders_CreatedAt" ON "Orders" ("CreatedAt"); + +CREATE INDEX "IX_Orders_CustomerId" ON "Orders" ("CustomerId"); + +CREATE INDEX "IX_Orders_IdentityReference" ON "Orders" ("IdentityReference"); + +CREATE INDEX "IX_ProductMultiBuys_IsActive" ON "ProductMultiBuys" ("IsActive"); + +CREATE UNIQUE INDEX "IX_ProductMultiBuys_ProductId_Quantity" ON "ProductMultiBuys" ("ProductId", "Quantity"); + +CREATE INDEX "IX_ProductMultiBuys_ProductId_SortOrder" ON "ProductMultiBuys" ("ProductId", "SortOrder"); + +CREATE INDEX "IX_ProductPhotos_ProductId" ON "ProductPhotos" ("ProductId"); + +CREATE INDEX "IX_Products_CategoryId" ON "Products" ("CategoryId"); + +CREATE INDEX "IX_ProductVariants_IsActive" ON "ProductVariants" ("IsActive"); + +CREATE UNIQUE INDEX "IX_ProductVariants_ProductId_Name" ON "ProductVariants" ("ProductId", "Name"); + +CREATE INDEX "IX_ProductVariants_ProductId_SortOrder" ON "ProductVariants" ("ProductId", "SortOrder"); + +CREATE INDEX "IX_PushSubscriptions_CustomerId" ON "PushSubscriptions" ("CustomerId"); + +CREATE UNIQUE INDEX "IX_PushSubscriptions_Endpoint" ON "PushSubscriptions" ("Endpoint"); + +CREATE INDEX "IX_PushSubscriptions_IsActive" ON "PushSubscriptions" ("IsActive"); + +CREATE INDEX "IX_PushSubscriptions_SubscribedAt" ON "PushSubscriptions" ("SubscribedAt"); + +CREATE INDEX "IX_PushSubscriptions_UserId" ON "PushSubscriptions" ("UserId"); + +CREATE INDEX "IX_Reviews_ApprovedByUserId" ON "Reviews" ("ApprovedByUserId"); + +CREATE INDEX "IX_Reviews_CreatedAt" ON "Reviews" ("CreatedAt"); + +CREATE INDEX "IX_Reviews_CustomerId" ON "Reviews" ("CustomerId"); + +CREATE UNIQUE INDEX "IX_Reviews_CustomerId_ProductId" ON "Reviews" ("CustomerId", "ProductId"); + +CREATE INDEX "IX_Reviews_IsActive" ON "Reviews" ("IsActive"); + +CREATE INDEX "IX_Reviews_IsApproved" ON "Reviews" ("IsApproved"); + +CREATE INDEX "IX_Reviews_OrderId" ON "Reviews" ("OrderId"); + +CREATE INDEX "IX_Reviews_ProductId" ON "Reviews" ("ProductId"); + +CREATE INDEX "IX_Reviews_ProductId_IsApproved_IsActive" ON "Reviews" ("ProductId", "IsApproved", "IsActive"); + +CREATE INDEX "IX_Reviews_Rating" ON "Reviews" ("Rating"); + +CREATE UNIQUE INDEX "IX_Users_Username" ON "Users" ("Username"); + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('20250922025753_AddProductMultiBuysTable', '9.0.9'); + +CREATE TABLE "SystemSettings" ( + "Key" TEXT NOT NULL CONSTRAINT "PK_SystemSettings" PRIMARY KEY, + "Value" TEXT NOT NULL, + "Description" TEXT NULL, + "CreatedAt" TEXT NOT NULL, + "UpdatedAt" TEXT NOT NULL +); + +CREATE UNIQUE INDEX "IX_SystemSettings_Key" ON "SystemSettings" ("Key"); + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('20250922040637_AddSystemSettingsTable', '9.0.9'); + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('20250928014850_AddVariantCollectionsAndSalesLedger', '9.0.9'); + +ALTER TABLE "ProductVariants" ADD "Weight" TEXT NULL; + +ALTER TABLE "ProductVariants" ADD "WeightUnit" INTEGER NULL; + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('20250928155814_AddWeightToProductVariants', '9.0.9'); + +COMMIT; + diff --git a/deploy-db-fix.sh b/deploy-db-fix.sh new file mode 100644 index 0000000..55db93a --- /dev/null +++ b/deploy-db-fix.sh @@ -0,0 +1,181 @@ +#!/bin/bash + +# Deployment Database Fix Script +# This script helps fix database schema issues on the deployed server + +echo "================================================" +echo "LittleShop Database Schema Fix" +echo "================================================" +echo "" + +# Configuration +DB_FILE="littleshop.db" +BACKUP_DIR="db-backups" +TIMESTAMP=$(date +%Y%m%d_%H%M%S) + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Function to check if database exists +check_database() { + if [ ! -f "$DB_FILE" ]; then + echo -e "${RED}Error: Database file $DB_FILE not found!${NC}" + echo "Please ensure you're running this script in the correct directory." + exit 1 + fi + echo -e "${GREEN}✓ Database file found: $DB_FILE${NC}" +} + +# Function to create backup +create_backup() { + echo -e "${YELLOW}Creating backup...${NC}" + + # Create backup directory if it doesn't exist + mkdir -p "$BACKUP_DIR" + + # Create backup with timestamp + BACKUP_FILE="$BACKUP_DIR/littleshop_${TIMESTAMP}.db" + cp "$DB_FILE" "$BACKUP_FILE" + + if [ -f "$BACKUP_FILE" ]; then + echo -e "${GREEN}✓ Backup created: $BACKUP_FILE${NC}" + else + echo -e "${RED}Error: Failed to create backup!${NC}" + exit 1 + fi +} + +# Function to check current schema +check_schema() { + echo -e "${YELLOW}Checking current database schema...${NC}" + + # Check if migrations table exists + MIGRATIONS_TABLE=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='__EFMigrationsHistory';") + + if [ "$MIGRATIONS_TABLE" -eq "0" ]; then + echo -e "${YELLOW}! Migrations table not found - database may need full initialization${NC}" + else + echo -e "${GREEN}✓ Migrations table exists${NC}" + + # List applied migrations + echo "" + echo "Applied migrations:" + sqlite3 "$DB_FILE" "SELECT MigrationId FROM __EFMigrationsHistory ORDER BY MigrationId;" + fi + + # Check for key tables + echo "" + echo "Checking core tables..." + + TABLES_TO_CHECK=("Products" "Orders" "Users" "Categories" "Customers" "ProductMultiBuys" "SystemSettings" "VariantCollections" "ProductVariants" "SalesLedger" "PushSubscriptions") + + for table in "${TABLES_TO_CHECK[@]}"; do + EXISTS=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='$table';") + if [ "$EXISTS" -eq "1" ]; then + COUNT=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM $table;") + echo -e "${GREEN}✓ $table exists (rows: $COUNT)${NC}" + else + echo -e "${YELLOW}✗ $table missing${NC}" + fi + done +} + +# Function to apply migration +apply_migration() { + echo "" + echo -e "${YELLOW}Applying migration scripts...${NC}" + + # Check which migration file to use + if [ -f "safe-migration.sql" ]; then + echo "Using safe-migration.sql" + MIGRATION_FILE="safe-migration.sql" + elif [ -f "apply-migration.sql" ]; then + echo "Using apply-migration.sql" + MIGRATION_FILE="apply-migration.sql" + elif [ -f "LittleShop/apply-migration.sql" ]; then + echo "Using LittleShop/apply-migration.sql" + MIGRATION_FILE="LittleShop/apply-migration.sql" + else + echo -e "${RED}Error: No migration file found!${NC}" + echo "Please ensure safe-migration.sql or apply-migration.sql is in the current directory." + exit 1 + fi + + # Apply the migration + echo "Applying $MIGRATION_FILE..." + OUTPUT=$(sqlite3 "$DB_FILE" < "$MIGRATION_FILE" 2>&1) + + if [ $? -eq 0 ]; then + echo -e "${GREEN}✓ Migration applied successfully${NC}" + echo "$OUTPUT" + else + echo -e "${RED}Error applying migration:${NC}" + echo "$OUTPUT" + echo "" + echo -e "${YELLOW}Attempting to restore from backup...${NC}" + cp "$BACKUP_FILE" "$DB_FILE" + echo -e "${GREEN}✓ Database restored from backup${NC}" + exit 1 + fi +} + +# Function to verify migration +verify_migration() { + echo "" + echo -e "${YELLOW}Verifying migration...${NC}" + + # Check migrations table + MIGRATION_COUNT=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM __EFMigrationsHistory;" 2>/dev/null) + + if [ -z "$MIGRATION_COUNT" ] || [ "$MIGRATION_COUNT" -eq "0" ]; then + echo -e "${RED}Warning: No migrations found in history${NC}" + else + echo -e "${GREEN}✓ $MIGRATION_COUNT migrations registered${NC}" + fi + + # Final schema check + echo "" + echo "Final schema status:" + check_schema +} + +# Main execution +main() { + echo "This script will:" + echo "1. Create a backup of your database" + echo "2. Check the current schema" + echo "3. Apply missing migrations" + echo "4. Verify the results" + echo "" + + read -p "Continue? (y/n): " -n 1 -r + echo "" + + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo "Aborted." + exit 1 + fi + + # Execute steps + check_database + create_backup + check_schema + apply_migration + verify_migration + + echo "" + echo -e "${GREEN}================================================${NC}" + echo -e "${GREEN}Database migration completed successfully!${NC}" + echo -e "${GREEN}================================================${NC}" + echo "" + echo "Next steps:" + echo "1. Test the application to ensure it works correctly" + echo "2. If issues occur, restore from: $BACKUP_FILE" + echo "" +} + +# Run main function +main \ No newline at end of file diff --git a/safe-migration.sql b/safe-migration.sql new file mode 100644 index 0000000..532b5f8 --- /dev/null +++ b/safe-migration.sql @@ -0,0 +1,142 @@ +-- Safe Migration Script for LittleShop Database +-- This script can be safely run on an existing database +-- It will check for existing objects before creating them + +-- Create the EF Migrations History table if it doesn't exist +CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" ( + "MigrationId" TEXT NOT NULL CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY, + "ProductVersion" TEXT NOT NULL +); + +-- Check and fix any schema issues with existing tables + +-- 1. Check if ProductMultiBuys table exists (from migration 20250922025753_AddProductMultiBuysTable) +CREATE TABLE IF NOT EXISTS "ProductMultiBuys" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_ProductMultiBuys" PRIMARY KEY, + "ProductId" TEXT NOT NULL, + "Quantity" INTEGER NOT NULL, + "Price" decimal(18,2) NOT NULL, + "Description" TEXT NOT NULL, + "IsActive" INTEGER NOT NULL DEFAULT 1, + CONSTRAINT "FK_ProductMultiBuys_Products_ProductId" FOREIGN KEY ("ProductId") REFERENCES "Products" ("Id") ON DELETE CASCADE +); + +-- 2. Check if SystemSettings table exists (from migration 20250922040637_AddSystemSettingsTable) +CREATE TABLE IF NOT EXISTS "SystemSettings" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_SystemSettings" PRIMARY KEY, + "Key" TEXT NOT NULL, + "Value" TEXT NOT NULL, + "Type" TEXT NOT NULL, + "Description" TEXT NULL, + "IsReadOnly" INTEGER NOT NULL DEFAULT 0, + "CreatedAt" TEXT NOT NULL, + "UpdatedAt" TEXT NOT NULL +); + +-- Create unique index if it doesn't exist +CREATE UNIQUE INDEX IF NOT EXISTS "IX_SystemSettings_Key" ON "SystemSettings" ("Key"); + +-- 3. Check if VariantCollections table exists (from migration 20250928014850_AddVariantCollectionsAndSalesLedger) +CREATE TABLE IF NOT EXISTS "VariantCollections" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_VariantCollections" PRIMARY KEY, + "Name" TEXT NOT NULL, + "Description" TEXT NULL, + "CreatedAt" TEXT NOT NULL, + "UpdatedAt" TEXT NOT NULL, + "IsActive" INTEGER NOT NULL DEFAULT 1 +); + +-- 4. Check if ProductVariants table exists +CREATE TABLE IF NOT EXISTS "ProductVariants" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_ProductVariants" PRIMARY KEY, + "CollectionId" TEXT NOT NULL, + "Sku" TEXT NOT NULL, + "Options" TEXT NOT NULL, + "PriceAdjustment" decimal(18,2) NOT NULL DEFAULT 0, + "QuantityInStock" INTEGER NOT NULL DEFAULT 0, + "Weight" TEXT NULL, + "WeightUnit" INTEGER NULL, + "CreatedAt" TEXT NOT NULL, + "UpdatedAt" TEXT NOT NULL, + "IsActive" INTEGER NOT NULL DEFAULT 1, + CONSTRAINT "FK_ProductVariants_VariantCollections_CollectionId" FOREIGN KEY ("CollectionId") REFERENCES "VariantCollections" ("Id") ON DELETE CASCADE +); + +-- 5. Check if SalesLedger table exists +CREATE TABLE IF NOT EXISTS "SalesLedger" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_SalesLedger" PRIMARY KEY, + "OrderId" TEXT NOT NULL, + "TransactionType" INTEGER NOT NULL, + "PaymentMethod" TEXT NULL, + "Amount" decimal(18,2) NOT NULL, + "Tax" decimal(18,2) NOT NULL DEFAULT 0, + "Discount" decimal(18,2) NOT NULL DEFAULT 0, + "NetAmount" decimal(18,2) NOT NULL, + "Currency" TEXT NOT NULL DEFAULT 'GBP', + "ExchangeRate" decimal(18,8) NOT NULL DEFAULT 1, + "TransactionDate" TEXT NOT NULL, + "RecordedAt" TEXT NOT NULL, + "Description" TEXT NULL, + "Reference" TEXT NULL, + "Status" INTEGER NOT NULL, + "ReconciledAt" TEXT NULL, + "ReconciledBy" TEXT NULL, + "Notes" TEXT NULL, + CONSTRAINT "FK_SalesLedger_Orders_OrderId" FOREIGN KEY ("OrderId") REFERENCES "Orders" ("Id") ON DELETE CASCADE +); + +-- 6. Add Weight columns to ProductVariants if they don't exist (migration 20250928155814_AddWeightToProductVariants) +-- SQLite doesn't support conditional column addition, so we need to handle this differently +-- This would need to be done programmatically or by checking the schema first + +-- 7. Check if PushSubscriptions table exists +CREATE TABLE IF NOT EXISTS "PushSubscriptions" ( + "Id" TEXT NOT NULL CONSTRAINT "PK_PushSubscriptions" PRIMARY KEY, + "UserId" TEXT NOT NULL, + "Endpoint" TEXT NOT NULL, + "P256dh" TEXT NOT NULL, + "Auth" TEXT NOT NULL, + "CreatedAt" TEXT NOT NULL, + "UpdatedAt" TEXT NOT NULL, + "IsActive" INTEGER NOT NULL DEFAULT 1, + CONSTRAINT "FK_PushSubscriptions_Users_UserId" FOREIGN KEY ("UserId") REFERENCES "Users" ("Id") ON DELETE CASCADE +); + +-- Insert migration history records if they don't exist +INSERT OR IGNORE INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('20250922025753_AddProductMultiBuysTable', '9.0.9'); + +INSERT OR IGNORE INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('20250922040637_AddSystemSettingsTable', '9.0.9'); + +INSERT OR IGNORE INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('20250928014850_AddVariantCollectionsAndSalesLedger', '9.0.9'); + +INSERT OR IGNORE INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('20250928155814_AddWeightToProductVariants', '9.0.9'); + +-- Add any missing columns to existing tables +-- Note: SQLite has limitations with ALTER TABLE, so some changes may require table recreation + +-- Ensure all necessary indexes exist +CREATE INDEX IF NOT EXISTS "IX_ProductMultiBuys_ProductId" ON "ProductMultiBuys" ("ProductId"); +CREATE UNIQUE INDEX IF NOT EXISTS "IX_ProductMultiBuys_ProductId_Quantity" ON "ProductMultiBuys" ("ProductId", "Quantity"); +CREATE INDEX IF NOT EXISTS "IX_ProductVariants_CollectionId" ON "ProductVariants" ("CollectionId"); +CREATE UNIQUE INDEX IF NOT EXISTS "IX_ProductVariants_Sku" ON "ProductVariants" ("Sku"); +CREATE INDEX IF NOT EXISTS "IX_SalesLedger_OrderId" ON "SalesLedger" ("OrderId"); +CREATE INDEX IF NOT EXISTS "IX_SalesLedger_TransactionDate" ON "SalesLedger" ("TransactionDate"); +CREATE INDEX IF NOT EXISTS "IX_PushSubscriptions_UserId" ON "PushSubscriptions" ("UserId"); + +-- Verify core tables exist +SELECT CASE + WHEN (SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='Products') = 0 + THEN RAISE(ABORT, 'Products table missing - database may need initialization') + WHEN (SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='Orders') = 0 + THEN RAISE(ABORT, 'Orders table missing - database may need initialization') + WHEN (SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='Users') = 0 + THEN RAISE(ABORT, 'Users table missing - database may need initialization') + ELSE 'Schema verification passed' +END; + +-- Output success message +SELECT 'Migration completed successfully' AS Result; \ No newline at end of file