Compare commits

..

No commits in common. '33b07db58d3f3741b4aec518f1df404354dec068' and 'a468f94493cf7957da1bdb426bc061866d713bab' have entirely different histories.

  1. 16
      api/ani/v1/routes/series/edits.js
  2. 6
      api/util/editstring.js
  3. 57
      db/ani/series.js

@ -41,13 +41,10 @@ module.exports = (app, router) => {
} }
catch {return res.status(500).send("There was an error trying to update your synopsis. Please try again.");} catch {return res.status(500).send("There was an error trying to update your synopsis. Please try again.");}
}) })
.get(app.auth.tokenPass, app.auth.permsPass('series-approve'), async (req, res) => { .get(app.auth.tokenPass, app.auth.permsPass('series-approve'), async (req, res) => { //working
const series = await Anime.findOne({id: req.params.id.toLowerCase()}); const series = await Anime.findOne({id: req.params.id.toLowerCase()});
if (!series || (series && !series.meta.completed && req.unauthorized)) {return res.status(400).send("A series with that ID doesn't exist!");} if (!series || (series && !series.meta.completed && req.unauthorized)) {return res.status(400).send("A series with that ID doesn't exist!");}
return res.send({synopsis: series.synopsis.synopsis, by: series.synopsis.by}); return res.send({synopsis: series.synopsis.synopsis, by: series.synopsis.by});
})
.get(app.auth.tokenPass, app.auth.permsPass('series-approve'), async (req, res) => {
}); });
[ //?SINGLE STRING FIELDS [ //?SINGLE STRING FIELDS
@ -61,14 +58,11 @@ module.exports = (app, router) => {
[ //?STRING LIST FIELDS [ //?STRING LIST FIELDS
['tags', 'tag', x => x.match(/^[a-z-]+$/) && x.length < 25], //TODO set list length limits ['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], ['genres', 'genre', x => x.match(/^[a-zA-Z- ]+$/) && x.length < 25],
['stream-at', 'location', x => x.match(/^[\w- ]+$/) && x.length < 25], //TODO doc cheeky field ['stream-at', 'location', x => x.match(/^[\w- ]+$/) && x.length < 40] //TODO doc cheeky field
['alt-names', 'name', x => x.match(/^[\w\-!?.:; ]+$/gm) && x.length < 150],
['publishers', 'publisher', x => x.match(/^[\w\-!?.:; ]+$/gm) && x.length < 150],
['studios', 'studio', x => x.match(/^[\w\-!?.:; ]+$/gm) && x.length < 150]
].forEach(x => edits.list(...x)); //it's just that shrimple ].forEach(x => edits.list(...x)); //it's just that shrimple
//air, completed, nsfw, externalLinks, videos, art, ratings, rating, watchers, likes, reviews, seasons, characters, related
//dependent fields can be modified string lists into number lists; seasons stored purely by numerical ID
// /ani/v1/series/code-geass --> /ep-1 /episode-1 /ep1 /1 /e1 -> seasons/1/episodes/1/ //router.use('/:id/altnames', app.auth.tokenPass, app.auth.permsPass('series-approve'))
}; };

@ -33,13 +33,13 @@ module.exports = {
const document = await model.findOne({id: req.params.id.toLowerCase()}); const document = await model.findOne({id: req.params.id.toLowerCase()});
if (!editCheck(document, req, res)) {return;} if (!editCheck(document, req, res)) {return;}
req.document = document; req.document = document;
req.listData = deepRoute ? document[deepRoute][camelName] : document[camelName]; req.listData = document[camelName];
next(); next();
}, app.util.list(router, `${routePrefix}/${name}`, true, bodyName, async (req, res, list, item) => { }, 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 && !match(item)) {res.status(400).send(`The ${camelName} you provided is invalid.`); return 0;}
if (item) {list[list.length - 1] = item;} if (item) {list[list.length - 1] = item;}
if (deepRoute) {req.document[deepRoute][camelName] = list} else {req.document[camelName] = list;} req.document[camelName] = list;
req.document.markModified(deepRoute ? `${deepRoute}.${camelName}` : camelName); req.document.markModified(camelName);
await req.document.save(); await req.document.save();
}, (req, res, next) => { }, (req, res, next) => {
if (!req.authenticatedUser) {return res.status(401).send("You must be authenticated before you do that!");} if (!req.authenticatedUser) {return res.status(401).send("You must be authenticated before you do that!");}

@ -10,7 +10,7 @@ module.exports = (connection) => connection.model('series', new Schema({
user: String, //uid user: String, //uid
timestamp: String, //Date.getTime(), timestamp: String, //Date.getTime(),
action: String action: String
}], default: () => ([])}, }], default: []},
completed: {type: Boolean, default: false}, //SUBMISSION completed 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 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: <uid>} approved: {type: Schema.Types.Mixed, default: false}, //boolean or {approved: Boolean, by: <uid>}
@ -19,13 +19,13 @@ module.exports = (connection) => connection.model('series', new Schema({
reviewFlags: {type: [{ reviewFlags: {type: [{
by: String, by: String,
reason: 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 }, //!REQ
name: {type: String, required: true, maxLength: 150}, //!REQ name: {type: String, required: true, maxLength: 150}, //!REQ
romaji: {type: String, required: true, maxLength: 150}, //!REQ romaji: {type: String, required: true, maxLength: 150}, //!REQ
kanji: {type: String, maxLength: 150, default: null}, 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: {
synopsis: {type: String, required: true, maxLength: 1000}, synopsis: {type: String, required: true, maxLength: 1000},
@ -33,40 +33,33 @@ module.exports = (connection) => connection.model('series', new Schema({
}, required: true}, //if not present, use "Synopsis not available yet" //!REQ }, required: true}, //if not present, use "Synopsis not available yet" //!REQ
genres: {type: [{type: String, maxLength: 25}], required: true}, //!REQ genres: {type: [{type: String, maxLength: 25}], required: true}, //!REQ
//TODO database for genres or cache //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}, nsfw: {type: Boolean, default: false},
nsfwReason: {type: String, default: null}, //gore, language, nudity, strong themes nsfwReason: {type: String, default: null}, //gore, language, nudity, strong themes
completed: {type: Boolean, default: false}, //SERIES completed completed: {type: Boolean, default: false}, //SERIES completed
streamAt: {type: [{type: String, maxLength: 25}], default: () => ([])}, streamAt: {type: [{type: String, maxLength: 25}], default: []},
publishers: {type: [{type: String, maxLength: 50}], default: () => ([])}, publishers: {type: [{type: String, maxLength: 50}], default: []},
studios: {type: [{type: String, maxLength: 50}], default: () => ([])}, studios: {type: [{type: String, maxLength: 50}], default: []},
air: { air: {
from: {type: String, default: null}, //absence of start date means anime is confirmed but not released //TODO special handling for unstarted series 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 to: {type: String, default: null} //null indicates still airing; completed: true + non-null "to" value means series is waiting on another season
}, },
externalLinks: { externalLinks: {
type: [{ type: [{
name: {type: String, maxLength: 100},
url: {type: String, maxLength: 300} }], default: {}
}], default: () => ([])
}, //streaming services, other databases, etc. //TODO externalLinks }, //streaming services, other databases, etc. //TODO externalLinks
officialSite: {type: String, default: null}, officialSite: {type: String, default: null},
videos: { videos: {type: Object, default: {}}, //OPs, EDs, trailers, etc.
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: { art: {
cover: {type: [String], default: () => ([])}, //first item of any list is default item cover: {type: [String], default: []}, //first item of any list is default item
icon: {type: [String], default: () => ([])}, //small 128x128 icon icon: {type: [String], default: []}, //small 128x128 icon
banner: {type: [String], default: () => ([])}, banner: {type: [String], default: []},
poster: {type: [String], default: () => ([])}, //used for BGs, portrait 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 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. display: {type: [String], default: []}, //used for some content BGs, landscape standard desktop res.
}, },
ratings: {type: [{ ratings: {type: [{
@ -74,18 +67,18 @@ module.exports = (connection) => connection.model('series', new Schema({
rating: Number 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. 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 likes: {type: Number, default: 0}, //no need to map by user here
reviews: {type: [{ //full review vs rating reviews: {type: [{ //full review vs rating
user: String, user: String,
ratings: { ratings: {
plot: {type: Number, min: 1, max: 5, required: true}, plot: Number,
characters: {type: Number, min: 1, max: 5, required: true}, characters: Number,
soundtrack: {type: Number, min: 1, max: 5, required: true}, soundtrack: Number,
animation: {type: Number, min: 1, max: 5, required: true} animation: Number
}, },
comments: {type: String, maxLength: 2000} comments: {type: String, maxLength: 2000}
}], default: () => ([]) }], default: []
}, },
/** /**
@ -95,7 +88,7 @@ module.exports = (connection) => connection.model('series', new Schema({
* Stores only IDs and barebones data, requires clients to fetch their contents * Stores only IDs and barebones data, requires clients to fetch their contents
*/ */
seasons: {type: [String], default: () => ([])}, seasons: {type: [String], default: []},
characters: {type: [String], default: () => ([])}, characters: {type: [String], default: []},
related: {type: [String], default: () => ([])} //TODO this shit even necessary? related: {type: [String], default: []} //TODO this shit even necessary?
})); }));
Loading…
Cancel
Save