Compare commits

...

16 Commits

Author SHA1 Message Date
oat 88fc57da82
changed up the zip package, fixed bad zip issues 2020-12-26 23:25:53 +03:00
oat 564d71c48f
okay 2020-12-26 23:03:34 +03:00
oat 4cbe252852
now no two users can have the same uuid 2020-10-12 21:13:45 +03:00
oat 0a150ad343
one more auth fix 2020-10-12 20:56:06 +03:00
oat f113c472fd
fixed a few issues with auth 2020-10-12 17:10:18 +03:00
oat 96facbf2e2
ok figured it out 2020-10-12 05:10:15 +03:00
oat df6c1545af
testing in prod time 2020-10-12 05:07:58 +03:00
oat e218255441
unhardcode base url (i think closes #3?) 2020-10-12 05:02:45 +03:00
oat 36f4da4a28
readme update 2020-10-12 04:15:11 +03:00
oat ef58af8a9a
user checking when user uploads 2020-10-12 03:39:44 +03:00
oat 795f983721
funny logins hooray oh yeah oh yeah 2020-10-12 03:27:47 +03:00
oat 6b07ef943d
fix up html formatting 2020-10-12 01:24:33 +03:00
oat 73d5962c5c
file uploading, now with the actual file 2020-10-12 01:21:32 +03:00
oat 1ed6dc6185
add chart data
steps, jumps, mines, etc
2020-09-04 18:02:42 +03:00
oat 64446588c9
schema updates
semi-final schemas, i think
2020-09-04 16:47:10 +03:00
jill dc88ad2b75 Merge pull request 'slightly better (but still temporary) frontend and /api' (#4) from mat/in-the-database-2:master into master
Reviewed-on: oat/in-the-database-2#4
closes #2
2020-09-04 04:43:26 +00:00
14 changed files with 677 additions and 369 deletions

3
.gitignore vendored
View File

@ -3,4 +3,5 @@ node_modules
built
config/config.json
*.log
.env
.env
storage/files/*

View File

@ -1,6 +1,8 @@
![in the database 2](./assets/full.png "in the database 2")
a database site for notitg modcharts, currently very very unfinished, basically just a boilerplate
a database site for notitg modcharts, currently very very unfinished
you can login with discord, upload and download files, thats about it, but im still proud of it
## setup

BIN
assets/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

682
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,20 +12,24 @@
"license": "MIT",
"dependencies": {
"@types/express": "github:types/express",
"@types/mongoose": "^5.7.36",
"adm-zip": "^0.5.1",
"axios": "^0.20.0",
"connect-mongo": "^3.2.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-fileupload": "^1.2.0",
"mongoose": "^5.10.2",
"express-session": "^1.17.1",
"mongoose": "^5.11.8",
"mongoose-int32": "^0.4.1",
"node-stream-zip": "^1.11.3",
"typescript": "^4.0.2",
"serve-favicon": "^2.5.0",
"typescript": "^4.1.3",
"uuid": "^8.3.2",
"winston": "^3.3.3"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.0.1",
"@typescript-eslint/parser": "^4.0.1",
"eslint": "^7.8.1"
"@types/express-session": "^1.17.3",
"@typescript-eslint/eslint-plugin": "^4.11.0",
"@typescript-eslint/parser": "^4.11.0",
"eslint": "^7.16.0"
}
}

View File

@ -1,14 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Index</title>
</head>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Index</title>
</head>
<body>
<h1>Hi</h1>
</body>
<body>
<h1>Hi</h1>
</body>
</html>

View File

@ -1,36 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>list</title>
</head>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>list</title>
</head>
<body>
<div id="doc-list">
</div>
<body>
<div id="doc-list">
</div>
<script src="https://cdn.jsdelivr.net/npm/axios@0.20.0/dist/axios.min.js"></script>
<script>
axios.get('/api/list').then(({ data }) => {
console.log(data);
const el = document.getElementById('doc-list');
for (const doc of data) {
let p = document.createElement('p');
p.innerText = `${doc.artist} - ${doc.title} by ${doc.credit}`;
el.insertAdjacentElement('beforeend', p);
<script src="https://cdn.jsdelivr.net/npm/axios@0.20.0/dist/axios.min.js"></script>
<script>
axios.get('/api/list').then(({ data }) => {
console.log(data);
const el = document.getElementById('doc-list');
for (const doc of data) {
let p = document.createElement('p');
p.innerHTML = `<b>${doc.artist} - ${doc.title}</b> by ${doc.credit}\nuploaded by ${doc.uploaderJSON.username}#${doc.uploaderJSON.discriminator}\n<a href="files/${doc.id}.zip">download</a>`;
let charts = document.createElement('ul');
for (const chart of doc.charts) {
let l = document.createElement('li');
l.innerText = `${chart.difficulty} ${chart.rating} - ${chart.name}`
charts.insertAdjacentElement('beforeend', l);
}
el.insertAdjacentElement('beforeend', charts);
}
});
</script>
</body>
if (doc.editable) {
p.innerHTML += ` <a href="../${doc.id}/edit">edit</a>`
}
el.insertAdjacentElement('beforeend', p);
let charts = document.createElement('ul');
for (const chart of doc.charts) {
let l = document.createElement('li');
l.innerHTML = `${chart.difficulty} ${chart.rating} - <b>${chart.name}</b><br>` +
`${chart.steps} steps, ${chart.mines} mines, ${chart.jumps} jumps, ${chart.hands} hands, ${chart.holds} holds, ${chart.rolls} rolls`
charts.insertAdjacentElement('beforeend', l);
}
el.insertAdjacentElement('beforeend', charts);
}
});
</script>
</body>
</html>

View File

@ -19,7 +19,7 @@
const file = document.getElementById('file');
if (file.files.length) {
console.log(file.files[0]);
const formData = new FormData();
formData.append('file', file.files[0]);
try {

View File

@ -1,3 +1,6 @@
import { User } from './schema';
import * as uuid from 'uuid';
const API_ENDPOINT = 'https://discord.com/api/v6';
const axios = require('axios').default;
@ -5,6 +8,7 @@ const axios = require('axios').default;
export function run(app) {
app.get('/discordauth', async (req, res) => {
const code = req.query.code;
const url = `http://${req.headers.host}/discordauth`;
if (code) {
try {
@ -13,7 +17,7 @@ export function run(app) {
client_secret: process.env.DISCORD_OAUTH_CLIENTSECRET,
grant_type: 'authorization_code',
code: code,
redirect_uri: 'http://localhost:8080/discordauth',
redirect_uri: url,
scope: 'identify'
});
@ -28,12 +32,49 @@ export function run(app) {
authorization: `${postRes.data.token_type} ${postRes.data.access_token}`
}
});
res.send(`hi ${userInfo.data.username}#${userInfo.data.discriminator}<br><img src="https://media.discordapp.net/avatars/${userInfo.data.id}/${userInfo.data.avatar}.png">`);
const users = await User.find({id: String(userInfo.data.id)});
let userUuid = '';
if (users.length === 0) {
let newUuid = uuid.v4();
while (User.find({uuid: newUuid})[0]) {
newUuid = uuid.v4();
}
const newUser = new User({
id: String(userInfo.data.id),
createdAt: new Date(),
username: userInfo.data.username,
discriminator: userInfo.data.discriminator,
avatar: userInfo.data.avatar,
uuid: newUuid,
});
userUuid = newUser.get('uuid');
newUser.save();
} else {
const user = users[0];
userUuid = user.get('uuid');
user.set('id', String(userInfo.data.id));
user.set('username', userInfo.data.username);
user.set('discriminator', userInfo.data.discriminator);
user.set('avatar', userInfo.data.avatar);
}
req.session!.discord = userInfo.data;
req.session!.uuid = userUuid;
res.send(`logged in as ${userInfo.data.username}#${userInfo.data.discriminator}<br><img src="https://media.discordapp.net/avatars/${userInfo.data.id}/${userInfo.data.avatar}.png"><br>ur useruuid is ${userUuid}`);
} catch(err) {
res.send(`whoooops<br>${err}`);
console.error(err);
}
} else {
res.send(`<a href="https://discord.com/api/oauth2/authorize?client_id=${process.env.DISCORD_OAUTH_CLIENTID}&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fdiscordauth&response_type=code&scope=identify">Click here!!</a>`);
res.send(`<a href="https://discord.com/api/oauth2/authorize?client_id=${process.env.DISCORD_OAUTH_CLIENTID}&redirect_uri=${encodeURI(url)}&response_type=code&scope=identify">Click here!!</a>`);
}
});
}

View File

@ -3,9 +3,12 @@ import * as mongoose from 'mongoose';
import * as fs from 'fs';
import * as winston from 'winston';
import * as fileUpload from 'express-fileupload';
import * as session from 'express-session';
import * as favicon from 'serve-favicon';
const MongoStore = require('connect-mongo')(session);
import * as format from './lib/format';
import { File } from './schema';
import { File, User } from './schema';
import * as upload from './upload';
import * as auth from './auth';
@ -52,8 +55,22 @@ db.then(() => {
// @ts-ignore
app.use(express.urlencoded({extended: true}));
app.use(favicon('assets/icon.ico'));
app.use(fileUpload({limits: { fileSize: 50 * 1024 * 1024 }}));
app.use(express.static('public', {extensions: ['html', 'htm']}));
app.use(express.static('storage', {extensions: ['zip']}));
app.use(session({
name: 'funnyuserdata',
secret: 'wenis',
store: new MongoStore({ mongooseConnection: mongoose.connection }),
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 365 * 10, // 10 years
httpOnly: true,
sameSite: 'lax',
},
resave: false,
saveUninitialized: true
}));
app.use('/assets', express.static('assets'));
app.set('db', db);
@ -63,8 +80,23 @@ db.then(() => {
upload.run(app);
auth.run(app);
app.get('/api/list', async (req, res) => { // only for testing
const docs = await File.find({});
app.get('/api/list', async (req, res) => {
const files = await File.find({});
const docs = [];
for (const doc of files) {
const d: any = doc.toJSON();
d.editable = false;
if (req.session) d.editable = req.session.uuid === d.uploader;
const user = await User.find({uuid: d.uploader});
if (user) {
d.uploaderJSON = user[0].toJSON(); // this is built upon 20 layers of metajank and i despise it
docs.push(d);
}
}
// TODO: filter out _id and __v? possibly more
res.send(docs);
});

View File

@ -1,6 +1,4 @@
export function parseSM(data: string) {
data = data.replace(/[\n\r]/g,'');
// steps
const difficulties = [];
const steps = data.split('#NOTES:');
@ -18,11 +16,25 @@ export function parseSM(data: string) {
diff.rating = Number(stepsSplit[3]);
diff.radarvalues = stepsSplit[4].split(',').map(v => Number(v));
const chart = stepsSplit[5];
diff.rawChart = chart;
diff.steps = chart.split(/[124]/g).length - 1;
diff.mines = chart.split('M').length - 1;
diff.jumps = chart.split(/[124]0{0,2}[124]/g).length - 1;
diff.hands = chart.split(/[124]0{0,1}[124]0{0,1}[124]/g).length - 1;
diff.holds = chart.split('2').length - 1;
diff.rolls = chart.split('4').length - 1;
diff.steps -= diff.jumps; // jumps are counted as 1 step
difficulties.push(diff);
}
}
}
data = data.replace(/[\n\r]/g,'');
// metadata
const lines = data.split(';').filter(l => l.startsWith('#'));
const obj: any = {};
@ -41,7 +53,7 @@ export function parseSM(data: string) {
const map = {};
for (const i in keys) {
map[Number(keys[i])] = Number(values[i]); // afaik maps are only numbers?
map[String(Number(keys[i])).replace('.', ',')] = Number(values[i]); // afaik maps are only numbers?
}
value = map;

View File

@ -1,10 +0,0 @@
import * as fs from 'fs';
export function returnStatic(page) {
return (req, res) => {
fs.readFile(`src/html/${page}`, 'utf8', (err, data) => {
if (err) throw err;
res.send(data);
});
};
}

View File

@ -1,39 +1,118 @@
/* eslint-disable no-unused-vars */
import * as mongoose from 'mongoose';
const Schema = mongoose.Schema;
export enum SMVersion {
OPENITG,
FUCKEXE,
NOTITG_V1,
NOTITG_V2,
NOTITG_V3,
NOTITG_V3_1,
NOTITG_V4,
NOTITG_V4_0_1,
STEPMANIA_3_95,
STEPMANIA_5_0,
STEPMANIA_5_1,
STEPMANIA_5_2,
STEPMANIA_5_3,
}
const Sample = new Schema({
start: {type: Number, default: 0},
length: {type: Number, default: 0}
});
const UserRating = new Schema({
rating: {type: Number, default: 0},
createdAt: Date,
user: {type: String, default: '00000000-0000-4000-a000-000000000000'}
});
const Chart = new Schema({
type: {type: String, default: 'dance-single'},
name: {type: String, default: ''},
difficulty: {type: String, default: 'Challenge'},
radarvalues: [Number],
rating: {type: Number, default: 0},
radarvalues: [Number]
ratingsVote: {type: [UserRating], default: []},
spoilered: {type: Boolean, default: false},
hidden: {type: Boolean, default: false},
steps: {type: Number, default: 0},
mines: {type: Number, default: 0},
jumps: {type: Number, default: 0},
hands: {type: Number, default: 0},
holds: {type: Number, default: 0},
rolls: {type: Number, default: 0},
});
const Comment = new Schema({
author: {type: String, default: '00000000-0000-4000-a000-000000000000'},
createdAt: Date,
content: {type: String, default: ''}
});
const FileSchema = new Schema({
id: {type: Number, default: 0},
title: {type: String, default: 'unknown'},
titleTranslit: String,
artist: {type: String, default: 'unknown'},
artistTranslit: String,
subtitle: String,
subtitleTranslit: String,
credit: String,
uploader: {type: String, default: '00000000-0000-4000-a000-000000000000'},
sample: Sample,
bpms: {type: Object, default: {'0': 0}},
charts: [Chart]
charts: {type: [Chart], default: []},
description: {type: String, default: ''},
createdAt: Date,
smVersion: {type: Number, default: 0}, // see SMVersion enum
ytLink: String,
customLink: String,
hidden: {type: Boolean, default: false},
comments: {type: [Comment], default: []},
});
export const File = mongoose.model('File', FileSchema);
const UserSchema = new Schema({ // this is pretty much just a discord user lol
id: String, // cus longass number
approved: Boolean
id: {type: String, default: 'notgiven!!!!!!!!!!!!'}, // discord id, cus longass number
createdAt: Date,
// caching
username: {type: String, default: 'User'},
discriminator: {type: String, default: '0000'},
avatar: String,
// used internally
uuid: {type: String, default: '00000000-0000-4000-a000-000000000000'},
approvedUpload: {type: Boolean, default: false},
approvedRate: {type: Boolean, default: false},
approvedComment: {type: Boolean, default: false},
});
export const User = mongoose.model('User', UserSchema);
export const User = mongoose.model('User', UserSchema);
const PackSchema = new Schema({
author: {type: String, default: '00000000-0000-4000-a000-000000000000'},
files: {type: [Number], default: []}, // ids
name: {type: String, default: 'Pack'},
description: {type: String, default: ''},
createdAt: Date,
hidden: {type: Boolean, default: false},
});
export const Pack = mongoose.model('Pack', PackSchema);

View File

@ -1,54 +1,75 @@
import { tmpdir } from 'os';
import * as fs from 'fs';
const StreamZip = require('node-stream-zip');
import * as AdmZip from 'adm-zip';
import { returnStatic } from './lib/util';
import { parseSM } from './lib/smparse';
import { File } from './schema';
import { File, User } from './schema';
export function run(app) {
const logger = app.get('logger');
app.post('/api/upload', async (req, res) => { // only for testing, very abusable
if (!req.files) return res.status(400).send('No files were given');
if (!req.session.uuid) return res.status(401).send('Not authorized, use /discordauth');
const user = (await User.find({uuid: req.session.uuid}))[0];
if (!user) return res.status(401).send('User doesn\'t exist, try re-logging in');
if (!user.get('approvedUpload')) return res.status(403).send('Your account is not allowed to upload files! Contact a moderator to verify your account');
const file = req.files.file;
if (file.mimetype !== 'application/zip' && file.mimetype !== 'application/x-zip-compressed') return res.status(400).send('Invalid filetype');
const dir = tmpdir() + '/' + file.md5;
fs.writeFile(dir, file.data, (err) => {
fs.writeFile(dir, file.data, async (err) => {
if (err) throw err;
const zip = new StreamZip({
file: dir,
storeEntries: true
});
try {
const zip = new AdmZip(dir);
const zipEntries = zip.getEntries();
zip.on('ready', () => {
const smFile = Object.values(zip.entries()).find((f: any) =>
!f.isDirectory && (f.name.endsWith('.sm'))
const smFile: any = Object.values(zipEntries).find((f: any) =>
!f.isDirectory && (f.entryName.endsWith('.sm'))
);
if (!smFile) {
res.status(400).send('No .sm found');
} else {
const data = zip.entryDataSync((smFile as any).name);
const data = smFile.getData().toString('utf8');
const chart = parseSM(data.toString());
logger.info(`${chart.artist} - ${chart.title} was just uploaded`);
const file = new File(chart);
file.save();
let id = 0;
for (const f of await File.find({})) {
id = Math.max(Number(f.id), id);
}
chart.id = id + 1;
// TODO: filter out _id and __v? possibly more
res.send(chart);
chart.uploader = req.session.uuid;
chart.createdAt = new Date();
fs.writeFile('./storage/files/' + (id + 1) + '.zip', file.data, (err) => {
if (err) throw err;
const file = new File(chart);
file.save();
// TODO: filter out _id and __v? possibly more
res.send(chart);
});
}
zip.close();
fs.unlink(dir, (err) => {
if (err) throw err;
});
});
} catch(err) {
logger.error(err.toString());
console.error(err);
res.status(400);
res.send(err.toString());
}
});
});
}