Compare commits

..

No commits in common. 'db5dcd953ae45057f21568629037848c1ada3a25' and '6fe11be108815e7e6243d2862caa0cc490e3020d' have entirely different histories.

  1. 3
      .gitignore
  2. 12
      api/ani/v1/routes/series.js
  3. 67
      api/ani/v1/routes/series/add.js
  4. 9
      api/util/startup/cache.js
  5. 26
      api/util/startup/cache/series.js
  6. 91
      db/ani/series.js
  7. 1
      package.json

3
.gitignore vendored

@ -1,4 +1,3 @@
auth.json auth.json
package-lock.json package-lock.json
node_modules/ node_modules/
/test.js

@ -3,19 +3,13 @@ const fs = require('fs');
module.exports = (app, parentRouter) => { module.exports = (app, parentRouter) => {
const router = Router() const router = Router()
router.use('/', (req, res, next) => { .get('/', (req, res) => res.send('/series/:id'));
req.listData = Object.keys(app.cache.series).map(series => {return {
name: app.cache.series[series].name,
romaji: app.cache.series[series].romaji,
kanji: app.cache.series[series].kanji
}}
);
return next();
}, app.util.list(router, '')); //list all anime {id: {name: "", romaji: "", kanji: ""}}
router.location = '/series'; router.location = '/series';
fs.readdirSync('./ani/v1/routes/series').filter(file => file.endsWith('.js')) fs.readdirSync('./ani/v1/routes/series').filter(file => file.endsWith('.js'))
.forEach(route => require(`./series/${route}`)(app, router)); //execute all router functions .forEach(route => require(`./series/${route}`)(app, router)); //execute all router functions
parentRouter.use('/series', router); parentRouter.use('/series', router);
//TODO get all series
}; };

@ -1,67 +0,0 @@
module.exports = (app, router) => {
const Anime = app.db.models.ani.series;
router.route('/:id')
.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
/**REQUIRED ITEMS
*
*!Submissions must include a name and id (romaji included)
* Only altNames, tags, and streaming locations can be omitted
*
* If a request has submissible criteria but not completable
* criteria, it will be treated as a submission even if the user
* is authorized to approve series.
*/
//TODO submit anyways if incomplete but user has approval permissions
//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 (await Anime.findOne({id: req.params.id})) {return res.status(400).send("An anime already exists with that ID!");}
if (
!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.match(/^[\w_\- ]+$/gm) || req.body.name.length > 150
|| req.body.romaji.length > 150
|| !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.");}
let series = new Anime({
id: req.params.id,
numericalId: app.cache.seriesCount + 1,
name: req.body.name,
romaji: req.body.romaji,
synopsis: {
by: req.body.synopsis ? req.authenticatedUser.id : null,
synopsis: req.body.synopsis || "A synopsis is not yet available for this series..."
},
meta: {
submitted: submitting ? req.authenticatedUser.id : false, //TODO make sure to update submitted status
creator: req.authenticatedUser.id,
edits: [{
user: req.authenticatedUser.id,
action: 'Submitted series',
timestamp: new Date().getTime()
}]
},
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;}
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
});
};

