feat: rename mukke to music, add cover art upload via mana-media

Rename the music module from "Mukke" to "Music" across the entire
codebase: API routes, web app module, shared packages, search provider,
dashboard widgets, i18n keys, app registry, and route paths.

Add POST /api/v1/music/cover/upload endpoint that uploads cover art
images through mana-media for deduplication, thumbnails, and Photos
gallery visibility.

Dexie table names (mukkePlaylists, mukkeProjects) kept unchanged to
preserve existing IndexedDB data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-05 15:25:34 +02:00
parent ee7ff7d5e8
commit d4700a07f9
64 changed files with 258 additions and 214 deletions

View file

@ -18,7 +18,7 @@ import {
// Module routes
import { calendarRoutes } from './modules/calendar/routes';
import { contactsRoutes } from './modules/contacts/routes';
import { mukkeRoutes } from './modules/mukke/routes';
import { musicRoutes } from './modules/music/routes';
import { chatRoutes } from './modules/chat/routes';
import { contextRoutes } from './modules/context/routes';
import { pictureRoutes } from './modules/picture/routes';
@ -48,7 +48,7 @@ app.use('/api/*', authMiddleware());
// ─── Module Routes ──────────────────────────────────────────
app.route('/api/v1/calendar', calendarRoutes);
app.route('/api/v1/contacts', contactsRoutes);
app.route('/api/v1/mukke', mukkeRoutes);
app.route('/api/v1/music', musicRoutes);
app.route('/api/v1/chat', chatRoutes);
app.route('/api/v1/context', contextRoutes);
app.route('/api/v1/picture', pictureRoutes);

View file

@ -1,6 +1,6 @@
/**
* Mukke module Audio upload, presigned URLs, cover art
* Ported from apps/mukke/apps/server
* Music module Audio upload, presigned URLs, cover art
* Renamed from Mukke.
*/
import { Hono } from 'hono';
@ -18,8 +18,8 @@ routes.post('/songs/upload', async (c) => {
const key = `users/${userId}/songs/${songId}/${filename}`;
try {
const { createMukkeStorage } = await import('@manacore/shared-storage');
const storage = createMukkeStorage();
const { createMusicStorage } = await import('@manacore/shared-storage');
const storage = createMusicStorage();
const uploadUrl = await storage.getUploadUrl(key, { expiresIn: 3600 });
return c.json({
@ -38,8 +38,8 @@ routes.get('/songs/:id/download-url', async (c) => {
if (!storagePath) return c.json({ error: 'storagePath required' }, 400);
try {
const { createMukkeStorage } = await import('@manacore/shared-storage');
const storage = createMukkeStorage();
const { createMusicStorage } = await import('@manacore/shared-storage');
const storage = createMusicStorage();
const url = await storage.getDownloadUrl(storagePath, { expiresIn: 3600 });
return c.json({ url });
} catch {
@ -47,6 +47,35 @@ routes.get('/songs/:id/download-url', async (c) => {
}
});
// ─── Cover Art Upload (via mana-media) ─────────────────────
routes.post('/cover/upload', async (c) => {
const userId = c.get('userId');
const formData = await c.req.formData();
const file = formData.get('file') as File | null;
if (!file) return c.json({ error: 'No file' }, 400);
if (file.size > 10 * 1024 * 1024) return c.json({ error: 'Max 10MB' }, 400);
if (!file.type.startsWith('image/')) return c.json({ error: 'Must be an image' }, 400);
try {
const { uploadImageToMedia } = await import('../../lib/media');
const buffer = await file.arrayBuffer();
const result = await uploadImageToMedia(buffer, file.name, { app: 'music', userId });
return c.json(
{
coverArtPath: result.id,
coverUrl: result.urls.thumbnail || result.urls.original,
mediaId: result.id,
},
201
);
} catch {
return c.json({ error: 'Upload failed' }, 500);
}
});
// ─── Cover Art URL ──────────────────────────────────────────
routes.get('/songs/:id/cover-url', async (c) => {
@ -54,8 +83,8 @@ routes.get('/songs/:id/cover-url', async (c) => {
if (!coverArtPath) return c.json({ url: null });
try {
const { createMukkeStorage } = await import('@manacore/shared-storage');
const storage = createMukkeStorage();
const { createMusicStorage } = await import('@manacore/shared-storage');
const storage = createMusicStorage();
const url = await storage.getDownloadUrl(coverArtPath, { expiresIn: 3600 });
return c.json({ url });
} catch {
@ -70,8 +99,8 @@ routes.post('/library/cover-urls', async (c) => {
if (!paths?.length) return c.json({ urls: {} });
try {
const { createMukkeStorage } = await import('@manacore/shared-storage');
const storage = createMukkeStorage();
const { createMusicStorage } = await import('@manacore/shared-storage');
const storage = createMusicStorage();
const urls: Record<string, string> = {};
for (const path of paths.slice(0, 50)) {
@ -88,4 +117,4 @@ routes.post('/library/cover-urls', async (c) => {
}
});
export { routes as mukkeRoutes };
export { routes as musicRoutes };