mirror of
https://github.com/KeyZox71/knl_meowscendence.git
synced 2025-12-31 21:56:41 +01:00
Merge branch 'main' into front
This commit is contained in:
@ -2,59 +2,151 @@ import fastifyJWT from '@fastify/jwt';
|
||||
import fastifyCookie from '@fastify/cookie';
|
||||
import cors from '@fastify/cors'
|
||||
import Database from 'better-sqlite3';
|
||||
import cors from '@fastify/cors';
|
||||
|
||||
var env = process.env.NODE_ENV || 'development';
|
||||
import { gUsers } from './gUsers.js';
|
||||
import { gUser } from './gUser.js';
|
||||
import { gNumberUsers } from './gNumberUsers.js';
|
||||
import { gFriends } from './gFriends.js';
|
||||
import { gNumberFriends } from './gNumberFriends.js';
|
||||
import { gMatchHistory } from './gMatchHistory.js';
|
||||
import { gNumberMatches } from './gNumberMatches.js';
|
||||
import { pUser } from './pUser.js';
|
||||
import { pFriend } from './pFriend.js';
|
||||
import { pMatchHistory } from './pMatchHistory.js';
|
||||
import { uMember } from './uMember.js';
|
||||
import { dUser } from './dUser.js';
|
||||
import { dMember } from './dMember.js';
|
||||
import { dFriends } from './dFriends.js';
|
||||
import { dFriend } from './dFriend.js';
|
||||
import { dMatchHistory } from './dMatchHistory.js';
|
||||
import { pAvatar } from './pAvatar.js';
|
||||
import { gAvatar } from './gAvatar.js';
|
||||
import { uAvatar } from './uAvatar.js';
|
||||
import { dAvatar } from './dAvatar.js';
|
||||
import { pPing } from './pPing.js';
|
||||
import { gPing } from './gPing.js';
|
||||
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
|
||||
let database;
|
||||
|
||||
if (!env || env === 'development') {
|
||||
database = new Database(":memory:", { verbose: console.log });
|
||||
database = new Database(':memory:', { verbose: console.log });
|
||||
} else {
|
||||
var dbPath = process.env.DB_PATH || '/db/db.sqlite'
|
||||
const dbPath = process.env.DB_PATH || '/db/db.sqlite'
|
||||
database = new Database(dbPath);
|
||||
}
|
||||
|
||||
function prepareDB() {
|
||||
database.exec(`
|
||||
CREATE TABLE IF NOT EXISTS userData (
|
||||
username TEXT PRIMARY KEY,
|
||||
displayName TEXT
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT,
|
||||
displayName TEXT,
|
||||
avatarId INTEGER,
|
||||
pongWins INTEGER,
|
||||
pongLosses INTEGER,
|
||||
tetrisWins INTEGER,
|
||||
tetrisLosses INTEGER,
|
||||
UNIQUE(username),
|
||||
CHECK(pongWins >= 0),
|
||||
CHECK(pongLosses >= 0),
|
||||
CHECK(tetrisWins >= 0),
|
||||
CHECK(tetrisLosses >= 0)
|
||||
) STRICT
|
||||
`);
|
||||
database.exec(`
|
||||
CREATE TABLE IF NOT EXISTS friends (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT,
|
||||
friendName TEXT,
|
||||
UNIQUE(username, friendName),
|
||||
CHECK(username != friendName)
|
||||
)
|
||||
) STRICT
|
||||
`);
|
||||
database.exec(`
|
||||
CREATE TABLE IF NOT EXISTS matchHistory (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
game TEXT,
|
||||
date INTEGER,
|
||||
player1 TEXT,
|
||||
player2 TEXT,
|
||||
matchId INTEGER,
|
||||
CHECK(game = 'pong' OR game = 'tetris'),
|
||||
CHECK(date >= 0),
|
||||
CHECK(player1 != player2)
|
||||
) STRICT
|
||||
`);
|
||||
database.exec(`
|
||||
CREATE TABLE IF NOT EXISTS activityTime (
|
||||
username TEXT PRIMARY KEY,
|
||||
time TEXT
|
||||
) STRICT
|
||||
`);
|
||||
database.exec(`
|
||||
CREATE TABLE IF NOT EXISTS images (
|
||||
imageId INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
fileName TEXT,
|
||||
mimeType TEXT,
|
||||
data BLOB
|
||||
) STRICT
|
||||
`);
|
||||
}
|
||||
|
||||
prepareDB();
|
||||
|
||||
// POST
|
||||
const createUser = database.prepare('INSERT INTO userData (username, displayName) VALUES (?, ?);');
|
||||
const createUser = database.prepare('INSERT INTO userData (username, displayName, avatarId, pongWins, pongLosses, tetrisWins, tetrisLosses) VALUES (?, ?, -1, 0, 0, 0, 0);');
|
||||
const addFriend = database.prepare('INSERT INTO friends (username, friendName) VALUES (?, ?);');
|
||||
const addMatch = database.prepare('INSERT INTO matchHistory (game, date, player1, player2, matchId) VALUES (?, ?, ?, ?, ?);');
|
||||
const incWinsPong = database.prepare('UPDATE userData SET pongWins = pongWins + 1 WHERE username = ?;');
|
||||
const incLossesPong = database.prepare('UPDATE userData SET pongLosses = pongLosses + 1 WHERE username = ?');
|
||||
const incWinsTetris = database.prepare('UPDATE userData SET tetrisWins = tetrisWins + 1 WHERE username = ?;');
|
||||
const incLossesTetris = database.prepare('UPDATE userData SET tetrisLosses = tetrisLosses + 1 WHERE username = ?');
|
||||
const setAvatarId = database.prepare('UPDATE userData SET avatarId = ? WHERE username = ?;');
|
||||
const postImage = database.prepare('INSERT INTO images (fileName, mimeType, data) VALUES (?, ?, ?);');
|
||||
const setActivityTime = database.prepare(`
|
||||
INSERT INTO activityTime (username, time)
|
||||
VALUES (?, ?)
|
||||
ON CONFLICT(username) DO UPDATE SET time = excluded.time;
|
||||
`);
|
||||
|
||||
// PATCH
|
||||
const changeDisplayName = database.prepare('UPDATE userData SET displayName = ? WHERE username = ?;');
|
||||
const changeAvatarId = database.prepare('UPDATE userData SET avatarId = ? WHERE username = ?;');
|
||||
|
||||
// GET
|
||||
const getUserInfo = database.prepare('SELECT * FROM userData WHERE username = ?;');
|
||||
const getUserData = database.prepare('SELECT * FROM userData;');
|
||||
const getFriends = database.prepare('SELECT friendName FROM friends WHERE username = ?;');
|
||||
// const isFriend = database.prepare('SELECT 1 FROM friends WHERE username = ? AND friendName = ?;');
|
||||
const getUserData = database.prepare('SELECT username, displayName, pongWins, pongLosses, tetrisWins, tetrisLosses FROM userData LIMIT ? OFFSET ?;');
|
||||
const getUserInfo = database.prepare('SELECT username, displayName, pongWins, pongLosses, tetrisWins, tetrisLosses FROM userData WHERE username = ?;');
|
||||
const getFriends = database.prepare('SELECT friendName FROM friends WHERE username = ? LIMIT ? OFFSET ?;');
|
||||
const getFriend = database.prepare('SELECT friendName FROM friends WHERE username = ? AND friendName = ?;');
|
||||
const getMatchHistory = database.prepare('SELECT matchId, date FROM matchHistory WHERE game = ? AND ? IN (player1, player2) LIMIT ? OFFSET ?;');
|
||||
const getNumberUsers = database.prepare('SELECT COUNT (DISTINCT username) AS n_users FROM userData;');
|
||||
const getNumberFriends = database.prepare('SELECT COUNT (DISTINCT friendName) AS n_friends FROM friends WHERE username = ?;');
|
||||
const getNumberMatches = database.prepare('SELECT COUNT (DISTINCT id) AS n_matches FROM matchHistory WHERE game = ? AND ? IN (player1, player2);');
|
||||
const getAvatarId = database.prepare('SELECT avatarId FROM userData WHERE username = ?;');
|
||||
const getImage = database.prepare('SELECT fileName, mimeType, data FROM images WHERE imageId = ?;');
|
||||
const getActivityTime = database.prepare('SELECT time FROM activityTime WHERE username = ?;')
|
||||
|
||||
// DELETE
|
||||
const deleteUser = database.prepare('DELETE FROM userData WHERE username = ?;');
|
||||
const deleteFriend = database.prepare('DELETE FROM friends WHERE username = ? AND friendName = ?;');
|
||||
const deleteFriends = database.prepare('DELETE FROM friends WHERE username = ?;');
|
||||
const deleteMatchHistory = database.prepare('DELETE FROM matchHistory WHERE game = ? AND ? IN (player1, player2);');
|
||||
const deleteStatsPong = database.prepare('UPDATE userData SET pongWins = 0, pongLosses = 0 WHERE username = ?;');
|
||||
const deleteStatsTetris = database.prepare('UPDATE userData SET tetrisWins = 0, tetrisLosses = 0 WHERE username = ?;');
|
||||
const deleteAvatarId = database.prepare('UPDATE userData SET avatarId = -1 WHERE username = ?;');
|
||||
const deleteImage = database.prepare('DELETE FROM images WHERE imageId = ?;');
|
||||
|
||||
const querySchema = { type: 'object', required: ['iStart', 'iEnd'], properties: { iStart: { type: 'integer', minimum: 0 }, iEnd: { type: 'integer', minimum: 0 } } };
|
||||
const bodySchemaMember = { type: 'object', properties: { displayName: { type: 'string' } } };
|
||||
const querySchemaMatchHistory = { type: 'object', required: ['game', 'iStart', 'iEnd'], properties: { game: { type: 'string' }, iStart: { type: 'integer', minimum: 0 }, iEnd: { type: 'integer', minimum: 0 } } };
|
||||
const bodySchemaMatchHistory = { type: 'object', required: ['game', 'date', 'myScore'], properties: { game: { type: 'string' }, date: { type: 'integer', minimum: 0 }, opponent: { type: 'string' }, myScore: { type: 'integer', minimum: 0 }, opponentScore: { type: 'integer', minimum: 0 } } };
|
||||
const querySchemaMatchHistoryGame = { type: 'object', required: ['game'], properties: { game: { type: 'string' } } };
|
||||
|
||||
/**
|
||||
* @param {import('fastify').FastifyInstance} fastify
|
||||
* @param {import('fastify').FastifyPluginOptions} options
|
||||
* @param {import('fastify').FastifyInstance} fastify
|
||||
* @param {import('fastify').FastifyPluginOptions} options
|
||||
*/
|
||||
export default async function(fastify, options) {
|
||||
|
||||
@ -71,8 +163,19 @@ export default async function(fastify, options) {
|
||||
},
|
||||
});
|
||||
fastify.register(fastifyCookie);
|
||||
fastify.register(cors, {
|
||||
origin: process.env.CORS_ORIGIN || 'http://localhost:5173',
|
||||
credentials: true,
|
||||
methods: [ "GET", "POST", "PATCH", "DELETE", "OPTIONS" ]
|
||||
});
|
||||
|
||||
fastify.decorate("authenticate", async function(request, reply) {
|
||||
fastify.addContentTypeParser(
|
||||
['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
|
||||
{ parseAs: 'buffer' },
|
||||
async (request, payload) => payload
|
||||
);
|
||||
|
||||
fastify.decorate('authenticate', async function(request, reply) {
|
||||
try {
|
||||
const jwt = await request.jwtVerify();
|
||||
request.user = jwt.user;
|
||||
@ -81,178 +184,89 @@ export default async function(fastify, options) {
|
||||
}
|
||||
});
|
||||
|
||||
fastify.decorate("authenticateAdmin", async function(request, reply) {
|
||||
fastify.decorate('authenticateAdmin', async function(request, reply) {
|
||||
try {
|
||||
const jwt = await request.jwtVerify();
|
||||
if (jwt.user !== 'admin') {
|
||||
throw ("");
|
||||
throw ('You lack administrator privileges');
|
||||
}
|
||||
request.user = jwt.user;
|
||||
} catch (err) {
|
||||
reply.code(401).send({ error: 'Unauthorized' });
|
||||
}
|
||||
});
|
||||
|
||||
// GET
|
||||
fastify.get('/users', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||
try {
|
||||
const users = getUserData.all();
|
||||
|
||||
return reply.code(200).send({ users });
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: "Internal server error" });
|
||||
}
|
||||
fastify.get('/users', { preHandler: [fastify.authenticate], schema: { querystring: querySchema } }, async (request, reply) => {
|
||||
return gUsers(request, reply, fastify, getUserData);
|
||||
});
|
||||
fastify.get('/users/count', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||
return gNumberUsers(request, reply, fastify, getNumberUsers);
|
||||
});
|
||||
fastify.get('/users/:userId', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||
try {
|
||||
const info = getUserInfo.get(request.params.userId);
|
||||
|
||||
return reply.code(200).send({ info });
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: "Internal server error" });
|
||||
}
|
||||
return gUser(request, reply, fastify, getUserInfo);
|
||||
});
|
||||
fastify.get('/users/:userId/friends', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||
try {
|
||||
const userId = request.params.userId;
|
||||
|
||||
if (!getUserInfo.get(userId)) {
|
||||
return reply.code(404).send({ error: "User does not exist" });
|
||||
}
|
||||
|
||||
if (userId == request.user || request.user == 'admin') {
|
||||
const friends = getFriends.all(userId);
|
||||
|
||||
if (!friends) {
|
||||
return reply.code(404).send({ error: "User does not have friends D:" });
|
||||
}
|
||||
return reply.code(200).send({ friends });
|
||||
}
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: "Internal server error" });
|
||||
}
|
||||
fastify.get('/users/:userId/friends', { preHandler: [fastify.authenticate], schema: { querystring: querySchema } }, async (request, reply) => {
|
||||
return gFriends(request, reply, fastify, getUserInfo, getFriends);
|
||||
});
|
||||
fastify.get('/users/:userId/friends/count', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||
return gNumberFriends(request, reply, fastify, getUserInfo, getNumberFriends);
|
||||
});
|
||||
fastify.get('/users/:userId/matchHistory', { preHandler: [fastify.authenticate], schema: { querystring: querySchemaMatchHistory } }, async (request, reply) => {
|
||||
return gMatchHistory(request, reply, fastify, getUserInfo, getMatchHistory);
|
||||
});
|
||||
fastify.get('/users/:userId/matchHistory/count', { preHandler: [fastify.authenticate], schema: { query: querySchemaMatchHistoryGame } }, async (request, reply) => {
|
||||
return gNumberMatches(request, reply, fastify, getUserInfo, getNumberMatches);
|
||||
});
|
||||
fastify.get('/users/:userId/avatar', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||
return gAvatar(request, reply, fastify, getUserInfo, getAvatarId, getImage);
|
||||
});
|
||||
fastify.get('/ping/:userId', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||
return gPing(request, reply, fastify, getActivityTime);
|
||||
});
|
||||
|
||||
// POST
|
||||
fastify.post('/users/:userId', { preHandler: [fastify.authenticateAdmin] }, async (request, reply) => {
|
||||
try {
|
||||
const userId = request.params.userId;
|
||||
|
||||
if (getUserInfo.get(userId)) {
|
||||
return reply.code(400).send({ error: "User already exist" });
|
||||
}
|
||||
createUser.run(userId, userId);
|
||||
return reply.code(200).send({ msg: "User created sucessfully" });
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: "Internal server error" });
|
||||
}
|
||||
})
|
||||
fastify.post('/users/:userId/friends', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||
try {
|
||||
const userId = request.params.userId;
|
||||
if (request.user != 'admin' && request.user != userId) {
|
||||
return reply.code(401).send({ error: "Unauthorized" });
|
||||
}
|
||||
if (!request.body || !request.body.user) {
|
||||
return reply.code(400).send({ error: "Please specify a user" });
|
||||
}
|
||||
if (!getUserInfo.get(userId)) {
|
||||
return reply.code(404).send({ error: "User does not exist" });
|
||||
}
|
||||
if (!getUserInfo.get(request.body.user)) {
|
||||
return reply.code(404).send({ error: "Friend does not exist" });
|
||||
}
|
||||
if (request.body.user === userId) {
|
||||
return reply.code(400).send({ error: "You can't add yourself :D" });
|
||||
}
|
||||
addFriend.run(userId, request.body.user)
|
||||
return reply.code(200).send({ msg: "Friend added sucessfully" });
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: "Internal server error" });
|
||||
}
|
||||
return pUser(request, reply, fastify, getUserInfo, createUser);
|
||||
});
|
||||
fastify.post('/users/:userId/friends/:friendId', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||
return pFriend(request, reply, fastify, getUserInfo, getFriend, addFriend);
|
||||
});
|
||||
fastify.post('/users/:userId/matchHistory', { preHandler: [fastify.authenticate], schema: { body: bodySchemaMatchHistory } }, async (request, reply) => {
|
||||
return pMatchHistory(request, reply, fastify, getUserInfo, addMatch, incWinsPong, incLossesPong, incWinsTetris, incLossesTetris);
|
||||
});
|
||||
fastify.post('/users/:userId/avatar', { bodyLimit: 5242880, preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||
return pAvatar(request, reply, fastify, getUserInfo, setAvatarId, postImage);
|
||||
});
|
||||
fastify.post('/ping', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||
return pPing(request, reply, fastify, setActivityTime);
|
||||
})
|
||||
|
||||
// PATCH
|
||||
fastify.patch('/users/:userId/:member', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||
try {
|
||||
const userId = request.params.userId;
|
||||
if (request.user != 'admin' && request.user != userId) {
|
||||
return reply.code(401).send({ error: "Unauthorized" });
|
||||
}
|
||||
if (!getUserInfo.get(userId)) {
|
||||
return reply.code(404).send({ error: "User does not exist" });
|
||||
}
|
||||
const member = request.params.member;
|
||||
|
||||
if (member === 'displayName') {
|
||||
if (!request.body || !request.body.displayName) {
|
||||
return reply.code(400).send({ error: "Please specify a displayName" });
|
||||
}
|
||||
|
||||
changeDisplayName.run(request.body.displayName, userId);
|
||||
return reply.code(200).send({ msg: "displayName modified sucessfully" });
|
||||
}
|
||||
return reply.code(400).send({ error: "Member does not exist" })
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: "Internal server error" });
|
||||
}
|
||||
})
|
||||
fastify.patch('/users/:userId/avatar', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||
return uAvatar(request, reply, fastify, getUserInfo, setAvatarId, getAvatarId, deleteAvatarId, postImage, deleteImage);
|
||||
});
|
||||
fastify.patch('/users/:userId/:member', { preHandler: [fastify.authenticate], schema: { body: bodySchemaMember } }, async (request, reply) => {
|
||||
return uMember(request, reply, fastify, getUserInfo, changeDisplayName, changeAvatarId);
|
||||
});
|
||||
|
||||
// DELETE
|
||||
/**
|
||||
* @description Can be used to delete a user from the db
|
||||
*/
|
||||
fastify.delete('/users/:userId', { preHandler: [fastify.authenticateAdmin] }, async (request, reply) => {
|
||||
try {
|
||||
if (!getUserInfo(request.params.userId)) {
|
||||
return reply.code(404).send({ error: "User does not exist" });
|
||||
}
|
||||
deleteUser.run(request.params.userId);
|
||||
deleteFriends.run(request.params.userId);
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: "Internal server error" });
|
||||
}
|
||||
return dUser(request, reply, fastify, getUserInfo, deleteMatchHistory, deleteFriends, deleteUser);
|
||||
});
|
||||
fastify.delete('/users/:userId/:member', { preHandler: fastify.authenticate }, async (request, reply) => {
|
||||
try {
|
||||
const user = request.user;
|
||||
const member = request.params.member;
|
||||
if (user == 'admin' || user == request.params.userId) {
|
||||
if (member == 'displayName') {
|
||||
changeDisplayName.run("", request.params.userId);
|
||||
return reply.code(200).send({ msg: "displayName cleared sucessfully" });
|
||||
}
|
||||
return reply.code(400).send({ msg: "member does not exist" })
|
||||
} else {
|
||||
return reply.code(401).send({ error: 'You dont have the right to delete this' });
|
||||
}
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: "Internal server error" });
|
||||
}
|
||||
|
||||
return dMember(request, reply, fastify, getUserInfo, changeDisplayName);
|
||||
});
|
||||
fastify.delete('/users/:userId/friends', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||
return dFriends(request, reply, fastify, getUserInfo, deleteFriends);
|
||||
});
|
||||
fastify.delete('/users/:userId/friends/:friendId', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||
try {
|
||||
const userId = request.params.userId;
|
||||
const friendId = request.params.friendId;
|
||||
if (!getUserInfo.get(userId)) {
|
||||
return reply.code(404).send({ error: "User does not exist" });
|
||||
}
|
||||
if (request.user != 'admin' && request.user != userId) {
|
||||
return reply.code(401).send({ error: "Unauthorized" });
|
||||
}
|
||||
deleteFriend.run(userId, friendId);
|
||||
return reply.code(200).send({ msg: "Friend remove sucessfully" });
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: "Internal server error" });
|
||||
}
|
||||
return dFriend(request, reply, fastify, getUserInfo, getFriend, deleteFriend);
|
||||
});
|
||||
fastify.delete('/users/:userId/matchHistory', { preHandler: [fastify.authenticate], schema: { query: querySchemaMatchHistoryGame } }, async (request, reply) => {
|
||||
return dMatchHistory(request, reply, fastify, getUserInfo, deleteMatchHistory, deleteStatsPong, deleteStatsTetris);
|
||||
});
|
||||
fastify.delete('/users/:userId/avatar', { preHandler: [fastify.authenticate] }, async (request, reply) => {
|
||||
return dAvatar(request, reply, fastify, getUserInfo, getAvatarId, deleteAvatarId, deleteImage);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user