@ -4,21 +4,16 @@ const chalk = require('chalk');
module.exports = async app => { module.exports = async app => {
app.cache = { app.cache = {
users: {}, users: {}
series: {},
seriesCount: 0
}; };
return new Promise(async resolve => { return new Promise(async resolve => {
const loaders = []; const loaders = [];
const spin = new spinnies(); const spin = new spinnies();
let userCache = spin.add("user", {text: "Caching Users..."}); let userCache = spin.add("ar", {text: "Caching Users..."});
loaders.push(require('./cache/users')(app, userCache)); loaders.push(require('./cache/users')(app, userCache));
let seriesCache = spin.add("series", {text: "Caching Series..."});
loaders.push(require('./cache/series')(app, seriesCache));
await Promise.all(loaders); await Promise.all(loaders);
console.log(''); console.log('');
resolve(0); resolve(0);

@ -1,26 +0,0 @@
const chalk = require('chalk');
module.exports = async (app, spinner) => {
const Series = app.db.models.ani.series;
return new Promise(async resolve => {
const st = new Date().getTime();
let amount = 1;
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;
spinner.update({text: `${chalk.gray('[PROC]')} >> ${chalk.blueBright(`Cached`)} ${chalk.white(`${amount}`)} ${chalk.blueBright(`ani DB series.`)}`});
app.cache.seriesCount++;
amount++;
}
const cacheTime = new Date().getTime() - st;
spinner.update({text: `${spinner.options.text.slice(0, 19).trim()} ${chalk.gray(`${cacheTime}ms >>`.padStart(8, '0').padStart(7, '0'))} ${spinner.options.text.slice(19).trim()}`});
spinner.status('non-spinnable');
resolve(0);
});
}

@ -1,88 +1,33 @@
const {Schema} = require("mongoose"); const {Schema} = require("mongoose");
module.exports = (connection) => connection.model('series', new Schema({ module.exports = (connection) => connection.model('series', new Schema({
id: {type: String, unique: true, required: true, maxLength: 25}, //!REQ id: {type: String, unique: true},
numericalId: {type: Number, unique: true, required: true, min: 1}, //!REQ
meta: { meta: {
locked: {type: Boolean, default: false}, locked: {type: Boolean, default: false},
creator: {type: String, required: true}, //uid //!REQ creator: String, //uid
edits: {type: [{ edits: {type: [{
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},
approved: {type: Schema.Types.Mixed, default: false}, //boolean or {approved: Boolean, by: <uid>} approved: {type: {
submitted: Schema.Types.Mixed, //can be false or a string with the ID of the submitter, //!REQ approved: Boolean,
hidden: {type: Boolean, defualt: false}, by: String
reviewFlags: {type: [{ }, default: false}
by: String,
reason: String,
}], default: []}
}, //!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: [String], default: []},
synopsis: {type: {
synopsis: {type: String, required: true, maxLength: 1000},
by: String //uid
}, required: true}, //if not present, use "Synopsis not available yet" //!REQ
genres: {type: [String], required: true}, //!REQ
//TODOdatabase for genres or cache
tags: {default: [], type: [String]},
nsfw: {type: Boolean, default: false},
nsfwReason: {type: String, default: null}, //gore, language, nudity, strong themes
completed: {type: Boolean, default: false}, //SERIES completed
streamAt: {type: [String], default: []},
publishers: {type: [String], default: []},
studios: {type: [String], 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, defualt: 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
officialSite: {type: String, defualt: null},
videos: {type: Object, default: {}}, //OPs, EDs, trailers, etc.
art: { name: String,
cover: {type: [String], default: []}, //first item of any list is default item romaji: String,
icon: {type: [String], default: []}, //small 128x128 icon kanji: String,
banner: {type: [String], default: []}, altNames: [String],
poster: {type: [String], default: []}, //used for BGs, portrait id: Number,
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: [{ synopsis: {
user: String, synopsis: String,
rating: Number by: String
}], default: []}, //all ratings mapped by user },
rating: {type: Number, default: 0}, //automatic collection of user ratings for avg. genres: [String],
watchers: {type: [String], default: []}, //people with this anime on their watching and/or watchlists tags: [String],
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: String
}], default: []},
/**
* !API-DEPENDENT FIELDS
* TODO add API-dependent fields for series 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: []}
})); }));

@ -10,7 +10,6 @@
"discord.js": "^14.7.1", "discord.js": "^14.7.1",
"dreidels": "^0.5.2", "dreidels": "^0.5.2",
"express": "^4.18.2", "express": "^4.18.2",
"fuse.js": "^6.6.2",
"fuzzysort": "^2.0.4", "fuzzysort": "^2.0.4",
"gradient-string": "^2.0.2", "gradient-string": "^2.0.2",
"helmet": "^6.0.1", "helmet": "^6.0.1",

Loading…
Cancel
Save