251 lines
5.5 KiB
JavaScript
251 lines
5.5 KiB
JavaScript
|
|
import express from "express";
|
||
|
|
import { v4 as uuidv4 } from "uuid";
|
||
|
|
import { validate, songValidation } from "../middleware/validate.js";
|
||
|
|
|
||
|
|
const router = express.Router();
|
||
|
|
|
||
|
|
// In-memory store (replace with database in production)
|
||
|
|
const songs = new Map([
|
||
|
|
[
|
||
|
|
"1",
|
||
|
|
{
|
||
|
|
id: "1",
|
||
|
|
title: "Amazing Grace",
|
||
|
|
artist: "John Newton",
|
||
|
|
key: "G",
|
||
|
|
originalKey: "G",
|
||
|
|
tempo: 72,
|
||
|
|
category: "Hymn",
|
||
|
|
lyrics: `[Verse 1]
|
||
|
|
[G]Amazing [D]grace, how [G]sweet the [G7]sound
|
||
|
|
That [C]saved a [G]wretch like [Em]me
|
||
|
|
I [G]once was [D]lost, but [G]now am [Em]found
|
||
|
|
Was [G]blind but [D]now I [G]see`,
|
||
|
|
createdBy: "system",
|
||
|
|
createdAt: new Date().toISOString(),
|
||
|
|
updatedAt: new Date().toISOString(),
|
||
|
|
},
|
||
|
|
],
|
||
|
|
[
|
||
|
|
"2",
|
||
|
|
{
|
||
|
|
id: "2",
|
||
|
|
title: "How Great Is Our God",
|
||
|
|
artist: "Chris Tomlin",
|
||
|
|
key: "C",
|
||
|
|
originalKey: "C",
|
||
|
|
tempo: 78,
|
||
|
|
category: "Contemporary",
|
||
|
|
lyrics: `[Verse 1]
|
||
|
|
The [C]splendor of the [Am]King
|
||
|
|
[F]Clothed in majesty [C]
|
||
|
|
Let all the earth re[Am]joice
|
||
|
|
[F]All the earth re[G]joice`,
|
||
|
|
createdBy: "system",
|
||
|
|
createdAt: new Date().toISOString(),
|
||
|
|
updatedAt: new Date().toISOString(),
|
||
|
|
},
|
||
|
|
],
|
||
|
|
]);
|
||
|
|
|
||
|
|
// Search songs (for worship list)
|
||
|
|
router.get("/search", (req, res) => {
|
||
|
|
const { q } = req.query;
|
||
|
|
|
||
|
|
if (!q) {
|
||
|
|
return res.json({
|
||
|
|
success: true,
|
||
|
|
songs: [],
|
||
|
|
total: 0,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
const searchLower = q.toLowerCase();
|
||
|
|
const result = Array.from(songs.values()).filter(
|
||
|
|
(song) =>
|
||
|
|
song.title.toLowerCase().includes(searchLower) ||
|
||
|
|
song.artist.toLowerCase().includes(searchLower),
|
||
|
|
);
|
||
|
|
|
||
|
|
res.json({
|
||
|
|
success: true,
|
||
|
|
songs: result,
|
||
|
|
total: result.length,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// Get all songs
|
||
|
|
router.get("/", (req, res) => {
|
||
|
|
const { search, key, category, sort } = req.query;
|
||
|
|
|
||
|
|
let result = Array.from(songs.values());
|
||
|
|
|
||
|
|
// Search filter
|
||
|
|
if (search) {
|
||
|
|
const searchLower = search.toLowerCase();
|
||
|
|
result = result.filter(
|
||
|
|
(song) =>
|
||
|
|
song.title.toLowerCase().includes(searchLower) ||
|
||
|
|
song.artist.toLowerCase().includes(searchLower),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Key filter
|
||
|
|
if (key) {
|
||
|
|
result = result.filter((song) => song.key === key);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Category filter
|
||
|
|
if (category) {
|
||
|
|
result = result.filter((song) => song.category === category);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Sort
|
||
|
|
if (sort) {
|
||
|
|
const [field, order] = sort.split(":");
|
||
|
|
result.sort((a, b) => {
|
||
|
|
const aVal = a[field] || "";
|
||
|
|
const bVal = b[field] || "";
|
||
|
|
const comparison =
|
||
|
|
typeof aVal === "string" ? aVal.localeCompare(bVal) : aVal - bVal;
|
||
|
|
return order === "desc" ? -comparison : comparison;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
res.json({
|
||
|
|
songs: result,
|
||
|
|
total: result.length,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// Get single song
|
||
|
|
router.get("/:id", (req, res) => {
|
||
|
|
const song = songs.get(req.params.id);
|
||
|
|
|
||
|
|
if (!song) {
|
||
|
|
return res.status(404).json({ error: "Song not found" });
|
||
|
|
}
|
||
|
|
|
||
|
|
res.json({ song });
|
||
|
|
});
|
||
|
|
|
||
|
|
// Create song
|
||
|
|
router.post("/", validate(songValidation), (req, res) => {
|
||
|
|
const { title, artist, key, tempo, category, lyrics } = req.body;
|
||
|
|
|
||
|
|
const song = {
|
||
|
|
id: uuidv4(),
|
||
|
|
title,
|
||
|
|
artist: artist || "Unknown",
|
||
|
|
key: key || "C",
|
||
|
|
originalKey: key || "C",
|
||
|
|
tempo: tempo || 72,
|
||
|
|
category: category || "Contemporary",
|
||
|
|
lyrics: lyrics || "",
|
||
|
|
createdBy: req.user.id,
|
||
|
|
createdAt: new Date().toISOString(),
|
||
|
|
updatedAt: new Date().toISOString(),
|
||
|
|
};
|
||
|
|
|
||
|
|
songs.set(song.id, song);
|
||
|
|
|
||
|
|
res.status(201).json({
|
||
|
|
message: "Song created successfully",
|
||
|
|
song,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// Update song
|
||
|
|
router.put("/:id", validate(songValidation), (req, res) => {
|
||
|
|
const song = songs.get(req.params.id);
|
||
|
|
|
||
|
|
if (!song) {
|
||
|
|
return res.status(404).json({ error: "Song not found" });
|
||
|
|
}
|
||
|
|
|
||
|
|
const { title, artist, key, tempo, category, lyrics } = req.body;
|
||
|
|
|
||
|
|
const updatedSong = {
|
||
|
|
...song,
|
||
|
|
title: title || song.title,
|
||
|
|
artist: artist || song.artist,
|
||
|
|
key: key || song.key,
|
||
|
|
tempo: tempo || song.tempo,
|
||
|
|
category: category || song.category,
|
||
|
|
lyrics: lyrics !== undefined ? lyrics : song.lyrics,
|
||
|
|
updatedAt: new Date().toISOString(),
|
||
|
|
};
|
||
|
|
|
||
|
|
songs.set(song.id, updatedSong);
|
||
|
|
|
||
|
|
res.json({
|
||
|
|
message: "Song updated successfully",
|
||
|
|
song: updatedSong,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// Delete song
|
||
|
|
router.delete("/:id", (req, res) => {
|
||
|
|
const song = songs.get(req.params.id);
|
||
|
|
|
||
|
|
if (!song) {
|
||
|
|
return res.status(404).json({ error: "Song not found" });
|
||
|
|
}
|
||
|
|
|
||
|
|
songs.delete(req.params.id);
|
||
|
|
|
||
|
|
res.json({ message: "Song deleted successfully" });
|
||
|
|
});
|
||
|
|
|
||
|
|
// Duplicate song
|
||
|
|
router.post("/:id/duplicate", (req, res) => {
|
||
|
|
const song = songs.get(req.params.id);
|
||
|
|
|
||
|
|
if (!song) {
|
||
|
|
return res.status(404).json({ error: "Song not found" });
|
||
|
|
}
|
||
|
|
|
||
|
|
const duplicateSong = {
|
||
|
|
...song,
|
||
|
|
id: uuidv4(),
|
||
|
|
title: `${song.title} (Copy)`,
|
||
|
|
createdBy: req.user.id,
|
||
|
|
createdAt: new Date().toISOString(),
|
||
|
|
updatedAt: new Date().toISOString(),
|
||
|
|
};
|
||
|
|
|
||
|
|
songs.set(duplicateSong.id, duplicateSong);
|
||
|
|
|
||
|
|
res.status(201).json({
|
||
|
|
message: "Song duplicated successfully",
|
||
|
|
song: duplicateSong,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// Transpose song
|
||
|
|
router.post("/:id/transpose", (req, res) => {
|
||
|
|
const song = songs.get(req.params.id);
|
||
|
|
|
||
|
|
if (!song) {
|
||
|
|
return res.status(404).json({ error: "Song not found" });
|
||
|
|
}
|
||
|
|
|
||
|
|
const { targetKey, useFlats } = req.body;
|
||
|
|
|
||
|
|
// Transposition logic would go here
|
||
|
|
// For now, just return the song with updated key
|
||
|
|
|
||
|
|
const transposedSong = {
|
||
|
|
...song,
|
||
|
|
key: targetKey || song.key,
|
||
|
|
// In a real implementation, lyrics would be transposed
|
||
|
|
};
|
||
|
|
|
||
|
|
res.json({
|
||
|
|
message: "Song transposed",
|
||
|
|
song: transposedSong,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
export default router;
|