diff --git a/api/ani/v1/routes/series/edits.js b/api/ani/v1/routes/series/edits.js index 8f27201..aa5a551 100644 --- a/api/ani/v1/routes/series/edits.js +++ b/api/ani/v1/routes/series/edits.js @@ -55,33 +55,11 @@ module.exports = (app, router) => { ].forEach(field => edits.stringWrap(...field)); - const editStringList = async (name, bodyName, match) => { - const camelName = app.util.bodyCase(name); - router.use(`/:id/${name}`, app.auth.tokenPass, app.auth.permsPass('series-approve'), async (req, res, next) => { - if (!req.params.id) {return await res.status(400).send("Missing ID!");} - const series = await Anime.findOne({id: req.params.id.toLowerCase()}); - if (!editCheck(series, req, res)) {return;} - req.series = series; - req.listData = series[camelName]; - next(); - }, app.util.list(router, `/:id/${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.series[camelName] = list; - req.series.markModified(camelName); - await req.series.save(); - }, (req, res, next) => { - if (!req.authenticatedUser) {return res.status(401).send("You must be authenticated before you do that!");} - if (req.unauthorized) {return res.status(401).send("You are not authorized to edit that!");} - return next(); - })); - }; - [ //?STRING LIST FIELDS ['tags', 'tag', x => x.match(/^[a-z-]+$/) && x.length < 25], //TODO set list length limits ['genres', 'genre', x => x.match(/^[a-zA-Z- ]+$/) && x.length < 25], ['stream-at', 'location', x => x.match(/^[\w- ]+$/) && x.length < 40] //TODO doc cheeky field - ].forEach(x => editStringList(...x)); + ].forEach(x => edits.list(...x)); //it's just that shrimple diff --git a/api/ani/v1/routes/user.js b/api/ani/v1/routes/user.js index acce197..9177ff5 100644 --- a/api/ani/v1/routes/user.js +++ b/api/ani/v1/routes/user.js @@ -94,5 +94,5 @@ module.exports = (app, router) => { if (!req.authenticatedUser) {return res.status(401).send("You must be authenticated before you do that!");} if (req.unauthorized) {return res.status(401).send("You are not authorized to edit users!");} return next(); - })); + })); //TODO convert to util usage }; \ No newline at end of file diff --git a/api/util/editstring.js b/api/util/editstring.js index 53bf6d1..bfdf058 100644 --- a/api/util/editstring.js +++ b/api/util/editstring.js @@ -24,6 +24,28 @@ module.exports = { await this.string(req, res, name, match).catch(() => res.status(500).send(`There was an error trying to update your ${name}. Please try again.`)); }); return route + }, + + list: function (name, bodyName, match, forcePermission, deepRoute) { + const camelName = app.util.bodyCase(name); + router.use(`${routePrefix}/${name}`, app.auth.tokenPass, app.auth.permsPass(forcePermission || permission), async (req, res, next) => { + if (!req.params.id) {return await res.status(400).send("Missing ID!");} + const document = await model.findOne({id: req.params.id.toLowerCase()}); + if (!editCheck(document, req, res)) {return;} + req.document = document; + req.listData = 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); + await req.document.save(); + }, (req, res, next) => { + if (!req.authenticatedUser) {return res.status(401).send("You must be authenticated before you do that!");} + if (req.unauthorized) {return res.status(401).send("You are not authorized to edit that!");} + return next(); + })); } } } diff --git a/api/util/startup/cache/series.js b/api/util/startup/cache/series.js index d09dbdc..cbdd1b7 100644 --- a/api/util/startup/cache/series.js +++ b/api/util/startup/cache/series.js @@ -7,12 +7,12 @@ module.exports = async (app, spinner) => { const st = new Date().getTime(); let amount = 1; + app.cache.seriesQueue = []; for await (const series of Series.find()) { let {id, name, romaji, kanji, altNames, genres, tags} = series; app.cache.series[series.id] = {id, name, romaji, kanji, altNames, genres, tags}; //keep an in-memory index of series' searchable items app.cache.series[series.id].synopsis = series.synopsis.synopsis; - app.cache.seriesQueue = []; if (series.meta.submitted) {app.cache.seriesQueue.push({id: series.id, submitter: series.meta.submitted});} spinner.update({text: `${chalk.gray('[PROC]')} >> ${chalk.blueBright(`Cached`)} ${chalk.white(`${amount}`)} ${chalk.blueBright(`ani DB series.`)}`}); app.cache.seriesCount++; diff --git a/db/ani/series.js b/db/ani/series.js index c420f50..8be7036 100644 --- a/db/ani/series.js +++ b/db/ani/series.js @@ -1,7 +1,7 @@ const {Schema} = require("mongoose"); module.exports = (connection) => connection.model('series', new Schema({ - id: {type: String, unique: true, required: true, maxLength: 25}, //!REQ + id: {type: String, unique: true, required: true, maxLength: 25, lowercase: true}, //!REQ numericalId: {type: Number, unique: true, required: true, min: 1}, //!REQ meta: { locked: {type: Boolean, default: false}, @@ -33,7 +33,7 @@ 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}]}, + tags: {default: [], type: [{type: String, maxLength: 25}], lowercase: true}, nsfw: {type: Boolean, default: false}, nsfwReason: {type: String, default: null}, //gore, language, nudity, strong themes @@ -45,7 +45,11 @@ module.exports = (connection) => connection.model('series', new Schema({ 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: Object, default: {}}, //streaming services, other databases, etc. //TODO externalLinks + externalLinks: { + type: [{ + + }], default: {} + }, //streaming services, other databases, etc. //TODO externalLinks officialSite: {type: String, default: null}, videos: {type: Object, default: {}}, //OPs, EDs, trailers, etc. @@ -61,20 +65,21 @@ module.exports = (connection) => connection.model('series', new Schema({ ratings: {type: [{ user: String, rating: Number - }], default: []}, //all ratings mapped by user + }], 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 likes: {type: Number, default: 0}, //no need to map by user here reviews: {type: [{ //full review vs rating - user: String, - ratings: { - plot: Number, - characters: Number, - soundtrack: Number, - animation: Number - }, - comments: {type: String, maxLength: 2000} - }], default: []}, + user: String, + ratings: { + plot: Number, + characters: Number, + soundtrack: Number, + animation: Number + }, + comments: {type: String, maxLength: 2000} + }], default: [] + }, /** * !API-DEPENDENT FIELDS