feat: Add Dockerignore files for backend and frontend
This commit is contained in:
13
.dockerignore
Normal file
13
.dockerignore
Normal file
@@ -0,0 +1,13 @@
|
||||
node_modules
|
||||
dist
|
||||
.git
|
||||
.gitignore
|
||||
.env
|
||||
docker-compose.yml
|
||||
README.md
|
||||
*.log
|
||||
.DS_Store
|
||||
backend/node_modules
|
||||
backend/dist
|
||||
frontend/node_modules
|
||||
frontend/dist
|
||||
3
backend/.dockerignore
Normal file
3
backend/.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
dist
|
||||
.env
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:21-alpine
|
||||
FROM node:22-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -24,4 +24,4 @@ RUN mkdir -p uploads/videos uploads/images data
|
||||
|
||||
EXPOSE 5551
|
||||
|
||||
CMD ["node", "dist/server.js"]
|
||||
CMD ["node", "dist/src/server.js"]
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"loginEnabled": false,
|
||||
"defaultAutoPlay": false,
|
||||
"defaultAutoLoop": false,
|
||||
"maxConcurrentDownloads": 1,
|
||||
"isPasswordSet": true,
|
||||
"language": "en",
|
||||
"password": "$2b$10$OC6wVmMg4p32ynOddLfALO8AqK/ByA.UjiLBldjCs3l/QhTm.qvvK"
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"isDownloading": false,
|
||||
"title": "",
|
||||
"timestamp": 1763754827104,
|
||||
"activeDownloads": [],
|
||||
"queuedDownloads": []
|
||||
}
|
||||
57
backend/drizzle/0000_known_guardsmen.sql
Normal file
57
backend/drizzle/0000_known_guardsmen.sql
Normal file
@@ -0,0 +1,57 @@
|
||||
CREATE TABLE `collection_videos` (
|
||||
`collection_id` text NOT NULL,
|
||||
`video_id` text NOT NULL,
|
||||
`order` integer,
|
||||
PRIMARY KEY(`collection_id`, `video_id`),
|
||||
FOREIGN KEY (`collection_id`) REFERENCES `collections`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`video_id`) REFERENCES `videos`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `collections` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`title` text,
|
||||
`created_at` text NOT NULL,
|
||||
`updated_at` text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `downloads` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`title` text NOT NULL,
|
||||
`timestamp` integer,
|
||||
`filename` text,
|
||||
`total_size` text,
|
||||
`downloaded_size` text,
|
||||
`progress` integer,
|
||||
`speed` text,
|
||||
`status` text DEFAULT 'active' NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `settings` (
|
||||
`key` text PRIMARY KEY NOT NULL,
|
||||
`value` text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `videos` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`title` text NOT NULL,
|
||||
`author` text,
|
||||
`date` text,
|
||||
`source` text,
|
||||
`source_url` text,
|
||||
`video_filename` text,
|
||||
`thumbnail_filename` text,
|
||||
`video_path` text,
|
||||
`thumbnail_path` text,
|
||||
`thumbnail_url` text,
|
||||
`added_at` text,
|
||||
`created_at` text NOT NULL,
|
||||
`updated_at` text,
|
||||
`part_number` integer,
|
||||
`total_parts` integer,
|
||||
`series_title` text,
|
||||
`rating` integer,
|
||||
`description` text,
|
||||
`view_count` integer,
|
||||
`duration` text
|
||||
);
|
||||
384
backend/drizzle/meta/0000_snapshot.json
Normal file
384
backend/drizzle/meta/0000_snapshot.json
Normal file
@@ -0,0 +1,384 @@
|
||||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "458257d1-ceab-4f29-ac3f-6ac2576d37f5",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"collection_videos": {
|
||||
"name": "collection_videos",
|
||||
"columns": {
|
||||
"collection_id": {
|
||||
"name": "collection_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"video_id": {
|
||||
"name": "video_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"order": {
|
||||
"name": "order",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"collection_videos_collection_id_collections_id_fk": {
|
||||
"name": "collection_videos_collection_id_collections_id_fk",
|
||||
"tableFrom": "collection_videos",
|
||||
"tableTo": "collections",
|
||||
"columnsFrom": [
|
||||
"collection_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"collection_videos_video_id_videos_id_fk": {
|
||||
"name": "collection_videos_video_id_videos_id_fk",
|
||||
"tableFrom": "collection_videos",
|
||||
"tableTo": "videos",
|
||||
"columnsFrom": [
|
||||
"video_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"collection_videos_collection_id_video_id_pk": {
|
||||
"columns": [
|
||||
"collection_id",
|
||||
"video_id"
|
||||
],
|
||||
"name": "collection_videos_collection_id_video_id_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"collections": {
|
||||
"name": "collections",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "downloads",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"filename": {
|
||||
"name": "filename",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"total_size": {
|
||||
"name": "total_size",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"downloaded_size": {
|
||||
"name": "downloaded_size",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"progress": {
|
||||
"name": "progress",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"speed": {
|
||||
"name": "speed",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'active'"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"settings": {
|
||||
"name": "settings",
|
||||
"columns": {
|
||||
"key": {
|
||||
"name": "key",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"videos": {
|
||||
"name": "videos",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"author": {
|
||||
"name": "author",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"date": {
|
||||
"name": "date",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"source": {
|
||||
"name": "source",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"source_url": {
|
||||
"name": "source_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"video_filename": {
|
||||
"name": "video_filename",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"thumbnail_filename": {
|
||||
"name": "thumbnail_filename",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"video_path": {
|
||||
"name": "video_path",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"thumbnail_path": {
|
||||
"name": "thumbnail_path",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"thumbnail_url": {
|
||||
"name": "thumbnail_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"added_at": {
|
||||
"name": "added_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"part_number": {
|
||||
"name": "part_number",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"total_parts": {
|
||||
"name": "total_parts",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"series_title": {
|
||||
"name": "series_title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"rating": {
|
||||
"name": "rating",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"view_count": {
|
||||
"name": "view_count",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"duration": {
|
||||
"name": "duration",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
13
backend/drizzle/meta/_journal.json
Normal file
13
backend/drizzle/meta/_journal.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "sqlite",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "6",
|
||||
"when": 1764043254513,
|
||||
"tag": "0000_known_guardsmen",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
"start": "ts-node src/server.ts",
|
||||
"dev": "nodemon src/server.ts",
|
||||
"build": "tsc",
|
||||
"generate": "drizzle-kit generate",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import { getSettings } from '../src/services/storageService';
|
||||
|
||||
console.log('Imported getSettings:', typeof getSettings);
|
||||
23
backend/src/db/migrate.ts
Normal file
23
backend/src/db/migrate.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { migrate } from 'drizzle-orm/better-sqlite3/migrator';
|
||||
import path from 'path';
|
||||
import { ROOT_DIR } from '../config/paths';
|
||||
import { db } from './index';
|
||||
|
||||
export function runMigrations() {
|
||||
try {
|
||||
console.log('Running database migrations...');
|
||||
// In production/docker, the drizzle folder is copied to the root or src/drizzle
|
||||
// We need to find where it is.
|
||||
// Based on Dockerfile: COPY . . -> it should be at /app/drizzle
|
||||
|
||||
const migrationsFolder = path.join(ROOT_DIR, 'drizzle');
|
||||
|
||||
migrate(db, { migrationsFolder });
|
||||
console.log('Database migrations completed successfully.');
|
||||
} catch (error) {
|
||||
console.error('Error running database migrations:', error);
|
||||
// Don't throw, as we might want the app to start even if migration fails (though it might be broken)
|
||||
// But for initial setup, it's critical.
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,10 @@ app.use(express.urlencoded({ extended: true }));
|
||||
// Initialize storage (create directories, etc.)
|
||||
storageService.initializeStorage();
|
||||
|
||||
// Run database migrations
|
||||
import { runMigrations } from "./db/migrate";
|
||||
runMigrations();
|
||||
|
||||
// Serve static files
|
||||
app.use("/videos", express.static(VIDEOS_DIR));
|
||||
app.use("/images", express.static(IMAGES_DIR));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { COLLECTIONS_DATA_PATH, STATUS_DATA_PATH, VIDEOS_DATA_PATH } from '../config/paths';
|
||||
import { COLLECTIONS_DATA_PATH, DATA_DIR, STATUS_DATA_PATH, VIDEOS_DATA_PATH } from '../config/paths';
|
||||
import { db } from '../db';
|
||||
import { collections, collectionVideos, downloads, settings, videos } from '../db/schema';
|
||||
|
||||
@@ -10,21 +10,42 @@ const SETTINGS_DATA_PATH = path.join(path.dirname(VIDEOS_DATA_PATH), 'settings.j
|
||||
export async function runMigration() {
|
||||
console.log('Starting migration...');
|
||||
const results = {
|
||||
videos: 0,
|
||||
collections: 0,
|
||||
settings: 0,
|
||||
downloads: 0,
|
||||
errors: [] as string[]
|
||||
videos: { count: 0, path: VIDEOS_DATA_PATH, found: false },
|
||||
collections: { count: 0, path: COLLECTIONS_DATA_PATH, found: false },
|
||||
settings: { count: 0, path: SETTINGS_DATA_PATH, found: false },
|
||||
downloads: { count: 0, path: STATUS_DATA_PATH, found: false },
|
||||
errors: [] as string[],
|
||||
warnings: [] as string[]
|
||||
};
|
||||
|
||||
// Check for common misconfiguration (nested data directory)
|
||||
const nestedDataPath = path.join(DATA_DIR, 'data');
|
||||
if (fs.existsSync(nestedDataPath)) {
|
||||
results.warnings.push(`Found nested data directory at ${nestedDataPath}. Your volume mount might be incorrect (mounting /data to /app/data instead of /app/data contents).`);
|
||||
}
|
||||
|
||||
// Migrate Videos
|
||||
if (fs.existsSync(VIDEOS_DATA_PATH)) {
|
||||
results.videos.found = true;
|
||||
try {
|
||||
const videosData = fs.readJSONSync(VIDEOS_DATA_PATH);
|
||||
console.log(`Found ${videosData.length} videos to migrate.`);
|
||||
|
||||
for (const video of videosData) {
|
||||
try {
|
||||
// Fix for missing createdAt in legacy data
|
||||
let createdAt = video.createdAt;
|
||||
if (!createdAt) {
|
||||
if (video.addedAt) {
|
||||
createdAt = video.addedAt;
|
||||
} else if (video.id && /^\d{13}$/.test(video.id)) {
|
||||
// If ID is a timestamp (13 digits), use it
|
||||
createdAt = new Date(parseInt(video.id)).toISOString();
|
||||
} else {
|
||||
createdAt = new Date().toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
await db.insert(videos).values({
|
||||
id: video.id,
|
||||
title: video.title,
|
||||
@@ -38,7 +59,7 @@ export async function runMigration() {
|
||||
thumbnailPath: video.thumbnailPath,
|
||||
thumbnailUrl: video.thumbnailUrl,
|
||||
addedAt: video.addedAt,
|
||||
createdAt: video.createdAt,
|
||||
createdAt: createdAt,
|
||||
updatedAt: video.updatedAt,
|
||||
partNumber: video.partNumber,
|
||||
totalParts: video.totalParts,
|
||||
@@ -48,7 +69,7 @@ export async function runMigration() {
|
||||
viewCount: video.viewCount,
|
||||
duration: video.duration,
|
||||
}).onConflictDoNothing();
|
||||
results.videos++;
|
||||
results.videos.count++;
|
||||
} catch (error: any) {
|
||||
console.error(`Error migrating video ${video.id}:`, error);
|
||||
results.errors.push(`Video ${video.id}: ${error.message}`);
|
||||
@@ -61,6 +82,7 @@ export async function runMigration() {
|
||||
|
||||
// Migrate Collections
|
||||
if (fs.existsSync(COLLECTIONS_DATA_PATH)) {
|
||||
results.collections.found = true;
|
||||
try {
|
||||
const collectionsData = fs.readJSONSync(COLLECTIONS_DATA_PATH);
|
||||
console.log(`Found ${collectionsData.length} collections to migrate.`);
|
||||
@@ -75,7 +97,7 @@ export async function runMigration() {
|
||||
createdAt: collection.createdAt || new Date().toISOString(),
|
||||
updatedAt: collection.updatedAt,
|
||||
}).onConflictDoNothing();
|
||||
results.collections++;
|
||||
results.collections.count++;
|
||||
|
||||
// Insert Collection Videos
|
||||
if (collection.videos && collection.videos.length > 0) {
|
||||
@@ -103,6 +125,7 @@ export async function runMigration() {
|
||||
|
||||
// Migrate Settings
|
||||
if (fs.existsSync(SETTINGS_DATA_PATH)) {
|
||||
results.settings.found = true;
|
||||
try {
|
||||
const settingsData = fs.readJSONSync(SETTINGS_DATA_PATH);
|
||||
console.log('Found settings.json to migrate.');
|
||||
@@ -115,7 +138,7 @@ export async function runMigration() {
|
||||
target: settings.key,
|
||||
set: { value: JSON.stringify(value) },
|
||||
});
|
||||
results.settings++;
|
||||
results.settings.count++;
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Error migrating settings:', error);
|
||||
@@ -125,6 +148,7 @@ export async function runMigration() {
|
||||
|
||||
// Migrate Status (Downloads)
|
||||
if (fs.existsSync(STATUS_DATA_PATH)) {
|
||||
results.downloads.found = true;
|
||||
try {
|
||||
const statusData = fs.readJSONSync(STATUS_DATA_PATH);
|
||||
console.log('Found status.json to migrate.');
|
||||
@@ -155,7 +179,7 @@ export async function runMigration() {
|
||||
status: 'active',
|
||||
}
|
||||
});
|
||||
results.downloads++;
|
||||
results.downloads.count++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +199,7 @@ export async function runMigration() {
|
||||
status: 'queued',
|
||||
}
|
||||
});
|
||||
results.downloads++;
|
||||
results.downloads.count++;
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
|
||||
3
frontend/.dockerignore
Normal file
3
frontend/.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
dist
|
||||
.env
|
||||
@@ -1,5 +1,5 @@
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import { Route, Routes, useLocation } from 'react-router-dom';
|
||||
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
|
||||
import AuthorVideos from '../pages/AuthorVideos';
|
||||
import CollectionPage from '../pages/CollectionPage';
|
||||
import Home from '../pages/Home';
|
||||
@@ -152,6 +152,10 @@ const AnimatedRoutes = ({
|
||||
</PageTransition>
|
||||
}
|
||||
/>
|
||||
{/* Redirect /login to home if already authenticated (or login disabled) */}
|
||||
<Route path="/login" element={<Navigate to="/" replace />} />
|
||||
{/* Catch all - redirect to home */}
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</AnimatePresence>
|
||||
);
|
||||
|
||||
@@ -234,8 +234,39 @@ const SettingsPage: React.FC = () => {
|
||||
setSaving(true);
|
||||
try {
|
||||
const res = await axios.post(`${API_URL}/settings/migrate`);
|
||||
console.log('Migration results:', res.data.results);
|
||||
setMessage({ text: 'Migration completed successfully!', type: 'success' });
|
||||
const results = res.data.results;
|
||||
console.log('Migration results:', results);
|
||||
|
||||
let msg = 'Migration Report:\n';
|
||||
let hasData = false;
|
||||
|
||||
if (results.warnings && results.warnings.length > 0) {
|
||||
msg += `\n⚠️ WARNINGS:\n${results.warnings.join('\n')}\n`;
|
||||
}
|
||||
|
||||
const categories = ['videos', 'collections', 'settings', 'downloads'];
|
||||
categories.forEach(cat => {
|
||||
const data = results[cat];
|
||||
if (data) {
|
||||
if (data.found) {
|
||||
msg += `\n✅ ${cat}: ${data.count} items migrated`;
|
||||
hasData = true;
|
||||
} else {
|
||||
msg += `\n❌ ${cat}: File not found at ${data.path}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (results.errors && results.errors.length > 0) {
|
||||
msg += `\n\n⛔ ERRORS:\n${results.errors.join('\n')}`;
|
||||
}
|
||||
|
||||
if (!hasData && (!results.errors || results.errors.length === 0)) {
|
||||
msg += '\n\n⚠️ No data files were found to migrate. Please check your volume mappings.';
|
||||
}
|
||||
|
||||
alert(msg);
|
||||
setMessage({ text: hasData ? 'Migration completed. See details in alert.' : 'Migration finished but no data found.', type: hasData ? 'success' : 'warning' });
|
||||
} catch (error: any) {
|
||||
console.error('Migration failed:', error);
|
||||
setMessage({
|
||||
|
||||
Reference in New Issue
Block a user