OK, I think that's enough
This commit is contained in:
commit
7e92fdd379
36 changed files with 5439 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.env
|
||||||
|
node_modules/
|
||||||
|
dist/
|
17
.prettierrc
Normal file
17
.prettierrc
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"trailingComma": "none",
|
||||||
|
"tabWidth": 4,
|
||||||
|
"useTabs": false,
|
||||||
|
"printWidth": 120,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false,
|
||||||
|
"quoteProps": "preserve",
|
||||||
|
"jsxSingleQuote": false,
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"bracketSameLine": false,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"proseWrap": "never",
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"embeddedLanguageFormatting": "auto",
|
||||||
|
"singleAttributePerLine": true
|
||||||
|
}
|
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
|
"editor.tabSize": 4,
|
||||||
|
"prettier.tabWidth": 4
|
||||||
|
}
|
2033
package-lock.json
generated
Normal file
2033
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
35
package.json
Normal file
35
package.json
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/express": "^4.17.17",
|
||||||
|
"@types/lodash": "^4.14.195",
|
||||||
|
"@types/node": "^20.3.1",
|
||||||
|
"concurrently": "^8.2.0",
|
||||||
|
"nodemon": "^2.0.22",
|
||||||
|
"tsc-alias": "^1.8.6",
|
||||||
|
"typescript": "^5.1.3"
|
||||||
|
},
|
||||||
|
"name": "trollcallquick",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "npx tsc",
|
||||||
|
"start": "node dist/index.js",
|
||||||
|
"dev": "trap \"rm -rf dist/*;fuser -k 3000/tcp\" SIGINT;concurrently \"npx tsc --watch\" \"tsc-alias -w\" \"nodemon -q dist/index.js\"",
|
||||||
|
"devonce": "npx tsc && tsc-alias -p tsconfig.json;trap \"rm -rf dist/*;fuser -k 3000/tcp\" SIGINT;node dist/index.js"
|
||||||
|
},
|
||||||
|
"author": "MeowcaTheoRange",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/cookie-parser": "^1.4.3",
|
||||||
|
"body-parser": "^1.20.2",
|
||||||
|
"cookie-parser": "^1.4.6",
|
||||||
|
"dotenv": "^16.3.0",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"mongodb": "^5.6.0",
|
||||||
|
"nanoid": "^3.3.6",
|
||||||
|
"tsconfig-paths": "^4.2.0",
|
||||||
|
"yup": "^1.2.0"
|
||||||
|
}
|
||||||
|
}
|
8
src/app/api/index.ts
Normal file
8
src/app/api/index.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { Router } from "express";
|
||||||
|
import { trollRouter } from "./troll";
|
||||||
|
import { userRouter } from "./user";
|
||||||
|
|
||||||
|
export const apiRouter = Router();
|
||||||
|
|
||||||
|
apiRouter.use("/", trollRouter);
|
||||||
|
apiRouter.use("/", userRouter);
|
21
src/app/api/troll/index.ts
Normal file
21
src/app/api/troll/index.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import { ServerTrollToClientTroll } from "@/lib/trollcall/convert/troll";
|
||||||
|
import { getSingleTroll } from "@/lib/trollcall/troll";
|
||||||
|
import { getSingleUser } from "@/lib/trollcall/user";
|
||||||
|
import { Router } from "express";
|
||||||
|
|
||||||
|
export const trollRouter = Router();
|
||||||
|
|
||||||
|
trollRouter.get("/user/:user/troll/:troll", async (req, res, next) => {
|
||||||
|
const { params } = req;
|
||||||
|
const user = await getSingleUser({
|
||||||
|
name: params.user
|
||||||
|
});
|
||||||
|
if (user == null) return res.sendStatus(404);
|
||||||
|
const troll = await getSingleTroll({
|
||||||
|
"name.0": params.troll,
|
||||||
|
"owners.0": user._id
|
||||||
|
});
|
||||||
|
if (troll == null) return res.sendStatus(404);
|
||||||
|
const serverTroll = await ServerTrollToClientTroll(troll);
|
||||||
|
res.json(serverTroll);
|
||||||
|
});
|
63
src/app/api/user/index.ts
Normal file
63
src/app/api/user/index.ts
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import { MergeServerUsers, ServerUserToClientUser, SubmitUserToServerUser } from "@/lib/trollcall/convert/user";
|
||||||
|
import { changeUser, createUser, getSingleUser } from "@/lib/trollcall/user";
|
||||||
|
import { PartialUserSchema, SubmitUser, SubmitUserSchema } from "@/types/client/user";
|
||||||
|
import { ServerUser } from "@/types/user";
|
||||||
|
import { Router } from "express";
|
||||||
|
|
||||||
|
export const userRouter = Router();
|
||||||
|
|
||||||
|
userRouter.get("/user/:user/", async (req, res, next) => {
|
||||||
|
const { params } = req;
|
||||||
|
const user = await getSingleUser({
|
||||||
|
name: params.user
|
||||||
|
});
|
||||||
|
if (user == null) return res.sendStatus(404);
|
||||||
|
const serverUser = await ServerUserToClientUser(user);
|
||||||
|
res.json(serverUser);
|
||||||
|
});
|
||||||
|
|
||||||
|
userRouter.post("/user/", async (req, res, next) => {
|
||||||
|
const { body } = req;
|
||||||
|
let validatedUser: SubmitUser;
|
||||||
|
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.sendStatus(409);
|
||||||
|
// 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.sendStatus(503);
|
||||||
|
// Give cookies
|
||||||
|
res.cookie("TROLLCALL_NAME", newUser.name, { maxAge: 31540000 })
|
||||||
|
.cookie("TROLLCALL_CODE", newUser.code, { maxAge: 31540000 })
|
||||||
|
.json(newUser);
|
||||||
|
});
|
||||||
|
|
||||||
|
userRouter.put("/user/:user/", async (req, res, next) => {
|
||||||
|
const { body, params, cookies } = req;
|
||||||
|
let validatedUser: Partial<SubmitUser>;
|
||||||
|
try {
|
||||||
|
validatedUser = (await PartialUserSchema.validate(body)) as Partial<SubmitUser>;
|
||||||
|
} catch (err) {
|
||||||
|
return res.status(400).send(err);
|
||||||
|
}
|
||||||
|
const checkExistingUser = await getSingleUser({
|
||||||
|
name: params.user
|
||||||
|
});
|
||||||
|
if (checkExistingUser == null) return res.sendStatus(404);
|
||||||
|
if (checkExistingUser.code !== cookies.TROLLCALL_CODE || checkExistingUser.name !== cookies.TROLLCALL_NAME)
|
||||||
|
return res.sendStatus(403);
|
||||||
|
const serverUser = SubmitUserToServerUser(validatedUser);
|
||||||
|
const bothUsers = MergeServerUsers(checkExistingUser, serverUser);
|
||||||
|
const newUser = await changeUser(bothUsers);
|
||||||
|
if (newUser == null) return res.sendStatus(503);
|
||||||
|
// Give cookies, redundant style
|
||||||
|
res.cookie("TROLLCALL_NAME", newUser.name, { maxAge: 31540000 })
|
||||||
|
.cookie("TROLLCALL_CODE", newUser.code, { maxAge: 31540000 })
|
||||||
|
.json(newUser);
|
||||||
|
});
|
6
src/app/index.ts
Normal file
6
src/app/index.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { Router } from "express";
|
||||||
|
import { apiRouter } from "./api/index";
|
||||||
|
|
||||||
|
export const appRouter = Router();
|
||||||
|
|
||||||
|
appRouter.use("/api/", apiRouter);
|
18
src/index.ts
Normal file
18
src/index.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
import express, { Express } from "express";
|
||||||
|
import { appRouter } from "./app";
|
||||||
|
|
||||||
|
import bodyParser from "body-parser";
|
||||||
|
import cookieParser from "cookie-parser";
|
||||||
|
|
||||||
|
const app: Express = express();
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
app.use(cookieParser());
|
||||||
|
|
||||||
|
app.use("/", appRouter);
|
||||||
|
|
||||||
|
app.listen(process.env.PORT, () => {
|
||||||
|
console.log(`⚡️[server]: Server is running at http://localhost:${process.env.PORT}`);
|
||||||
|
});
|
54
src/lib/db/crud.ts
Normal file
54
src/lib/db/crud.ts
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import { FindCursor, Filter as MDBFilter, ObjectId } 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.
|
||||||
|
|
||||||
|
export type WithId<TSchema> = Omit<TSchema, "_id"> & {
|
||||||
|
_id: ObjectId;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Document = { [key: string]: any };
|
||||||
|
export type Filter<TypeSchema> = MDBFilter<Document>;
|
||||||
|
|
||||||
|
export async function createOne(collection: string, doc: any) {
|
||||||
|
const selectedCollection = mainDB.collection(collection);
|
||||||
|
return await selectedCollection.insertOne(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function readOne(collection: string, find: Filter<Document>) {
|
||||||
|
const selectedCollection = mainDB.collection(collection);
|
||||||
|
return await selectedCollection.findOne(find);
|
||||||
|
} // MongoDB and its finicky type safety
|
||||||
|
|
||||||
|
export function readMany(collection: string, find: Filter<Document>) {
|
||||||
|
const selectedCollection = mainDB.collection(collection);
|
||||||
|
return selectedCollection.find(find);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function countMany(collection: string, find: any) {
|
||||||
|
const selectedCollection = mainDB.collection(collection);
|
||||||
|
return await selectedCollection.countDocuments(find);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function replaceOne(collection: string, find: any, update: any) {
|
||||||
|
const selectedCollection = mainDB.collection(collection);
|
||||||
|
return await selectedCollection.replaceOne(find, update);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateOne(collection: string, find: any, update: any) {
|
||||||
|
const selectedCollection = mainDB.collection(collection);
|
||||||
|
return await selectedCollection.updateOne(find, update);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteOne(collection: string, find: any) {
|
||||||
|
const selectedCollection = mainDB.collection(collection);
|
||||||
|
return await selectedCollection.findOneAndDelete(find);
|
||||||
|
}
|
||||||
|
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
7
src/lib/db/mongodb.ts
Normal file
7
src/lib/db/mongodb.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { MongoClient } from "mongodb";
|
||||||
|
|
||||||
|
if (process.env.MONGODB_DATABASE == null) process.exit();
|
||||||
|
|
||||||
|
export const client = new MongoClient(process.env.MONGODB_DATABASE, {});
|
||||||
|
|
||||||
|
export const mainDB = client.db("trollcall_test");
|
11
src/lib/trollcall/convert/flair.ts
Normal file
11
src/lib/trollcall/convert/flair.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { ClientFlair, ServerFlair } from "@/types/flair";
|
||||||
|
import { sanitize } from "../utility/merge";
|
||||||
|
|
||||||
|
export async function ServerFlairToClientFlair(serverFlair: ServerFlair): Promise<ClientFlair> {
|
||||||
|
const sanitizedFlair = sanitize(serverFlair);
|
||||||
|
let clientFlair: ClientFlair = {
|
||||||
|
...sanitizedFlair
|
||||||
|
};
|
||||||
|
|
||||||
|
return clientFlair;
|
||||||
|
}
|
23
src/lib/trollcall/convert/troll.ts
Normal file
23
src/lib/trollcall/convert/troll.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { Class, TrueSign } from "@/types/assist/extended_zodiac";
|
||||||
|
import { ClientTroll, ServerTroll } from "@/types/troll";
|
||||||
|
import { getManyFlairs } from "../flair";
|
||||||
|
import { getManyUsers } from "../user";
|
||||||
|
import { cutArray, sanitize } from "../utility/merge";
|
||||||
|
import { ServerFlairToClientFlair } from "./flair";
|
||||||
|
import { ServerUserToClientUser } from "./user";
|
||||||
|
|
||||||
|
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);
|
||||||
|
let clientTroll: ClientTroll = {
|
||||||
|
...sanitizedTroll,
|
||||||
|
trueSign: TrueSign[serverTroll.trueSign],
|
||||||
|
falseSign: serverTroll.falseSign != null ? TrueSign[serverTroll.falseSign] : null,
|
||||||
|
class: Class[serverTroll.class],
|
||||||
|
owners: cutArray(owners),
|
||||||
|
flairs: cutArray(flairs)
|
||||||
|
};
|
||||||
|
|
||||||
|
return clientTroll;
|
||||||
|
}
|
38
src/lib/trollcall/convert/user.ts
Normal file
38
src/lib/trollcall/convert/user.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { TrueSign } from "@/types/assist/extended_zodiac";
|
||||||
|
import { SubmitUser } from "@/types/client/user";
|
||||||
|
import { ClientUser, ServerUser } from "@/types/user";
|
||||||
|
import { nanoid } from "nanoid";
|
||||||
|
import { getManyFlairs } from "../flair";
|
||||||
|
import { cutArray, cutObject, removeCode, sanitize } from "../utility/merge";
|
||||||
|
import { ServerFlairToClientFlair } from "./flair";
|
||||||
|
|
||||||
|
export async function ServerUserToClientUser(serverUser: ServerUser): Promise<ClientUser> {
|
||||||
|
const sanitizedUser = removeCode(sanitize(serverUser));
|
||||||
|
const flairs = await getManyFlairs({ _id: { $in: serverUser.flairs } }, ServerFlairToClientFlair);
|
||||||
|
let clientUser: ClientUser = {
|
||||||
|
...sanitizedUser,
|
||||||
|
trueSign: TrueSign[serverUser.trueSign],
|
||||||
|
flairs: cutArray(flairs),
|
||||||
|
updatedDate: serverUser.updatedDate?.getTime()
|
||||||
|
};
|
||||||
|
return clientUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SubmitUserToServerUser(submitUser: Partial<SubmitUser>): Omit<Partial<ServerUser>, "_id"> {
|
||||||
|
let serverUser: Omit<Partial<ServerUser>, "_id"> = {
|
||||||
|
...submitUser,
|
||||||
|
flairs: [],
|
||||||
|
code: submitUser.code || nanoid(16),
|
||||||
|
updatedDate: new Date()
|
||||||
|
};
|
||||||
|
return serverUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MergeServerUsers(submitUser: ServerUser, merge: Partial<Omit<ServerUser, "_id">>): ServerUser {
|
||||||
|
let serverUser: ServerUser = {
|
||||||
|
...submitUser,
|
||||||
|
...cutObject(merge),
|
||||||
|
updatedDate: new Date()
|
||||||
|
};
|
||||||
|
return serverUser;
|
||||||
|
}
|
28
src/lib/trollcall/flair.ts
Normal file
28
src/lib/trollcall/flair.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { ServerFlair } from "@/types/flair";
|
||||||
|
import { Filter, cursorToArray, readMany, readOne } from "../db/crud";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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> {
|
||||||
|
const flair = (await readOne("flairs", query)) as ServerFlair | null;
|
||||||
|
return flair;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that returns many ServerFlairs from the database using a FindCursor.
|
||||||
|
* @param query A partial Find query. Can contain an ID.
|
||||||
|
* @param func A function to run on every ServerFlairs returned. Helps reduce loops.
|
||||||
|
* @returns An array of ServerFlairs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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)[];
|
||||||
|
return flair;
|
||||||
|
}
|
28
src/lib/trollcall/troll.ts
Normal file
28
src/lib/trollcall/troll.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { ServerTroll } from "@/types/troll";
|
||||||
|
import { Filter, cursorToArray, readMany, readOne } from "../db/crud";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that returns one ServerTrolls from the database.
|
||||||
|
* @param query A partial Find query. Can contain an ID.
|
||||||
|
* @returns A ServerTroll.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function getSingleTroll(query: Filter<ServerTroll>): Promise<ServerTroll | null> {
|
||||||
|
const troll = (await readOne("trolls", query)) as ServerTroll | null;
|
||||||
|
return troll;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that returns many ServerTrolls from the database using a FindCursor.
|
||||||
|
* @param query A partial Find query. Can contain an ID.
|
||||||
|
* @param func A function to run on every ServerTrolls returned. Helps reduce loops.
|
||||||
|
* @returns An array of ServerTrolls.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function getManyTrolls(
|
||||||
|
query: Filter<ServerTroll>,
|
||||||
|
func?: (input: any) => any
|
||||||
|
): Promise<(ServerTroll | null)[]> {
|
||||||
|
const troll = (await cursorToArray(readMany("trolls", query), func)) as (ServerTroll | null)[];
|
||||||
|
return troll;
|
||||||
|
}
|
50
src/lib/trollcall/user.ts
Normal file
50
src/lib/trollcall/user.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import { ServerUser } from "@/types/user";
|
||||||
|
import { Filter, createOne, cursorToArray, readMany, readOne, replaceOne } from "../db/crud";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that returns one ServerUser from the database.
|
||||||
|
* @param query A partial Find query. Can contain an ID.
|
||||||
|
* @returns A ServerUser.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function getSingleUser(query: Filter<ServerUser>): Promise<ServerUser | null> {
|
||||||
|
const user = (await readOne("users", query)) as ServerUser | null;
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that returns many ServerUsers from the database using a FindCursor.
|
||||||
|
* @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 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)[];
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that puts one ServerUser into the database.
|
||||||
|
* @param user A ServerUser.
|
||||||
|
* @returns A ServerUser, or null, depending on if the operation succeeded.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function createUser(user: Omit<ServerUser, "_id">): Promise<Omit<ServerUser, "_id"> | null> {
|
||||||
|
const newUser = await createOne("users", user);
|
||||||
|
return newUser.acknowledged ? user : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function that changes one database user with the given params.
|
||||||
|
* @param user A ServerUser.
|
||||||
|
* @returns A ServerUser, or null, depending on if the operation succeeded.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function changeUser(user: ServerUser): Promise<ServerUser | null> {
|
||||||
|
const newUser = await replaceOne("users", { _id: user._id }, user);
|
||||||
|
return newUser.acknowledged ? user : null;
|
||||||
|
}
|
38
src/lib/trollcall/utility/merge.ts
Normal file
38
src/lib/trollcall/utility/merge.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { WithId } from "@/lib/db/crud";
|
||||||
|
import _ from "lodash";
|
||||||
|
|
||||||
|
export function cutArray<T>(array: T[]) {
|
||||||
|
const cut: any[] = [];
|
||||||
|
array.forEach((value, i) => {
|
||||||
|
if (value == null) return;
|
||||||
|
if (Array.isArray(value)) cut[i] = cutArray(value);
|
||||||
|
else if (typeof value == "object") cut[i] = cutObject(value);
|
||||||
|
else cut[i] = value;
|
||||||
|
});
|
||||||
|
return cut as NonNullable<T>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cutObject<T extends { [key: string]: any }>(object: T) {
|
||||||
|
const keys = Object.keys(object);
|
||||||
|
let cut: { [key: string]: any } = {};
|
||||||
|
keys.forEach(key => {
|
||||||
|
var val = object[key];
|
||||||
|
if (val == null) return;
|
||||||
|
if (Array.isArray(val)) cut[key] = cutArray(val);
|
||||||
|
else if (_.isObject(val)) cut[key] = cutObject(val);
|
||||||
|
else cut[key] = val;
|
||||||
|
});
|
||||||
|
return cut as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
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"> {
|
||||||
|
const sanitized: Partial<Type> = serverType;
|
||||||
|
delete sanitized.code;
|
||||||
|
return sanitized as Omit<Type, "code">;
|
||||||
|
}
|
16
src/types/assist/branding.ts
Normal file
16
src/types/assist/branding.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
// needlessly complicated branding guide. oh well
|
||||||
|
// this helps you get the gist of how we represent TrollCall within the site
|
||||||
|
|
||||||
|
export const brand = {
|
||||||
|
name: "TrollCall",
|
||||||
|
code: "TrollCallNext",
|
||||||
|
owner: "MeowcaTheoRange"
|
||||||
|
};
|
||||||
|
export const domain = {
|
||||||
|
main: "trollcall",
|
||||||
|
tld: "xyz",
|
||||||
|
owner: "Redact4K"
|
||||||
|
};
|
||||||
|
export const fullDomain = domain.main + "." + domain.tld;
|
||||||
|
export const source = "the original Hiveswap Troll Call";
|
||||||
|
export const sourceCopyright = "Homestuck and HIVESWAP ©️ Homestuck Inc.";
|
102
src/types/assist/color.ts
Normal file
102
src/types/assist/color.ts
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
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),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const clamp = (n: number, mi: number, ma: number) =>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
2182
src/types/assist/extended_zodiac.ts
Normal file
2182
src/types/assist/extended_zodiac.ts
Normal file
File diff suppressed because it is too large
Load diff
6
src/types/assist/generics.ts
Normal file
6
src/types/assist/generics.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import * as yup from "yup";
|
||||||
|
|
||||||
|
export const PolicySchema = yup
|
||||||
|
.string()
|
||||||
|
.oneOf(["yes", "ask", "no"])
|
||||||
|
.default("no");
|
51
src/types/assist/language.ts
Normal file
51
src/types/assist/language.ts
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
export function AdaptivePossessive(owner: string, possession: string) {
|
||||||
|
return owner + (owner.endsWith("s") ? "'" : "'s") + " " + possession;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PronounGrouper(
|
||||||
|
pronouns: [string, string, string],
|
||||||
|
sep?: string,
|
||||||
|
amount?: number
|
||||||
|
) {
|
||||||
|
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 + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AgeConverter(sweeps: number, years?: boolean) {
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ArraySample(array: any[]) {
|
||||||
|
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(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PesterchumNameFormatter(string: string) {
|
||||||
|
return (
|
||||||
|
string +
|
||||||
|
" [" +
|
||||||
|
string.replace(/^(([a-z])[a-z]+)(([A-Z])[a-z]+)$/, "$2$4").toUpperCase() +
|
||||||
|
"]"
|
||||||
|
);
|
||||||
|
}
|
6
src/types/assist/mongo.ts
Normal file
6
src/types/assist/mongo.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { ObjectId } from "mongodb";
|
||||||
|
import * as yup from "yup";
|
||||||
|
|
||||||
|
export const ObjectIdSchema = yup.mixed((value): value is ObjectId =>
|
||||||
|
ObjectId.isValid(value)
|
||||||
|
);
|
53
src/types/client/dialoglog.ts
Normal file
53
src/types/client/dialoglog.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import * as yup from "yup";
|
||||||
|
|
||||||
|
export const SubmitDialogSchema = yup.object({
|
||||||
|
owners: yup.array().of(yup.string().required()).required().min(1),
|
||||||
|
name: yup.string().required().min(3).max(100),
|
||||||
|
description: yup.string().max(10000).ensure(),
|
||||||
|
characters: yup
|
||||||
|
.array()
|
||||||
|
.of(
|
||||||
|
yup
|
||||||
|
.object({
|
||||||
|
troll: yup.string().required(),
|
||||||
|
time: yup.string().ensure()
|
||||||
|
})
|
||||||
|
.required()
|
||||||
|
)
|
||||||
|
.required(),
|
||||||
|
log: yup
|
||||||
|
.array()
|
||||||
|
.of(
|
||||||
|
yup
|
||||||
|
.object({
|
||||||
|
character: yup.number().notRequired().min(0),
|
||||||
|
quirk: yup.string().default("default"),
|
||||||
|
text: yup.string().required().max(2000)
|
||||||
|
})
|
||||||
|
.required()
|
||||||
|
)
|
||||||
|
.required()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SubmitDialog = yup.InferType<typeof SubmitDialogSchema>;
|
||||||
|
|
||||||
|
export const PartialDialogSchema = yup.object({
|
||||||
|
owners: yup.array().of(yup.string()).min(1),
|
||||||
|
name: yup.string().min(3).max(100),
|
||||||
|
description: yup.string().max(10000),
|
||||||
|
characters: yup.array().of(
|
||||||
|
yup.object({
|
||||||
|
troll: yup.string(),
|
||||||
|
time: yup.string()
|
||||||
|
})
|
||||||
|
),
|
||||||
|
log: yup.array().of(
|
||||||
|
yup.object({
|
||||||
|
character: yup.number().min(0),
|
||||||
|
quirk: yup.string().default("default"),
|
||||||
|
text: yup.string().max(2000)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
export type PartialDialog = yup.InferType<typeof PartialDialogSchema>;
|
26
src/types/client/flair.ts
Normal file
26
src/types/client/flair.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import * as yup from "yup";
|
||||||
|
import { ColorSchema } from "../assist/color";
|
||||||
|
|
||||||
|
export const SubmitFlairSchema = yup
|
||||||
|
.object({
|
||||||
|
name: yup.string().required().min(10).max(50),
|
||||||
|
alt: yup.string().max(1000),
|
||||||
|
|
||||||
|
color: ColorSchema.required(),
|
||||||
|
link: yup.string().notRequired().url()
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export type SubmitFlair = yup.InferType<typeof SubmitFlairSchema>;
|
||||||
|
|
||||||
|
export const PartialFlairSchema = yup
|
||||||
|
.object({
|
||||||
|
name: yup.string().min(10).max(50),
|
||||||
|
alt: yup.string().max(1000),
|
||||||
|
|
||||||
|
color: ColorSchema,
|
||||||
|
link: yup.string().url()
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export type PartialFlair = yup.InferType<typeof PartialFlairSchema>;
|
10
src/types/client/quirks.ts
Normal file
10
src/types/client/quirks.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import * as yup from "yup";
|
||||||
|
import { QuirkSchema } from "../quirks";
|
||||||
|
|
||||||
|
export const SubmitQuirkHolderSchema = yup
|
||||||
|
.array()
|
||||||
|
.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"));
|
||||||
|
|
||||||
|
export type SubmitQuirkHolder = yup.InferType<typeof SubmitQuirkHolderSchema>;
|
236
src/types/client/troll.ts
Normal file
236
src/types/client/troll.ts
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
import { ColorSchema } from "@/types/assist/color";
|
||||||
|
import * as yup from "yup";
|
||||||
|
import { ClassKeys, TrueSignKeys } from "../assist/extended_zodiac";
|
||||||
|
import { PolicySchema } from "../assist/generics";
|
||||||
|
import { SubmitQuirkHolderSchema } from "./quirks";
|
||||||
|
|
||||||
|
export const SubmitTrollSchema = yup
|
||||||
|
.object({
|
||||||
|
// Name and identification
|
||||||
|
name: yup
|
||||||
|
.tuple([
|
||||||
|
yup
|
||||||
|
.string()
|
||||||
|
.required()
|
||||||
|
.matches(/^[A-z]+$/, "Letters only")
|
||||||
|
.length(6)
|
||||||
|
.lowercase(),
|
||||||
|
yup
|
||||||
|
.string()
|
||||||
|
.required()
|
||||||
|
.matches(/^[A-z]+$/, "Letters only")
|
||||||
|
.length(6)
|
||||||
|
.lowercase()
|
||||||
|
])
|
||||||
|
.required(),
|
||||||
|
description: yup.string().max(10000).ensure(),
|
||||||
|
pronunciation: yup
|
||||||
|
.tuple([
|
||||||
|
yup
|
||||||
|
.string()
|
||||||
|
.required()
|
||||||
|
.matches(/^[A-z-]+$/, "Letters only")
|
||||||
|
.lowercase(),
|
||||||
|
yup
|
||||||
|
.string()
|
||||||
|
.required()
|
||||||
|
.matches(/^[A-z-]+$/, "Letters only")
|
||||||
|
.lowercase()
|
||||||
|
])
|
||||||
|
.required(),
|
||||||
|
pronouns: yup
|
||||||
|
.array()
|
||||||
|
.of(
|
||||||
|
yup
|
||||||
|
.tuple([
|
||||||
|
yup
|
||||||
|
.string()
|
||||||
|
.required()
|
||||||
|
.matches(/^[A-z]+$/, "Letters only")
|
||||||
|
.min(1)
|
||||||
|
.max(10)
|
||||||
|
.lowercase(), // she, he, they
|
||||||
|
yup
|
||||||
|
.string()
|
||||||
|
.required()
|
||||||
|
.matches(/^[A-z]+$/, "Letters only")
|
||||||
|
.min(1)
|
||||||
|
.max(10)
|
||||||
|
.lowercase(), // her, him, them
|
||||||
|
yup
|
||||||
|
.string()
|
||||||
|
.required()
|
||||||
|
.matches(/^[A-z]+$/, "Letters only")
|
||||||
|
.min(1)
|
||||||
|
.max(10)
|
||||||
|
.lowercase() // hers, his, theirs
|
||||||
|
])
|
||||||
|
.required()
|
||||||
|
)
|
||||||
|
.required()
|
||||||
|
.min(1),
|
||||||
|
gender: yup
|
||||||
|
.string()
|
||||||
|
.required()
|
||||||
|
.matches(/^[A-z-_]+$/, "Letters only")
|
||||||
|
.min(3)
|
||||||
|
.max(30),
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
})
|
||||||
|
.required(),
|
||||||
|
facts: yup.array().of(yup.string().required().min(5).max(100)).required().min(3).max(10),
|
||||||
|
|
||||||
|
// Hiveswap identity
|
||||||
|
trueSign: yup.string().required().oneOf(TrueSignKeys),
|
||||||
|
falseSign: yup
|
||||||
|
.string()
|
||||||
|
.nullable()
|
||||||
|
.transform(v => {
|
||||||
|
return v === "" ? null : v;
|
||||||
|
})
|
||||||
|
.oneOf(TrueSignKeys), // "Keelez Bunbat"
|
||||||
|
class: yup.string().required().oneOf(ClassKeys),
|
||||||
|
|
||||||
|
// Trollian
|
||||||
|
username: yup
|
||||||
|
.string()
|
||||||
|
.required()
|
||||||
|
.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
|
||||||
|
|
||||||
|
// Physical stuff
|
||||||
|
species: yup
|
||||||
|
.string()
|
||||||
|
.notRequired()
|
||||||
|
.matches(/^([A-z-]+)|()$/, "Letters only"), // "Troll-*" if defined. Otherwise, just "Troll".
|
||||||
|
height: yup.number().required().positive(), // Inches
|
||||||
|
age: yup.number().required().positive(), // Sweeps
|
||||||
|
image: yup.string().required().url(),
|
||||||
|
// Meta stuff
|
||||||
|
policies: yup
|
||||||
|
.object({
|
||||||
|
fanart: PolicySchema.required(),
|
||||||
|
fanartOthers: PolicySchema.required(),
|
||||||
|
kinning: PolicySchema.required(),
|
||||||
|
shipping: PolicySchema.required(),
|
||||||
|
fanfiction: PolicySchema.required()
|
||||||
|
})
|
||||||
|
.required()
|
||||||
|
// owners: yup.array().of(yup.string().required()).required().min(1),
|
||||||
|
// flairs: yup.array().of(yup.mixed()).required().ensure(),
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export type SubmitTroll = yup.InferType<typeof SubmitTrollSchema>;
|
||||||
|
|
||||||
|
export const PartialTrollSchema = yup
|
||||||
|
.object({
|
||||||
|
// Name and identification
|
||||||
|
name: yup.tuple([
|
||||||
|
yup
|
||||||
|
.string()
|
||||||
|
.matches(/^[A-z]+$/, "Letters only")
|
||||||
|
.length(6)
|
||||||
|
.lowercase(),
|
||||||
|
yup
|
||||||
|
.string()
|
||||||
|
.matches(/^[A-z]+$/, "Letters only")
|
||||||
|
.length(6)
|
||||||
|
.lowercase()
|
||||||
|
]),
|
||||||
|
description: yup.string().max(10000),
|
||||||
|
pronunciation: yup.tuple([
|
||||||
|
yup
|
||||||
|
.string()
|
||||||
|
.matches(/^[A-z-]+$/, "Letters only")
|
||||||
|
.lowercase(),
|
||||||
|
yup
|
||||||
|
.string()
|
||||||
|
.matches(/^[A-z-]+$/, "Letters only")
|
||||||
|
.lowercase()
|
||||||
|
]),
|
||||||
|
pronouns: yup
|
||||||
|
.array()
|
||||||
|
.of(
|
||||||
|
yup.tuple([
|
||||||
|
yup
|
||||||
|
.string()
|
||||||
|
.matches(/^[A-z]+$/, "Letters only")
|
||||||
|
.min(1)
|
||||||
|
.max(10)
|
||||||
|
.lowercase(), // she, he, they
|
||||||
|
yup
|
||||||
|
.string()
|
||||||
|
.matches(/^[A-z]+$/, "Letters only")
|
||||||
|
.min(1)
|
||||||
|
.max(10)
|
||||||
|
.lowercase(), // her, him, them
|
||||||
|
yup
|
||||||
|
.string()
|
||||||
|
.matches(/^[A-z]+$/, "Letters only")
|
||||||
|
.min(1)
|
||||||
|
.max(10)
|
||||||
|
.lowercase() // hers, his, theirs
|
||||||
|
])
|
||||||
|
)
|
||||||
|
.min(1),
|
||||||
|
gender: yup
|
||||||
|
.string()
|
||||||
|
.matches(/^[A-z-_]+$/, "Letters only")
|
||||||
|
.min(3)
|
||||||
|
.max(30),
|
||||||
|
|
||||||
|
// Personal
|
||||||
|
preferences: yup.object({
|
||||||
|
love: yup.array().of(yup.string().min(5).max(100)).min(3).max(10),
|
||||||
|
hate: yup.array().of(yup.string().min(5).max(100)).min(3).max(10)
|
||||||
|
}),
|
||||||
|
facts: yup.array().of(yup.string().min(5).max(100)).min(3).max(10),
|
||||||
|
|
||||||
|
// Hiveswap identity
|
||||||
|
trueSign: yup.string().oneOf(TrueSignKeys),
|
||||||
|
falseSign: yup
|
||||||
|
.string()
|
||||||
|
.nullable()
|
||||||
|
.transform(v => {
|
||||||
|
return v === "" ? null : v;
|
||||||
|
})
|
||||||
|
.oneOf(TrueSignKeys), // "Keelez Bunbat"
|
||||||
|
class: yup.string().oneOf(ClassKeys),
|
||||||
|
|
||||||
|
// Trollian
|
||||||
|
username: yup
|
||||||
|
.string()
|
||||||
|
.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),
|
||||||
|
yup.number().min(0).max(255)
|
||||||
|
]),
|
||||||
|
quirks: SubmitQuirkHolderSchema, // DO NOT HANDLE RIGHT NOW.
|
||||||
|
// Handled! :D
|
||||||
|
|
||||||
|
// Physical stuff
|
||||||
|
species: yup.string().matches(/^([A-z-]+)|()$/, "Letters only"), // "Troll-*" if defined. Otherwise, just "Troll".
|
||||||
|
height: yup.number().positive(), // Inches
|
||||||
|
age: yup.number().positive(), // Sweeps
|
||||||
|
image: yup.string().url(),
|
||||||
|
// Meta stuff
|
||||||
|
policies: yup.object({
|
||||||
|
fanart: PolicySchema,
|
||||||
|
fanartOthers: PolicySchema,
|
||||||
|
kinning: PolicySchema,
|
||||||
|
shipping: PolicySchema,
|
||||||
|
fanfiction: PolicySchema
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export type PartialTroll = yup.InferType<typeof PartialTrollSchema>;
|
50
src/types/client/user.ts
Normal file
50
src/types/client/user.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import * as yup from "yup";
|
||||||
|
import { ColorSchema } from "../assist/color";
|
||||||
|
import { TrueSignKeys } from "../assist/extended_zodiac";
|
||||||
|
|
||||||
|
export const SubmitUserSchema = yup
|
||||||
|
.object({
|
||||||
|
name: yup
|
||||||
|
.string()
|
||||||
|
.required()
|
||||||
|
.matches(/^[\w-]+$/, "No special characters or spaces")
|
||||||
|
.min(3)
|
||||||
|
.max(50)
|
||||||
|
.lowercase(),
|
||||||
|
description: yup.string().max(10000).ensure(),
|
||||||
|
url: yup.string().notRequired().url(),
|
||||||
|
trueSign: yup.string().required().oneOf(TrueSignKeys),
|
||||||
|
color: ColorSchema.required(),
|
||||||
|
pfp: yup.string().notRequired().url(),
|
||||||
|
code: yup.string().notRequired().max(256, "Too secure!!")
|
||||||
|
// flairs: yup.array().of(ClientFlairSchema).required(),
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export type SubmitUser = yup.InferType<typeof SubmitUserSchema>;
|
||||||
|
|
||||||
|
export const PartialUserSchema = yup
|
||||||
|
.object({
|
||||||
|
name: yup
|
||||||
|
.string()
|
||||||
|
.matches(/^[\w-]+$/, "No special characters or spaces")
|
||||||
|
.min(3)
|
||||||
|
.max(50)
|
||||||
|
.lowercase(),
|
||||||
|
description: yup.string().max(10000),
|
||||||
|
url: yup.string().url(),
|
||||||
|
trueSign: yup
|
||||||
|
.string()
|
||||||
|
.nullable()
|
||||||
|
.transform(v => {
|
||||||
|
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)]),
|
||||||
|
pfp: yup.string().url(),
|
||||||
|
code: yup.string().max(256, "Too secure!!")
|
||||||
|
// flairs: yup.array().of(ClientFlairSchema).required(),
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export type PartialUser = yup.InferType<typeof PartialUserSchema>;
|
45
src/types/dialoglog.ts
Normal file
45
src/types/dialoglog.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { WithId } from "@/lib/db/crud";
|
||||||
|
import * as yup from "yup";
|
||||||
|
import { ObjectIdSchema } from "./assist/mongo";
|
||||||
|
import { SubmitDialogSchema } from "./client/dialoglog";
|
||||||
|
import { ClientTroll, ClientTrollSchema } from "./troll";
|
||||||
|
import { ClientUserSchema } from "./user";
|
||||||
|
|
||||||
|
export const ServerDialogSchema = SubmitDialogSchema.shape({
|
||||||
|
owners: yup.array().of(ObjectIdSchema.required()).required().min(1),
|
||||||
|
characters: yup
|
||||||
|
.array()
|
||||||
|
.of(
|
||||||
|
yup
|
||||||
|
.object({
|
||||||
|
troll: ObjectIdSchema.required(),
|
||||||
|
time: yup.string().ensure()
|
||||||
|
})
|
||||||
|
.required()
|
||||||
|
)
|
||||||
|
.required()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ServerDialog = WithId<yup.InferType<typeof ServerDialogSchema>>;
|
||||||
|
|
||||||
|
export const ClientDialogSchema = SubmitDialogSchema.shape({
|
||||||
|
owners: yup.array().of(ClientUserSchema.required()).required().min(1),
|
||||||
|
characters: yup
|
||||||
|
.array()
|
||||||
|
.of(
|
||||||
|
yup
|
||||||
|
.object({
|
||||||
|
troll: ClientTrollSchema.required(),
|
||||||
|
time: yup.string().ensure()
|
||||||
|
})
|
||||||
|
.required()
|
||||||
|
)
|
||||||
|
.required()
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface ClientDialog extends yup.InferType<typeof ClientDialogSchema> {
|
||||||
|
characters: {
|
||||||
|
troll: ClientTroll;
|
||||||
|
time: string;
|
||||||
|
}[];
|
||||||
|
} // [SEARCH: HACK] a hack. thanks, jquense
|
11
src/types/flair.ts
Normal file
11
src/types/flair.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { WithId } from "@/lib/db/crud";
|
||||||
|
import * as yup from "yup";
|
||||||
|
import { SubmitFlairSchema } from "./client/flair";
|
||||||
|
|
||||||
|
export const ServerFlairSchema = SubmitFlairSchema.shape({});
|
||||||
|
|
||||||
|
export type ServerFlair = WithId<yup.InferType<typeof ServerFlairSchema>>;
|
||||||
|
|
||||||
|
export const ClientFlairSchema = SubmitFlairSchema.shape({});
|
||||||
|
|
||||||
|
export type ClientFlair = yup.InferType<typeof ClientFlairSchema>;
|
87
src/types/quirks.ts
Normal file
87
src/types/quirks.ts
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import * as yup from "yup";
|
||||||
|
|
||||||
|
// See FlaringK's Quirk Builder for type info.
|
||||||
|
// https://flaringk.github.io/quirkbuilder/
|
||||||
|
// https://discord.com/channels/294616636726444033/1067996841532215337/1111282886038016130
|
||||||
|
|
||||||
|
export const QuirkSchema = yup
|
||||||
|
.object({
|
||||||
|
quirk: yup
|
||||||
|
.array()
|
||||||
|
.of(
|
||||||
|
yup
|
||||||
|
.object({
|
||||||
|
type: yup
|
||||||
|
.string()
|
||||||
|
.oneOf([
|
||||||
|
"prefix",
|
||||||
|
"suffix",
|
||||||
|
"simple",
|
||||||
|
"regex",
|
||||||
|
"case",
|
||||||
|
"case_simple",
|
||||||
|
"case_regex",
|
||||||
|
"case_pos"
|
||||||
|
])
|
||||||
|
.required(),
|
||||||
|
find: yup.string().notRequired(),
|
||||||
|
replace: yup
|
||||||
|
.array()
|
||||||
|
.of(
|
||||||
|
yup
|
||||||
|
.string()
|
||||||
|
.required()
|
||||||
|
.matches(/^([A-z-]+)|()$/, "Letters only")
|
||||||
|
)
|
||||||
|
.required()
|
||||||
|
.min(1),
|
||||||
|
condition: yup.string().notRequired()
|
||||||
|
})
|
||||||
|
.required()
|
||||||
|
)
|
||||||
|
.required()
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export type Quirk = yup.InferType<typeof QuirkSchema>;
|
||||||
|
|
||||||
|
export const ServerQuirkHolderSchema = yup.mixed(); // cant do SHIT in yup
|
||||||
|
|
||||||
|
export type ServerQuirkHolder = { [key: string]: Quirk };
|
||||||
|
|
||||||
|
export const QuirkReplacementTypes: {
|
||||||
|
[key: string]: { find: string | null; replace: string };
|
||||||
|
} = {
|
||||||
|
prefix: {
|
||||||
|
find: null,
|
||||||
|
replace: "The text that precedes the main text."
|
||||||
|
},
|
||||||
|
suffix: {
|
||||||
|
find: null,
|
||||||
|
replace: "The text that proceeds the main text."
|
||||||
|
},
|
||||||
|
simple: {
|
||||||
|
find: "Text to find what you want to replace.",
|
||||||
|
replace: "The text that replaces the found text."
|
||||||
|
},
|
||||||
|
regex: {
|
||||||
|
find: "A Regular Expresion to find what you want to replace.",
|
||||||
|
replace: "A replacement expression that replaces the found text."
|
||||||
|
},
|
||||||
|
case: {
|
||||||
|
find: null,
|
||||||
|
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.",
|
||||||
|
replace: '"lower" for lowercase. "upper" for uppercase.'
|
||||||
|
},
|
||||||
|
case_regex: {
|
||||||
|
find: "A Regular Expresion to find what you want to change the case of.",
|
||||||
|
replace: '"lower" for lowercase. "upper" for uppercase.'
|
||||||
|
},
|
||||||
|
case_pos: {
|
||||||
|
find: 'A comma-separated array of properties.\n\n1st is number of characters (type "Infinity" for all),\n2nd is start position (start, end),\n3rd is character seperation (like Gamzee for selecting every other character),\n 4th is the union modifier. "true" means the position will affect the entire sentence. "false" or no input means the position will affect each word.\n\nExample: "2, start, 1, false" for words to look like "GrEetings EvErybody".',
|
||||||
|
replace: '"lower" for lowercase. "upper" for uppercase.'
|
||||||
|
}
|
||||||
|
};
|
30
src/types/troll.ts
Normal file
30
src/types/troll.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import { WithId } from "@/lib/db/crud";
|
||||||
|
import * as yup from "yup";
|
||||||
|
import { ClassSchema, TrueSignSchema } from "./assist/extended_zodiac";
|
||||||
|
import { ObjectIdSchema } from "./assist/mongo";
|
||||||
|
import { SubmitTrollSchema } from "./client/troll";
|
||||||
|
import { ClientFlairSchema } from "./flair";
|
||||||
|
import { ServerQuirkHolder, ServerQuirkHolderSchema } from "./quirks";
|
||||||
|
import { ClientUserSchema } from "./user";
|
||||||
|
|
||||||
|
export const ServerTrollSchema = SubmitTrollSchema.shape({
|
||||||
|
owners: yup.array().of(ObjectIdSchema.required()).required().min(1),
|
||||||
|
flairs: yup.array().of(ObjectIdSchema.required()).required(),
|
||||||
|
quirks: ServerQuirkHolderSchema.required(),
|
||||||
|
updatedDate: yup.date().notRequired()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ServerTroll = WithId<yup.InferType<typeof ServerTrollSchema>>;
|
||||||
|
|
||||||
|
export const ClientTrollSchema = SubmitTrollSchema.shape({
|
||||||
|
owners: yup.array().of(ClientUserSchema.required()).required().min(1),
|
||||||
|
flairs: yup.array().of(ClientFlairSchema.required()).required(),
|
||||||
|
quirks: ServerQuirkHolderSchema.required(),
|
||||||
|
trueSign: TrueSignSchema.required(),
|
||||||
|
falseSign: TrueSignSchema.notRequired(),
|
||||||
|
class: ClassSchema.required()
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface ClientTroll extends yup.InferType<typeof ClientTrollSchema> {
|
||||||
|
quirks: ServerQuirkHolder;
|
||||||
|
} // [SEARCH: HACK] a hack. thanks, jquense
|
22
src/types/user.ts
Normal file
22
src/types/user.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { WithId } from "@/lib/db/crud";
|
||||||
|
import * as yup from "yup";
|
||||||
|
import { TrueSignSchema } from "./assist/extended_zodiac";
|
||||||
|
import { ObjectIdSchema } from "./assist/mongo";
|
||||||
|
import { SubmitUserSchema } from "./client/user";
|
||||||
|
import { ClientFlairSchema } from "./flair";
|
||||||
|
|
||||||
|
export const ServerUserSchema = SubmitUserSchema.shape({
|
||||||
|
flairs: yup.array().of(ObjectIdSchema.required()).required(),
|
||||||
|
code: yup.string().required(),
|
||||||
|
updatedDate: yup.date().notRequired()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ServerUser = WithId<yup.InferType<typeof ServerUserSchema>>;
|
||||||
|
|
||||||
|
export const ClientUserSchema = SubmitUserSchema.shape({
|
||||||
|
flairs: yup.array().of(ClientFlairSchema.required()).required(),
|
||||||
|
trueSign: TrueSignSchema.required(),
|
||||||
|
updatedDate: yup.number().notRequired()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ClientUser = yup.InferType<typeof ClientUserSchema>;
|
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2021",
|
||||||
|
"module": "commonjs",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noErrorTruncation": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"rootDir": "src",
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
"outDir": "./dist/"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.d.ts", "src/**/*.ts"]
|
||||||
|
}
|
Loading…
Reference in a new issue