const express = require("express"); const router = express.Router(); const { query } = require("../db"); const { v4: uuidv4 } = require("uuid"); const { success, error, notFound, badRequest, } = require("../utils/responseHandler"); const { buildWhereClause, buildPagination, buildSearchCondition, } = require("../utils/queryBuilder"); const { authenticate } = require("../middleware/auth"); // Common SQL fragment const SELECT_SONG_FIELDS = "SELECT *, chords as key_chord FROM songs"; // GET search songs (for worship list song picker) router.get("/search", async (req, res) => { try { const { q } = req.query; if (!q || q.trim() === "") { return success(res, { songs: [], total: 0 }); } const searchTerm = `%${q.toLowerCase()}%`; const searchCondition = buildSearchCondition( searchTerm, ["title", "artist", "singer"], 1, ); const result = await query( `${SELECT_SONG_FIELDS} WHERE ${searchCondition} ORDER BY title ASC LIMIT 20`, [searchTerm], ); success(res, { songs: result.rows, total: result.rowCount }); } catch (err) { error(res, "Failed to search songs"); } }); // GET all songs router.get("/", async (req, res) => { try { const { search, artist, band, limit = 100, offset = 0 } = req.query; const params = []; const conditions = []; if (search) { params.push(`%${search.toLowerCase()}%`); conditions.push( `(LOWER(title) LIKE $${params.length} OR LOWER(lyrics) LIKE $${params.length})`, ); } if (artist) { params.push(`%${artist.toLowerCase()}%`); conditions.push(`LOWER(artist) LIKE $${params.length}`); } if (band) { params.push(`%${band.toLowerCase()}%`); conditions.push(`LOWER(band) LIKE $${params.length}`); } const whereClause = buildWhereClause(conditions); const { clause: paginationClause, params: paginationParams } = buildPagination(limit, offset, params.length + 1); const result = await query( `${SELECT_SONG_FIELDS}${whereClause} ORDER BY title ASC${paginationClause}`, [...params, ...paginationParams], ); success(res, { songs: result.rows, total: result.rowCount }); } catch (err) { error(res, "Failed to fetch songs"); } }); // GET single song by ID router.get("/:id", async (req, res) => { try { const result = await query(`${SELECT_SONG_FIELDS} WHERE id = $1`, [ req.params.id, ]); if (result.rows.length === 0) { return notFound(res, "Song"); } success(res, { song: result.rows[0] }); } catch (err) { error(res, "Failed to fetch song"); } }); // POST create new song router.post("/", authenticate, async (req, res) => { try { const { title, artist, band, singer, lyrics, chords, key_chord, memo } = req.body; if (!title) { return badRequest(res, "Title is required"); } const id = uuidv4(); const now = Math.floor(Date.now() / 1000); const chordsValue = chords || key_chord || ""; const result = await query( `INSERT INTO songs (id, title, artist, band, singer, lyrics, chords, memo, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *, chords as key_chord`, [ id, title, artist || "", band || "", singer || "", lyrics || "", chordsValue, memo || "", now, now, ], ); success(res, { song: result.rows[0] }, 201); } catch (err) { error(res, "Failed to create song"); } }); // PUT update song router.put("/:id", authenticate, async (req, res) => { try { const { title, artist, band, singer, lyrics, chords, key_chord, memo } = req.body; const now = Math.floor(Date.now() / 1000); const chordsValue = chords || key_chord; const result = await query( `UPDATE songs SET title = COALESCE($1, title), artist = COALESCE($2, artist), band = COALESCE($3, band), singer = COALESCE($4, singer), lyrics = COALESCE($5, lyrics), chords = COALESCE($6, chords), memo = COALESCE($7, memo), updated_at = $8 WHERE id = $9 RETURNING *, chords as key_chord`, [ title, artist, band, singer, lyrics, chordsValue, memo, now, req.params.id, ], ); if (result.rows.length === 0) { return notFound(res, "Song"); } success(res, { song: result.rows[0] }); } catch (err) { error(res, "Failed to update song"); } }); // DELETE song router.delete("/:id", authenticate, async (req, res) => { try { const result = await query("DELETE FROM songs WHERE id = $1 RETURNING id", [ req.params.id, ]); if (result.rows.length === 0) { return notFound(res, "Song"); } success(res, { message: "Song deleted" }); } catch (err) { error(res, "Failed to delete song"); } }); // GET song count router.get("/stats/count", async (req, res) => { try { const result = await query("SELECT COUNT(*) as count FROM songs"); success(res, { count: parseInt(result.rows[0].count) }); } catch (err) { error(res, "Failed to count songs"); } }); module.exports = router;