diff --git a/api/ani/v1/routes/series/add.js b/api/ani/v1/routes/series/add.js index 6c4edef..3c809aa 100644 --- a/api/ani/v1/routes/series/add.js +++ b/api/ani/v1/routes/series/add.js @@ -67,7 +67,7 @@ module.exports = (app, router) => { }; let options = req.body; if (options.altNames) {series.altNames = validateStringList(150, options.altNames, 'altNames'); if (!series.altNames) {return;}} - if (options.tags) {series.tags = validateStringList(25, options.tags, 'tags', 25, /^[\w-]+$/gm);if (!series.tags) {return;}} + if (options.tags) {series.tags = validateStringList(25, options.tags, 'tags', 25, /^[\w-]+$/gm); if (!series.tags) {return;}} if (options.nsfw === true && !(options.nsfwReason || ['gore', 'language', 'nudity', 'themes'].includes(options.nsfwReason))) {return badReq("You marked this series as nsfw, but did not provide a reason.");} else if (options.nsfw) { series.nsfw = options.nsfw; diff --git a/api/util/editstring.js b/api/util/editstring.js index bfdf058..d8cadba 100644 --- a/api/util/editstring.js +++ b/api/util/editstring.js @@ -33,13 +33,13 @@ module.exports = { const document = await model.findOne({id: req.params.id.toLowerCase()}); if (!editCheck(document, req, res)) {return;} req.document = document; - req.listData = document[camelName]; + req.listData = deepRoute ? document[deepRoute][camelName] : document[camelName]; next(); }, app.util.list(router, `${routePrefix}/${name}`, true, bodyName, async (req, res, list, item) => { if (item && !match(item)) {res.status(400).send(`The ${camelName} you provided is invalid.`); return 0;} if (item) {list[list.length - 1] = item;} - req.document[camelName] = list; - req.document.markModified(camelName); + if (deepRoute) {req.document[deepRoute][camelName] = list} else {req.document[camelName] = list;} + req.document.markModified(deepRoute ? `${deepRoute}.${camelName}` : camelName); await req.document.save(); }, (req, res, next) => { if (!req.authenticatedUser) {return res.status(401).send("You must be authenticated before you do that!");} diff --git a/db/ani/series.js b/db/ani/series.js index 8be7036..3184955 100644 --- a/db/ani/series.js +++ b/db/ani/series.js @@ -10,7 +10,7 @@ module.exports = (connection) => connection.model('series', new Schema({ user: String, //uid timestamp: String, //Date.getTime(), action: String - }], default: []}, + }], default: () => ([])}, completed: {type: Boolean, default: false}, //SUBMISSION completed viewable: {type: Boolean, default: false}, // exists solely as completion override. WILL NOT make a completed series invisible if false approved: {type: Schema.Types.Mixed, default: false}, //boolean or {approved: Boolean, by: } @@ -19,13 +19,13 @@ module.exports = (connection) => connection.model('series', new Schema({ reviewFlags: {type: [{ by: String, reason: String, - }], default: []} // notes left by an admin or curator about work that needs to be done on a series for it to be approved + }], default: () => ([])} // notes left by an admin or curator about work that needs to be done on a series for it to be approved }, //!REQ name: {type: String, required: true, maxLength: 150}, //!REQ romaji: {type: String, required: true, maxLength: 150}, //!REQ kanji: {type: String, maxLength: 150, default: null}, - altNames: {type: [{type: String, maxLength: 150}], default: []}, + altNames: {type: [{type: String, maxLength: 150}], default: () => ([])}, synopsis: {type: { synopsis: {type: String, required: true, maxLength: 1000}, @@ -33,33 +33,40 @@ module.exports = (connection) => connection.model('series', new Schema({ }, required: true}, //if not present, use "Synopsis not available yet" //!REQ genres: {type: [{type: String, maxLength: 25}], required: true}, //!REQ //TODO database for genres or cache - tags: {default: [], type: [{type: String, maxLength: 25}], lowercase: true}, + tags: {default: () => ([]), type: [{type: String, maxLength: 25}]}, nsfw: {type: Boolean, default: false}, nsfwReason: {type: String, default: null}, //gore, language, nudity, strong themes completed: {type: Boolean, default: false}, //SERIES completed - streamAt: {type: [{type: String, maxLength: 25}], default: []}, - publishers: {type: [{type: String, maxLength: 50}], default: []}, - studios: {type: [{type: String, maxLength: 50}], default: []}, + streamAt: {type: [{type: String, maxLength: 25}], default: () => ([])}, + publishers: {type: [{type: String, maxLength: 50}], default: () => ([])}, + studios: {type: [{type: String, maxLength: 50}], default: () => ([])}, air: { from: {type: String, default: null}, //absence of start date means anime is confirmed but not released //TODO special handling for unstarted series to: {type: String, default: null} //null indicates still airing; completed: true + non-null "to" value means series is waiting on another season }, externalLinks: { type: [{ - - }], default: {} + name: {type: String, maxLength: 100}, + url: {type: String, maxLength: 300} + }], default: () => ([]) }, //streaming services, other databases, etc. //TODO externalLinks officialSite: {type: String, default: null}, - videos: {type: Object, default: {}}, //OPs, EDs, trailers, etc. + videos: { + type: [{ + title: {type: String, maxLength: 100}, // Colors - Flow + subtitle: {type: String, maxLength: 100}, // Code Geass OP 1 (Season 1), + url: {type: String, maxLength: 500} // https://youtube.com/watch?v=... + }], default: () => ([]) + }, //OPs, EDs, trailers, etc. art: { - cover: {type: [String], default: []}, //first item of any list is default item - icon: {type: [String], default: []}, //small 128x128 icon - banner: {type: [String], default: []}, - poster: {type: [String], default: []}, //used for BGs, portrait - widePoster: {type: [String], default: []}, //used for desktop, client should format banners or displays at its discretion if unavailable - display: {type: [String], default: []}, //used for some content BGs, landscape standard desktop res. + cover: {type: [String], default: () => ([])}, //first item of any list is default item + icon: {type: [String], default: () => ([])}, //small 128x128 icon + banner: {type: [String], default: () => ([])}, + poster: {type: [String], default: () => ([])}, //used for BGs, portrait + widePoster: {type: [String], default: () => ([])}, //used for desktop, client should format banners or displays at its discretion if unavailable + display: {type: [String], default: () => ([])}, //used for some content BGs, landscape standard desktop res. }, ratings: {type: [{ @@ -67,7 +74,7 @@ module.exports = (connection) => connection.model('series', new Schema({ rating: Number }], default: () => ([])}, //all ratings mapped by user rating: {type: Number, default: 0}, //automatic collection of user ratings for avg. - watchers: {type: [String], default: []}, //people with this anime on their watching and/or watchlists + watchers: {type: [String], default: () => ([])}, //people with this anime on their watching and/or watchlists likes: {type: Number, default: 0}, //no need to map by user here reviews: {type: [{ //full review vs rating user: String, @@ -78,7 +85,7 @@ module.exports = (connection) => connection.model('series', new Schema({ animation: Number }, comments: {type: String, maxLength: 2000} - }], default: [] + }], default: () => ([]) }, /** @@ -88,7 +95,7 @@ module.exports = (connection) => connection.model('series', new Schema({ * Stores only IDs and barebones data, requires clients to fetch their contents */ - seasons: {type: [String], default: []}, - characters: {type: [String], default: []}, - related: {type: [String], default: []} //TODO this shit even necessary? + seasons: {type: [String], default: () => ([])}, + characters: {type: [String], default: () => ([])}, + related: {type: [String], default: () => ([])} //TODO this shit even necessary? })); \ No newline at end of file