permissions!

This commit is contained in:
MeowcaTheoRange 2023-06-30 20:35:15 -05:00
parent 8cde6799df
commit 6a5a85c773
33 changed files with 683 additions and 666 deletions

View file

@ -2,7 +2,7 @@
"trailingComma": "none", "trailingComma": "none",
"tabWidth": 4, "tabWidth": 4,
"useTabs": false, "useTabs": false,
"printWidth": 120, "printWidth": 80,
"semi": true, "semi": true,
"singleQuote": false, "singleQuote": false,
"quoteProps": "preserve", "quoteProps": "preserve",

View file

@ -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"; 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. // 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"> & { export type WithId<TSchema> = Omit<TSchema, "_id"> & {
_id: ObjectId; _id: ObjectId;
}; };
export type Document = { [key: string]: any }; 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) { export async function createOne(collection: string, doc: any) {
const selectedCollection = mainDB.collection(collection); const selectedCollection = mainDB.collection(collection);
@ -20,9 +21,13 @@ export async function readOne(collection: string, find: Filter<Document>) {
return await selectedCollection.findOne(find); return await selectedCollection.findOne(find);
} // MongoDB and its finicky type safety } // 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); const selectedCollection = mainDB.collection(collection);
return selectedCollection.find(find); return selectedCollection.find(find).sort(sort);
} }
export async function countMany(collection: string, find: any) { 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); 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[] = []; let array: T[] = [];
while (await cursor.hasNext()) { while (await cursor.hasNext()) {
array.push(await func(await cursor.next())); array.push(await func(await cursor.next()));

View file

@ -1,7 +1,9 @@
import { ClientFlair, ServerFlair } from "@/types/flair"; import { ClientFlair, ServerFlair } from "@/types/flair";
import { sanitize } from "../utility/merge"; 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); const sanitizedFlair = sanitize(serverFlair);
let clientFlair: ClientFlair = { let clientFlair: ClientFlair = {
...sanitizedFlair ...sanitizedFlair

View file

@ -1,23 +1,56 @@
import { Class, TrueSign } from "@/types/assist/extended_zodiac"; import { Class, TrueSign } from "@/types/assist/extended_zodiac";
import { SubmitTroll } from "@/types/client/troll";
import { ClientTroll, ServerTroll } from "@/types/troll"; import { ClientTroll, ServerTroll } from "@/types/troll";
import { getManyFlairs } from "../flair"; import { getManyFlairs } from "../flair";
import { getManyUsers } from "../user"; import { cutArray, cutObject, sanitize } from "../utility/merge";
import { cutArray, sanitize } from "../utility/merge";
import { ServerFlairToClientFlair } from "./flair"; 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 sanitizedTroll = sanitize(serverTroll);
const owners = await getManyUsers({ _id: { $in: serverTroll.owners } }, ServerUserToClientUser); const flairs = await getManyFlairs(
const flairs = await getManyFlairs({ _id: { $in: serverTroll.flairs } }, ServerFlairToClientFlair); { _id: { $in: serverTroll.flairs } },
ServerFlairToClientFlair
);
let clientTroll: ClientTroll = { let clientTroll: ClientTroll = {
...sanitizedTroll, ...sanitizedTroll,
trueSign: TrueSign[serverTroll.trueSign], trueSign: TrueSign[serverTroll.trueSign],
falseSign: serverTroll.falseSign != null ? TrueSign[serverTroll.falseSign] : null, falseSign:
serverTroll.falseSign != null
? TrueSign[serverTroll.falseSign]
: null,
class: Class[serverTroll.class], class: Class[serverTroll.class],
owners: cutArray(owners), owners: [],
flairs: cutArray(flairs) flairs: cutArray(flairs)
}; };
return clientTroll; 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;
}

View file

@ -6,9 +6,14 @@ import { getManyFlairs } from "../flair";
import { cutArray, cutObject, removeCode, sanitize } from "../utility/merge"; import { cutArray, cutObject, removeCode, sanitize } from "../utility/merge";
import { ServerFlairToClientFlair } from "./flair"; 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 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 = { let clientUser: ClientUser = {
...sanitizedUser, ...sanitizedUser,
trueSign: TrueSign[serverUser.trueSign], trueSign: TrueSign[serverUser.trueSign],
@ -18,7 +23,9 @@ export async function ServerUserToClientUser(serverUser: ServerUser): Promise<Cl
return clientUser; 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"> = { let serverUser: Omit<Partial<ServerUser>, "_id"> = {
...submitUser, ...submitUser,
flairs: [], flairs: [],
@ -28,7 +35,10 @@ export function SubmitUserToServerUser(submitUser: Partial<SubmitUser>): Omit<Pa
return serverUser; 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 = { let serverUser: ServerUser = {
...submitUser, ...submitUser,
...cutObject(merge), ...cutObject(merge),

View file

@ -1,13 +1,18 @@
import { ServerFlair } from "@/types/flair"; import { ServerFlair } from "@/types/flair";
import { Sort } from "mongodb";
import { Filter, cursorToArray, readMany, readOne } from "../db/crud"; import { Filter, cursorToArray, readMany, readOne } from "../db/crud";
const FlairSort: Sort = { updatedDate: -1, _id: -1 };
/** /**
* A function that returns one ServerFlairs from the database. * A function that returns one ServerFlairs from the database.
* @param query A partial Find query. Can contain an ID. * @param query A partial Find query. Can contain an ID.
* @returns A ServerFlair. * @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; const flair = (await readOne("flairs", query)) as ServerFlair | null;
return flair; return flair;
} }
@ -23,6 +28,9 @@ export async function getManyFlairs(
query: Filter<ServerFlair>, query: Filter<ServerFlair>,
func?: (input: any) => any func?: (input: any) => any
): Promise<(ServerFlair | null)[]> { ): 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; return flair;
} }

View 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
);
}

View file

@ -1,5 +1,15 @@
import { ServerTroll } from "@/types/troll"; 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. * A function that returns one ServerTrolls from the database.
@ -7,7 +17,9 @@ import { Filter, cursorToArray, readMany, readOne } from "../db/crud";
* @returns A ServerTroll. * @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; const troll = (await readOne("trolls", query)) as ServerTroll | null;
return troll; return troll;
} }
@ -19,10 +31,59 @@ export async function getSingleTroll(query: Filter<ServerTroll>): Promise<Server
* @returns An array of ServerTrolls. * @returns An array of ServerTrolls.
*/ */
export async function getManyTrolls( export async function getManyTrolls<T>(
query: Filter<ServerTroll>, query: Filter<ServerTroll>,
func?: (input: any) => any func?: (input: any) => T
): Promise<(ServerTroll | null)[]> { ): Promise<(Awaited<T> | null)[]> {
const troll = (await cursorToArray(readMany("trolls", query), func)) as (ServerTroll | null)[]; const troll = (await cursorToArray(
readMany("trolls", query, TrollSort),
func
)) as (Awaited<T> | null)[];
return troll; 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;
}

View file

@ -1,5 +1,15 @@
import { ServerUser } from "@/types/user"; 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. * A function that returns one ServerUser from the database.
@ -7,7 +17,9 @@ import { Filter, createOne, cursorToArray, readMany, readOne, replaceOne } from
* @returns A ServerUser. * @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; const user = (await readOne("users", query)) as ServerUser | null;
return user; return user;
} }
@ -23,7 +35,30 @@ export async function getManyUsers<T>(
query: Filter<ServerUser>, query: Filter<ServerUser>,
func?: (input: any) => T func?: (input: any) => T
): Promise<(Awaited<T> | null)[]> { ): 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; return user;
} }
@ -33,7 +68,9 @@ export async function getManyUsers<T>(
* @returns A ServerUser, or null, depending on if the operation succeeded. * @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); const newUser = await createOne("users", user);
return newUser.acknowledged ? user : null; return newUser.acknowledged ? user : null;
} }

View file

@ -25,13 +25,17 @@ export function cutObject<T extends { [key: string]: any }>(object: T) {
return cut as 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; const sanitized: Partial<Type> = serverType;
delete sanitized._id; delete sanitized._id;
return sanitized as Omit<Type, "_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; const sanitized: Partial<Type> = serverType;
delete sanitized.code; delete sanitized.code;
return sanitized as Omit<Type, "code">; return sanitized as Omit<Type, "code">;

View file

@ -1,4 +1,3 @@
import "@/styles/globals.css";
import type { AppProps } from "next/app"; import type { AppProps } from "next/app";
import React from "react"; import React from "react";

3
src/pages/_error.tsx Normal file
View file

@ -0,0 +1,3 @@
export default function ErrorPage() {
return "404";
}

View 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();
}

View 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();
}

