refactor: api
This commit is contained in:
parent
3c616f4f6f
commit
69d10ef429
19 changed files with 285 additions and 230 deletions
|
@ -6,6 +6,7 @@ import { sign64, unsign64 } from 'lib/utils/crypto';
|
|||
import config from 'lib/config';
|
||||
import prisma from 'lib/prisma';
|
||||
import { OAuth, User } from '@prisma/client';
|
||||
import { HTTPMethod } from 'find-my-way';
|
||||
|
||||
export interface NextApiFile {
|
||||
fieldname: string;
|
||||
|
@ -16,7 +17,7 @@ export interface NextApiFile {
|
|||
size: number;
|
||||
}
|
||||
|
||||
interface UserExtended extends User {
|
||||
export interface UserExtended extends User {
|
||||
oauth: OAuth[];
|
||||
}
|
||||
|
||||
|
@ -27,55 +28,98 @@ export type NextApiReq = NextApiRequest & {
|
|||
files?: NextApiFile[];
|
||||
};
|
||||
|
||||
export type NextApiRes = NextApiResponse & {
|
||||
error: (message: string) => void;
|
||||
forbid: (message: string, extra?: any) => void;
|
||||
bad: (message: string) => void;
|
||||
json: (json: Record<string, any>, status?: number) => void;
|
||||
ratelimited: (remaining: number) => void;
|
||||
setCookie: (name: string, value: unknown, options: CookieSerializeOptions) => void;
|
||||
setUserCookie: (id: number) => void;
|
||||
export type NextApiResExtra =
|
||||
| 'badRequest'
|
||||
| 'unauthorized'
|
||||
| 'forbidden'
|
||||
| 'ratelimited'
|
||||
| 'notFound'
|
||||
| 'error';
|
||||
export type NextApiResExtraObj = {
|
||||
[key in NextApiResExtra]: (message: any, extra?: Record<string, any>) => void;
|
||||
};
|
||||
|
||||
export type NextApiRes = NextApiResponse &
|
||||
NextApiResExtraObj & {
|
||||
json: (json: Record<string, any>, status?: number) => void;
|
||||
setCookie: (name: string, value: unknown, options: CookieSerializeOptions) => void;
|
||||
setUserCookie: (id: number) => void;
|
||||
};
|
||||
|
||||
export type ZiplineApiConfig = {
|
||||
methods: HTTPMethod[];
|
||||
user?: boolean;
|
||||
administrator?: boolean;
|
||||
middleware?: any[];
|
||||
};
|
||||
|
||||
export const withZipline =
|
||||
(handler: (req: NextApiRequest, res: NextApiResponse) => unknown) => (req: NextApiReq, res: NextApiRes) => {
|
||||
(
|
||||
handler: (req: NextApiRequest, res: NextApiResponse, user?: UserExtended) => unknown,
|
||||
api_config: ZiplineApiConfig = { methods: ['GET'] }
|
||||
) =>
|
||||
(req: NextApiReq, res: NextApiRes) => {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Content-Allow-Methods', 'GET,HEAD,POST,OPTIONS');
|
||||
res.setHeader('Access-Control-Max-Age', '86400');
|
||||
|
||||
res.error = (message: string) => {
|
||||
// Used when the client sends wrong information, etc.
|
||||
res.badRequest = (message: string, extra: Record<string, any> = {}) => {
|
||||
res.json(
|
||||
{
|
||||
error: message,
|
||||
code: 400,
|
||||
...extra,
|
||||
},
|
||||
500
|
||||
400
|
||||
);
|
||||
};
|
||||
|
||||
res.forbid = (message: string, extra: any = {}) => {
|
||||
// If the user is not logged in
|
||||
res.unauthorized = (message: string, extra: Record<string, any> = {}) => {
|
||||
res.json(
|
||||
{
|
||||
error: '403: ' + message,
|
||||
error: message,
|
||||
code: 401,
|
||||
...extra,
|
||||
},
|
||||
401
|
||||
);
|
||||
};
|
||||
|
||||
// If the user is logged in but doesn't have permission to do something
|
||||
res.forbidden = (message: string, extra: Record<string, any> = {}) => {
|
||||
res.json(
|
||||
{
|
||||
error: message,
|
||||
code: 403,
|
||||
...extra,
|
||||
},
|
||||
403
|
||||
);
|
||||
};
|
||||
|
||||
res.bad = (message: string) => {
|
||||
res.notFound = (message: string, extra: Record<string, any> = {}) => {
|
||||
res.json(
|
||||
{
|
||||
error: '401: ' + message,
|
||||
error: message,
|
||||
code: 404,
|
||||
...extra,
|
||||
},
|
||||
401
|
||||
404
|
||||
);
|
||||
};
|
||||
|
||||
res.ratelimited = (remaining: number) => {
|
||||
res.setHeader('X-Ratelimit-Remaining', Math.floor(remaining / 1000)).json(
|
||||
res.ratelimited = (message: number, extra: Record<string, any> = {}) => {
|
||||
const retry = Math.floor(message / 1000);
|
||||
|
||||
res.setHeader('X-Ratelimit-Remaining', retry);
|
||||
res.json(
|
||||
{
|
||||
error: '429: ratelimited',
|
||||
error: `ratelimited - try again in ${retry} seconds`,
|
||||
code: 429,
|
||||
...extra,
|
||||
},
|
||||
429
|
||||
);
|
||||
|
@ -129,8 +173,16 @@ export const withZipline =
|
|||
}
|
||||
};
|
||||
|
||||
res.setCookie = (name: string, value: unknown, options?: CookieSerializeOptions) =>
|
||||
setCookie(res, name, value, options || {});
|
||||
res.setCookie = (name: string, value: unknown, options: CookieSerializeOptions = {}) => {
|
||||
if ('maxAge' in options) {
|
||||
options.expires = new Date(Date.now() + options.maxAge * 1000);
|
||||
options.maxAge /= 1000;
|
||||
}
|
||||
|
||||
const signed = sign64(String(value), config.core.secret);
|
||||
|
||||
res.setHeader('Set-Cookie', serialize(name, signed, options));
|
||||
};
|
||||
|
||||
res.setUserCookie = (id: number) => {
|
||||
req.cleanCookie('user');
|
||||
|
@ -141,21 +193,32 @@ export const withZipline =
|
|||
});
|
||||
};
|
||||
|
||||
if (!api_config.methods.includes(req.method as HTTPMethod)) {
|
||||
return res.json(
|
||||
{
|
||||
error: 'method not allowed',
|
||||
code: 405,
|
||||
},
|
||||
405
|
||||
);
|
||||
}
|
||||
|
||||
if (api_config.middleware) {
|
||||
for (let i = 0; i !== api_config.middleware.length; ++i) {
|
||||
api_config.middleware[i](req, res, (result) => {
|
||||
if (result instanceof Error) return res.error(result.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (api_config.user) {
|
||||
return req.user().then((user) => {
|
||||
if (!user) return res.unauthorized('not logged in');
|
||||
if (api_config.administrator && !user.administrator) return res.forbidden('not an administrator');
|
||||
|
||||
return handler(req, res, user);
|
||||
});
|
||||
}
|
||||
|
||||
return handler(req, res);
|
||||
};
|
||||
|
||||
export const setCookie = (
|
||||
res: NextApiResponse,
|
||||
name: string,
|
||||
value: unknown,
|
||||
options: CookieSerializeOptions = {}
|
||||
) => {
|
||||
if ('maxAge' in options) {
|
||||
options.expires = new Date(Date.now() + options.maxAge * 1000);
|
||||
options.maxAge /= 1000;
|
||||
}
|
||||
|
||||
const signed = sign64(String(value), config.core.secret);
|
||||
|
||||
res.setHeader('Set-Cookie', serialize(name, signed, options));
|
||||
};
|
||||
|
|
|
@ -5,10 +5,11 @@ import Logger from 'lib/logger';
|
|||
import config from 'lib/config';
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
// handle invites
|
||||
if (req.method === 'POST' && req.body) {
|
||||
if (!config.features.invites && req.body.code) return res.forbid('invites are disabled');
|
||||
if (!config.features.invites && req.body.code) return res.badRequest('invites are disabled');
|
||||
if (!config.features.user_registration && !req.body.code)
|
||||
return res.forbid('user registration is disabled');
|
||||
return res.badRequest('user registration is disabled');
|
||||
|
||||
const { code, username, password } = req.body as {
|
||||
code?: string;
|
||||
|
@ -18,13 +19,13 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
const invite = await prisma.invite.findUnique({
|
||||
where: { code: code ?? '' },
|
||||
});
|
||||
if (!invite && code) return res.bad('invalid invite code');
|
||||
if (!invite && code) return res.badRequest('invalid invite code');
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: { username },
|
||||
});
|
||||
|
||||
if (user) return res.bad('username already exists');
|
||||
if (user) return res.badRequest('username already exists');
|
||||
const hashed = await hashPassword(password);
|
||||
const newUser = await prisma.user.create({
|
||||
data: {
|
||||
|
@ -56,8 +57,8 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
}
|
||||
|
||||
const user = await req.user();
|
||||
if (!user) return res.forbid('not logged in');
|
||||
if (!user.administrator) return res.forbid('you arent an administrator');
|
||||
if (!user) return res.unauthorized('not logged in');
|
||||
if (!user.administrator) return res.forbidden('you arent an administrator');
|
||||
|
||||
if (req.method !== 'POST') return res.status(405).end();
|
||||
|
||||
|
@ -67,15 +68,15 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
administrator: boolean;
|
||||
};
|
||||
|
||||
if (!username) return res.bad('no username');
|
||||
if (!password) return res.bad('no auth');
|
||||
if (!username) return res.badRequest('no username');
|
||||
if (!password) return res.badRequest('no password');
|
||||
|
||||
const existing = await prisma.user.findFirst({
|
||||
where: {
|
||||
username,
|
||||
},
|
||||
});
|
||||
if (existing) return res.forbid('user exists');
|
||||
if (existing) return res.badRequest('user exists');
|
||||
|
||||
const hashed = await hashPassword(password);
|
||||
|
||||
|
@ -95,4 +96,6 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
return res.json(newUser);
|
||||
}
|
||||
|
||||
export default withZipline(handler);
|
||||
export default withZipline(handler, {
|
||||
methods: ['POST'],
|
||||
});
|
||||
|
|
|
@ -15,13 +15,13 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
});
|
||||
|
||||
if (!image) return res.status(404).end(JSON.stringify({ error: 'Image not found' }));
|
||||
if (!password) return res.forbid('No password provided');
|
||||
if (!password) return res.badRequest('No password provided');
|
||||
|
||||
const valid = await checkPassword(password as string, image.password);
|
||||
if (!valid) return res.forbid('Wrong password');
|
||||
if (!valid) return res.badRequest('Wrong password');
|
||||
|
||||
const data = await datasource.get(image.file);
|
||||
if (!data) return res.error('Image not found');
|
||||
if (!data) return res.notFound('Image not found');
|
||||
|
||||
const size = await datasource.size(image.file);
|
||||
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
import prisma from 'lib/prisma';
|
||||
import { NextApiReq, NextApiRes, withZipline } from 'lib/middleware/withZipline';
|
||||
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'lib/middleware/withZipline';
|
||||
import { randomChars } from 'lib/util';
|
||||
import Logger from 'lib/logger';
|
||||
import config from 'lib/config';
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
if (!config.features.invites) return res.forbid('invites are disabled');
|
||||
|
||||
const user = await req.user();
|
||||
if (!user) return res.forbid('not logged in');
|
||||
if (!user.administrator) return res.forbid('you arent an administrator');
|
||||
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
if (!config.features.invites) return res.badRequest('invites are disabled');
|
||||
|
||||
if (req.method === 'POST') {
|
||||
const { expires_at, count } = req.body as {
|
||||
|
@ -19,8 +15,8 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
|
||||
const expiry = expires_at ? new Date(expires_at) : null;
|
||||
if (expiry) {
|
||||
if (!expiry.getTime()) return res.bad('invalid date');
|
||||
if (expiry.getTime() < Date.now()) return res.bad('date is in the past');
|
||||
if (!expiry.getTime()) return res.badRequest('invalid date');
|
||||
if (expiry.getTime() < Date.now()) return res.badRequest('date is in the past');
|
||||
}
|
||||
const counts = count ? count : 1;
|
||||
|
||||
|
@ -58,14 +54,6 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
|
||||
return res.json(invite);
|
||||
}
|
||||
} else if (req.method === 'GET') {
|
||||
const invites = await prisma.invite.findMany({
|
||||
orderBy: {
|
||||
created_at: 'desc',
|
||||
},
|
||||
});
|
||||
|
||||
return res.json(invites);
|
||||
} else if (req.method === 'DELETE') {
|
||||
const { code } = req.query as { code: string };
|
||||
|
||||
|
@ -78,7 +66,19 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
Logger.get('invite').info(`${user.username} (${user.id}) deleted invite ${invite.code}`);
|
||||
|
||||
return res.json(invite);
|
||||
} else {
|
||||
const invites = await prisma.invite.findMany({
|
||||
orderBy: {
|
||||
created_at: 'desc',
|
||||
},
|
||||
});
|
||||
|
||||
return res.json(invites);
|
||||
}
|
||||
}
|
||||
|
||||
export default withZipline(handler);
|
||||
export default withZipline(handler, {
|
||||
methods: ['GET', 'POST', 'DELETE'],
|
||||
user: true,
|
||||
administrator: true,
|
||||
});
|
||||
|
|
|
@ -4,7 +4,6 @@ import { checkPassword, createToken, hashPassword } from 'lib/util';
|
|||
import Logger from 'lib/logger';
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
if (req.method !== 'POST') return res.status(405).end();
|
||||
const { username, password } = req.body as {
|
||||
username: string;
|
||||
password: string;
|
||||
|
@ -30,10 +29,10 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
},
|
||||
});
|
||||
|
||||
if (!user) return res.status(404).end(JSON.stringify({ error: 'User not found' }));
|
||||
if (!user) return res.notFound('user not found');
|
||||
|
||||
const valid = await checkPassword(password, user.password);
|
||||
if (!valid) return res.forbid('Wrong password');
|
||||
if (!valid) return res.unauthorized('Wrong password');
|
||||
|
||||
res.setUserCookie(user.id);
|
||||
Logger.get('user').info(`User ${user.username} (${user.id}) logged in`);
|
||||
|
@ -41,4 +40,6 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
return res.json({ success: true });
|
||||
}
|
||||
|
||||
export default withZipline(handler);
|
||||
export default withZipline(handler, {
|
||||
methods: ['POST'],
|
||||
});
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
|
||||
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
|
||||
import Logger from 'lib/logger';
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
const user = await req.user();
|
||||
if (!user) return res.forbid('not logged in');
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
req.cleanCookie('user');
|
||||
|
||||
Logger.get('user').info(`User ${user.username} (${user.id}) logged out`);
|
||||
|
@ -12,4 +9,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
return res.json({ success: true });
|
||||
}
|
||||
|
||||
export default withZipline(handler);
|
||||
export default withZipline(handler, {
|
||||
methods: ['GET'],
|
||||
user: true,
|
||||
});
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
import prisma from 'lib/prisma';
|
||||
import { NextApiReq, NextApiRes, withZipline } from 'lib/middleware/withZipline';
|
||||
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'lib/middleware/withZipline';
|
||||
import { OauthProviders } from '@prisma/client';
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
const user = await req.user();
|
||||
if (!user) return res.error('not logged in');
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
if (req.method === 'DELETE') {
|
||||
if (!user.password && user.oauth.length === 1)
|
||||
return res.forbid("can't unlink account without a password, please set one then unlink.");
|
||||
if (!user.password && user.oauth?.length === 1)
|
||||
return res.badRequest("can't unlink account without a password, please set one then unlink.");
|
||||
|
||||
const { provider } = req.body as { provider: OauthProviders };
|
||||
|
||||
|
@ -44,8 +41,11 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
|
||||
return res.json(nuser);
|
||||
} else {
|
||||
return res.json(user.oauth);
|
||||
return res.json(user.oauth ?? []);
|
||||
}
|
||||
}
|
||||
|
||||
export default withZipline(handler);
|
||||
export default withZipline(handler, {
|
||||
methods: ['DELETE', 'GET'],
|
||||
user: true,
|
||||
});
|
||||
|
|
|
@ -7,8 +7,7 @@ import config from 'lib/config';
|
|||
import { sendShorten } from 'lib/discord';
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
if (req.method !== 'POST') return res.forbid('no allow');
|
||||
if (!req.headers.authorization) return res.forbid('no authorization');
|
||||
if (!req.headers.authorization) return res.badRequest('no authorization');
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
|
@ -16,13 +15,13 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
},
|
||||
});
|
||||
|
||||
if (!user) return res.forbid('authorization incorect');
|
||||
if (!req.body) return res.error('no body');
|
||||
if (!req.body.url) return res.error('no url');
|
||||
if (!user) return res.unauthorized('authorization incorect');
|
||||
if (!req.body) return res.badRequest('no body');
|
||||
if (!req.body.url) return res.badRequest('no url');
|
||||
|
||||
const maxUrlViews = req.headers['max-views'] ? Number(req.headers['max-views']) : null;
|
||||
if (isNaN(maxUrlViews)) return res.error('invalid max views (invalid number)');
|
||||
if (maxUrlViews < 0) return res.error('invalid max views (max views < 0)');
|
||||
if (isNaN(maxUrlViews)) return res.badRequest('invalid max views (invalid number)');
|
||||
if (maxUrlViews < 0) return res.badRequest('invalid max views (max views < 0)');
|
||||
|
||||
const rand = randomChars(zconfig.urls.length);
|
||||
|
||||
|
@ -35,7 +34,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
},
|
||||
});
|
||||
|
||||
if (existing) return res.error('vanity already exists');
|
||||
if (existing) return res.badRequest('vanity already exists');
|
||||
}
|
||||
|
||||
const url = await prisma.url.create({
|
||||
|
@ -71,4 +70,6 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
});
|
||||
}
|
||||
|
||||
export default withZipline(handler);
|
||||
export default withZipline(handler, {
|
||||
methods: ['POST'],
|
||||
});
|
||||
|
|
|
@ -1,17 +1,25 @@
|
|||
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
|
||||
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
|
||||
import prisma from 'lib/prisma';
|
||||
import config from 'lib/config';
|
||||
import { Stats } from '@prisma/client';
|
||||
import { getStats } from 'server/util';
|
||||
import datasource from 'lib/datasource';
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
const user = await req.user();
|
||||
if (!user) return res.forbid('not logged in');
|
||||
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
if (req.method === 'POST') {
|
||||
if (!user.administrator) return res.forbidden('not an administrator');
|
||||
|
||||
if (req.method === 'GET') {
|
||||
const stats = await getStats(prisma, datasource);
|
||||
const stats_data = await prisma.stats.create({
|
||||
data: {
|
||||
data: stats,
|
||||
},
|
||||
});
|
||||
|
||||
return res.json(stats_data);
|
||||
} else {
|
||||
let amount = typeof req.query.amount === 'string' ? Number(req.query.amount) : 2;
|
||||
if (isNaN(amount)) return res.bad('invalid amount');
|
||||
if (isNaN(amount)) return res.badRequest('invalid amount');
|
||||
|
||||
// get stats per day
|
||||
|
||||
|
@ -35,18 +43,10 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
}
|
||||
|
||||
return res.json(stats);
|
||||
} else if (req.method === 'POST') {
|
||||
if (!user.administrator) return res.forbid('unable to force update stats as a non-admin');
|
||||
|
||||
const stats = await getStats(prisma, datasource);
|
||||
const stats_data = await prisma.stats.create({
|
||||
data: {
|
||||
data: stats,
|
||||
},
|
||||
});
|
||||
|
||||
return res.json(stats_data);
|
||||
}
|
||||
}
|
||||
|
||||
export default withZipline(handler);
|
||||
export default withZipline(handler, {
|
||||
methods: ['GET', 'POST'],
|
||||
user: true,
|
||||
});
|
||||
|
|
|
@ -18,8 +18,7 @@ import sharp from 'sharp';
|
|||
const uploader = multer();
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
if (req.method !== 'POST') return res.forbid('invalid method');
|
||||
if (!req.headers.authorization) return res.forbid('no authorization');
|
||||
if (!req.headers.authorization) return res.forbidden('no authorization');
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
|
@ -27,9 +26,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
},
|
||||
});
|
||||
|
||||
if (!user) return res.forbid('authorization incorrect');
|
||||
|
||||
await run(uploader.array('file'))(req, res);
|
||||
if (!user) return res.forbidden('authorization incorrect');
|
||||
|
||||
const response: { files: string[]; expires_at?: Date } = { files: [] };
|
||||
const expires_at = req.headers['expires-at'] as string;
|
||||
|
@ -37,7 +34,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
|
||||
if (expires_at) {
|
||||
expiry = parseExpiry(expires_at);
|
||||
if (!expiry) return res.error('invalid date');
|
||||
if (!expiry) return res.badRequest('invalid date');
|
||||
else {
|
||||
response.expires_at = expiry;
|
||||
}
|
||||
|
@ -49,13 +46,14 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
const imageCompressionPercent = req.headers['image-compression-percent']
|
||||
? Number(req.headers['image-compression-percent'])
|
||||
: null;
|
||||
if (isNaN(imageCompressionPercent)) return res.error('invalid image compression percent (invalid number)');
|
||||
if (isNaN(imageCompressionPercent))
|
||||
return res.badRequest('invalid image compression percent (invalid number)');
|
||||
if (imageCompressionPercent < 0 || imageCompressionPercent > 100)
|
||||
return res.error('invalid image compression percent (% < 0 || % > 100)');
|
||||
return res.badRequest('invalid image compression percent (% < 0 || % > 100)');
|
||||
|
||||
const fileMaxViews = req.headers['max-views'] ? Number(req.headers['max-views']) : null;
|
||||
if (isNaN(fileMaxViews)) return res.error('invalid max views (invalid number)');
|
||||
if (fileMaxViews < 0) return res.error('invalid max views (max views < 0)');
|
||||
if (isNaN(fileMaxViews)) return res.badRequest('invalid max views (invalid number)');
|
||||
if (fileMaxViews < 0) return res.badRequest('invalid max views (max views < 0)');
|
||||
|
||||
// handle partial uploads before ratelimits
|
||||
if (req.headers['content-range']) {
|
||||
|
@ -174,17 +172,17 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!req.files) return res.error('no files');
|
||||
if (req.files && req.files.length === 0) return res.error('no files');
|
||||
if (!req.files) return res.badRequest('no files');
|
||||
if (req.files && req.files.length === 0) return res.badRequest('no files');
|
||||
|
||||
for (let i = 0; i !== req.files.length; ++i) {
|
||||
const file = req.files[i];
|
||||
if (file.size > zconfig.uploader[user.administrator ? 'admin_limit' : 'user_limit'])
|
||||
return res.error(`file[${i}]: size too big`);
|
||||
return res.badRequest(`file[${i}]: size too big`);
|
||||
|
||||
const ext = file.originalname.split('.').pop();
|
||||
if (zconfig.uploader.disabled_extensions.includes(ext))
|
||||
return res.error(`file[${i}]: disabled extension recieved: ${ext}`);
|
||||
return res.badRequest(`file[${i}]: disabled extension recieved: ${ext}`);
|
||||
let fileName: string;
|
||||
|
||||
switch (format) {
|
||||
|
@ -289,19 +287,10 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
return res.json(response);
|
||||
}
|
||||
|
||||
function run(middleware: any) {
|
||||
return (req, res) =>
|
||||
new Promise((resolve, reject) => {
|
||||
middleware(req, res, (result) => {
|
||||
if (result instanceof Error) reject(result);
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default async function handlers(req, res) {
|
||||
return withZipline(handler)(req, res);
|
||||
}
|
||||
export default withZipline(handler, {
|
||||
methods: ['POST'],
|
||||
middleware: [uploader.array('file')],
|
||||
});
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
import prisma from 'lib/prisma';
|
||||
import { hashPassword } from 'lib/util';
|
||||
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
|
||||
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
|
||||
import Logger from 'lib/logger';
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
const user = await req.user();
|
||||
if (!user) return res.forbid('not logged in');
|
||||
|
||||
if (!user.administrator) return res.forbid('not an administrator');
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
const { id } = req.query as { id: string };
|
||||
|
||||
const target = await prisma.user.findFirst({
|
||||
|
@ -17,23 +12,19 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
},
|
||||
});
|
||||
|
||||
if (!target) return res.error('user not found');
|
||||
if (!target) return res.notFound('user not found');
|
||||
|
||||
if (req.method === 'GET') {
|
||||
delete target.password;
|
||||
|
||||
return res.json(target);
|
||||
} else if (req.method === 'DELETE') {
|
||||
if (req.method === 'DELETE') {
|
||||
const newTarget = await prisma.user.delete({
|
||||
where: { id: target.id },
|
||||
});
|
||||
if (newTarget.administrator && !user.superAdmin) return res.error('cannot delete administrator');
|
||||
if (newTarget.administrator && !user.superAdmin) return res.forbidden('cannot delete administrator');
|
||||
|
||||
delete newTarget.password;
|
||||
|
||||
return res.json(newTarget);
|
||||
} else if (req.method === 'PATCH') {
|
||||
if (target.administrator && !user.superAdmin) return res.forbid('cannot modify administrator');
|
||||
if (target.administrator && !user.superAdmin) return res.forbidden('cannot modify administrator');
|
||||
|
||||
if (req.body.password) {
|
||||
const hashed = await hashPassword(req.body.password);
|
||||
|
@ -57,7 +48,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
},
|
||||
});
|
||||
if (existing && user.username !== req.body.username) {
|
||||
return res.forbid('username is already taken');
|
||||
return res.badRequest('username is already taken');
|
||||
}
|
||||
await prisma.user.update({
|
||||
where: { id: target.id },
|
||||
|
@ -114,7 +105,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
}
|
||||
}
|
||||
|
||||
if (invalidDomains.length) return res.forbid('invalid domains', { invalidDomains });
|
||||
if (invalidDomains.length) return res.badRequest('invalid domains', { invalidDomains });
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: target.id },
|
||||
|
@ -150,7 +141,15 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
);
|
||||
|
||||
return res.json(newUser);
|
||||
} else {
|
||||
delete target.password;
|
||||
|
||||
return res.json(target);
|
||||
}
|
||||
}
|
||||
|
||||
export default withZipline(handler);
|
||||
export default withZipline(handler, {
|
||||
methods: ['GET', 'DELETE', 'PATCH'],
|
||||
user: true,
|
||||
administrator: true,
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
|
||||
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
|
||||
import prisma from 'lib/prisma';
|
||||
import Logger from 'lib/logger';
|
||||
import { Zip, ZipPassThrough } from 'fflate';
|
||||
|
@ -7,10 +7,7 @@ import { readdir, stat } from 'fs/promises';
|
|||
import { createReadStream, createWriteStream } from 'fs';
|
||||
import { tmpdir } from 'os';
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
const user = await req.user();
|
||||
if (!user) return res.forbid('not logged in');
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
if (req.method === 'POST') {
|
||||
const files = await prisma.image.findMany({
|
||||
where: {
|
||||
|
@ -18,7 +15,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
},
|
||||
});
|
||||
|
||||
if (!files.length) return res.error('no files found');
|
||||
if (!files.length) return res.notFound('no files found');
|
||||
|
||||
const zip = new Zip();
|
||||
const export_name = `zipline_export_${user.id}_${Date.now()}.zip`;
|
||||
|
@ -116,7 +113,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
const export_name = req.query.name as string;
|
||||
if (export_name) {
|
||||
const parts = export_name.split('_');
|
||||
if (Number(parts[2]) !== user.id) return res.forbid('cannot access export');
|
||||
if (Number(parts[2]) !== user.id) return res.unauthorized('cannot access export owned by another user');
|
||||
|
||||
const stream = createReadStream(tmpdir() + `/${export_name}`);
|
||||
|
||||
|
@ -142,4 +139,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
}
|
||||
}
|
||||
|
||||
export default withZipline(handler);
|
||||
export default withZipline(handler, {
|
||||
methods: ['GET', 'POST'],
|
||||
user: true,
|
||||
});
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
|
||||
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
|
||||
import prisma from 'lib/prisma';
|
||||
import { chunk } from 'lib/util';
|
||||
import Logger from 'lib/logger';
|
||||
import datasource from 'lib/datasource';
|
||||
import config from 'lib/config';
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
const user = await req.user();
|
||||
if (!user) return res.forbid('not logged in');
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
if (req.method === 'DELETE') {
|
||||
if (req.body.all) {
|
||||
const files = await prisma.image.findMany({
|
||||
|
@ -30,7 +27,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
|
||||
return res.json({ count });
|
||||
} else {
|
||||
if (!req.body.id) return res.error('no file id');
|
||||
if (!req.body.id) return res.badRequest('no file id');
|
||||
|
||||
const image = await prisma.image.delete({
|
||||
where: {
|
||||
|
@ -48,7 +45,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
return res.json(image);
|
||||
}
|
||||
} else if (req.method === 'PATCH') {
|
||||
if (!req.body.id) return res.error('no file id');
|
||||
if (!req.body.id) return res.badRequest('no file id');
|
||||
|
||||
let image;
|
||||
|
||||
|
@ -103,4 +100,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
}
|
||||
}
|
||||
|
||||
export default withZipline(handler);
|
||||
export default withZipline(handler, {
|
||||
methods: ['GET', 'DELETE', 'PATCH'],
|
||||
user: true,
|
||||
});
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
import prisma from 'lib/prisma';
|
||||
import { hashPassword } from 'lib/util';
|
||||
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
|
||||
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
|
||||
import Logger from 'lib/logger';
|
||||
import config from 'lib/config';
|
||||
import { discord_auth, github_auth, google_auth } from 'lib/oauth';
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
const user = await req.user();
|
||||
if (!user) return res.forbid('not logged in');
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
if (user.oauth) {
|
||||
// this will probably change before the stable release
|
||||
if (user.oauth.find((o) => o.provider === 'GITHUB')) {
|
||||
|
@ -137,9 +134,8 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
username: req.body.username,
|
||||
},
|
||||
});
|
||||
if (existing && user.username !== req.body.username) {
|
||||
return res.forbid('username is already taken');
|
||||
}
|
||||
if (existing && user.username !== req.body.username) return res.badRequest('username is already taken');
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { username: req.body.username },
|
||||
|
@ -195,7 +191,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
}
|
||||
}
|
||||
|
||||
if (invalidDomains.length) return res.forbid('invalid domains', { invalidDomains });
|
||||
if (invalidDomains.length) return res.badRequest('invalid domains', { invalidDomains });
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
|
@ -234,4 +230,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
}
|
||||
}
|
||||
|
||||
export default withZipline(handler);
|
||||
export default withZipline(handler, {
|
||||
methods: ['GET', 'PATCH'],
|
||||
user: true,
|
||||
});
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
|
||||
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
|
||||
import prisma from 'lib/prisma';
|
||||
import config from 'lib/config';
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
const user = await req.user();
|
||||
if (!user) return res.forbid('not logged in');
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
const take = Number(req.query.take ?? 4);
|
||||
|
||||
if (take > 50) return res.error("take can't be more than 50");
|
||||
if (take > 50) return res.badRequest("take can't be more than 50");
|
||||
|
||||
let images = await prisma.image.findMany({
|
||||
take,
|
||||
|
@ -39,4 +36,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
return res.json(images);
|
||||
}
|
||||
|
||||
export default withZipline(handler);
|
||||
export default withZipline(handler, {
|
||||
methods: ['GET'],
|
||||
user: true,
|
||||
});
|
||||
|
|
|
@ -1,26 +1,24 @@
|
|||
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
|
||||
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
|
||||
import prisma from 'lib/prisma';
|
||||
import { createToken } from 'lib/util';
|
||||
import Logger from 'lib/logger';
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
const user = await req.user();
|
||||
if (!user) return res.forbid('not logged in');
|
||||
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
const updated = await prisma.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: {
|
||||
token: createToken(),
|
||||
},
|
||||
});
|
||||
|
||||
if (req.method === 'PATCH') {
|
||||
const updated = await prisma.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: {
|
||||
token: createToken(),
|
||||
},
|
||||
});
|
||||
Logger.get('user').info(`User ${user.username} (${user.id}) reset their token`);
|
||||
|
||||
Logger.get('user').info(`User ${user.username} (${user.id}) reset their token`);
|
||||
|
||||
return res.json({ success: updated.token });
|
||||
}
|
||||
return res.json({ success: updated.token });
|
||||
}
|
||||
|
||||
export default withZipline(handler);
|
||||
export default withZipline(handler, {
|
||||
methods: ['PATCH'],
|
||||
user: true,
|
||||
});
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
|
||||
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
|
||||
import prisma from 'lib/prisma';
|
||||
import config from 'lib/config';
|
||||
import Logger from 'lib/logger';
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
const user = await req.user();
|
||||
if (!user) return res.forbid('not logged in');
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
|
||||
if (req.method === 'DELETE') {
|
||||
if (!req.body.id) return res.bad('no url id');
|
||||
if (!req.body.id) return res.badRequest('no url id');
|
||||
|
||||
const url = await prisma.url.delete({
|
||||
where: {
|
||||
|
@ -40,4 +37,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
}
|
||||
}
|
||||
|
||||
export default withZipline(handler);
|
||||
export default withZipline(handler, {
|
||||
methods: ['GET', 'DELETE'],
|
||||
user: true,
|
||||
});
|
||||
|
|
|
@ -9,29 +9,29 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
const invite = await prisma.invite.findUnique({
|
||||
where: { code },
|
||||
});
|
||||
if (!invite) return res.bad('invalid invite code');
|
||||
if (!invite) return res.badRequest('invalid invite code');
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: { username },
|
||||
});
|
||||
|
||||
if (user) return res.bad('username already exists');
|
||||
if (user) return res.badRequest('username already exists');
|
||||
return res.json({ success: true });
|
||||
}
|
||||
|
||||
const user = await req.user();
|
||||
if (!user) return res.forbid('not logged in');
|
||||
if (!user.administrator) return res.forbid("you aren't an administrator");
|
||||
if (!user) return res.unauthorized('not logged in');
|
||||
if (!user.administrator) return res.forbidden('not an administrator');
|
||||
|
||||
if (req.method === 'DELETE') {
|
||||
if (req.body.id === user.id) return res.forbid("you can't delete your own account");
|
||||
if (req.body.id === user.id) return res.badRequest("you can't delete your own account");
|
||||
|
||||
const deleteUser = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: req.body.id,
|
||||
},
|
||||
});
|
||||
if (!deleteUser) return res.forbid("user doesn't exist");
|
||||
if (!deleteUser) return res.notFound("user doesn't exist");
|
||||
|
||||
if (req.body.delete_images) {
|
||||
const files = await prisma.image.findMany({
|
||||
|
@ -82,4 +82,6 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
}
|
||||
}
|
||||
|
||||
export default withZipline(handler);
|
||||
export default withZipline(handler, {
|
||||
methods: ['GET', 'POST', 'DELETE'],
|
||||
});
|
||||
|
|
|
@ -3,10 +3,7 @@ import config from 'lib/config';
|
|||
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
const user = await req.user();
|
||||
if (!user) return res.forbid('not logged in');
|
||||
|
||||
if (!config.website.show_version) return res.bad('version hidden');
|
||||
if (!config.website.show_version) return res.forbidden('version hidden');
|
||||
|
||||
const pkg = JSON.parse(await readFile('package.json', 'utf8'));
|
||||
|
||||
|
@ -19,4 +16,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
|||
});
|
||||
}
|
||||
|
||||
export default withZipline(handler);
|
||||
export default withZipline(handler, {
|
||||
methods: ['GET'],
|
||||
user: true,
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue