Migrated read APIs to Nitro
This commit is contained in:
1 parent
eb4e9f0700
commit
ee5809a489
26 files changed
+558
No files matched your search
@@ -0,0 +1,16 @@
|
|||||||
|
import { getRouterParam } from "h3"
|
||||||
|
import { Album } from "../../models/album"
|
||||||
|
import { requireAuthenticatedUser } from "../../utils/auth"
|
||||||
|
import { parseMongoId, validationResponse } from "../../utils/api-validation"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
await requireAuthenticatedUser(event)
|
||||||
|
|
||||||
|
const parsedId = parseMongoId(getRouterParam(event, "id"))
|
||||||
|
if ("error" in parsedId) return validationResponse(event, parsedId.error)
|
||||||
|
|
||||||
|
return Album.findOne({ _id: parsedId.value })
|
||||||
|
.populate("artists")
|
||||||
|
.populate("genre")
|
||||||
|
.exec()
|
||||||
|
})
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { getQuery, getRouterParam } from "h3"
|
||||||
|
import { Track } from "../../../models/track"
|
||||||
|
import { requireAuthenticatedUser } from "../../../utils/auth"
|
||||||
|
import { parseMongoId, parseNumber, validationResponse } from "../../../utils/api-validation"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
await requireAuthenticatedUser(event)
|
||||||
|
|
||||||
|
const parsedId = parseMongoId(getRouterParam(event, "id"))
|
||||||
|
if ("error" in parsedId) return validationResponse(event, parsedId.error)
|
||||||
|
|
||||||
|
const query = getQuery(event)
|
||||||
|
const limit = parseNumber(query.limit, 9, 1)
|
||||||
|
if ("error" in limit) return validationResponse(event, limit.error)
|
||||||
|
const skip = parseNumber(query.skip, 0, 0)
|
||||||
|
if ("error" in skip) return validationResponse(event, skip.error)
|
||||||
|
|
||||||
|
return Track.find({ album: parsedId.value })
|
||||||
|
.limit(limit.value || 9)
|
||||||
|
.skip(skip.value || 0)
|
||||||
|
.populate("artists")
|
||||||
|
.populate({ path: "album", select: "-artists -genre" })
|
||||||
|
.exec()
|
||||||
|
})
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { getQuery } from "h3"
|
||||||
|
import { Album } from "../../models/album"
|
||||||
|
import { requireAuthenticatedUser } from "../../utils/auth"
|
||||||
|
import { parseMongoIds, validationResponse } from "../../utils/api-validation"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
await requireAuthenticatedUser(event)
|
||||||
|
|
||||||
|
const parsedIds = parseMongoIds(getQuery(event).ids)
|
||||||
|
if ("error" in parsedIds) return validationResponse(event, parsedIds.error)
|
||||||
|
|
||||||
|
return Album.find({ _id: { $in: parsedIds.value } })
|
||||||
|
.populate("artists")
|
||||||
|
.populate("genre")
|
||||||
|
.exec()
|
||||||
|
})
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { Album } from "../../models/album"
|
||||||
|
import { requireAuthenticatedUser } from "../../utils/auth"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
await requireAuthenticatedUser(event)
|
||||||
|
|
||||||
|
return Album.find({}).populate("artists").populate("genre")
|
||||||
|
})
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { Artist } from "../../models/artist"
|
||||||
|
import { requireAuthenticatedUser } from "../../utils/auth"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
await requireAuthenticatedUser(event)
|
||||||
|
|
||||||
|
return Artist.find({})
|
||||||
|
})
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { Genre } from "../../models/genre"
|
||||||
|
import { requireAuthenticatedUser } from "../../utils/auth"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
await requireAuthenticatedUser(event)
|
||||||
|
|
||||||
|
return Genre.find({})
|
||||||
|
})
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { Track } from "../../models/track"
|
||||||
|
import { requireAuthenticatedUser } from "../../utils/auth"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
await requireAuthenticatedUser(event)
|
||||||
|
|
||||||
|
return Track.find({})
|
||||||
|
.populate({
|
||||||
|
path: "album",
|
||||||
|
select: "-artists",
|
||||||
|
populate: { path: "genre" },
|
||||||
|
})
|
||||||
|
.populate("artists")
|
||||||
|
})
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { getRouterParam } from "h3"
|
||||||
|
import { Artist } from "../../models/artist"
|
||||||
|
import { requireAuthenticatedUser } from "../../utils/auth"
|
||||||
|
import { parseMongoId, validationResponse } from "../../utils/api-validation"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
await requireAuthenticatedUser(event)
|
||||||
|
|
||||||
|
const parsedId = parseMongoId(getRouterParam(event, "id"))
|
||||||
|
if ("error" in parsedId) return validationResponse(event, parsedId.error)
|
||||||
|
|
||||||
|
return Artist.findOne({ _id: parsedId.value }).exec()
|
||||||
|
})
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { getQuery, getRouterParam } from "h3"
|
||||||
|
import { Album } from "../../../models/album"
|
||||||
|
import { requireAuthenticatedUser } from "../../../utils/auth"
|
||||||
|
import { parseMongoId, parseNumber, validationResponse } from "../../../utils/api-validation"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
await requireAuthenticatedUser(event)
|
||||||
|
|
||||||
|
const parsedId = parseMongoId(getRouterParam(event, "id"))
|
||||||
|
if ("error" in parsedId) return validationResponse(event, parsedId.error)
|
||||||
|
|
||||||
|
const query = getQuery(event)
|
||||||
|
const limit = parseNumber(query.limit, 9, 1)
|
||||||
|
if ("error" in limit) return validationResponse(event, limit.error)
|
||||||
|
const skip = parseNumber(query.skip, 0, 0)
|
||||||
|
if ("error" in skip) return validationResponse(event, skip.error)
|
||||||
|
|
||||||
|
return Album.find({ artists: parsedId.value })
|
||||||
|
.limit(limit.value || 9)
|
||||||
|
.skip(skip.value || 0)
|
||||||
|
.populate("genre")
|
||||||
|
.populate("artists")
|
||||||
|
.exec()
|
||||||
|
})
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { getQuery, getRouterParam } from "h3"
|
||||||
|
import { Track } from "../../../models/track"
|
||||||
|
import { requireAuthenticatedUser } from "../../../utils/auth"
|
||||||
|
import { parseMongoId, parseNumber, validationResponse } from "../../../utils/api-validation"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
await requireAuthenticatedUser(event)
|
||||||
|
|
||||||
|
const parsedId = parseMongoId(getRouterParam(event, "id"))
|
||||||
|
if ("error" in parsedId) return validationResponse(event, parsedId.error)
|
||||||
|
|
||||||
|
const query = getQuery(event)
|
||||||
|
const limit = parseNumber(query.limit, 9, 1)
|
||||||
|
if ("error" in limit) return validationResponse(event, limit.error)
|
||||||
|
const skip = parseNumber(query.skip, 0, 0)
|
||||||
|
if ("error" in skip) return validationResponse(event, skip.error)
|
||||||
|
|
||||||
|
return Track.find({ artists: parsedId.value })
|
||||||
|
.limit(limit.value || 9)
|
||||||
|
.skip(skip.value || 0)
|
||||||
|
.populate({ path: "album", select: "-artists" })
|
||||||
|
.populate("artists")
|
||||||
|
.exec()
|
||||||
|
})
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { getQuery } from "h3"
|
||||||
|
import { Artist } from "../../models/artist"
|
||||||
|
import { requireAuthenticatedUser } from "../../utils/auth"
|
||||||
|
import { parseMongoIds, validationResponse } from "../../utils/api-validation"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
await requireAuthenticatedUser(event)
|
||||||
|
|
||||||
|
const parsedIds = parseMongoIds(getQuery(event).ids)
|
||||||
|
if ("error" in parsedIds) return validationResponse(event, parsedIds.error)
|
||||||
|
|
||||||
|
return Artist.find({ _id: { $in: parsedIds.value } }).exec()
|
||||||
|
})
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import mongoose, { type PipelineStage } from "mongoose"
|
||||||
|
import { Album } from "../../../models/album"
|
||||||
|
import { parseMongoId, parseNumber, validationResponse } from "../../../utils/api-validation"
|
||||||
|
import { requireAuthenticatedUser } from "../../../utils/auth"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
await requireAuthenticatedUser(event)
|
||||||
|
|
||||||
|
const id = parseMongoId(getRouterParam(event, "id"))
|
||||||
|
if ("error" in id) return validationResponse(event, id.error)
|
||||||
|
|
||||||
|
const { limit: rawLimit } = getQuery(event)
|
||||||
|
const limit = parseNumber(rawLimit, 10, 1)
|
||||||
|
if ("error" in limit) return validationResponse(event, limit.error)
|
||||||
|
|
||||||
|
const pipeline: PipelineStage[] = [
|
||||||
|
{ $match: { genre: new mongoose.Types.ObjectId(id.value) } },
|
||||||
|
{ $sample: { size: limit.value || 10 } },
|
||||||
|
{
|
||||||
|
$lookup: {
|
||||||
|
from: "artists",
|
||||||
|
localField: "artists",
|
||||||
|
foreignField: "_id",
|
||||||
|
as: "artists",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ $unset: "genre" },
|
||||||
|
]
|
||||||
|
|
||||||
|
return Album.aggregate(pipeline)
|
||||||
|
})
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { Genre } from "../../models/genre"
|
||||||
|
import { parseNumber, validationResponse } from "../../utils/api-validation"
|
||||||
|
import { requireAuthenticatedUser } from "../../utils/auth"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
await requireAuthenticatedUser(event)
|
||||||
|
|
||||||
|
const { limit: rawLimit } = getQuery(event)
|
||||||
|
const limit = parseNumber(rawLimit, 10, 1)
|
||||||
|
if ("error" in limit) return validationResponse(event, limit.error)
|
||||||
|
|
||||||
|
const genres = await Genre.aggregate([{ $sample: { size: limit.value || 10 } }])
|
||||||
|
return genres
|
||||||
|
})
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import mongoose, { type PipelineStage } from "mongoose"
|
||||||
|
import { Track } from "../../../models/track"
|
||||||
|
import { parseMongoId, parseNumber, validationResponse } from "../../../utils/api-validation"
|
||||||
|
import { requireAuthenticatedUser } from "../../../utils/auth"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
await requireAuthenticatedUser(event)
|
||||||
|
|
||||||
|
const id = parseMongoId(getRouterParam(event, "id"))
|
||||||
|
if ("error" in id) return validationResponse(event, id.error)
|
||||||
|
|
||||||
|
const { limit: rawLimit } = getQuery(event)
|
||||||
|
const limit = parseNumber(rawLimit, 10, 1)
|
||||||
|
if ("error" in limit) return validationResponse(event, limit.error)
|
||||||
|
|
||||||
|
const pipeline: PipelineStage[] = [
|
||||||
|
{
|
||||||
|
$lookup: {
|
||||||
|
from: "albums",
|
||||||
|
localField: "album",
|
||||||
|
foreignField: "_id",
|
||||||
|
as: "album",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ $unwind: "$album" },
|
||||||
|
{
|
||||||
|
$lookup: {
|
||||||
|
from: "artists",
|
||||||
|
localField: "artists",
|
||||||
|
foreignField: "_id",
|
||||||
|
as: "artists",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ $match: { "album.genre": new mongoose.Types.ObjectId(id.value) } },
|
||||||
|
{ $sample: { size: limit.value || 10 } },
|
||||||
|
{ $unset: "album.artists" },
|
||||||
|
]
|
||||||
|
|
||||||
|
return Track.aggregate(pipeline)
|
||||||
|
})
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import Fuse from "fuse.js"
|
||||||
|
import { Album } from "../models/album"
|
||||||
|
import { Artist } from "../models/artist"
|
||||||
|
import { Track } from "../models/track"
|
||||||
|
import {
|
||||||
|
parseNumber,
|
||||||
|
parseQueryString,
|
||||||
|
validationResponse,
|
||||||
|
} from "../utils/api-validation"
|
||||||
|
import { requireAuthenticatedUser } from "../utils/auth"
|
||||||
|
|
||||||
|
const searchTypes = ["track", "album", "artist", "default"] as const
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
await requireAuthenticatedUser(event)
|
||||||
|
|
||||||
|
const query = getQuery(event)
|
||||||
|
const q = parseQueryString(query.q)
|
||||||
|
if ("error" in q) return validationResponse(event, q.error)
|
||||||
|
|
||||||
|
let type = "default"
|
||||||
|
if (query.type !== undefined) {
|
||||||
|
const parsedType = parseQueryString(query.type)
|
||||||
|
if ("error" in parsedType) return validationResponse(event, parsedType.error)
|
||||||
|
if (!searchTypes.includes(parsedType.value as (typeof searchTypes)[number])) {
|
||||||
|
return validationResponse(
|
||||||
|
event,
|
||||||
|
`Invalid enum value. Expected 'track' | 'album' | 'artist' | 'default', received '${parsedType.value}'`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
type = parsedType.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedLimit = parseNumber(query.limit, 9, 1)
|
||||||
|
if ("error" in parsedLimit) return validationResponse(event, parsedLimit.error)
|
||||||
|
const limit = parsedLimit.value
|
||||||
|
|
||||||
|
let results: { tracks: any[]; albums: any[]; artists: any[] } = {
|
||||||
|
tracks: [],
|
||||||
|
albums: [],
|
||||||
|
artists: [],
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "track" || type === "default") {
|
||||||
|
const rawTracks = await Track.find({})
|
||||||
|
.populate(["album", "artists"])
|
||||||
|
.lean()
|
||||||
|
|
||||||
|
const fuse = new Fuse(rawTracks, {
|
||||||
|
keys: ["name", "lyrics.text"],
|
||||||
|
threshold: 0.3,
|
||||||
|
distance: 100,
|
||||||
|
ignoreLocation: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
results.tracks = fuse.search(q.value).slice(0, limit).map((result) => result.item)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "album" || type === "default") {
|
||||||
|
const rawAlbums = await Album.find({})
|
||||||
|
.populate(["artists", "genre"])
|
||||||
|
.lean()
|
||||||
|
|
||||||
|
const fuse = new Fuse(rawAlbums, {
|
||||||
|
keys: ["name"],
|
||||||
|
threshold: 0.3,
|
||||||
|
distance: 100,
|
||||||
|
ignoreLocation: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
results.albums = fuse.search(q.value).slice(0, limit).map((result) => result.item)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "artist" || type === "default") {
|
||||||
|
const rawArtists = await Artist.find({}).lean()
|
||||||
|
|
||||||
|
const fuse = new Fuse(rawArtists, {
|
||||||
|
keys: ["name"],
|
||||||
|
threshold: 0.3,
|
||||||
|
distance: 100,
|
||||||
|
ignoreLocation: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
results.artists = fuse.search(q.value).slice(0, limit).map((result) => result.item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep the original API's split allocation, including when type selects one category.
|
||||||
|
results = {
|
||||||
|
tracks: results.tracks.slice(0, Math.ceil(limit / 3)),
|
||||||
|
albums: results.albums.slice(0, Math.floor(limit / 3)),
|
||||||
|
artists: results.artists.slice(0, Math.floor(limit / 3)),
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
})
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { getQuery } from "h3"
|
||||||
|
import { Track } from "../models/track"
|
||||||
|
import { requireAuthenticatedUser } from "../utils/auth"
|
||||||
|
import { parseMongoIds, validationResponse } from "../utils/api-validation"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
await requireAuthenticatedUser(event)
|
||||||
|
|
||||||
|
const parsedIds = parseMongoIds(getQuery(event).ids)
|
||||||
|
if ("error" in parsedIds) return validationResponse(event, parsedIds.error)
|
||||||
|
|
||||||
|
return Track.find({ _id: { $in: parsedIds.value } })
|
||||||
|
.populate({
|
||||||
|
path: "album",
|
||||||
|
select: "-artists",
|
||||||
|
populate: { path: "genre" },
|
||||||
|
})
|
||||||
|
.populate("artists")
|
||||||
|
.exec()
|
||||||
|
})
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { getRouterParam } from "h3"
|
||||||
|
import { Track } from "../../models/track"
|
||||||
|
import { requireAuthenticatedUser } from "../../utils/auth"
|
||||||
|
import { parseMongoId, validationResponse } from "../../utils/api-validation"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
await requireAuthenticatedUser(event)
|
||||||
|
|
||||||
|
const parsedId = parseMongoId(getRouterParam(event, "id"))
|
||||||
|
if ("error" in parsedId) return validationResponse(event, parsedId.error)
|
||||||
|
|
||||||
|
return Track.findOne({ _id: parsedId.value })
|
||||||
|
.populate({
|
||||||
|
path: "album",
|
||||||
|
select: "-artists",
|
||||||
|
populate: { path: "genre" },
|
||||||
|
})
|
||||||
|
.populate("artists")
|
||||||
|
.exec()
|
||||||
|
})
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { getRouterParam } from "h3"
|
||||||
|
import { Track } from "../../../models/track"
|
||||||
|
import { requireAuthenticatedUser } from "../../../utils/auth"
|
||||||
|
import { parseMongoId, validationResponse } from "../../../utils/api-validation"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
await requireAuthenticatedUser(event)
|
||||||
|
|
||||||
|
const parsedId = parseMongoId(getRouterParam(event, "id"))
|
||||||
|
if ("error" in parsedId) return validationResponse(event, parsedId.error)
|
||||||
|
|
||||||
|
const track = await Track.findOne({ _id: parsedId.value }).select("lyrics").exec()
|
||||||
|
if (!track) return {}
|
||||||
|
|
||||||
|
return track.lyrics
|
||||||
|
})
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { requireAuthenticatedUser } from "../../utils/auth"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
return await requireAuthenticatedUser(event)
|
||||||
|
})
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { User } from "../../models/user"
|
||||||
|
import { requireAuthenticatedUser } from "../../utils/auth"
|
||||||
|
import { setResponseStatus } from "h3"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const authenticatedUser = await requireAuthenticatedUser(event)
|
||||||
|
const user = await User.findById(authenticatedUser._id).populate("savedTracks")
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
setResponseStatus(event, 404)
|
||||||
|
return { message: "User not found" }
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.savedTracks
|
||||||
|
})
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { getQuery, setResponseStatus } from "h3"
|
||||||
|
import mongoose, { type ObjectId } from "mongoose"
|
||||||
|
import { User } from "../../models/user"
|
||||||
|
import { requireAuthenticatedUser } from "../../utils/auth"
|
||||||
|
import { parseMongoId, validationResponse } from "../../utils/api-validation"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const authenticatedUser = await requireAuthenticatedUser(event)
|
||||||
|
const parsedId = parseMongoId(getQuery(event).id)
|
||||||
|
if ("error" in parsedId) return validationResponse(event, parsedId.error)
|
||||||
|
|
||||||
|
const user = await User.findById(authenticatedUser._id)
|
||||||
|
if (!user) {
|
||||||
|
setResponseStatus(event, 404)
|
||||||
|
return { message: "User not found" }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve the legacy comparison behavior and empty-object response.
|
||||||
|
if ((user.savedTracks as ObjectId[]).includes(parsedId.value as unknown as ObjectId)) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
user.savedTracks.push(new mongoose.Types.ObjectId(parsedId.value))
|
||||||
|
await user.save()
|
||||||
|
return {}
|
||||||
|
})
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import mongoose, { Schema, type Model } from "mongoose"
|
||||||
|
|
||||||
|
const albumSchema = new Schema({
|
||||||
|
name: { type: String, required: true },
|
||||||
|
artists: {
|
||||||
|
type: [{ type: Schema.Types.ObjectId, ref: "Artist" }],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
file: { type: String, required: true },
|
||||||
|
genre: { type: Schema.Types.ObjectId, ref: "Genre", required: true },
|
||||||
|
})
|
||||||
|
|
||||||
|
export const Album =
|
||||||
|
(mongoose.models.Album as Model<any> | undefined) ??
|
||||||
|
(mongoose.model<any>("Album", albumSchema) as Model<any>)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import mongoose, { Schema, type Model } from "mongoose"
|
||||||
|
|
||||||
|
const artistSchema = new Schema({
|
||||||
|
name: { type: String, required: true },
|
||||||
|
file: { type: String, required: true },
|
||||||
|
})
|
||||||
|
|
||||||
|
export const Artist =
|
||||||
|
(mongoose.models.Artist as Model<any> | undefined) ??
|
||||||
|
(mongoose.model<any>("Artist", artistSchema) as Model<any>)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import mongoose, { Schema, type Model } from "mongoose"
|
||||||
|
|
||||||
|
const genreSchema = new Schema({
|
||||||
|
name: { type: String, required: true, unique: true },
|
||||||
|
})
|
||||||
|
|
||||||
|
export const Genre =
|
||||||
|
(mongoose.models.Genre as Model<any> | undefined) ??
|
||||||
|
(mongoose.model<any>("Genre", genreSchema) as Model<any>)
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import mongoose, { Schema, type Model } from "mongoose"
|
||||||
|
|
||||||
|
const trackSchema = new Schema({
|
||||||
|
name: { type: String, required: true },
|
||||||
|
album: { type: Schema.Types.ObjectId, ref: "Album", required: true },
|
||||||
|
artists: {
|
||||||
|
type: [{ type: Schema.Types.ObjectId, ref: "Artist" }],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
fileDir: { type: String, required: true },
|
||||||
|
durationInSeconds: { type: Number, required: true },
|
||||||
|
lyrics: {
|
||||||
|
type: {
|
||||||
|
synced: { type: Boolean, required: true },
|
||||||
|
text: { type: String, required: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const Track =
|
||||||
|
(mongoose.models.Track as Model<any> | undefined) ??
|
||||||
|
(mongoose.model<any>("Track", trackSchema) as Model<any>)
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import mongoose from "mongoose"
|
||||||
|
import { setResponseStatus, type H3Event } from "h3"
|
||||||
|
|
||||||
|
export type ParseResult<T> = { value: T } | { error: string }
|
||||||
|
|
||||||
|
export function parseMongoId(value: unknown): ParseResult<string> {
|
||||||
|
if (typeof value !== "string" || !mongoose.isValidObjectId(value)) {
|
||||||
|
return { error: "Invalid id" }
|
||||||
|
}
|
||||||
|
return { value }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseMongoIds(value: unknown): ParseResult<string[]> {
|
||||||
|
if (typeof value !== "string" && !Array.isArray(value)) {
|
||||||
|
return { error: "Required" }
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = Array.isArray(value) ? value : [value]
|
||||||
|
const ids: string[] = []
|
||||||
|
for (const item of values) {
|
||||||
|
const parsed = parseMongoId(item)
|
||||||
|
if ("error" in parsed) return parsed
|
||||||
|
ids.push(parsed.value)
|
||||||
|
}
|
||||||
|
return { value: ids }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseNumber(
|
||||||
|
value: unknown,
|
||||||
|
defaultValue: number,
|
||||||
|
minimum: number,
|
||||||
|
): ParseResult<number> {
|
||||||
|
if (value === undefined) return { value: defaultValue }
|
||||||
|
|
||||||
|
const parsed = Number(value)
|
||||||
|
if (Number.isNaN(parsed)) return { error: "Expected number, received nan" }
|
||||||
|
if (parsed < minimum) {
|
||||||
|
return { error: `Number must be greater than or equal to ${minimum}` }
|
||||||
|
}
|
||||||
|
return { value: parsed }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseQueryString(value: unknown): ParseResult<string> {
|
||||||
|
if (value === undefined) return { error: "Required" }
|
||||||
|
if (typeof value !== "string") return { error: "Expected string, received array" }
|
||||||
|
return { value }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validationResponse(event: H3Event, message: string) {
|
||||||
|
setResponseStatus(event, 400)
|
||||||
|
return { message }
|
||||||
|
}
|
||||||
Reference in new issue
Block a user