View 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

View 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();
}

View 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();
}

View file

@ -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 { changeUser, getSingleUser } from "@/lib/trollcall/user";
import { PartialUserSchema, SubmitUser } from "@/types/client/user"; import { PartialUserSchema, SubmitUser } from "@/types/client/user";
import { serialize } from "cookie"; import { serialize } from "cookie";
import { NextApiRequest, NextApiResponse } from "next"; 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; const { body, cookies, query, method } = req;
if (method === "GET") { if (method === "GET") {
const user = await getSingleUser({ const user = await getSingleUser({
@ -16,7 +28,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
} else if (method === "PUT") { } else if (method === "PUT") {
let validatedUser: Partial<SubmitUser>; let validatedUser: Partial<SubmitUser>;
try { try {
validatedUser = (await PartialUserSchema.validate(body)) as Partial<SubmitUser>; validatedUser = (await PartialUserSchema.validate(
body
)) as Partial<SubmitUser>;
} catch (err) { } catch (err) {
return res.status(400).send(err); return res.status(400).send(err);
} }
@ -24,16 +38,33 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
name: query.user name: query.user
}); });
if (checkExistingUser == null) return res.status(404).end(); if (checkExistingUser == null) return res.status(404).end();
if (checkExistingUser.code !== cookies.TROLLCALL_CODE || checkExistingUser.name !== cookies.TROLLCALL_NAME) if (!compareCredentials(checkExistingUser, cookies)) {
return res.status(403).end(); 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 serverUser = SubmitUserToServerUser(validatedUser);
const bothUsers = MergeServerUsers(checkExistingUser, serverUser); const bothUsers = MergeServerUsers(checkExistingUser, serverUser);
const newUser = await changeUser(bothUsers); const newUser = await changeUser(bothUsers);
if (newUser == null) return res.status(503).end(); if (newUser == null) return res.status(503).end();
// Give cookies, redundant style // Give cookies, redundant style
res.setHeader("Set-Cookie", [ res.setHeader("Set-Cookie", [
serialize("TROLLCALL_NAME", newUser.name, { path: "/", maxAge: 31540000 }), serialize("TROLLCALL_NAME", newUser.name, {
serialize("TROLLCALL_NAME", newUser.code, { path: "/", maxAge: 31540000 }) path: "/",
maxAge: 31540000
}),
serialize("TROLLCALL_CODE", newUser.code, {
path: "/",
maxAge: 31540000
}),
serialize("TROLLCALL_PFP", newUser.pfp ?? "", {
path: "/",
maxAge: 31540000
})
]).json(newUser); ]).json(newUser);
} else return res.status(405).end(); } else return res.status(405).end();
} }

View file

@ -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();
}

View file

@ -5,26 +5,44 @@ import { ServerUser } from "@/types/user";
import { serialize } from "cookie"; import { serialize } from "cookie";
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(
const { body } = req; req: NextApiRequest,
let validatedUser; res: NextApiResponse
try { ) {
validatedUser = await SubmitUserSchema.validate(body); const { body, method } = req;
} catch (err) { if (method === "POST") {
return res.status(400).send(err); let validatedUser;
} try {
const checkExistingUser = await getSingleUser({ validatedUser = await SubmitUserSchema.validate(body);
name: validatedUser.name } catch (err) {
}); return res.status(400).send(err);
if (checkExistingUser != null) return res.status(409).end(); }
// we are sure this object is full, so cast partial const checkExistingUser = await getSingleUser({
const serverUser = SubmitUserToServerUser(validatedUser) as Omit<ServerUser, "_id">; name: validatedUser.name
const newUser = await createUser(serverUser); });
if (newUser == null) return res.status(503).end(); if (checkExistingUser != null) return res.status(409).end();
// Give cookies // we are sure this object is full, so cast partial
res.setHeader("Set-Cookie", [ const serverUser = SubmitUserToServerUser(validatedUser) as Omit<
serialize("TROLLCALL_NAME", newUser.name, { path: "/", maxAge: 31540000 }), ServerUser,
serialize("TROLLCALL_NAME", newUser.code, { path: "/", maxAge: 31540000 }) "_id"
]); >;
res.json(newUser); 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();
} }

View file

@ -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() { export default function Home() {
return ( return "Welcome home!";
<>
<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&nbsp;
<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>-&gt;</span>
</h2>
<p>Find in-depth information about Next.js features and&nbsp;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>-&gt;</span>
</h2>
<p>Learn about Next.js in an interactive course with&nbsp;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>-&gt;</span>
</h2>
<p>Discover and deploy boilerplate example Next.js&nbsp;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>-&gt;</span>
</h2>
<p>Instantly deploy your Next.js site to a shareable URL with&nbsp;Vercel.</p>
</a>
</div>
</main>
</>
);
} }

24
src/permissions.ts Normal file
View 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
};

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -3,100 +3,100 @@ import * as yup from "yup";
export type ColorTypes = [number, number, number]; export type ColorTypes = [number, number, number];
export const ColorSchema = yup.tuple([ 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) => 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 { export class Color3 {
R: number; R: number;
G: number; G: number;
B: number; B: number;
constructor(red: number, green: number, blue: number) { constructor(red: number, green: number, blue: number) {
this.R = red; this.R = red;
this.G = green; this.G = green;
this.B = blue; this.B = blue;
} }
static clone(color3: Color3) { static clone(color3: Color3) {
return new Color3(color3.R, color3.G, color3.B); return new Color3(color3.R, color3.G, color3.B);
} }
static fromRGB(red: number, green: number, blue: number) { static fromRGB(red: number, green: number, blue: number) {
return new Color3(red / 255, green / 255, blue / 255); return new Color3(red / 255, green / 255, blue / 255);
} }
static fromHex(hex: string) { static fromHex(hex: string) {
// @ts-ignore // @ts-ignore
const hexSplit: [number, number, number] = ( const hexSplit: [number, number, number] = (
hex.match(new RegExp(`[0-9a-f]{1,${hex.length / 3}}`, "gi")) ?? [ hex.match(new RegExp(`[0-9a-f]{1,${hex.length / 3}}`, "gi")) ?? [
"0", "0",
"0", "0",
"0", "0"
] ]
).map((x) => parseInt(x, 16) / 255); ).map(x => parseInt(x, 16) / 255);
return new Color3(...hexSplit); return new Color3(...hexSplit);
} }
static fromInt(int: number) { static fromInt(int: number) {
return new Color3( return new Color3(
(int & 0xff0000) >> 16, (int & 0xff0000) >> 16,
(int & 0x00ff00) >> 8, (int & 0x00ff00) >> 8,
int & 0x0000ff int & 0x0000ff
); );
} }
static assumeColor( static assumeColor(
value: [number, number, number] | string | number, value: [number, number, number] | string | number,
rgb?: boolean rgb?: boolean
) { ) {
if (Color3.isColor(value)) { if (Color3.isColor(value)) {
if (Array.isArray(value)) if (Array.isArray(value))
return rgb ? Color3.fromRGB(...value) : new Color3(...value); return rgb ? Color3.fromRGB(...value) : new Color3(...value);
else if (typeof value === "string") return Color3.fromHex(value); else if (typeof value === "string") return Color3.fromHex(value);
else if (typeof value === "number") return Color3.fromInt(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() { toHex() {
return this.toInt().toString(16).padStart(6, "0"); return this.toInt().toString(16).padStart(6, "0");
} }
toInt() { toInt() {
return ( return (
(Math.round(this.R * 255) << 16) + (Math.round(this.R * 255) << 16) +
(Math.round(this.G * 255) << 8) + (Math.round(this.G * 255) << 8) +
Math.round(this.B * 255) Math.round(this.B * 255)
); );
} }
toRGB(): [number, number, number] { toRGB(): [number, number, number] {
return [this.R * 255, this.G * 255, this.B * 255]; return [this.R * 255, this.G * 255, this.B * 255];
} }
multiply(mult: number) { multiply(mult: number) {
return new Color3(this.R * mult, this.G * mult, this.B * mult); return new Color3(this.R * mult, this.G * mult, this.B * mult);
} }
lighten(mult: number) { lighten(mult: number) {
return new Color3( return new Color3(
clamp(this.R + (mult / 100) * (1 - this.R), 0, 1), clamp(this.R + (mult / 100) * (1 - this.R), 0, 1),
clamp(this.G + (mult / 100) * (1 - this.G), 0, 1), clamp(this.G + (mult / 100) * (1 - this.G), 0, 1),
clamp(this.B + (mult / 100) * (1 - this.B), 0, 1) clamp(this.B + (mult / 100) * (1 - this.B), 0, 1)
); );
} }
darken(mult: number) { darken(mult: number) {
return new Color3( return new Color3(
clamp(this.R - (mult / 100) * this.R, 0, 1), clamp(this.R - (mult / 100) * this.R, 0, 1),
clamp(this.G - (mult / 100) * this.G, 0, 1), clamp(this.G - (mult / 100) * this.G, 0, 1),
clamp(this.B - (mult / 100) * this.B, 0, 1) clamp(this.B - (mult / 100) * this.B, 0, 1)
); );
} }
} }

View file

@ -316,7 +316,9 @@ export const SignColorSchema = yup.object({
description: yup.string().required(), description: yup.string().required(),
sign: yup.string().required(), sign: yup.string().required(),
color: ColorSchema.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 = { export type SignColorType = {

View file

@ -1,6 +1,6 @@
import * as yup from "yup"; import * as yup from "yup";
export const PolicySchema = yup export const PolicySchema = yup
.string() .string()
.oneOf(["yes", "ask", "no"]) .oneOf(["yes", "ask", "no"])
.default("no"); .default("no");

View file

@ -1,51 +1,53 @@
export function AdaptivePossessive(owner: string, possession: string) { export function AdaptivePossessive(owner: string, possession: string) {
return owner + (owner.endsWith("s") ? "'" : "'s") + " " + possession; return owner + (owner.endsWith("s") ? "'" : "'s") + " " + possession;
} }
export function PronounGrouper( export function PronounGrouper(
pronouns: [string, string, string], pronouns: [string, string, string],
sep?: string, sep?: string,
amount?: number amount?: number
) { ) {
return pronouns.slice(0, amount ?? 2).join(sep ?? "/"); return pronouns.slice(0, amount ?? 2).join(sep ?? "/");
} }
export function HeightConverter(inches: number) { export function HeightConverter(inches: number) {
var feetandinches = Math.floor(inches / 12) + "'" + (inches % 12) + '"'; var feetandinches = Math.floor(inches / 12) + "'" + (inches % 12) + '"';
var meters = Math.floor((inches / 39.37) * 100) / 100 + "m"; var meters = Math.floor((inches / 39.37) * 100) / 100 + "m";
return feetandinches + " (" + meters + ")"; return feetandinches + " (" + meters + ")";
} }
export function AgeConverter(sweeps: number, years?: boolean) { export function AgeConverter(sweeps: number, years?: boolean) {
return years return years
? `${Math.round(sweeps * 2.1667 * 10) / 10} years` ? `${Math.round(sweeps * 2.1667 * 10) / 10} years`
: `${sweeps} sweeps`; : `${sweeps} sweeps`;
} }
export function Pluralize(stringe: string) { export function Pluralize(stringe: string) {
if (stringe.match(/(?:s|ch|sh|x|z|[^aeiou]o)$/)) return stringe + "es"; 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|o)$/)) return stringe + "s";
else if (stringe.match(/[^aeiou]y$/)) return stringe.slice(0, -1) + "ies"; 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 if (stringe.match(/(?:f|fe)$/)) return stringe.slice(0, -1) + "ves";
else return stringe + "s"; else return stringe + "s";
} }
export function ArraySample(array: any[]) { 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) { export function ProperNounCase(string: string) {
return string return string
.split(" ") .split(" ")
.map((x) => x.charAt(0).toUpperCase() + x.slice(1).toLowerCase()) .map(x => x.charAt(0).toUpperCase() + x.slice(1).toLowerCase())
.join(" "); .join(" ");
} }
export function PesterchumNameFormatter(string: string) { export function PesterchumNameFormatter(string: string) {
return ( return (
string + string +
" [" + " [" +
string.replace(/^(([a-z])[a-z]+)(([A-Z])[a-z]+)$/, "$2$4").toUpperCase() + string
"]" .replace(/^(([a-z])[a-z]+)(([A-Z])[a-z]+)$/, "$2$4")
); .toUpperCase() +
"]"
);
} }

View file

@ -2,5 +2,5 @@ import { ObjectId } from "mongodb";
import * as yup from "yup"; import * as yup from "yup";
export const ObjectIdSchema = yup.mixed((value): value is ObjectId => export const ObjectIdSchema = yup.mixed((value): value is ObjectId =>
ObjectId.isValid(value) ObjectId.isValid(value)
); );

View file

@ -3,8 +3,17 @@ import { QuirkSchema } from "../quirks";
export const SubmitQuirkHolderSchema = yup export const SubmitQuirkHolderSchema = yup
.array() .array()
.of(yup.tuple([yup.string().required().lowercase(), QuirkSchema.required()]).required()) .of(
yup
.tuple([
yup.string().required().lowercase(),
QuirkSchema.required()
])
.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>; export type SubmitQuirkHolder = yup.InferType<typeof SubmitQuirkHolderSchema>;

View file

@ -79,11 +79,26 @@ export const SubmitTrollSchema = yup
// Personal // Personal
preferences: yup preferences: yup
.object({ .object({
love: yup.array().of(yup.string().required().min(5).max(100)).required().min(3).max(10), love: yup
hate: yup.array().of(yup.string().required().min(5).max(100)).required().min(3).max(10) .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(), .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 // Hiveswap identity
trueSign: yup.string().required().oneOf(TrueSignKeys), trueSign: yup.string().required().oneOf(TrueSignKeys),
@ -100,7 +115,10 @@ export const SubmitTrollSchema = yup
username: yup username: yup
.string() .string()
.required() .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, textColor: ColorSchema.notRequired(), // default to trueSign color if undefined,
quirks: SubmitQuirkHolderSchema.required(), // DO NOT HANDLE RIGHT NOW. quirks: SubmitQuirkHolderSchema.required(), // DO NOT HANDLE RIGHT NOW.
// Handled! :D // Handled! :D
@ -208,7 +226,10 @@ export const PartialTrollSchema = yup
// Trollian // Trollian
username: yup username: yup
.string() .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([ textColor: yup.tuple([
yup.number().min(0).max(255), yup.number().min(0).max(255),
yup.number().min(0).max(255), yup.number().min(0).max(255),

View file

@ -40,7 +40,11 @@ export const PartialUserSchema = yup
return v === "" ? null : v; return v === "" ? null : v;
}) })
.oneOf(TrueSignKeys), .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(), pfp: yup.string().url(),
code: yup.string().max(256, "Too secure!!") code: yup.string().max(256, "Too secure!!")
// flairs: yup.array().of(ClientFlairSchema).required(), // flairs: yup.array().of(ClientFlairSchema).required(),

View file

@ -70,7 +70,8 @@ export const QuirkReplacementTypes: {
}, },
case: { case: {
find: null, 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: { case_simple: {
find: "Text to find what you want to change the case of.", find: "Text to find what you want to change the case of.",