Blame Raw
Cohee · e3f41666 · · 156 lines (6.1 KB)
1 contributor
1import fs from 'node:fs';
2import path from 'node:path';
3
4import express from 'express';
5import sanitize from 'sanitize-filename';
6
7import { invalidateThumbnail } from './thumbnails.js';
8import { thumbnailDimensions, readMetadataIndex, renameMetadata, removeMetadata, getOrGenerateMetadataBatch } from './image-metadata.js';
9import { getImages } from '../util.js';
10import { getFileNameValidationFunction } from '../middleware/validateFileName.js';
11
12export const router = express.Router();
13
14router.post('/all', async function (request, response) {
15 try {
16 const images = getImages(request.user.directories.backgrounds);
17 const config = { width: thumbnailDimensions.bg[0], height: thumbnailDimensions.bg[1] };
18
19 // Get metadata for all images to provide isAnimated flag to client
20 const relativePaths = images.map(img => path.join('backgrounds', img));
21 const { results: metadataMap } = await getOrGenerateMetadataBatch(request.user.directories.root, relativePaths, 'bg');
22
23 // Build response with metadata for each image
24 const imagesWithMetadata = images.map(img => {
25 const relativePath = path.join('backgrounds', img);
26 const metadata = metadataMap[relativePath];
27 return {
28 filename: img,
29 isAnimated: metadata?.isAnimated ?? false,
30 };
31 });
32
33 response.json({ images: imagesWithMetadata, config });
34 } catch (error) {
35 console.error('[Backgrounds] Error fetching backgrounds:', error);
36 response.status(500).json({ error: 'Failed to fetch backgrounds' });
37 }
38});
39
40/**
41 * POST /api/backgrounds/folders
42 * Returns folders and per-image folderIds from the metadata index.
43 * Loaded separately from /all to avoid blocking image rendering.
44 */
45router.post('/folders', async function (request, response) {
46 try {
47 const index = await readMetadataIndex(request.user.directories.root);
48 const folders = index.folders || [];
49
50 // Build a slim map of image → folderIds for the frontend
51 /** @type {Object.<string, string[]>} */
52 const imageFolderMap = {};
53 for (const [relativePath, meta] of Object.entries(index.images)) {
54 if (Array.isArray(meta.folderIds) && meta.folderIds.length > 0) {
55 // Strip the directory prefix to get just the filename
56 const filename = relativePath.split('/').pop() || relativePath;
57 imageFolderMap[filename] = meta.folderIds;
58 }
59 }
60
61 response.json({ folders, imageFolderMap });
62 } catch (error) {
63 console.error('[Backgrounds] Folders endpoint error:', error);
64 response.status(500).json({ error: 'Internal server error.' });
65 }
66});
67
68router.post('/delete', getFileNameValidationFunction('bg'), async function (request, response) {
69 try {
70 if (!request.body) return response.sendStatus(400);
71
72 if (request.body.bg !== sanitize(request.body.bg)) {
73 console.error('Malicious bg name prevented');
74 return response.sendStatus(403);
75 }
76
77 const fileName = path.join(request.user.directories.backgrounds, sanitize(request.body.bg));
78
79 if (!fs.existsSync(fileName)) {
80 console.error('BG file not found');
81 return response.sendStatus(400);
82 }
83
84 fs.unlinkSync(fileName);
85 invalidateThumbnail(request.user.directories, 'bg', request.body.bg);
86
87 // Remove metadata for deleted image
88 const relativePath = path.join('backgrounds', request.body.bg);
89 await removeMetadata(request.user.directories.root, relativePath).catch(err => {
90 console.warn('[Backgrounds] Failed to remove metadata:', err.message);
91 });
92
93 return response.send('ok');
94 } catch (err) {
95 console.error(err);
96 response.sendStatus(500);
97 }
98});
99
100router.post('/rename', async function (request, response) {
101 try {
102 if (!request.body) return response.sendStatus(400);
103
104 const oldFileName = path.join(request.user.directories.backgrounds, sanitize(request.body.old_bg));
105 const newFileName = path.join(request.user.directories.backgrounds, sanitize(request.body.new_bg));
106
107 if (!fs.existsSync(oldFileName)) {
108 console.error('BG file not found');
109 return response.sendStatus(400);
110 }
111
112 if (fs.existsSync(newFileName)) {
113 console.error('New BG file already exists');
114 return response.sendStatus(400);
115 }
116
117 fs.copyFileSync(oldFileName, newFileName);
118 fs.unlinkSync(oldFileName);
119 invalidateThumbnail(request.user.directories, 'bg', request.body.old_bg);
120
121 // Update metadata for renamed image
122 const oldRelativePath = path.join('backgrounds', request.body.old_bg);
123 const newRelativePath = path.join('backgrounds', request.body.new_bg);
124 await renameMetadata(request.user.directories.root, oldRelativePath, newRelativePath).catch(err => {
125 console.warn('[Backgrounds] Failed to rename metadata:', err.message);
126 });
127
128 return response.send('ok');
129 } catch (err) {
130 console.error(err);
131 response.sendStatus(500);
132 }
133});
134
135router.post('/upload', async function (request, response) {
136 try {
137 if (!request.body || !request.file) return response.sendStatus(400);
138
139 const img_path = path.join(request.file.destination, request.file.filename);
140 const filename = sanitize(request.file.originalname);
141 fs.copyFileSync(img_path, path.join(request.user.directories.backgrounds, filename));
142 fs.unlinkSync(img_path);
143 invalidateThumbnail(request.user.directories, 'bg', filename);
144
145 // Generate metadata for the new image
146 const relativePath = path.join('backgrounds', filename);
147 await getOrGenerateMetadataBatch(request.user.directories.root, [relativePath], 'bg').catch(err => {
148 console.warn('[Backgrounds] Failed to generate metadata for upload:', err.message);
149 });
150
151 response.send(filename);
152 } catch (err) {
153 console.error(err);
154 response.sendStatus(500);
155 }
156});