Initial commit - Church Music Database
This commit is contained in:
250
new-site/backend/api/songs.js
Normal file
250
new-site/backend/api/songs.js
Normal file
@@ -0,0 +1,250 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user