permissions!
This commit is contained in:
parent
8cde6799df
commit
6a5a85c773
33 changed files with 683 additions and 666 deletions
|
@ -2,7 +2,7 @@
|
|||
"trailingComma": "none",
|
||||
"tabWidth": 4,
|
||||
"useTabs": false,
|
||||
"printWidth": 120,
|
||||
"printWidth": 80,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"quoteProps": "preserve",
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import { FindCursor, Filter as MDBFilter, ObjectId } from "mongodb";
|
||||
import { FindCursor, Filter as MDBFilter, ObjectId, Sort } from "mongodb";
|
||||
import { mainDB } from "./mongodb";
|
||||
|
||||
// Functionally identical to the MongoDB type functions, but this is here if you want to extend TrollCall to another NoSQL database.
|
||||
// That would be hard, though, as some parts of the codebase touch raw MongoDB concepts.
|
||||
|
||||
export type WithId<TSchema> = Omit<TSchema, "_id"> & {
|
||||
_id: ObjectId;
|
||||
};
|
||||
|
||||
export type Document = { [key: string]: any };
|
||||
export type Filter<TypeSchema> = MDBFilter<Document>;
|
||||
export type Filter<T> = MDBFilter<Document>;
|
||||
|
||||
export async function createOne(collection: string, doc: any) {
|
||||
const selectedCollection = mainDB.collection(collection);
|
||||
|
@ -20,9 +21,13 @@ export async function readOne(collection: string, find: Filter<Document>) {
|
|||
return await selectedCollection.findOne(find);
|
||||
} // MongoDB and its finicky type safety
|
||||
|
||||
export function readMany(collection: string, find: Filter<Document>) {
|
||||
export function readMany(
|
||||
collection: string,
|
||||
find: Filter<Document>,
|
||||
sort: Sort
|
||||
) {
|
||||
const selectedCollection = mainDB.collection(collection);
|
||||
return selectedCollection.find(find);
|
||||
return selectedCollection.find(find).sort(sort);
|
||||
}
|
||||
|
||||
export async function countMany(collection: string, find: any) {
|
||||
|
@ -45,7 +50,10 @@ export async function deleteOne(collection: string, find: any) {
|
|||
return await selectedCollection.findOneAndDelete(find);
|
||||
}
|
||||
|
||||
export async function cursorToArray<T>(cursor: FindCursor<T>, func: (input: any) => any = x => x) {
|
||||
export async function cursorToArray<T>(
|
||||
cursor: FindCursor<T>,
|
||||
func: (input: any) => any = x => x
|
||||
) {
|
||||
let array: T[] = [];
|
||||
while (await cursor.hasNext()) {
|
||||
array.push(await func(await cursor.next()));
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { ClientFlair, ServerFlair } from "@/types/flair";
|
||||
import { sanitize } from "../utility/merge";
|
||||
|
||||
export async function ServerFlairToClientFlair(serverFlair: ServerFlair): Promise<ClientFlair> {
|
||||
export async function ServerFlairToClientFlair(
|
||||
serverFlair: ServerFlair
|
||||
): Promise<ClientFlair> {
|
||||
const sanitizedFlair = sanitize(serverFlair);
|
||||
let clientFlair: ClientFlair = {
|
||||
...sanitizedFlair
|
||||
|
|
|
@ -1,23 +1,56 @@
|
|||
import { Class, TrueSign } from "@/types/assist/extended_zodiac";
|
||||
import { SubmitTroll } from "@/types/client/troll";
|
||||
import { ClientTroll, ServerTroll } from "@/types/troll";
|
||||
import { getManyFlairs } from "../flair";
|
||||
import { getManyUsers } from "../user";
|
||||
import { cutArray, sanitize } from "../utility/merge";
|
||||
import { cutArray, cutObject, sanitize } from "../utility/merge";
|
||||
import { ServerFlairToClientFlair } from "./flair";
|
||||
import { ServerUserToClientUser } from "./user";
|
||||
|
||||
export async function ServerTrollToClientTroll(serverTroll: ServerTroll): Promise<ClientTroll> {
|
||||
export async function ServerTrollToClientTroll(
|
||||
serverTroll: ServerTroll
|
||||
): Promise<ClientTroll> {
|
||||
const sanitizedTroll = sanitize(serverTroll);
|
||||
const owners = await getManyUsers({ _id: { $in: serverTroll.owners } }, ServerUserToClientUser);
|
||||
const flairs = await getManyFlairs({ _id: { $in: serverTroll.flairs } }, ServerFlairToClientFlair);
|
||||
const flairs = await getManyFlairs(
|
||||
{ _id: { $in: serverTroll.flairs } },
|
||||
ServerFlairToClientFlair
|
||||
);
|
||||
let clientTroll: ClientTroll = {
|
||||
...sanitizedTroll,
|
||||
trueSign: TrueSign[serverTroll.trueSign],
|
||||
falseSign: serverTroll.falseSign != null ? TrueSign[serverTroll.falseSign] : null,
|
||||
falseSign:
|
||||
serverTroll.falseSign != null
|
||||
? TrueSign[serverTroll.falseSign]
|
||||
: null,
|
||||
class: Class[serverTroll.class],
|
||||
owners: cutArray(owners),
|
||||
owners: [],
|
||||
flairs: cutArray(flairs)
|
||||
};
|
||||
|
||||
return clientTroll;
|
||||
}
|
||||
|
||||
export function SubmitTrollToServerTroll(
|
||||
submitTroll: Partial<SubmitTroll>
|
||||
): Omit<Partial<ServerTroll>, "_id"> {
|
||||
let serverTroll: Omit<Partial<ServerTroll>, "_id"> = {
|
||||
...submitTroll,
|
||||
quirks: submitTroll.quirks
|
||||
? Object.fromEntries(submitTroll.quirks)
|
||||
: undefined,
|
||||
owners: [],
|
||||
flairs: [],
|
||||
updatedDate: new Date()
|
||||
};
|
||||
return serverTroll;
|
||||
}
|
||||
|
||||
export function MergeServerTrolls(
|
||||
submitTroll: ServerTroll,
|
||||
merge: Partial<Omit<ServerTroll, "_id">>
|
||||
): ServerTroll {
|
||||
let serverTroll: ServerTroll = {
|
||||
...submitTroll,
|
||||
...cutObject(merge),
|
||||
updatedDate: new Date()
|
||||
};
|
||||
return serverTroll;
|
||||
}
|
||||
|
|
|
@ -6,9 +6,14 @@ import { getManyFlairs } from "../flair";
|
|||
import { cutArray, cutObject, removeCode, sanitize } from "../utility/merge";
|
||||
import { ServerFlairToClientFlair } from "./flair";
|
||||
|
||||
export async function ServerUserToClientUser(serverUser: ServerUser): Promise<ClientUser> {
|
||||
export async function ServerUserToClientUser(
|
||||
serverUser: ServerUser
|
||||
): Promise<ClientUser> {
|
||||
const sanitizedUser = removeCode(sanitize(serverUser));
|
||||
const flairs = await getManyFlairs({ _id: { $in: serverUser.flairs } }, ServerFlairToClientFlair);
|
||||
const flairs = await getManyFlairs(
|
||||
{ _id: { $in: serverUser.flairs } },
|
||||
ServerFlairToClientFlair
|
||||
);
|
||||
let clientUser: ClientUser = {
|
||||
...sanitizedUser,
|
||||
trueSign: TrueSign[serverUser.trueSign],
|
||||
|
@ -18,7 +23,9 @@ export async function ServerUserToClientUser(serverUser: ServerUser): Promise<Cl
|
|||
return clientUser;
|
||||
}
|
||||
|
||||
export function SubmitUserToServerUser(submitUser: Partial<SubmitUser>): Omit<Partial<ServerUser>, "_id"> {
|
||||
export function SubmitUserToServerUser(
|
||||
submitUser: Partial<SubmitUser>
|
||||
): Omit<Partial<ServerUser>, "_id"> {
|
||||
let serverUser: Omit<Partial<ServerUser>, "_id"> = {
|
||||
...submitUser,
|
||||
flairs: [],
|
||||
|
@ -28,7 +35,10 @@ export function SubmitUserToServerUser(submitUser: Partial<SubmitUser>): Omit<Pa
|
|||
return serverUser;
|
||||
}
|
||||
|
||||
export function MergeServerUsers(submitUser: ServerUser, merge: Partial<Omit<ServerUser, "_id">>): ServerUser {
|
||||
export function MergeServerUsers(
|
||||
submitUser: ServerUser,
|
||||
merge: Partial<Omit<ServerUser, "_id">>
|
||||
): ServerUser {
|
||||
let serverUser: ServerUser = {
|
||||
...submitUser,
|
||||
...cutObject(merge),
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
import { ServerFlair } from "@/types/flair";
|
||||
import { Sort } from "mongodb";
|
||||
import { Filter, cursorToArray, readMany, readOne } from "../db/crud";
|
||||
|
||||
const FlairSort: Sort = { updatedDate: -1, _id: -1 };
|
||||
|
||||
/**
|
||||
* A function that returns one ServerFlairs from the database.
|
||||
* @param query A partial Find query. Can contain an ID.
|
||||
* @returns A ServerFlair.
|
||||
*/
|
||||
|
||||
export async function getSingleFlair(query: Filter<ServerFlair>): Promise<ServerFlair | null> {
|
||||
export async function getSingleFlair(
|
||||
query: Filter<ServerFlair>
|
||||
): Promise<ServerFlair | null> {
|
||||
const flair = (await readOne("flairs", query)) as ServerFlair | null;
|
||||
return flair;
|
||||
}
|
||||
|
@ -23,6 +28,9 @@ export async function getManyFlairs(
|
|||
query: Filter<ServerFlair>,
|
||||
func?: (input: any) => any
|
||||
): Promise<(ServerFlair | null)[]> {
|
||||
const flair = (await cursorToArray(readMany("flairs", query), func)) as (ServerFlair | null)[];
|
||||
const flair = (await cursorToArray(
|
||||
readMany("flairs", query, FlairSort),
|
||||
func
|
||||
)) as (ServerFlair | null)[];
|
||||
return flair;
|
||||
}
|
||||
|
|
27
src/lib/trollcall/perms.ts
Normal file
27
src/lib/trollcall/perms.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { Levels, Permissions } from "@/permissions";
|
||||
import { ServerUser } from "@/types/user";
|
||||
|
||||
export function getLevel(user: ServerUser) {
|
||||
let highestLevel = "USER";
|
||||
for (let level of Permissions) {
|
||||
if (user.flairs.some(oid => level.values.includes(oid.toString())))
|
||||
highestLevel = level.name;
|
||||
}
|
||||
return highestLevel;
|
||||
}
|
||||
|
||||
export function compareLevels(level: string, compareLevel: string) {
|
||||
return Levels[level] > Levels[compareLevel];
|
||||
}
|
||||
|
||||
export function compareCredentials(
|
||||
user: ServerUser,
|
||||
cookies: Partial<{
|
||||
[key: string]: string;
|
||||
}>
|
||||
) {
|
||||
return (
|
||||
user.code === cookies.TROLLCALL_CODE &&
|
||||
user.name === cookies.TROLLCALL_NAME
|
||||
);
|
||||
}
|
|
@ -1,5 +1,15 @@
|
|||
import { ServerTroll } from "@/types/troll";
|
||||
import { Filter, cursorToArray, readMany, readOne } from "../db/crud";
|
||||
import { Sort } from "mongodb";
|
||||
import {
|
||||
Filter,
|
||||
createOne,
|
||||
cursorToArray,
|
||||
readMany,
|
||||
readOne,
|
||||
replaceOne
|
||||
} from "../db/crud";
|
||||
|
||||
const TrollSort: Sort = { updatedDate: -1, _id: -1 };
|
||||
|
||||
/**
|
||||
* A function that returns one ServerTrolls from the database.
|
||||
|
@ -7,7 +17,9 @@ import { Filter, cursorToArray, readMany, readOne } from "../db/crud";
|
|||
* @returns A ServerTroll.
|
||||
*/
|
||||
|
||||
export async function getSingleTroll(query: Filter<ServerTroll>): Promise<ServerTroll | null> {
|
||||
export async function getSingleTroll(
|
||||
query: Filter<ServerTroll>
|
||||
): Promise<ServerTroll | null> {
|
||||
const troll = (await readOne("trolls", query)) as ServerTroll | null;
|
||||
return troll;
|
||||
}
|
||||
|
@ -19,10 +31,59 @@ export async function getSingleTroll(query: Filter<ServerTroll>): Promise<Server
|
|||
* @returns An array of ServerTrolls.
|
||||
*/
|
||||
|
||||
export async function getManyTrolls(
|
||||
export async function getManyTrolls<T>(
|
||||
query: Filter<ServerTroll>,
|
||||
func?: (input: any) => any
|
||||
): Promise<(ServerTroll | null)[]> {
|
||||
const troll = (await cursorToArray(readMany("trolls", query), func)) as (ServerTroll | null)[];
|
||||
func?: (input: any) => T
|
||||
): Promise<(Awaited<T> | null)[]> {
|
||||
const troll = (await cursorToArray(
|
||||
readMany("trolls", query, TrollSort),
|
||||
func
|
||||
)) as (Awaited<T> | null)[];
|
||||
return troll;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that returns many ServerTrolls from the database using a FindCursor, limited by count.
|
||||
* @param query A partial Find query. Can contain an ID.
|
||||
* @param func A function to run on every ServerTroll returned. Helps reduce loops.
|
||||
* @returns An array of ServerTrolls.
|
||||
*/
|
||||
|
||||
export async function getManyPagedTrolls<T>(
|
||||
query: Filter<ServerTroll>,
|
||||
func?: (input: any) => T,
|
||||
count: number = 5,
|
||||
page: number = 0
|
||||
): Promise<(Awaited<T> | null)[]> {
|
||||
const find = readMany("trolls", query, TrollSort)
|
||||
.limit(count)
|
||||
.skip(page * count);
|
||||
const user = (await cursorToArray(find, func)) as (Awaited<T> | null)[];
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that puts one ServerTroll into the database.
|
||||
* @param troll A ServerTroll.
|
||||
* @returns A ServerTroll, or null, depending on if the operation succeeded.
|
||||
*/
|
||||
|
||||
export async function createTroll(
|
||||
troll: Omit<ServerTroll, "_id">
|
||||
): Promise<Omit<ServerTroll, "_id"> | null> {
|
||||
const newTroll = await createOne("trolls", troll);
|
||||
return newTroll.acknowledged ? troll : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that changes one database troll with the given params.
|
||||
* @param troll A ServerTroll.
|
||||
* @returns A ServerTroll, or null, depending on if the operation succeeded.
|
||||
*/
|
||||
|
||||
export async function changeTroll(
|
||||
troll: ServerTroll
|
||||
): Promise<ServerTroll | null> {
|
||||
const newTroll = await replaceOne("trolls", { _id: troll._id }, troll);
|
||||
return newTroll.acknowledged ? troll : null;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
import { ServerUser } from "@/types/user";
|
||||
import { Filter, createOne, cursorToArray, readMany, readOne, replaceOne } from "../db/crud";
|
||||
import { Sort } from "mongodb";
|
||||
import {
|
||||
Filter,
|
||||
createOne,
|
||||
cursorToArray,
|
||||
readMany,
|
||||
readOne,
|
||||
replaceOne
|
||||
} from "../db/crud";
|
||||
|
||||
const UserSort: Sort = { updatedDate: -1, _id: -1 };
|
||||
|
||||
/**
|
||||
* A function that returns one ServerUser from the database.
|
||||
|
@ -7,7 +17,9 @@ import { Filter, createOne, cursorToArray, readMany, readOne, replaceOne } from
|
|||
* @returns A ServerUser.
|
||||
*/
|
||||
|
||||
export async function getSingleUser(query: Filter<ServerUser>): Promise<ServerUser | null> {
|
||||
export async function getSingleUser(
|
||||
query: Filter<ServerUser>
|
||||
): Promise<ServerUser | null> {
|
||||
const user = (await readOne("users", query)) as ServerUser | null;
|
||||
return user;
|
||||
}
|
||||
|
@ -23,7 +35,30 @@ export async function getManyUsers<T>(
|
|||
query: Filter<ServerUser>,
|
||||
func?: (input: any) => T
|
||||
): Promise<(Awaited<T> | null)[]> {
|
||||
const user = (await cursorToArray(readMany("users", query), func)) as (Awaited<T> | null)[];
|
||||
const user = (await cursorToArray(
|
||||
readMany("users", query, UserSort),
|
||||
func
|
||||
)) as (Awaited<T> | null)[];
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that returns many ServerUsers from the database using a FindCursor, limited by count.
|
||||
* @param query A partial Find query. Can contain an ID.
|
||||
* @param func A function to run on every ServerUser returned. Helps reduce loops.
|
||||
* @returns An array of ServerUsers.
|
||||
*/
|
||||
|
||||
export async function getManyPagedUsers<T>(
|
||||
query: Filter<ServerUser>,
|
||||
func?: (input: any) => T,
|
||||
count: number = 5,
|
||||
page: number = 0
|
||||
): Promise<(Awaited<T> | null)[]> {
|
||||
const find = readMany("users", query, UserSort)
|
||||
.limit(count)
|
||||
.skip(page * count);
|
||||
const user = (await cursorToArray(find, func)) as (Awaited<T> | null)[];
|
||||
return user;
|
||||
}
|
||||
|
||||
|
@ -33,7 +68,9 @@ export async function getManyUsers<T>(
|
|||
* @returns A ServerUser, or null, depending on if the operation succeeded.
|
||||
*/
|
||||
|
||||
export async function createUser(user: Omit<ServerUser, "_id">): Promise<Omit<ServerUser, "_id"> | null> {
|
||||
export async function createUser(
|
||||
user: Omit<ServerUser, "_id">
|
||||
): Promise<Omit<ServerUser, "_id"> | null> {
|
||||
const newUser = await createOne("users", user);
|
||||
return newUser.acknowledged ? user : null;
|
||||
}
|
||||
|
|
|
@ -25,13 +25,17 @@ export function cutObject<T extends { [key: string]: any }>(object: T) {
|
|||
return cut as T;
|
||||
}
|
||||
|
||||
export function sanitize<Type extends WithId<{}>>(serverType: Type): Omit<Type, "_id"> {
|
||||
export function sanitize<Type extends WithId<{}>>(
|
||||
serverType: Type
|
||||
): Omit<Type, "_id"> {
|
||||
const sanitized: Partial<Type> = serverType;
|
||||
delete sanitized._id;
|
||||
return sanitized as Omit<Type, "_id">;
|
||||
}
|
||||
|
||||
export function removeCode<Type extends { code: string }>(serverType: Type): Omit<Type, "code"> {
|
||||
export function removeCode<Type extends { code: string }>(
|
||||
serverType: Type
|
||||
): Omit<Type, "code"> {
|
||||
const sanitized: Partial<Type> = serverType;
|
||||
delete sanitized.code;
|
||||
return sanitized as Omit<Type, "code">;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import "@/styles/globals.css";
|
||||
import type { AppProps } from "next/app";
|
||||
import React from "react";
|
||||
|
||||
|
|
3
src/pages/_error.tsx
Normal file
3
src/pages/_error.tsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default function ErrorPage() {
|
||||
return "404";
|
||||
}
|
21
src/pages/api/troll/.../[[...page]]/index.ts
Normal file
21
src/pages/api/troll/.../[[...page]]/index.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { ServerTrollToClientTroll } from "@/lib/trollcall/convert/troll";
|
||||
import { getManyPagedTrolls } from "@/lib/trollcall/troll";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const { method, query } = req;
|
||||
const page = query.page ? query.page[0] : 0;
|
||||
if (method === "GET") {
|
||||
const trolls = await getManyPagedTrolls(
|
||||
{},
|
||||
ServerTrollToClientTroll,
|
||||
5,
|
||||
page
|
||||
);
|
||||
if (trolls == null) return res.status(404).end();
|
||||
res.json(trolls);
|
||||
} else return res.status(405).end();
|
||||
}
|
28
src/pages/api/troll/[user]/.../[[...page]]/index.ts
Normal file
28
src/pages/api/troll/[user]/.../[[...page]]/index.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { ServerTrollToClientTroll } from "@/lib/trollcall/convert/troll";
|
||||
import { getManyPagedTrolls } from "@/lib/trollcall/troll";
|
||||
import { getSingleUser } from "@/lib/trollcall/user";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const { method, query } = req;
|
||||
const page = query.page ? query.page[0] : 0;
|
||||
if (method === "GET") {
|
||||
const user = await getSingleUser({
|
||||
name: query.user
|
||||
});
|
||||
if (user == null) return res.status(404).end();
|
||||
const trolls = await getManyPagedTrolls(
|
||||
{
|
||||
"owners.0": user._id
|
||||
},
|
||||
ServerTrollToClientTroll,
|
||||
5,
|
||||
page
|
||||
);
|
||||
if (trolls == null) return res.status(404).end();
|
||||
res.json(trolls);
|
||||
} else return res.status(405).end();
|
||||
}
|
73
src/pages/api/troll/[user]/[troll]/index.ts
Normal file
73
src/pages/api/troll/[user]/[troll]/index.ts
Normal file
|
@ -0,0 +1,73 @@
|
|||
import {
|
||||
MergeServerTrolls,
|
||||
ServerTrollToClientTroll,
|
||||
SubmitTrollToServerTroll
|
||||
} from "@/lib/trollcall/convert/troll";
|
||||
import {
|
||||
compareCredentials,
|
||||
compareLevels,
|
||||
getLevel
|
||||
} from "@/lib/trollcall/perms";
|
||||
import { changeTroll, getSingleTroll } from "@/lib/trollcall/troll";
|
||||
import { getSingleUser } from "@/lib/trollcall/user";
|
||||
import { SubmitTroll } from "@/types/client/troll";
|
||||
import { PartialUserSchema } from "@/types/client/user";
|
||||
import { Router } from "express";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export const trollRouter = Router();
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const { query, cookies, method, body } = req;
|
||||
if (method === "GET") {
|
||||
const user = await getSingleUser({
|
||||
name: query.user
|
||||
});
|
||||
if (user == null) return res.status(404).end();
|
||||
const troll = await getSingleTroll({
|
||||
"name.0": query.troll,
|
||||
"owners.0": user._id
|
||||
});
|
||||
if (troll == null) return res.status(404).end();
|
||||
const serverTroll = await ServerTrollToClientTroll(troll);
|
||||
res.json(serverTroll);
|
||||
} else if (method === "PUT") {
|
||||
let validatedTroll: Partial<SubmitTroll>;
|
||||
try {
|
||||
validatedTroll = (await PartialUserSchema.validate(
|
||||
body
|
||||
)) as Partial<SubmitTroll>;
|
||||
} catch (err) {
|
||||
return res.status(400).send(err);
|
||||
}
|
||||
const checkUser = await getSingleUser({
|
||||
name: query.user
|
||||
});
|
||||
if (checkUser == null) return res.status(404).end();
|
||||
if (!compareCredentials(checkUser, cookies)) {
|
||||
const thisUser = await getSingleUser({
|
||||
name: query.user
|
||||
});
|
||||
if (thisUser == null || !compareCredentials(thisUser, cookies))
|
||||
return res.status(403).end();
|
||||
if (!compareLevels(getLevel(thisUser), "MODERATOR"))
|
||||
return res.status(403).end();
|
||||
}
|
||||
const editingTroll = await getSingleTroll({
|
||||
"name.0": query.troll,
|
||||
"owners": checkUser._id
|
||||
});
|
||||
if (editingTroll == null) return res.status(404).end();
|
||||
const serverTroll = SubmitTrollToServerTroll(validatedTroll);
|
||||
const bothTrolls = MergeServerTrolls(editingTroll, serverTroll);
|
||||
const newTroll = await changeTroll(bothTrolls);
|
||||
if (newTroll == null) return res.status(503).end();
|
||||
res.json(newTroll);
|
||||
} else return res.status(405).end();
|
||||
}
|
||||
|
||||
// 1119731948118605995
|
||||
// 1089603977131331584
|
42
src/pages/api/troll/index.ts
Normal file
42
src/pages/api/troll/index.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { SubmitTrollToServerTroll } from "@/lib/trollcall/convert/troll";
|
||||
import { compareCredentials } from "@/lib/trollcall/perms";
|
||||
import { createTroll, getSingleTroll } from "@/lib/trollcall/troll";
|
||||
import { getSingleUser } from "@/lib/trollcall/user";
|
||||
import { SubmitTrollSchema } from "@/types/client/troll";
|
||||
import { ServerTroll } from "@/types/troll";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const { body, method, cookies } = req;
|
||||
if (method === "POST") {
|
||||
let validatedTroll;
|
||||
try {
|
||||
validatedTroll = await SubmitTrollSchema.validate(body);
|
||||
} catch (err) {
|
||||
return res.status(400).send(err);
|
||||
}
|
||||
const checkUser = await getSingleUser({
|
||||
name: cookies.TROLLCALL_NAME
|
||||
});
|
||||
if (checkUser == null) return res.status(404).end();
|
||||
if (!compareCredentials(checkUser, cookies))
|
||||
return res.status(403).end();
|
||||
const checkExistingTroll = await getSingleTroll({
|
||||
"name.0": validatedTroll.name[0],
|
||||
"owners": checkUser._id
|
||||
});
|
||||
if (checkExistingTroll != null) return res.status(409).end();
|
||||
// we are sure this object is full, so cast partial
|
||||
let serverTroll = SubmitTrollToServerTroll(validatedTroll) as Omit<
|
||||
ServerTroll,
|
||||
"_id"
|
||||
>;
|
||||
serverTroll.owners[0] = checkUser._id;
|
||||
const newTroll = await createTroll(serverTroll);
|
||||
if (newTroll == null) return res.status(503).end();
|
||||
res.json(newTroll);
|
||||
} else return res.status(405).end();
|
||||
}
|
21
src/pages/api/user/.../[[...page]]/index.ts
Normal file
21
src/pages/api/user/.../[[...page]]/index.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { ServerUserToClientUser } from "@/lib/trollcall/convert/user";
|
||||
import { getManyPagedUsers } from "@/lib/trollcall/user";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const { method, query } = req;
|
||||
const page = query.page ? query.page[0] : 0;
|
||||
if (method === "GET") {
|
||||
const users = await getManyPagedUsers(
|
||||
{},
|
||||
ServerUserToClientUser,
|
||||
5,
|
||||
page
|
||||
);
|
||||
if (users == null) return res.status(404).end();
|
||||
res.json(users);
|
||||
} else return res.status(405).end();
|
||||
}
|
|
@ -1,10 +1,22 @@
|
|||
import { MergeServerUsers, ServerUserToClientUser, SubmitUserToServerUser } from "@/lib/trollcall/convert/user";
|
||||
import {
|
||||
MergeServerUsers,
|
||||
ServerUserToClientUser,
|
||||
SubmitUserToServerUser
|
||||
} from "@/lib/trollcall/convert/user";
|
||||
import {
|
||||
compareCredentials,
|
||||
compareLevels,
|
||||
getLevel
|
||||
} from "@/lib/trollcall/perms";
|
||||
import { changeUser, getSingleUser } from "@/lib/trollcall/user";
|
||||
import { PartialUserSchema, SubmitUser } from "@/types/client/user";
|
||||
import { serialize } from "cookie";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const { body, cookies, query, method } = req;
|
||||
if (method === "GET") {
|
||||
const user = await getSingleUser({
|
||||
|
@ -16,7 +28,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
} else if (method === "PUT") {
|
||||
let validatedUser: Partial<SubmitUser>;
|
||||
try {
|
||||
validatedUser = (await PartialUserSchema.validate(body)) as Partial<SubmitUser>;
|
||||
validatedUser = (await PartialUserSchema.validate(
|
||||
body
|
||||
)) as Partial<SubmitUser>;
|
||||
} catch (err) {
|
||||
return res.status(400).send(err);
|
||||
}
|
||||
|
@ -24,16 +38,33 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||
name: query.user
|
||||
});
|
||||
if (checkExistingUser == null) return res.status(404).end();
|
||||
if (checkExistingUser.code !== cookies.TROLLCALL_CODE || checkExistingUser.name !== cookies.TROLLCALL_NAME)
|
||||
return res.status(403).end();
|
||||
if (!compareCredentials(checkExistingUser, cookies)) {
|
||||
const thisUser = await getSingleUser({
|
||||
name: query.user
|
||||
});
|
||||
if (thisUser == null || !compareCredentials(thisUser, cookies))
|
||||
return res.status(403).end();
|
||||
if (!compareLevels(getLevel(thisUser), "MODERATOR"))
|
||||
return res.status(403).end();
|
||||
}
|
||||
const serverUser = SubmitUserToServerUser(validatedUser);
|
||||
const bothUsers = MergeServerUsers(checkExistingUser, serverUser);
|
||||
const newUser = await changeUser(bothUsers);
|
||||
if (newUser == null) return res.status(503).end();
|
||||
// Give cookies, redundant style
|
||||
res.setHeader("Set-Cookie", [
|
||||
serialize("TROLLCALL_NAME", newUser.name, { path: "/", maxAge: 31540000 }),
|
||||
serialize("TROLLCALL_NAME", newUser.code, { path: "/", maxAge: 31540000 })
|
||||
serialize("TROLLCALL_NAME", newUser.name, {
|
||||
path: "/",
|
||||
maxAge: 31540000
|
||||
}),
|
||||
serialize("TROLLCALL_CODE", newUser.code, {
|
||||
path: "/",
|
||||
maxAge: 31540000
|
||||
}),
|
||||
serialize("TROLLCALL_PFP", newUser.pfp ?? "", {
|
||||
path: "/",
|
||||
maxAge: 31540000
|
||||
})
|
||||
]).json(newUser);
|
||||
} else return res.status(405).end();
|
||||
}
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
import { ServerTrollToClientTroll } from "@/lib/trollcall/convert/troll";
|
||||
import { getSingleTroll } from "@/lib/trollcall/troll";
|
||||
import { getSingleUser } from "@/lib/trollcall/user";
|
||||
import { Router } from "express";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export const trollRouter = Router();
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { query, method } = req;
|
||||
if (method === "GET") {
|
||||
const user = await getSingleUser({
|
||||
name: query.user
|
||||
});
|
||||
if (user == null) return res.status(404).end();
|
||||
const troll = await getSingleTroll({
|
||||
"name.0": query.troll,
|
||||
"owners.0": user._id
|
||||
});
|
||||
if (troll == null) return res.status(404).end();
|
||||
const serverTroll = await ServerTrollToClientTroll(troll);
|
||||
res.json(serverTroll);
|
||||
} else return res.status(405).end();
|
||||
}
|
|
@ -5,26 +5,44 @@ import { ServerUser } from "@/types/user";
|
|||
import { serialize } from "cookie";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const { body } = req;
|
||||
let validatedUser;
|
||||
try {
|
||||
validatedUser = await SubmitUserSchema.validate(body);
|
||||
} catch (err) {
|
||||
return res.status(400).send(err);
|
||||
}
|
||||
const checkExistingUser = await getSingleUser({
|
||||
name: validatedUser.name
|
||||
});
|
||||
if (checkExistingUser != null) return res.status(409).end();
|
||||
// we are sure this object is full, so cast partial
|
||||
const serverUser = SubmitUserToServerUser(validatedUser) as Omit<ServerUser, "_id">;
|
||||
const newUser = await createUser(serverUser);
|
||||
if (newUser == null) return res.status(503).end();
|
||||
// Give cookies
|
||||
res.setHeader("Set-Cookie", [
|
||||
serialize("TROLLCALL_NAME", newUser.name, { path: "/", maxAge: 31540000 }),
|
||||
serialize("TROLLCALL_NAME", newUser.code, { path: "/", maxAge: 31540000 })
|
||||
]);
|
||||
res.json(newUser);
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
const { body, method } = req;
|
||||
if (method === "POST") {
|
||||
let validatedUser;
|
||||
try {
|
||||
validatedUser = await SubmitUserSchema.validate(body);
|
||||
} catch (err) {
|
||||
return res.status(400).send(err);
|
||||
}
|
||||
const checkExistingUser = await getSingleUser({
|
||||
name: validatedUser.name
|
||||
});
|
||||
if (checkExistingUser != null) return res.status(409).end();
|
||||
// we are sure this object is full, so cast partial
|
||||
const serverUser = SubmitUserToServerUser(validatedUser) as Omit<
|
||||
ServerUser,
|
||||
"_id"
|
||||
>;
|
||||
const newUser = await createUser(serverUser);
|
||||
if (newUser == null) return res.status(503).end();
|
||||
// Give cookies
|
||||
res.setHeader("Set-Cookie", [
|
||||
serialize("TROLLCALL_NAME", newUser.name, {
|
||||
path: "/",
|
||||
maxAge: 31540000
|
||||
}),
|
||||
serialize("TROLLCALL_CODE", newUser.code, {
|
||||
path: "/",
|
||||
maxAge: 31540000
|
||||
}),
|
||||
serialize("TROLLCALL_PFP", newUser.pfp ?? "", {
|
||||
path: "/",
|
||||
maxAge: 31540000
|
||||
})
|
||||
]);
|
||||
res.json(newUser);
|
||||
} else return res.status(405).end();
|
||||
}
|
||||
|
|
|
@ -1,115 +1,3 @@
|
|||
import styles from "@/styles/Home.module.css";
|
||||
import { Inter } from "next/font/google";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import React from "react";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Create Next App</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Generated by create next app"
|
||||
/>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
href="/favicon.ico"
|
||||
/>
|
||||
</Head>
|
||||
<main className={`${styles.main} ${inter.className}`}>
|
||||
<div className={styles.description}>
|
||||
<p>
|
||||
Get started by editing
|
||||
<code className={styles.code}>src/pages/index.tsx</code>
|
||||
</p>
|
||||
<div>
|
||||
<a
|
||||
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
By{" "}
|
||||
<Image
|
||||
src="/vercel.svg"
|
||||
alt="Vercel Logo"
|
||||
className={styles.vercelLogo}
|
||||
width={100}
|
||||
height={24}
|
||||
priority
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.center}>
|
||||
<Image
|
||||
className={styles.logo}
|
||||
src="/next.svg"
|
||||
alt="Next.js Logo"
|
||||
width={180}
|
||||
height={37}
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.grid}>
|
||||
<a
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
|
||||
className={styles.card}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2>
|
||||
Docs <span>-></span>
|
||||
</h2>
|
||||
<p>Find in-depth information about Next.js features and API.</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
|
||||
className={styles.card}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2>
|
||||
Learn <span>-></span>
|
||||
</h2>
|
||||
<p>Learn about Next.js in an interactive course with quizzes!</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
|
||||
className={styles.card}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2>
|
||||
Templates <span>-></span>
|
||||
</h2>
|
||||
<p>Discover and deploy boilerplate example Next.js projects.</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
|
||||
className={styles.card}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<h2>
|
||||
Deploy <span>-></span>
|
||||
</h2>
|
||||
<p>Instantly deploy your Next.js site to a shareable URL with Vercel.</p>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
return "Welcome home!";
|
||||
}
|
||||
|
|
24
src/permissions.ts
Normal file
24
src/permissions.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
export const Permissions = [
|
||||
{
|
||||
name: "SUPPORTER",
|
||||
values: ["6487ec00cd90481163238f79", "648b76878de421ccb5937f54"]
|
||||
},
|
||||
{
|
||||
name: "MODERATOR",
|
||||
values: ["649ef0a9c62f6a4c8eb3f98a"]
|
||||
},
|
||||
{
|
||||
name: "ADMIN",
|
||||
values: ["6488077200893ada4afdd09c"]
|
||||
},
|
||||
{
|
||||
name: "OWNER",
|
||||
values: ["6487eb5fcd90481163238f6a"]
|
||||
}
|
||||
];
|
||||
export const Levels: { [key: string]: number } = {
|
||||
"SUPPORTER": 0,
|
||||
"MODERATOR": 1,
|
||||
"ADMIN": 2,
|
||||
"OWNER": 3
|
||||
};
|
|
@ -1,229 +0,0 @@
|
|||
.main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 6rem;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.description {
|
||||
display: inherit;
|
||||
justify-content: inherit;
|
||||
align-items: inherit;
|
||||
font-size: 0.85rem;
|
||||
max-width: var(--max-width);
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.description a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.description p {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
background-color: rgba(var(--callout-rgb), 0.5);
|
||||
border: 1px solid rgba(var(--callout-border-rgb), 0.3);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.code {
|
||||
font-weight: 700;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(25%, auto));
|
||||
width: var(--max-width);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 1rem 1.2rem;
|
||||
border-radius: var(--border-radius);
|
||||
background: rgba(var(--card-rgb), 0);
|
||||
border: 1px solid rgba(var(--card-border-rgb), 0);
|
||||
transition: background 200ms, border 200ms;
|
||||
}
|
||||
|
||||
.card span {
|
||||
display: inline-block;
|
||||
transition: transform 200ms;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.7rem;
|
||||
}
|
||||
|
||||
.card p {
|
||||
margin: 0;
|
||||
opacity: 0.6;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
max-width: 30ch;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding: 4rem 0;
|
||||
}
|
||||
|
||||
.center::before {
|
||||
background: var(--secondary-glow);
|
||||
border-radius: 50%;
|
||||
width: 480px;
|
||||
height: 360px;
|
||||
margin-left: -400px;
|
||||
}
|
||||
|
||||
.center::after {
|
||||
background: var(--primary-glow);
|
||||
width: 240px;
|
||||
height: 180px;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.center::before,
|
||||
.center::after {
|
||||
content: '';
|
||||
left: 50%;
|
||||
position: absolute;
|
||||
filter: blur(45px);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.logo {
|
||||
position: relative;
|
||||
}
|
||||
/* Enable hover only on non-touch devices */
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
.card:hover {
|
||||
background: rgba(var(--card-rgb), 0.1);
|
||||
border: 1px solid rgba(var(--card-border-rgb), 0.15);
|
||||
}
|
||||
|
||||
.card:hover span {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
.card:hover span {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile */
|
||||
@media (max-width: 700px) {
|
||||
.content {
|
||||
padding: 4rem;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
margin-bottom: 120px;
|
||||
max-width: 320px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 1rem 2.5rem;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.center {
|
||||
padding: 8rem 0 6rem;
|
||||
}
|
||||
|
||||
.center::before {
|
||||
transform: none;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.description a {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.description p,
|
||||
.description div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.description p {
|
||||
align-items: center;
|
||||
inset: 0 0 auto;
|
||||
padding: 2rem 1rem 1.4rem;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25);
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
rgba(var(--background-start-rgb), 1),
|
||||
rgba(var(--callout-rgb), 0.5)
|
||||
);
|
||||
background-clip: padding-box;
|
||||
backdrop-filter: blur(24px);
|
||||
}
|
||||
|
||||
.description div {
|
||||
align-items: flex-end;
|
||||
pointer-events: none;
|
||||
inset: auto 0 0;
|
||||
padding: 2rem;
|
||||
height: 200px;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent 0%,
|
||||
rgb(var(--background-end-rgb)) 40%
|
||||
);
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tablet and Smaller Desktop */
|
||||
@media (min-width: 701px) and (max-width: 1120px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(2, 50%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.vercelLogo {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.logo {
|
||||
filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
:root {
|
||||
--max-width: 1100px;
|
||||
--border-radius: 12px;
|
||||
--font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
|
||||
'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro',
|
||||
'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace;
|
||||
|
||||
--foreground-rgb: 0, 0, 0;
|
||||
--background-start-rgb: 214, 219, 220;
|
||||
--background-end-rgb: 255, 255, 255;
|
||||
|
||||
--primary-glow: conic-gradient(
|
||||
from 180deg at 50% 50%,
|
||||
#16abff33 0deg,
|
||||
#0885ff33 55deg,
|
||||
#54d6ff33 120deg,
|
||||
#0071ff33 160deg,
|
||||
transparent 360deg
|
||||
);
|
||||
--secondary-glow: radial-gradient(
|
||||
rgba(255, 255, 255, 1),
|
||||
rgba(255, 255, 255, 0)
|
||||
);
|
||||
|
||||
--tile-start-rgb: 239, 245, 249;
|
||||
--tile-end-rgb: 228, 232, 233;
|
||||
--tile-border: conic-gradient(
|
||||
#00000080,
|
||||
#00000040,
|
||||
#00000030,
|
||||
#00000020,
|
||||
#00000010,
|
||||
#00000010,
|
||||
#00000080
|
||||
);
|
||||
|
||||
--callout-rgb: 238, 240, 241;
|
||||
--callout-border-rgb: 172, 175, 176;
|
||||
--card-rgb: 180, 185, 188;
|
||||
--card-border-rgb: 131, 134, 135;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--foreground-rgb: 255, 255, 255;
|
||||
--background-start-rgb: 0, 0, 0;
|
||||
--background-end-rgb: 0, 0, 0;
|
||||
|
||||
--primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
|
||||
--secondary-glow: linear-gradient(
|
||||
to bottom right,
|
||||
rgba(1, 65, 255, 0),
|
||||
rgba(1, 65, 255, 0),
|
||||
rgba(1, 65, 255, 0.3)
|
||||
);
|
||||
|
||||
--tile-start-rgb: 2, 13, 46;
|
||||
--tile-end-rgb: 2, 5, 19;
|
||||
--tile-border: conic-gradient(
|
||||
#ffffff80,
|
||||
#ffffff40,
|
||||
#ffffff30,
|
||||
#ffffff20,
|
||||
#ffffff10,
|
||||
#ffffff10,
|
||||
#ffffff80
|
||||
);
|
||||
|
||||
--callout-rgb: 20, 20, 20;
|
||||
--callout-border-rgb: 108, 108, 108;
|
||||
--card-rgb: 100, 100, 100;
|
||||
--card-border-rgb: 200, 200, 200;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
color: rgb(var(--foreground-rgb));
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
rgb(var(--background-end-rgb))
|
||||
)
|
||||
rgb(var(--background-start-rgb));
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
|
@ -3,100 +3,100 @@ import * as yup from "yup";
|
|||
export type ColorTypes = [number, number, number];
|
||||
|
||||
export const ColorSchema = yup.tuple([
|
||||
yup.number().required().min(0).max(255),
|
||||
yup.number().required().min(0).max(255),
|
||||
yup.number().required().min(0).max(255),
|
||||
yup.number().required().min(0).max(255),
|
||||
yup.number().required().min(0).max(255),
|
||||
yup.number().required().min(0).max(255)
|
||||
]);
|
||||
|
||||
const clamp = (n: number, mi: number, ma: number) =>
|
||||
Math.max(mi, Math.min(n, ma));
|
||||
Math.max(mi, Math.min(n, ma));
|
||||
|
||||
export class Color3 {
|
||||
R: number;
|
||||
G: number;
|
||||
B: number;
|
||||
constructor(red: number, green: number, blue: number) {
|
||||
this.R = red;
|
||||
this.G = green;
|
||||
this.B = blue;
|
||||
}
|
||||
static clone(color3: Color3) {
|
||||
return new Color3(color3.R, color3.G, color3.B);
|
||||
}
|
||||
static fromRGB(red: number, green: number, blue: number) {
|
||||
return new Color3(red / 255, green / 255, blue / 255);
|
||||
}
|
||||
static fromHex(hex: string) {
|
||||
// @ts-ignore
|
||||
const hexSplit: [number, number, number] = (
|
||||
hex.match(new RegExp(`[0-9a-f]{1,${hex.length / 3}}`, "gi")) ?? [
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
]
|
||||
).map((x) => parseInt(x, 16) / 255);
|
||||
return new Color3(...hexSplit);
|
||||
}
|
||||
static fromInt(int: number) {
|
||||
return new Color3(
|
||||
(int & 0xff0000) >> 16,
|
||||
(int & 0x00ff00) >> 8,
|
||||
int & 0x0000ff
|
||||
);
|
||||
}
|
||||
static assumeColor(
|
||||
value: [number, number, number] | string | number,
|
||||
rgb?: boolean
|
||||
) {
|
||||
if (Color3.isColor(value)) {
|
||||
if (Array.isArray(value))
|
||||
return rgb ? Color3.fromRGB(...value) : new Color3(...value);
|
||||
else if (typeof value === "string") return Color3.fromHex(value);
|
||||
else if (typeof value === "number") return Color3.fromInt(value);
|
||||
R: number;
|
||||
G: number;
|
||||
B: number;
|
||||
constructor(red: number, green: number, blue: number) {
|
||||
this.R = red;
|
||||
this.G = green;
|
||||
this.B = blue;
|
||||
}
|
||||
static clone(color3: Color3) {
|
||||
return new Color3(color3.R, color3.G, color3.B);
|
||||
}
|
||||
static fromRGB(red: number, green: number, blue: number) {
|
||||
return new Color3(red / 255, green / 255, blue / 255);
|
||||
}
|
||||
static fromHex(hex: string) {
|
||||
// @ts-ignore
|
||||
const hexSplit: [number, number, number] = (
|
||||
hex.match(new RegExp(`[0-9a-f]{1,${hex.length / 3}}`, "gi")) ?? [
|
||||
"0",
|
||||
"0",
|
||||
"0"
|
||||
]
|
||||
).map(x => parseInt(x, 16) / 255);
|
||||
return new Color3(...hexSplit);
|
||||
}
|
||||
static fromInt(int: number) {
|
||||
return new Color3(
|
||||
(int & 0xff0000) >> 16,
|
||||
(int & 0x00ff00) >> 8,
|
||||
int & 0x0000ff
|
||||
);
|
||||
}
|
||||
static assumeColor(
|
||||
value: [number, number, number] | string | number,
|
||||
rgb?: boolean
|
||||
) {
|
||||
if (Color3.isColor(value)) {
|
||||
if (Array.isArray(value))
|
||||
return rgb ? Color3.fromRGB(...value) : new Color3(...value);
|
||||
else if (typeof value === "string") return Color3.fromHex(value);
|
||||
else if (typeof value === "number") return Color3.fromInt(value);
|
||||
}
|
||||
throw new Error("Not a valid color type");
|
||||
}
|
||||
static isColor(
|
||||
value: [number, number, number] | string | number,
|
||||
rgb?: boolean
|
||||
) {
|
||||
return (
|
||||
(Array.isArray(value) &&
|
||||
value.length === 3 &&
|
||||
value.every(x => typeof x === "number" && !isNaN(x))) ||
|
||||
(typeof value === "string" && !isNaN(parseInt(value, 16))) ||
|
||||
(typeof value === "number" && !isNaN(value))
|
||||
);
|
||||
}
|
||||
throw new Error("Not a valid color type");
|
||||
}
|
||||
static isColor(
|
||||
value: [number, number, number] | string | number,
|
||||
rgb?: boolean
|
||||
) {
|
||||
return (
|
||||
(Array.isArray(value) &&
|
||||
value.length === 3 &&
|
||||
value.every((x) => typeof x === "number" && !isNaN(x))) ||
|
||||
(typeof value === "string" && !isNaN(parseInt(value, 16))) ||
|
||||
(typeof value === "number" && !isNaN(value))
|
||||
);
|
||||
}
|
||||
|
||||
toHex() {
|
||||
return this.toInt().toString(16).padStart(6, "0");
|
||||
}
|
||||
toInt() {
|
||||
return (
|
||||
(Math.round(this.R * 255) << 16) +
|
||||
(Math.round(this.G * 255) << 8) +
|
||||
Math.round(this.B * 255)
|
||||
);
|
||||
}
|
||||
toRGB(): [number, number, number] {
|
||||
return [this.R * 255, this.G * 255, this.B * 255];
|
||||
}
|
||||
multiply(mult: number) {
|
||||
return new Color3(this.R * mult, this.G * mult, this.B * mult);
|
||||
}
|
||||
lighten(mult: number) {
|
||||
return new Color3(
|
||||
clamp(this.R + (mult / 100) * (1 - this.R), 0, 1),
|
||||
clamp(this.G + (mult / 100) * (1 - this.G), 0, 1),
|
||||
clamp(this.B + (mult / 100) * (1 - this.B), 0, 1)
|
||||
);
|
||||
}
|
||||
darken(mult: number) {
|
||||
return new Color3(
|
||||
clamp(this.R - (mult / 100) * this.R, 0, 1),
|
||||
clamp(this.G - (mult / 100) * this.G, 0, 1),
|
||||
clamp(this.B - (mult / 100) * this.B, 0, 1)
|
||||
);
|
||||
}
|
||||
toHex() {
|
||||
return this.toInt().toString(16).padStart(6, "0");
|
||||
}
|
||||
toInt() {
|
||||
return (
|
||||
(Math.round(this.R * 255) << 16) +
|
||||
(Math.round(this.G * 255) << 8) +
|
||||
Math.round(this.B * 255)
|
||||
);
|
||||
}
|
||||
toRGB(): [number, number, number] {
|
||||
return [this.R * 255, this.G * 255, this.B * 255];
|
||||
}
|
||||
multiply(mult: number) {
|
||||
return new Color3(this.R * mult, this.G * mult, this.B * mult);
|
||||
}
|
||||
lighten(mult: number) {
|
||||
return new Color3(
|
||||
clamp(this.R + (mult / 100) * (1 - this.R), 0, 1),
|
||||
clamp(this.G + (mult / 100) * (1 - this.G), 0, 1),
|
||||
clamp(this.B + (mult / 100) * (1 - this.B), 0, 1)
|
||||
);
|
||||
}
|
||||
darken(mult: number) {
|
||||
return new Color3(
|
||||
clamp(this.R - (mult / 100) * this.R, 0, 1),
|
||||
clamp(this.G - (mult / 100) * this.G, 0, 1),
|
||||
clamp(this.B - (mult / 100) * this.B, 0, 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -316,7 +316,9 @@ export const SignColorSchema = yup.object({
|
|||
description: yup.string().required(),
|
||||
sign: yup.string().required(),
|
||||
color: ColorSchema.required(),
|
||||
dates: yup.tuple([yup.string().required(), yup.string().required()]).required()
|
||||
dates: yup
|
||||
.tuple([yup.string().required(), yup.string().required()])
|
||||
.required()
|
||||
});
|
||||
|
||||
export type SignColorType = {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as yup from "yup";
|
||||
|
||||
export const PolicySchema = yup
|
||||
.string()
|
||||
.oneOf(["yes", "ask", "no"])
|
||||
.default("no");
|
||||
.string()
|
||||
.oneOf(["yes", "ask", "no"])
|
||||
.default("no");
|
||||
|
|
|
@ -1,51 +1,53 @@
|
|||
export function AdaptivePossessive(owner: string, possession: string) {
|
||||
return owner + (owner.endsWith("s") ? "'" : "'s") + " " + possession;
|
||||
return owner + (owner.endsWith("s") ? "'" : "'s") + " " + possession;
|
||||
}
|
||||
|
||||
export function PronounGrouper(
|
||||
pronouns: [string, string, string],
|
||||
sep?: string,
|
||||
amount?: number
|
||||
pronouns: [string, string, string],
|
||||
sep?: string,
|
||||
amount?: number
|
||||
) {
|
||||
return pronouns.slice(0, amount ?? 2).join(sep ?? "/");
|
||||
return pronouns.slice(0, amount ?? 2).join(sep ?? "/");
|
||||
}
|
||||
|
||||
export function HeightConverter(inches: number) {
|
||||
var feetandinches = Math.floor(inches / 12) + "'" + (inches % 12) + '"';
|
||||
var meters = Math.floor((inches / 39.37) * 100) / 100 + "m";
|
||||
return feetandinches + " (" + meters + ")";
|
||||
var feetandinches = Math.floor(inches / 12) + "'" + (inches % 12) + '"';
|
||||
var meters = Math.floor((inches / 39.37) * 100) / 100 + "m";
|
||||
return feetandinches + " (" + meters + ")";
|
||||
}
|
||||
|
||||
export function AgeConverter(sweeps: number, years?: boolean) {
|
||||
return years
|
||||
? `${Math.round(sweeps * 2.1667 * 10) / 10} years`
|
||||
: `${sweeps} sweeps`;
|
||||
return years
|
||||
? `${Math.round(sweeps * 2.1667 * 10) / 10} years`
|
||||
: `${sweeps} sweeps`;
|
||||
}
|
||||
|
||||
export function Pluralize(stringe: string) {
|
||||
if (stringe.match(/(?:s|ch|sh|x|z|[^aeiou]o)$/)) return stringe + "es";
|
||||
else if (stringe.match(/[aeiou](?:y|o)$/)) return stringe + "s";
|
||||
else if (stringe.match(/[^aeiou]y$/)) return stringe.slice(0, -1) + "ies";
|
||||
else if (stringe.match(/(?:f|fe)$/)) return stringe.slice(0, -1) + "ves";
|
||||
else return stringe + "s";
|
||||
if (stringe.match(/(?:s|ch|sh|x|z|[^aeiou]o)$/)) return stringe + "es";
|
||||
else if (stringe.match(/[aeiou](?:y|o)$/)) return stringe + "s";
|
||||
else if (stringe.match(/[^aeiou]y$/)) return stringe.slice(0, -1) + "ies";
|
||||
else if (stringe.match(/(?:f|fe)$/)) return stringe.slice(0, -1) + "ves";
|
||||
else return stringe + "s";
|
||||
}
|
||||
|
||||
export function ArraySample(array: any[]) {
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
}
|
||||
|
||||
export function ProperNounCase(string: string) {
|
||||
return string
|
||||
.split(" ")
|
||||
.map((x) => x.charAt(0).toUpperCase() + x.slice(1).toLowerCase())
|
||||
.join(" ");
|
||||
return string
|
||||
.split(" ")
|
||||
.map(x => x.charAt(0).toUpperCase() + x.slice(1).toLowerCase())
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
export function PesterchumNameFormatter(string: string) {
|
||||
return (
|
||||
string +
|
||||
" [" +
|
||||
string.replace(/^(([a-z])[a-z]+)(([A-Z])[a-z]+)$/, "$2$4").toUpperCase() +
|
||||
"]"
|
||||
);
|
||||
return (
|
||||
string +
|
||||
" [" +
|
||||
string
|
||||
.replace(/^(([a-z])[a-z]+)(([A-Z])[a-z]+)$/, "$2$4")
|
||||
.toUpperCase() +
|
||||
"]"
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,5 +2,5 @@ import { ObjectId } from "mongodb";
|
|||
import * as yup from "yup";
|
||||
|
||||
export const ObjectIdSchema = yup.mixed((value): value is ObjectId =>
|
||||
ObjectId.isValid(value)
|
||||
ObjectId.isValid(value)
|
||||
);
|
||||
|
|
|
@ -3,8 +3,17 @@ import { QuirkSchema } from "../quirks";
|
|||
|
||||
export const SubmitQuirkHolderSchema = yup
|
||||
.array()
|
||||
.of(yup.tuple([yup.string().required().lowercase(), QuirkSchema.required()]).required())
|
||||
.of(
|
||||
yup
|
||||
.tuple([
|
||||
yup.string().required().lowercase(),
|
||||
QuirkSchema.required()
|
||||
])
|
||||
.required()
|
||||
)
|
||||
.required()
|
||||
.test("has-default", 'Needs "default" Quirk Mode', v => v.some(([k, v]) => k === "default" || k === "Default"));
|
||||
.test("has-default", 'Needs "default" Quirk Mode', v =>
|
||||
v.some(([k, v]) => k === "default" || k === "Default")
|
||||
);
|
||||
|
||||
export type SubmitQuirkHolder = yup.InferType<typeof SubmitQuirkHolderSchema>;
|
||||
|
|
|
@ -79,11 +79,26 @@ export const SubmitTrollSchema = yup
|
|||
// Personal
|
||||
preferences: yup
|
||||
.object({
|
||||
love: yup.array().of(yup.string().required().min(5).max(100)).required().min(3).max(10),
|
||||
hate: yup.array().of(yup.string().required().min(5).max(100)).required().min(3).max(10)
|
||||
love: yup
|
||||
.array()
|
||||
.of(yup.string().required().min(5).max(100))
|
||||
.required()
|
||||
.min(3)
|
||||
.max(10),
|
||||
hate: yup
|
||||
.array()
|
||||
.of(yup.string().required().min(5).max(100))
|
||||
.required()
|
||||
.min(3)
|
||||
.max(10)
|
||||
})
|
||||
.required(),
|
||||
facts: yup.array().of(yup.string().required().min(5).max(100)).required().min(3).max(10),
|
||||
facts: yup
|
||||
.array()
|
||||
.of(yup.string().required().min(5).max(100))
|
||||
.required()
|
||||
.min(3)
|
||||
.max(10),
|
||||
|
||||
// Hiveswap identity
|
||||
trueSign: yup.string().required().oneOf(TrueSignKeys),
|
||||
|
@ -100,7 +115,10 @@ export const SubmitTrollSchema = yup
|
|||
username: yup
|
||||
.string()
|
||||
.required()
|
||||
.matches(/^(([a-z])[a-z]+)(([A-Z])[a-z]+)$/, "Username must match Pesterchum formatting."),
|
||||
.matches(
|
||||
/^(([a-z])[a-z]+)(([A-Z])[a-z]+)$/,
|
||||
"Username must match Pesterchum formatting."
|
||||
),
|
||||
textColor: ColorSchema.notRequired(), // default to trueSign color if undefined,
|
||||
quirks: SubmitQuirkHolderSchema.required(), // DO NOT HANDLE RIGHT NOW.
|
||||
// Handled! :D
|
||||
|
@ -208,7 +226,10 @@ export const PartialTrollSchema = yup
|
|||
// Trollian
|
||||
username: yup
|
||||
.string()
|
||||
.matches(/^(([a-z])[a-z]+)(([A-Z])[a-z]+)$/, "Username must match Pesterchum formatting."),
|
||||
.matches(
|
||||
/^(([a-z])[a-z]+)(([A-Z])[a-z]+)$/,
|
||||
"Username must match Pesterchum formatting."
|
||||
),
|
||||
textColor: yup.tuple([
|
||||
yup.number().min(0).max(255),
|
||||
yup.number().min(0).max(255),
|
||||
|
|
|
@ -40,7 +40,11 @@ export const PartialUserSchema = yup
|
|||
return v === "" ? null : v;
|
||||
})
|
||||
.oneOf(TrueSignKeys),
|
||||
color: yup.tuple([yup.number().min(0).max(255), yup.number().min(0).max(255), yup.number().min(0).max(255)]),
|
||||
color: yup.tuple([
|
||||
yup.number().min(0).max(255),
|
||||
yup.number().min(0).max(255),
|
||||
yup.number().min(0).max(255)
|
||||
]),
|
||||
pfp: yup.string().url(),
|
||||
code: yup.string().max(256, "Too secure!!")
|
||||
// flairs: yup.array().of(ClientFlairSchema).required(),
|
||||
|
|
|
@ -70,7 +70,8 @@ export const QuirkReplacementTypes: {
|
|||
},
|
||||
case: {
|
||||
find: null,
|
||||
replace: 'Change the case of all text. "lower" for lowercase. "upper" for uppercase.'
|
||||
replace:
|
||||
'Change the case of all text. "lower" for lowercase. "upper" for uppercase.'
|
||||
},
|
||||
case_simple: {
|
||||
find: "Text to find what you want to change the case of.",
|
||||
|
|
Loading…
Reference in a new issue