master
Kit Kasune 2 years ago
parent db5dcd953a
commit 685eec13b6
  1. 5
      .idea/.gitignore
  2. 7
      .idea/discord.xml
  3. 8
      .idea/modules.xml
  4. 12
      .idea/natsuki-api.iml
  5. 6
      .idea/vcs.xml
  6. 164
      api/ani/v1/routes/series/add.js
  7. 22
      db/ani/series.js
  8. 3
      package.json

5
.idea/.gitignore vendored

@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/natsuki-api.iml" filepath="$PROJECT_DIR$/.idea/natsuki-api.iml" />
</modules>
</component>
</project>

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -1,67 +1,117 @@
// how many hours have i spent banging my head at the wall now?
module.exports = (app, router) => { module.exports = (app, router) => {
const Anime = app.db.models.ani.series; const Anime = app.db.models.ani.series;
router.route('/:id') router.route('/:id')
.post(app.auth.token, app.auth.perms('series-submit'), app.auth.permsPass('series-approve'), async (req, res) => { .post(app.auth.token, app.auth.perms('series-submit'), app.auth.permsPass('series-approve'), async (req, res) => {
let submitting = req.unauthorized; //if user doesn't have series-approve, they still have perms to submit by this point let submitting = req.unauthorized; //if user doesn't have series-approve, they still have perms to submit by this point
/**REQUIRED ITEMS /**REQUIRED ITEMS
* *
*!Submissions must include a name and id (romaji included) *!Submissions must include a name and id (romaji included)
* Only altNames, tags, and streaming locations can be omitted * Only altNames, tags, and streaming locations can be omitted
* *
* If a request has submissible criteria but not completable * If a request has submissible criteria but not completable
* criteria, it will be treated as a submission even if the user * criteria, it will be treated as a submission even if the user
* is authorized to approve series. * is authorized to approve series.
*/ */
//TODO submit anyways if incomplete but user has approval permissions //TODO submit anyways if incomplete but user has approval permissions
//TODO un-submitted (incomplete) but curated route //TODO un-submitted (incomplete) but curated route
if (!req.params.id) {return res.status(400).send("You didn't include an anime ID in your request!");} if (!req.params.id) {return res.status(400).send("You didn't include an anime ID in your request!");}
if (await Anime.findOne({id: req.params.id})) {return res.status(400).send("An anime already exists with that ID!");} if (await Anime.findOne({id: req.params.id})) {return res.status(400).send("An anime already exists with that ID!");}
if ( if (
!req.body //i just ate dinner and i can't even think straight !req.body //i just ate dinner and i can't even think straight
|| !req.body.name || !req.body.romaji || !req.params.id || !req.authenticatedUser || !req.authenticatedUser.id || !req.body.name || !req.body.romaji || !req.params.id || !req.authenticatedUser || !req.authenticatedUser.id
|| !req.body.name.match(/^[\w_\- ]+$/gm) || req.body.name.length > 150 || !req.body.name.match(/^[\w_\- ]+$/gm) || req.body.name.length > 150
|| req.body.romaji.length > 150 || req.body.romaji.length > 150
|| !req.params.id.match(/^[\w_\-]+$/gm) || req.params.id.length > 25 || !req.params.id.match(/^[\w_\-]+$/gm) || req.params.id.length > 25
) {return res.status(400).send("The server cannot accept your request as your body is missing fields or is malformed. Ensure fields aren't too long and that they don't contain illegal characters.");} ) {return res.status(400).send("The server cannot accept your request as your body is missing fields or is malformed. Ensure fields aren't too long and that they don't contain illegal characters.");}
let series = new Anime({ let series = new Anime({
id: req.params.id, id: req.params.id,
numericalId: app.cache.seriesCount + 1, numericalId: app.cache.seriesCount + 1,
name: req.body.name, name: req.body.name,
romaji: req.body.romaji, romaji: req.body.romaji,
synopsis: { synopsis: {
by: req.body.synopsis ? req.authenticatedUser.id : null, by: req.body.synopsis ? req.authenticatedUser.id : null,
synopsis: req.body.synopsis || "A synopsis is not yet available for this series..." synopsis: req.body.synopsis || "A synopsis is not yet available for this series..."
}, },
meta: { meta: {
submitted: submitting ? req.authenticatedUser.id : false, //TODO make sure to update submitted status submitted: submitting ? req.authenticatedUser.id : false, //TODO make sure to update submitted status
creator: req.authenticatedUser.id, creator: req.authenticatedUser.id,
edits: [{ edits: [{
user: req.authenticatedUser.id, user: req.authenticatedUser.id,
action: 'Submitted series', action: 'Submitted series',
timestamp: new Date().getTime() timestamp: new Date().getTime()
}] }]
}, },
genres: req.body.genres && Array.isArray(req.body.genres) && req.body.genres.length ? req.body.genres : [] genres: req.body.genres && Array.isArray(req.body.genres) && req.body.genres.length ? req.body.genres : []
}); });
if (!req.body.synopsis || !req.body.genres || !Array.isArray(req.body.genres) || !req.body.genres.length) {series.meta.submitted = req.authenticatedUser.id;} if (!req.body.synopsis || !req.body.genres || !Array.isArray(req.body.genres) || !req.body.genres.length) {series.meta.submitted = req.authenticatedUser.id;}
return series.save().then(async () => {
app.cache.series[series.id] = { //VALIDATION
id: series.id,
name: series.name, const badReq = msg => {res.status(400).send(msg); return null;};
romaji: series.romaji, const validateStringList = (maxLength, list, listName, regex) => {
kanji: series.kanji, let bad = false;
altNames: series.altNames, list.forEach(item => {bad = !bad && typeof item === 'string' && item.length < maxLength && (!regex || item.match(regex))});
genres: series.genres, if (bad) {return badReq();}
tags: series.tags return list;
}; };
return res.send(`Your series was successfully ${series.meta.submitted ? 'submitted' : "added"}.`); let options = req.body;
}).catch((e) => {console.error(e); res.status(500).send("There was an error trying to process your request. It's likely that our database found something wrong with your body fields, and the server didn't realize. Check your request and try again.");}); if (options.altNames && Array.isArray(options.altNames) && options.altNames.length <= 10) {
//TODO remove console error let bad = false;
}); options.altNames.forEach(name => bad = !bad && typeof name === 'string' && name.length < 150);
if (bad) {return badReq("Your altNames did not contain purely strings, or one of them was too long.");}
series.altNames = options.altNames;
}
if (options.tags && Array.isArray(options.tags) && options.tags.length <= 10) {
let bad = false;
options.tags.forEach(tag => bad = !bad && typeof tag === 'string' && tag.length < 25 && tag.match(/^[\w-]+$/gm));
if (bad) {return badReq("Your tags did not contain purely strings, or one of them was too long, or contained invalud characters");}
series.tags = options.tags.map(tag => tag.toLowerCase());
}
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;
series.nsfwReason = options.nsfwReason;
}
if (options.genres && Array.isArray(options.genres) && options.genres.length <= 10) {
let bad = false;
options.genres.forEach(tag => bad = !bad && typeof tag === 'string' && tag.length < 25 && tag.match(/^[\w- ]+$/gm));
if (bad) {return badReq("Your genres did not contain purely strings, or one of them was too long.");}
series.genres = options.genres;
} //TODO genres as DB
if (options.streamAt && Array.isArray(options.streamAt) && options.streamAt.length <= 10) {
let bad = false;
options.streamAt.forEach(tag => bad = !bad && typeof tag === 'string' && tag.length < 25 && tag.match(/^[\w- ]+$/gm));
if (bad) {return badReq("Your streamAt locations did not contain purely strings, or one of them was too long.");}
series.streamAt = options.streamAt;
}
if (options.publishers && Array.isArray(options.publishers) && options.publishers.length <= 10) {
let bad = false;
options.publishers.forEach(tag => bad = !bad && typeof tag === 'string' && tag.length < 25 && tag.match(/^[\w- ]+$/gm));
if (bad) {return badReq("Your publishers did not contain purely strings, or one of them was too long.");}
series.publishers = options.publishers;
}
return series.save().then(async () => {
app.cache.series[series.id] = {
id: series.id,
name: series.name,
romaji: series.romaji,
kanji: series.kanji,
altNames: series.altNames,
genres: series.genres,
tags: series.tags
};
return res.send(`Your series was successfully ${series.meta.submitted ? 'submitted' : "added"}.`);
}).catch((e) => {console.error(e); res.status(500).send("There was an error trying to process your request. It's likely that our database found something wrong with your body fields, and the server didn't realize. Check your request and try again.");});
//TODO remove console error
});
}; };

@ -14,7 +14,7 @@ module.exports = (connection) => connection.model('series', new Schema({
completed: {type: Boolean, default: false}, //SUBMISSION completed completed: {type: Boolean, default: false}, //SUBMISSION completed
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>}
submitted: Schema.Types.Mixed, //can be false or a string with the ID of the submitter, //!REQ submitted: Schema.Types.Mixed, //can be false or a string with the ID of the submitter, //!REQ
hidden: {type: Boolean, defualt: false}, hidden: {type: Boolean, default: false},
reviewFlags: {type: [{ reviewFlags: {type: [{
by: String, by: String,
reason: String, reason: String,
@ -24,28 +24,28 @@ module.exports = (connection) => connection.model('series', new Schema({
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: [String], 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},
by: String //uid by: String //uid
}, required: true}, //if not present, use "Synopsis not available yet" //!REQ }, required: true}, //if not present, use "Synopsis not available yet" //!REQ
genres: {type: [String], required: true}, //!REQ genres: {type: [{type: String, maxLength: 25}], required: true}, //!REQ
//TODOdatabase for genres or cache //TODO database for genres or cache
tags: {default: [], type: [String]}, tags: {default: [], type: [{type: String, maxLength: 25}]},
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: [String], default: []}, streamAt: {type: [{type: String, maxLength: 25}], default: []},
publishers: {type: [String], default: []}, publishers: {type: [{type: String, maxLength: 50}], default: []},
studios: {type: [String], 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, defualt: 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: {type: Object, default: {}}, //streaming services, other databases, etc. //TODO externalLinks externalLinks: {type: Object, default: {}}, //streaming services, other databases, etc. //TODO externalLinks
officialSite: {type: String, defualt: null}, officialSite: {type: String, default: null},
videos: {type: Object, default: {}}, //OPs, EDs, trailers, etc. videos: {type: Object, default: {}}, //OPs, EDs, trailers, etc.
art: { art: {
@ -72,7 +72,7 @@ module.exports = (connection) => connection.model('series', new Schema({
soundtrack: Number, soundtrack: Number,
animation: Number animation: Number
}, },
comments: String comments: {type: String, maxLength: 2000}
}], default: []}, }], default: []},
/** /**

@ -18,7 +18,8 @@
"mongoose": "^6.8.4" "mongoose": "^6.8.4"
}, },
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "start": "cd api && node index",
"test": "node test"
}, },
"keywords": [], "keywords": [],
"author": "wubzygd", "author": "wubzygd",

Loading…
Cancel
Save