feat: add Docker configuration and database initialization
- Introduced a .dockerignore file to exclude unnecessary files from Docker builds. - Created a Dockerfile for building the Next.js application with optimized production settings. - Added a docker-compose.yml file for orchestrating services, including NextAV, FFmpeg, and Nginx. - Refactored database access to use a singleton pattern for better management and initialization of the SQLite database. - Updated API routes to utilize the new database access method, enhancing consistency across the application.
This commit is contained in:
parent
158f9f7a23
commit
0c1119be46
|
|
@ -0,0 +1,66 @@
|
|||
# Dependencies
|
||||
node_modules
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Production build
|
||||
dist
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# IDE and editor files
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Docker
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
docker-compose.yml
|
||||
|
||||
# Documentation
|
||||
README.md
|
||||
*.md
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Temporary folders
|
||||
tmp
|
||||
temp
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
# Use official Node.js runtime as the base image
|
||||
FROM node:22.18.0 AS base
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
|
||||
# Install build dependencies for native modules
|
||||
RUN apt-get update && apt-get install -y \
|
||||
python3 \
|
||||
make \
|
||||
g++ \
|
||||
libsqlite3-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install pnpm globally
|
||||
RUN npm install -g pnpm
|
||||
|
||||
# Copy package files and install all dependencies (including dev dependencies)
|
||||
COPY package.json package-lock.json ./
|
||||
RUN pnpm install
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Rebuild better-sqlite3 to ensure native bindings are compiled correctly
|
||||
RUN pnpm rebuild better-sqlite3
|
||||
|
||||
# Ensure database file exists and has proper permissions
|
||||
RUN touch /app/media.db && chmod 666 /app/media.db
|
||||
|
||||
# Create directories for media storage
|
||||
RUN mkdir -p /app/data /app/media
|
||||
|
||||
# Build the application
|
||||
RUN pnpm build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
# Install FFmpeg and FFprobe for thumbnail generation
|
||||
RUN apt-get update && apt-get install -y \
|
||||
ffmpeg \
|
||||
sqlite3 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN groupadd --system --gid 1001 nodejs
|
||||
RUN useradd --system --uid 1001 --gid nodejs nextjs
|
||||
|
||||
# Create media directories
|
||||
RUN mkdir -p /app/data /app/media
|
||||
|
||||
# Copy built application
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
COPY --from=builder /app/media.db ./media.db
|
||||
|
||||
# Set up volume for persistent data
|
||||
VOLUME ["/app/data", "/app/media"]
|
||||
|
||||
# Switch to non-root user
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
CMD ["node", "server.js"]
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
version: '3.8'
|
||||
|
||||
services:
|
||||
nextav:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
- ./media:/app/media
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- DATABASE_URL=file:/app/data/nextav.db
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
depends_on:
|
||||
- ffmpeg
|
||||
|
||||
# FFmpeg service for thumbnail generation (optional - can use host FFmpeg)
|
||||
ffmpeg:
|
||||
image: jrottenberg/ffmpeg:4.4-alpine
|
||||
volumes:
|
||||
- ./media:/media:ro
|
||||
command: tail -f /dev/null # Keep container running
|
||||
restart: unless-stopped
|
||||
|
||||
# Nginx reverse proxy (optional for production)
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./ssl:/etc/nginx/ssl:ro
|
||||
depends_on:
|
||||
- nextav
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- production
|
||||
|
||||
volumes:
|
||||
nextav_data:
|
||||
driver: local
|
||||
nextav_media:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: nextav-network
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
output: 'standalone',
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { NextResponse } from 'next/server';
|
||||
import db from '@/db';
|
||||
import { getDatabase } from '@/db';
|
||||
|
||||
export async function POST(request: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
|
|
@ -10,6 +10,7 @@ export async function POST(request: Request, { params }: { params: Promise<{ id:
|
|||
return NextResponse.json({ error: 'Invalid media ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
const db = getDatabase();
|
||||
// Check if media exists
|
||||
const media = db.prepare(`
|
||||
SELECT id FROM media WHERE id = ?
|
||||
|
|
@ -55,6 +56,7 @@ export async function DELETE(request: Request, { params }: { params: Promise<{ i
|
|||
return NextResponse.json({ error: 'Invalid media ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
const db = getDatabase();
|
||||
// Check if bookmark exists
|
||||
const bookmark = db.prepare(`
|
||||
SELECT id FROM bookmarks WHERE media_id = ?
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { NextResponse } from 'next/server';
|
||||
import db from '@/db';
|
||||
import { getDatabase } from '@/db';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
|
|
@ -26,6 +26,7 @@ export async function GET(request: Request) {
|
|||
}
|
||||
|
||||
try {
|
||||
const db = getDatabase();
|
||||
// Get total count for pagination
|
||||
const countQuery = `
|
||||
SELECT COUNT(*) as total
|
||||
|
|
@ -63,6 +64,7 @@ export async function GET(request: Request) {
|
|||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const db = getDatabase();
|
||||
const { mediaId } = await request.json();
|
||||
|
||||
if (!mediaId) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import db from '@/db';
|
||||
import { getDatabase } from '@/db';
|
||||
|
||||
const VIDEO_EXTENSIONS = ["mp4", "mkv", "avi", "mov", "wmv", "flv", "webm", "m4v"];
|
||||
const PHOTO_EXTENSIONS = ["jpg", "jpeg", "png", "gif", "bmp", "webp", "tiff", "svg"];
|
||||
|
|
@ -16,6 +16,7 @@ export async function GET(request: Request) {
|
|||
}
|
||||
|
||||
try {
|
||||
const db = getDatabase();
|
||||
const files = fs.readdirSync(dirPath);
|
||||
|
||||
// Get media files from database for this path
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
import { NextResponse } from 'next/server';
|
||||
import { existsSync } from 'fs';
|
||||
import path from 'path';
|
||||
import { getDatabase } from '@/db';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
// Check if database is accessible by actually connecting to it
|
||||
try {
|
||||
const db = getDatabase();
|
||||
// Test a simple query
|
||||
db.prepare('SELECT 1').get();
|
||||
} catch (dbError) {
|
||||
return NextResponse.json(
|
||||
{ status: 'unhealthy', error: `Database not accessible: ${(dbError as Error).message}` },
|
||||
{ status: 503 }
|
||||
);
|
||||
}
|
||||
|
||||
// Check if media directory is accessible
|
||||
const mediaRoot = process.env.NEXT_PUBLIC_MEDIA_ROOT || '/app/media';
|
||||
if (!existsSync(mediaRoot)) {
|
||||
return NextResponse.json(
|
||||
{ status: 'unhealthy', error: 'Media directory not accessible' },
|
||||
{ status: 503 }
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({ status: 'healthy' });
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{ status: 'unhealthy', error: (error as Error).message },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import db from '@/db';
|
||||
import { getDatabase } from '@/db';
|
||||
|
||||
export async function DELETE(request: NextRequest, { params: paramsPromise }: { params: Promise<{ id: string }> }) {
|
||||
const params = await paramsPromise;
|
||||
const db = getDatabase();
|
||||
const id = parseInt(params.id, 10);
|
||||
if (isNaN(id)) {
|
||||
return NextResponse.json({ error: 'Invalid ID' }, { status: 400 });
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
|
||||
import { NextResponse } from 'next/server';
|
||||
import db from '@/db';
|
||||
import { getDatabase } from '@/db';
|
||||
|
||||
export async function GET() {
|
||||
const db = getDatabase();
|
||||
const libraries = db.prepare('SELECT * FROM libraries').all();
|
||||
return NextResponse.json(libraries);
|
||||
}
|
||||
|
|
@ -14,6 +15,7 @@ export async function POST(request: Request) {
|
|||
}
|
||||
|
||||
try {
|
||||
const db = getDatabase();
|
||||
const info = db.prepare('INSERT INTO libraries (path) VALUES (?)').run(path);
|
||||
return NextResponse.json({ id: info.lastInsertRowid, path });
|
||||
} catch (error: any) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import db from "@/db";
|
||||
import { getDatabase } from "@/db";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
|
|
@ -8,6 +8,7 @@ export async function GET(
|
|||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
const { id } = await params;
|
||||
const db = getDatabase();
|
||||
try {
|
||||
const photoId = parseInt(id);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { NextResponse } from 'next/server';
|
||||
import db from '@/db';
|
||||
import { getDatabase } from '@/db';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
|
|
@ -25,6 +25,7 @@ export async function GET(request: Request) {
|
|||
params.push(`%${search}%`, `%${search}%`);
|
||||
}
|
||||
|
||||
const db = getDatabase();
|
||||
// Get total count for pagination
|
||||
const countQuery = `
|
||||
SELECT COUNT(*) as total
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { NextResponse } from 'next/server';
|
||||
import db from '@/db';
|
||||
import { getDatabase } from '@/db';
|
||||
|
||||
export async function DELETE(request: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
const db = getDatabase();
|
||||
try {
|
||||
const parsedId = parseInt(id);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { NextResponse } from 'next/server';
|
||||
import db from '@/db';
|
||||
import { getDatabase } from '@/db';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
const db = getDatabase();
|
||||
const { searchParams } = new URL(request.url);
|
||||
const mediaId = searchParams.get('mediaId');
|
||||
|
||||
|
|
@ -33,6 +34,7 @@ export async function GET(request: Request) {
|
|||
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const db = getDatabase();
|
||||
const { mediaId, rating } = await request.json();
|
||||
|
||||
if (!mediaId || !rating) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import db from "@/db";
|
||||
import { getDatabase } from "@/db";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
|
|
@ -8,6 +8,7 @@ export async function GET(
|
|||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
const { id } = await params;
|
||||
const db = getDatabase();
|
||||
try {
|
||||
const videoId = parseInt(id);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { NextResponse } from 'next/server';
|
||||
import db from '@/db';
|
||||
import { getDatabase } from '@/db';
|
||||
|
||||
export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
const db = getDatabase();
|
||||
try {
|
||||
const parsedId = parseInt(id);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
|
||||
import { NextResponse } from "next/server";
|
||||
import db from "@/db";
|
||||
import { getDatabase } from "@/db";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const db = getDatabase();
|
||||
|
||||
const limit = Math.min(parseInt(searchParams.get('limit') || '50'), 100);
|
||||
const offset = parseInt(searchParams.get('offset') || '0');
|
||||
|
|
|
|||
128
src/db/index.ts
128
src/db/index.ts
|
|
@ -1,68 +1,82 @@
|
|||
|
||||
import Database from 'better-sqlite3';
|
||||
import Database, { Database as DatabaseType } from 'better-sqlite3';
|
||||
import path from 'path';
|
||||
|
||||
const dbPath = path.join(process.cwd(), 'media.db');
|
||||
const db = new Database(dbPath);
|
||||
let db: DatabaseType | null = null;
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS libraries (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
path TEXT NOT NULL UNIQUE
|
||||
);
|
||||
`);
|
||||
function initializeDatabase() {
|
||||
if (db) return db;
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS media (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
library_id INTEGER,
|
||||
path TEXT NOT NULL UNIQUE,
|
||||
type TEXT NOT NULL,
|
||||
title TEXT,
|
||||
size INTEGER,
|
||||
thumbnail TEXT,
|
||||
bookmark_count INTEGER DEFAULT 0,
|
||||
star_count INTEGER DEFAULT 0,
|
||||
avg_rating REAL DEFAULT 0.0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (library_id) REFERENCES libraries (id)
|
||||
);
|
||||
`);
|
||||
const dbPath = path.join(process.cwd(), 'media.db');
|
||||
db = new Database(dbPath);
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS bookmarks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
media_id INTEGER NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (media_id) REFERENCES media(id) ON DELETE CASCADE
|
||||
);
|
||||
`);
|
||||
// Create tables
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS libraries (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
path TEXT NOT NULL UNIQUE
|
||||
);
|
||||
`);
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS stars (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
media_id INTEGER NOT NULL,
|
||||
rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 5),
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (media_id) REFERENCES media(id) ON DELETE CASCADE
|
||||
);
|
||||
`);
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS media (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
library_id INTEGER,
|
||||
path TEXT NOT NULL UNIQUE,
|
||||
type TEXT NOT NULL,
|
||||
title TEXT,
|
||||
size INTEGER,
|
||||
thumbnail TEXT,
|
||||
bookmark_count INTEGER DEFAULT 0,
|
||||
star_count INTEGER DEFAULT 0,
|
||||
avg_rating REAL DEFAULT 0.0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (library_id) REFERENCES libraries (id)
|
||||
);
|
||||
`);
|
||||
|
||||
// Create indexes for performance
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_bookmarks_media_id ON bookmarks(media_id);`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_stars_media_id ON stars(media_id);`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_bookmark_count ON media(bookmark_count);`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_star_count ON media(star_count);`);
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS bookmarks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
media_id INTEGER NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (media_id) REFERENCES media(id) ON DELETE CASCADE
|
||||
);
|
||||
`);
|
||||
|
||||
// Pagination and filtering indexes
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_type_created_at ON media(type, created_at);`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_path ON media(path);`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_library_id ON media(library_id);`);
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS stars (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
media_id INTEGER NOT NULL,
|
||||
rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 5),
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (media_id) REFERENCES media(id) ON DELETE CASCADE
|
||||
);
|
||||
`);
|
||||
|
||||
// Full-text search indexes
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_title ON media(title);`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_type_path ON media(type, path);`);
|
||||
// Create indexes for performance
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_bookmarks_media_id ON bookmarks(media_id);`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_stars_media_id ON stars(media_id);`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_bookmark_count ON media(bookmark_count);`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_star_count ON media(star_count);`);
|
||||
|
||||
export default db;
|
||||
// Pagination and filtering indexes
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_type_created_at ON media(type, created_at);`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_path ON media(path);`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_library_id ON media(library_id);`);
|
||||
|
||||
// Full-text search indexes
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_title ON media(title);`);
|
||||
db.exec(`CREATE INDEX IF NOT EXISTS idx_media_type_path ON media(type, path);`);
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
export function getDatabase(): DatabaseType {
|
||||
return initializeDatabase();
|
||||
}
|
||||
|
||||
// For backward compatibility, export the database instance getter
|
||||
export default getDatabase;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import db from "@/db";
|
||||
import { getDatabase } from "@/db";
|
||||
import { glob } from "glob";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
|
|
@ -34,6 +34,7 @@ const generatePhotoThumbnail = (photoPath: string, thumbnailPath: string) => {
|
|||
};
|
||||
|
||||
const scanLibrary = async (library: { id: number; path: string }) => {
|
||||
const db = getDatabase();
|
||||
// Scan videos - handle all case variations
|
||||
const videoFiles = await glob(`${library.path}/**/*.*`, { nodir: true });
|
||||
|
||||
|
|
@ -118,6 +119,7 @@ const scanLibrary = async (library: { id: number; path: string }) => {
|
|||
};
|
||||
|
||||
export const scanAllLibraries = async () => {
|
||||
const db = getDatabase();
|
||||
const libraries = db.prepare("SELECT * FROM libraries").all() as { id: number; path: string }[];
|
||||
for (const library of libraries) {
|
||||
await scanLibrary(library);
|
||||
|
|
@ -125,6 +127,7 @@ export const scanAllLibraries = async () => {
|
|||
};
|
||||
|
||||
export const scanSelectedLibrary = async (libraryId: number) => {
|
||||
const db = getDatabase();
|
||||
const library = db.prepare("SELECT * FROM libraries WHERE id = ?").get(libraryId) as { id: number; path: string } | undefined;
|
||||
if (!library) {
|
||||
throw new Error(`Library with ID ${libraryId} not found`);
|
||||
|
|
|
|||
Loading…
Reference in New Issue