Compare commits

..

No commits in common. "fd5e5bcc8bc7a319cda5708fc59a9b08f9febee3" and "729aba68ceb055b5ebb84108f40cd4a19c4617a5" have entirely different histories.

34 changed files with 521 additions and 1058 deletions

View File

@ -1,19 +1,11 @@
"use server"; "use server";
import { auth } from "@/auth";
import { prisma } from "@/prisma/prismaClient"; import { prisma } from "@/prisma/prismaClient";
import { isEmailVerified } from "@/util/isEmailVerified";
export const createGameSystem = async (name: string) => { export const createGameSystem = async (name: string) => {
const session = await auth();
if (!session?.user?.id) return null;
if (!isEmailVerified(session.user.id)) return null;
const { id } = await prisma.gameSystem.create({ const { id } = await prisma.gameSystem.create({
data: { data: {
name, name,
authorId: session.user.id,
}, },
select: { select: {
id: true, id: true,

View File

@ -1,18 +1,13 @@
"use server"; "use server";
import { auth } from "@/auth";
import { prisma } from "@/prisma/prismaClient"; import { prisma } from "@/prisma/prismaClient";
import { isEmailVerified } from "@/util/isEmailVerified";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
export const createSchema = async (form: FormData) => { export const createSchema = async (form: FormData) => {
const name = form.get("name")?.toString(); const name = form.get("name")?.toString();
const gsId = form.get("gsId")?.toString(); const gsId = form.get("gsId")?.toString();
const session = await auth(); if (!name || !gsId) return;
if (!name || !gsId || !session?.user?.id || !isEmailVerified(session.user.id))
return;
const { id } = await prisma.schema.create({ const { id } = await prisma.schema.create({
data: { data: {
@ -21,7 +16,6 @@ export const createSchema = async (form: FormData) => {
types: "{}", types: "{}",
version: 0, version: 0,
gameSystemId: gsId, gameSystemId: gsId,
authorId: session.user.id,
}, },
select: { id: true }, select: { id: true },
}); });

View File

@ -1,21 +1,19 @@
"use client"; import { prisma } from "@/prisma/prismaClient";
import { createGameSystem } from "@/actions/GameSystems/create";
import { useToast } from "@/components/toast";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
export default function CreateGameSystem() { export default function CreateGameSystem() {
const { createToast } = useToast();
async function create(form: FormData) { async function create(form: FormData) {
"use server";
const name = form.get("name")?.toString(); const name = form.get("name")?.toString();
if (!name) if (!name) return;
return createToast({ msg: "Please provide a name", fading: true }); const { id } = await prisma.gameSystem.create({
createToast({ msg: "Creating Game System", fading: true }); data: {
const id = await createGameSystem(name); name,
if (!id) },
return createToast({ select: {
msg: "Issue creating game system. Is your email verified?", id: true,
fading: true, },
type: "error",
}); });
redirect(`/game-systems/${id}`); redirect(`/game-systems/${id}`);
} }
@ -24,6 +22,7 @@ export default function CreateGameSystem() {
<form action={create}> <form action={create}>
<input <input
type="text" type="text"
// {...bind}
name="name" name="name"
placeholder="Create a new game system..." placeholder="Create a new game system..."
className="w-min" className="w-min"

View File

@ -1,5 +0,0 @@
import { MDEditor } from "@/components/mdeditor";
export default function Testing() {
return <MDEditor />;
}

View File

@ -5,21 +5,14 @@ import Credentials from "next-auth/providers/credentials";
import Discord from "next-auth/providers/discord"; import Discord from "next-auth/providers/discord";
import bcrypt from "bcryptjs"; import bcrypt from "bcryptjs";
import { SecretClient } from "@/lib/secret/init";
const prisma = new PrismaClient(); const prisma = new PrismaClient();
export const { handlers, signIn, signOut, auth } = NextAuth(async () => { export const { handlers, signIn, signOut, auth } = NextAuth({
const sClient = SecretClient();
const clientId = await sClient.fetchSecret("discord_client_id");
const clientSecret = await sClient.fetchSecret("discord_client_secret");
return {
providers: [ providers: [
Discord({ Discord({
clientId, clientId: process.env.DISCORD_CLIENT_ID,
clientSecret, clientSecret: process.env.DISCORD_CLIENT_SECRET,
}), }),
Credentials({ Credentials({
credentials: { credentials: {
@ -70,7 +63,6 @@ export const { handlers, signIn, signOut, auth } = NextAuth(async () => {
}), }),
], ],
adapter: PrismaAdapter(prisma), adapter: PrismaAdapter(prisma),
};
}); });
async function saltAndHashPassword(password: string) { async function saltAndHashPassword(password: string) {
const hash = await bcrypt.hash(password, 10); const hash = await bcrypt.hash(password, 10);

View File

@ -1,21 +0,0 @@
import CodeMirror from "@uiw/react-codemirror";
import { markdown } from "@codemirror/lang-markdown";
import { duotoneDark } from "@uiw/codemirror-theme-duotone";
interface TextEditorProps {
value: string;
onChange: (value: string) => void;
}
export const TextEditor: React.FC<TextEditorProps> = ({ value, onChange }) => {
return (
<CodeMirror
value={value}
extensions={[markdown({ extensions: [] })]}
onChange={(value, _viewUpdate) => {
onChange(value);
}}
theme={duotoneDark}
/>
);
};

View File

@ -1,19 +0,0 @@
"use client";
import { useDeferredValue, useState } from "react";
import { TextEditor } from "./TextEditor";
import { TTCMD } from "../ttcmd";
export const MDEditor: React.FC = () => {
const [text, setText] = useState("??<<2d6,$0.distribution>>");
const body = useDeferredValue(text);
return (
<div className="flex gap-8">
<div className="w-1/2">
<TextEditor value={text} onChange={setText} />
</div>
<div className="w-1/2">
<TTCMD body={body} parserId="preview" title="preview" />
</div>
</div>
);
};

View File

@ -1,17 +1,9 @@
import { sum } from "./utils/sum";
export class Dice { export class Dice {
private count!: number; private count!: number;
private sides!: number; private sides!: number;
private diceString: string;
toString() {
return this.diceString;
}
constructor(dice: string) { constructor(dice: string) {
this.parseDice(dice); this.parseDice(dice);
this.diceString = dice;
} }
private parseDice(dice: string) { private parseDice(dice: string) {
@ -21,23 +13,11 @@ export class Dice {
} }
public roll() { public roll() {
let results = []; let total = 0;
for (let i = 0; i < this.count; i++) { for (let i = 0; i < this.count; i++) {
results.push(this.rollSingle()); total += this.rollSingle();
} }
return { return total;
total: sum(...results),
max: Math.max(...results),
min: Math.min(...results),
results,
};
}
public rollMax() {
return this.roll().max;
}
public rollMin() {
return this.roll().min;
} }
private rollSingle() { private rollSingle() {
@ -45,13 +25,13 @@ export class Dice {
} }
public rollAvg() { public rollAvg() {
return this.roll().total / this.count; return this.roll() / this.count;
} }
public rollTimes(times: number) { public rollTimes(times: number) {
let total = 0; let total = 0;
for (let i = 0; i < times; i++) { for (let i = 0; i < times; i++) {
total += this.roll().total; total += this.roll();
} }
return total; return total;
} }
@ -78,36 +58,32 @@ export class Dice {
return this.computeDistribution(); return this.computeDistribution();
} }
public computeDistribution(): Record<number, number> { private computeDistribution(): Record<number, number> {
const maxSum = this.count * this.sides;
const dp: number[][] = Array.from({ length: this.count + 1 }, () =>
Array(maxSum + 1).fill(0)
);
dp[0][0] = 1;
for (let dice = 1; dice <= this.count; dice++) {
for (let sum = 0; sum <= maxSum; sum++) {
for (let face = 1; face <= this.sides; face++) {
if (sum >= face) {
dp[dice][sum] += dp[dice - 1][sum - face];
}
}
}
}
const distribution: Record<number, number> = {}; const distribution: Record<number, number> = {};
for (let sum = this.count; sum <= maxSum; sum++) {
distribution[sum] = dp[this.count][sum]; // Helper function to compute the sum distribution for given number of dice
const computeSumDistribution = (
dice: number,
sides: number,
currentSum: number,
currentDice: number
): void => {
if (currentDice === dice) {
distribution[currentSum] = (distribution[currentSum] || 0) + 1;
return;
} }
for (let i = 1; i <= sides; i++) {
computeSumDistribution(dice, sides, currentSum + i, currentDice + 1);
}
};
// Compute distribution
computeSumDistribution(this.count, this.sides, 0, 0);
return distribution; return distribution;
} }
// STATIC // STATIC
static isDice(d: string) { static isDice(d: string) {
return /\d+[dD]\d+/.test(d); return /\d+[dD]\d+/.test(d);
} }
} }
// globalThis.Dice = Dice;

View File

@ -1,7 +1,5 @@
// import { mkdirSync, readFileSync, writeFileSync } from "fs"; // import { mkdirSync, readFileSync, writeFileSync } from "fs";
import { writeFile } from "fs/promises"; // import { writeFile } from "fs/promises";
import { mkdirSync, readFileSync } from "fs";
export class DHSecretClient { export class DHSecretClient {
private token!: Promise<string>; //Set by init private token!: Promise<string>; //Set by init
@ -15,10 +13,14 @@ export class DHSecretClient {
* @param dhBaseUri uri for hosted Dragon's Hoard instance * @param dhBaseUri uri for hosted Dragon's Hoard instance
* @param cacheDir path to cache dir * @param cacheDir path to cache dir
*/ */
constructor(private dhBaseUri: string, private cacheDir: string) { constructor(
private dhBaseUri: string,
private cacheDir: string,
) {
this.cacheLocation = this.cacheDir.trim().replace(/\/^/, "") + "/.dh_cache"; this.cacheLocation = this.cacheDir.trim().replace(/\/^/, "") + "/.dh_cache";
mkdirSync(this.cacheDir, { recursive: true }); // mkdirSync(this.cacheDir, { recursive: true });
this.readDiskCache(); // writeFileSync(this.cacheLocation, "{}", { encoding: "utf-8", flag: "wx" });
// this.readDiskCache();
this.token = this.fetchToken(); this.token = this.fetchToken();
} }
@ -41,22 +43,19 @@ export class DHSecretClient {
return token; return token;
} }
private readDiskCache() { // private readDiskCache() {
const cache = readFileSync(this.cacheLocation, "utf-8"); // const cache = readFileSync(this.cacheLocation, "utf-8");
if (!cache) { // this.cache = JSON.parse(cache || "{}");
this.cache = {}; // }
this.writeDiskCache(); // private async writeDiskCache() {
} else this.cache = JSON.parse(cache || "{}"); // await writeFile(this.cacheLocation, JSON.stringify(this.cache), "utf-8");
} // }
private async writeDiskCache() {
await writeFile(this.cacheLocation, JSON.stringify(this.cache), "utf-8");
}
private writeCache(key: string, value: string, expires?: number) { private writeCache(key: string, value: string, expires?: number) {
this.cache[key] = { value, expires }; this.cache[key] = { value, expires };
this.writeDiskCache(); // this.writeDiskCache();
} }
private readCache(key: string) { private readCache(key: string) {
@ -74,10 +73,7 @@ export class DHSecretClient {
} }
async fetchSecret(secret_name: string, environment?: string) { async fetchSecret(secret_name: string, environment?: string) {
const uri = const uri = this.dhBaseUri + "/api/keys/" + secret_name +
this.dhBaseUri +
"/api/keys/" +
secret_name +
(environment ? "?env=" + environment : ""); (environment ? "?env=" + environment : "");
const cached = this.readCache(secret_name); const cached = this.readCache(secret_name);

View File

@ -5,8 +5,8 @@ if (!globalThis.Secrets) {
"https://dragonshoard.cyborggrizzly.com", "https://dragonshoard.cyborggrizzly.com",
process.env.NODE_ENV === "development" process.env.NODE_ENV === "development"
? "./.dragonshoard" ? "./.dragonshoard"
: "/.dragonshoard" : "/.dragonshoard",
); );
} }
export const SecretClient = (): DHSecretClient => globalThis.Secrets; export const SecretClient = () => globalThis.Secrets;

View File

@ -1,27 +1,18 @@
import { PublicationAtom } from "@/recoil/atoms/publication"; import { PublicationAtom } from "@/recoil/atoms/publication";
import { useState, useEffect, useCallback, useRef, ReactNode } from "react"; import { useState, useEffect, useCallback, useRef } from "react";
import { useRecoilValue } from "recoil"; import { useRecoilValue } from "recoil";
import { TTCQueryResolver } from "../ttcQuery/TTCResolvers"; import { TTCQueryResolver } from "../ttcQuery/TTCResolvers";
export function Resolver({ resolver }: { resolver: string }) { export function Resolver({ resolver }: { resolver: string }) {
const parser = useRecoilValue(PublicationAtom); const parser = useRecoilValue(PublicationAtom);
const [res] = useState(new TTCQueryResolver(parser)); const [res] = useState(new TTCQueryResolver(parser));
const [content, setContent] = useState<ReactNode>(""); const [content, setContent] = useState("");
useEffect(() => { useEffect(() => {
let resolved = res.resolve(resolver); setContent(res.resolve(resolver));
setContent(
typeof resolved?.display === "function" ? (
<resolved.display />
) : (
resolved?.display
)
);
}, [resolver, res]); }, [resolver, res]);
return <span>{content}</span>; return <span>{content}</span>;
} }
const defaultTemplate = "$x";
export function OnDemandResolver({ export function OnDemandResolver({
resolver, resolver,
template, template,
@ -33,27 +24,17 @@ export function OnDemandResolver({
}) { }) {
const parser = useRecoilValue(PublicationAtom); const parser = useRecoilValue(PublicationAtom);
const res = useRef(new TTCQueryResolver(parser)); const res = useRef(new TTCQueryResolver(parser));
const [content, setContent] = useState<ReactNode>(""); const [content, setContent] = useState("");
const generateContent = useCallback(() => { const generateContent = useCallback(() => {
setContent(() => { let content = template;
let content = template || defaultTemplate; const stackIdxs = Array.from(new Set(template.match(/\$\d/g)));
const stackIdxs = Array.from(new Set(content.match(/\$(?:\d+|x)/g)));
for (const idx of stackIdxs) { for (const idx of stackIdxs) {
let thing = res.current.getFromStack(idx); let thing = res.current.getFromStack(idx);
if (Array.isArray(thing)) thing = thing.at(0); if (Array.isArray(thing)) thing = thing.at(0);
console.log(thing); if (typeof thing === "function") thing = thing();
if (typeof thing.display === "function") { content = content.replaceAll(idx, thing as string);
const disp = thing.display();
if (typeof disp === "string" || typeof disp === "number")
content = content.replaceAll(idx, disp.toString());
else return disp as ReactNode;
} }
// else if (idx === defaultTemplate && ) setContent(content);
else content = content.replaceAll(idx, thing.display as string);
return content;
}
});
}, [res, template]); }, [res, template]);
const resolve = useCallback(() => { const resolve = useCallback(() => {
@ -62,16 +43,12 @@ export function OnDemandResolver({
}, [res, resolver, generateContent]); }, [res, resolver, generateContent]);
return ( return (
<div className="my-2 rounded-md p-1 bg-black/20 flex flex-col"> <>
<button <button onMouseDown={() => setContent("")} onClick={resolve}>
className="text-primary-600"
onMouseDown={() => setContent("")}
onClick={resolve}
>
{title ?? "Resolve"} {title ?? "Resolve"}
</button> </button>
<br /> <br />
{!!content && <span>{content}</span>} {!!content && <span>{content}</span>}
</div> </>
); );
} }

View File

@ -549,7 +549,6 @@ export const buildOnlyDefaultElements = () => {
// paragraph // paragraph
registerIdentifier( registerIdentifier(
"p", "p",
// /(?<=\n\n|^)([\s\S]*?)(?=\n\n|$)/g,
/(?<=\n\n)([\s\S]*?)(?=\n\n)/g, /(?<=\n\n)([\s\S]*?)(?=\n\n)/g,
(s) => { (s) => {
return { return {
@ -803,11 +802,11 @@ export const buildOnlyDefaultElements = () => {
// on-demand resolver // on-demand resolver
registerIdentifier( registerIdentifier(
"on-demand resolver", "on-demand resolver",
/\?\?\[.*?\](\(.*?\))<<(.*?)>>/g, /\?\?\[.*?\](\(.*?\))?<<(.*?)>>/g,
(s) => { (s) => {
const inp = s.match(/(?<=<<)(.*?)(?=>>)/)![0]; const inp = s.match(/(?<=<<)(.*?)(?=>>)/)![0];
const title = s.match(/(?<=\?\?\[)(.*?)(?=\])/)![0]; const template = s.match(/(?<=\?\?\[)(.*?)(?=\])/)![0];
const template = s.match(/(?<=\]\()(.*?)(?=\))/)![0]; const title = s.match(/(?<=\]\()(.*?)(?=\))/)?.at(0);
if (inp == undefined) if (inp == undefined)
return { return {
content: "Error parsing resolver: " + s, content: "Error parsing resolver: " + s,
@ -932,7 +931,7 @@ function search(
function generateId(t: string, usedIds: string[]) { function generateId(t: string, usedIds: string[]) {
let id = t let id = t
.toLowerCase() .toLowerCase()
.replace(/[^a-z\s-\d]/gi, "") .replace(/[^a-z\s]/gi, "")
.trim() .trim()
.replaceAll(" ", "-"); .replaceAll(" ", "-");
let idNum = 1; let idNum = 1;

View File

@ -11,10 +11,7 @@ export const createElements = (body: string): Token[] => {
const tokenize = (body: string) => { const tokenize = (body: string) => {
const tokenizedBody: TokenMarker[] = []; const tokenizedBody: TokenMarker[] = [];
body = body body = body.replaceAll(/[ \t]+\n/g, "\n").replaceAll(/\n{3,}/g, "\n\n");
.trim()
.replaceAll(/[ \t]+\n/g, "\n")
.replaceAll(/\n{3,}/g, "\n\n");
const addToken = (thing: TokenMarker) => { const addToken = (thing: TokenMarker) => {
tokenizedBody.push(thing); tokenizedBody.push(thing);
@ -26,7 +23,6 @@ const tokenize = (body: string) => {
const rx = new RegExp(token.rx); const rx = new RegExp(token.rx);
let match; let match;
while ((match = rx.exec(body)) !== null) { while ((match = rx.exec(body)) !== null) {
if (type === "p") debugger;
const start = match.index; const start = match.index;
const end = rx.lastIndex; const end = rx.lastIndex;
@ -273,10 +269,7 @@ export function extractFrontMatter(body: string): [FrontMatter, string] {
const rx = /^---([\s\S]*?)---/; const rx = /^---([\s\S]*?)---/;
const [_, frontmatterString] = body.match(rx) || ["", ""]; const [_, frontmatterString] = body.match(rx) || ["", ""];
console.log(body.replace(rx, "")); body = body.replace(rx, "");
body = body.replace(rx, "").trim();
console.log(body);
const frontMatter: FrontMatter = {}; const frontMatter: FrontMatter = {};

View File

@ -1,48 +0,0 @@
import { FC } from "react";
export const DiceChart: FC<{ dice: Record<string, number> }> = ({ dice }) => {
const data = Object.entries(dice).sort((a, b) => Number(a[0]) - Number(b[0]));
const max = Math.max(...data.map((d) => d[1]));
const _min = Math.min(...data.map((d) => d[1]));
const uniqueValues = Array.from(new Set(data.map((d) => d[1])));
return (
<div className="p-8 bg-black/30 rounded-md max-w-[35rem]">
<div className="relative flex px-2 gap-1 justify-around items-end h-48 border-b border-l">
<div className="absolute inset-0 flex flex-col-reverse">
{uniqueValues.map((_, i) => (
<div
key={"dicechartline" + i}
className="relative w-full border-t border-mixed-300/50 flex-grow"
>
{/* <div className="absolute right-full text-xs flex items-center -translate-y-1/2">
<span>
{Math.round(max / uniqueValues.length) * (i + 1)} {}
</span>
<div className="border-b w-1 ml-1"></div>
</div> */}
</div>
))}
</div>
{data.map((d) => (
<div
key={"dice" + d[0]}
title={d[0] + ": " + d[1]}
className="flex-grow px-[1px] border hover:border-mixed-300/50 border-transparent h-full flex transition-colors rounded-md @container"
>
<div
className="bg-purple-800 relative rounded-t-sm w-full mt-auto"
style={{ height: (d[1] / max) * 100 + "%" }}
>
<span className="absolute top-full left-1/2 -translate-x-1/2 text-xs @[.75rem]:inline hidden">
{d[0]}
</span>
</div>
</div>
))}
</div>
</div>
);
};

View File

@ -1,20 +1,11 @@
/* eslint-disable react/display-name */
import { ReactNode } from "react";
import { Dice } from "../dice"; import { Dice } from "../dice";
import { sum } from "../utils/sum";
import { DiceChart } from "./DiceChart";
import { TTCQueryParser } from "./TTCQueryParser"; import { TTCQueryParser } from "./TTCQueryParser";
interface StackItem {
value: any;
display: ReactNode | (() => ReactNode);
}
export class TTCQueryResolver { export class TTCQueryResolver {
private parser: TTCQueryParser | null; private parser: TTCQueryParser | null;
private context: QueryableObject | null = null; private context: QueryableObject | null = null;
private stack: StackItem[] = []; private stack: any[] = [];
constructor(parser?: TTCQueryParser) { constructor(parser?: TTCQueryParser) {
this.parser = parser || null; this.parser = parser || null;
@ -28,35 +19,21 @@ export class TTCQueryResolver {
this.context = obj; this.context = obj;
} }
public resolve(resolver: string, onDemand?: boolean): StackItem | undefined { public resolve(resolver: string, onDemand?: boolean) {
try {
const resList = resolver.split(","); const resList = resolver.split(",");
for (const res of resList) { for (const res of resList) {
this.stack.push(this.parseResolver(res)); this.stack.push(this.parseResolver(res));
} }
const last = this.stack.at(-1); const last = this.stack.at(-1);
if (typeof last?.display === "function" && !onDemand) { if (typeof last === "function" && !onDemand) return last();
last.display = last.display();
}
return last; return last;
} catch (e) {
return { value: e?.toString(), display: e?.toString() };
}
} }
private parseResolver(resolver: string): StackItem { private parseResolver(resolver: string) {
if (this.isArithmetic(resolver)) return this.solveArithmetic(resolver); if (this.isArithmetic(resolver)) return this.solveArithmetic(resolver);
if (this.isQuery(resolver)) return this.runQuery(resolver); if (this.isQuery(resolver)) return this.runQuery(resolver);
if (Dice.isDice(resolver)) { return resolver;
const value = new Dice(resolver);
const dice: StackItem = {
value,
display: () => value.toString(),
};
return dice;
}
return { value: resolver, display: resolver };
} }
private isQuery(resolver: string) { private isQuery(resolver: string) {
return ( return (
@ -65,82 +42,57 @@ export class TTCQueryResolver {
/^\$\d\./.test(resolver) /^\$\d\./.test(resolver)
); );
} }
private runQuery(query: string): StackItem { private runQuery(query: string) {
if (!this.parser) throw "Parser not defined in query resolver"; if (!this.parser) throw "Parser not defined in query resolver";
if (query.startsWith("$")) { if (query.startsWith("$")) {
const [_, idx, q] = query.match(/^(\$\d+)\.(.*)/) || []; const [_, idx, q] = query.match(/^(\$\d+)\.(.*)/) || [];
if (!_) throw "Detected stack query but did not match the regex"; if (!_) throw "Detected stack query but did not match the regex";
const stackItem = this.getFromStack(idx); const stackItem = this.getFromStack(idx);
if (typeof stackItem === "string" && Dice.isDice(stackItem)) {
if (stackItem.value instanceof Dice) { return this.handleDice(stackItem, q);
return {
value: query,
display: this.handleDice(stackItem.value, q),
};
} }
const [res] = this.parser.search(q, stackItem.value as QueryableObject); return this.parser.search(q, stackItem as QueryableObject);
debugger;
if (Dice.isDice(res)) {
const value = new Dice(res);
return {
value,
display: () => value.toString(),
};
}
return {
value: res,
display() {
return (
this.value.render ?? this.value ?? "Error resolving query: " + query
);
},
};
} }
// if (query.startsWith("?") || query.startsWith("_")) // if (query.startsWith("?") || query.startsWith("_"))
const res = return query.startsWith("_") && this.context
query.startsWith("_") && this.context
? this.parser.search(query.replace("_", "^"), this.context).at(0) ? this.parser.search(query.replace("_", "^"), this.context).at(0)
: this.parser.search(query.replace(/^[?_].?/, "")).at(0); : this.parser.search(query.replace(/^[?_].?/, "")).at(0);
if (Dice.isDice(res)) {
const value = new Dice(res);
return {
value,
display: () => value.toString(),
};
} }
return {
value: res, private handleDice(dice: string, query: string) {
display() { const d = new Dice(dice);
return ( const [method, n] = query.split(":");
this.value.render ?? this.value ?? "Error resolving query: " + query let num = Number(n);
); if (n && n.startsWith("$")) num = this.getFromStack(n);
}, switch (method) {
}; case "roll":
return () => d.roll();
case "rollAvg":
return () => d.rollAvg();
case "rollTimes":
return () => d.rollTimes(num);
case "rollTimesAvg":
return () => d.rollTimesAvg(num);
default:
return "No valid method provided for dice";
}
} }
private isArithmetic(resolver: string) { private isArithmetic(resolver: string) {
return resolver.split(/\+|\/|-|\*|\^/).filter((e) => !!e).length > 1; return resolver.split(/\+|\/|-|\*|\^/).filter((e) => !!e).length > 1;
} }
private solveArithmetic(resolver: string): StackItem { private solveArithmetic(resolver: string) {
const [n1, op, n2] = resolver const [n1, op, n2] = resolver
.match(/(\$?\d+)([+\-*\/^])(\$?\d+)/) .match(/(\$?\d+)([+\-*\/^])(\$?\d+)/)
?.slice(1) || ["", "+", ""]; ?.slice(1) || ["", "+", ""];
let num1: number = Number(n1), let num1 = Number(n1),
num2: number = Number(n2); num2 = Number(n2);
if (n1.startsWith("$")) { if (n1.startsWith("$")) num1 = this.getFromStack<number>(n1);
const result = this.getFromStack(n1).value; if (n2.startsWith("$")) num2 = this.getFromStack<number>(n2);
num1 = result instanceof Dice ? result.roll().total : Number(result);
}
if (n2.startsWith("$")) {
const result = this.getFromStack(n1).value;
num2 = result instanceof Dice ? result.roll().total : Number(result);
}
const thing: StackItem = {
value: () => {
switch (op) { switch (op) {
case "+": case "+":
return num1 + num2; return num1 + num2;
@ -155,51 +107,11 @@ export class TTCQueryResolver {
default: default:
throw "Arithmetic detected but no proper operator assigned"; throw "Arithmetic detected but no proper operator assigned";
} }
},
display() {
return typeof this.value === "function" ? this.value() : this.value;
},
};
return thing;
} }
public getFromStack(stackIndex: string): StackItem { public getFromStack<T>(stackIndex: string): T {
if (stackIndex === "$x") return this.stack.at(-1)!;
const i = Number(stackIndex.replace("$", "")); const i = Number(stackIndex.replace("$", ""));
const val = this.stack[i]; const val = this.stack[i] as T;
return val; return val;
} }
private handleDice(dice: Dice, query: string) {
const [method, n] = query.split(":");
let num = Number(n);
// if (n && n.startsWith("$")) num = this.getFromStack(n);
switch (method) {
case "roll":
return () => dice.roll.apply(dice).total;
case "rollAvg":
return () => dice.rollAvg.apply(dice);
case "rollTimes":
return () => dice.rollTimes.apply(dice, [num]);
case "rollTimesAvg":
return () => dice.rollTimesAvg.apply(dice, [num]);
case "rollLowest":
return () => dice.rollMin.apply(dice);
case "rollHighest":
return () => dice.rollMax.apply(dice);
case "rollDropHighest":
return () =>
sum(...dice.roll.apply(dice).results.toSorted().toSpliced(-1, 1));
case "rollDropLowest":
return () =>
sum(...dice.roll.apply(dice).results.toSorted().toSpliced(0, 1));
case "distribution":
return () => DiceChart({ dice: dice.getRollDistribution.apply(dice) });
case "distribution.normalized":
return () =>
DiceChart({ dice: dice.getNormalizedRollDistribution.apply(dice) });
default:
return () => "No valid method provided for dice";
}
}
} }

View File

@ -1,3 +0,0 @@
export const sum = (...args: number[]) => {
return args.reduce((a, b) => a + b, 0);
};

View File

@ -2,7 +2,7 @@
title: How to use ttcMD title: How to use ttcMD
author: Emmaline Autumn author: Emmaline Autumn
date: March 14th, 2024 date: March 14th, 2024
updated: August 21st, 2024 updated: March 14th, 2024
--- ---
# Table of Contents # Table of Contents
@ -368,17 +368,17 @@ Let's say you want to get the the result of rolling a dice field. You would simp
This works very similarly to the normal resolver, but when it renders it will have a button that you can press. When you press it, it recalculates its value. This is very useful for dice and decks of cards. It has not been fully implemented yet This works very similarly to the normal resolver, but when it renders it will have a button that you can press. When you press it, it recalculates its value. This is very useful for dice and decks of cards. It has not been fully implemented yet
Here's the syntax: `??[Text](Template)<<List::QueryValue>>`. Template is a basic string that has access to all values of the resolver using the `$#` variables. If template is left blank, the value that the resolver finally reaches will be rendered. In lieu of a template, you can also have it render the display of a field if it has one. Text is the text that will be rendered in the button. If not provided, the button will simply same "Resolve." Here's the syntax: `??[Template](Text?)<<List::QueryValue>>`. Template is a basic string that has access to all values of the resolver using the `$#` variables. If template is left blank, the value that the resolver finally reaches will be rendered. In lieu of a template, you can also have it render the display of a field if it has one. Text is the text that will be rendered in the button. If not provided, the button will simply same "Resolve."
[][] [][]
[[ [[
To use the dice as an example again, here's how you would do that: `??[Roll 2d6](Rolling $0, you got: $1)<<_.path.to.dice,$0.roll>>` To use the dice as an example again, here's how you would do that: `??[Rolling $0, you got: $1](Roll 2d6)<<_.path.to.dice,$0.roll>>`
??[Roll 2d6](Rolling $0, you got: $1)<<_.path.to.dice,$0.roll>> ??[Rolling $0, you got: $1](Roll 2d6)<<_.path.to.dice,$0.roll>>
]] ]]
[[ [[
For drawing a card and having it show the card's display: `??[]()<<_.path.to.deck,$0.draw,$1.display>>` For drawing a card and having it show the card's display: `??[]<<_.path.to.deck,$0.draw,$1.display>>`
]] ]]
/[] /[]

View File

@ -13,8 +13,3 @@
4. name 4. name
5. is 5. is
6. welcome 6. welcome
- [-\_hello1234!@#$%^\&\*()-](#-_hello1234-)
# -_hello1234!@#$%^&*()-

472
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -11,13 +11,9 @@
}, },
"dependencies": { "dependencies": {
"@auth/prisma-adapter": "^2.4.2", "@auth/prisma-adapter": "^2.4.2",
"@codemirror/lang-markdown": "^6.2.5",
"@heroicons/react": "^2.1.1", "@heroicons/react": "^2.1.1",
"@prisma/client": "^5.18.0", "@prisma/client": "^5.18.0",
"@tailwindcss/container-queries": "^0.1.1",
"@types/bcryptjs": "^2.4.6", "@types/bcryptjs": "^2.4.6",
"@uiw/codemirror-theme-duotone": "^4.23.0",
"@uiw/react-codemirror": "^4.23.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"isomorphic-dompurify": "^2.4.0", "isomorphic-dompurify": "^2.4.0",
"jotai": "^2.9.3", "jotai": "^2.9.3",

View File

@ -0,0 +1,46 @@
-- CreateTable
CREATE TABLE `GameSystem` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NOT NULL,
`created` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `Schema` (
`id` VARCHAR(191) NOT NULL,
`gameSystemId` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NOT NULL,
`schema` JSON NOT NULL,
`version` INTEGER NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `Publication` (
`id` VARCHAR(191) NOT NULL,
`schemaId` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NOT NULL,
`data` JSON NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `Tag` (
`id` VARCHAR(191) NOT NULL,
`publicationId` VARCHAR(191) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- AddForeignKey
ALTER TABLE `Schema` ADD CONSTRAINT `Schema_gameSystemId_fkey` FOREIGN KEY (`gameSystemId`) REFERENCES `GameSystem`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `Publication` ADD CONSTRAINT `Publication_schemaId_fkey` FOREIGN KEY (`schemaId`) REFERENCES `Schema`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `Tag` ADD CONSTRAINT `Tag_publicationId_fkey` FOREIGN KEY (`publicationId`) REFERENCES `Publication`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1,8 @@
/*
Warnings:
- A unique constraint covering the columns `[name]` on the table `GameSystem` will be added. If there are existing duplicate values, this will fail.
*/
-- CreateIndex
CREATE UNIQUE INDEX `GameSystem_name_key` ON `GameSystem`(`name`);

View File

@ -0,0 +1,8 @@
/*
Warnings:
- Added the required column `types` to the `Schema` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE `Schema` ADD COLUMN `types` JSON NOT NULL;

View File

@ -0,0 +1,83 @@
/*
Warnings:
- Added the required column `authorId` to the `GameSystem` table without a default value. This is not possible if the table is not empty.
- Added the required column `authorId` to the `Publication` table without a default value. This is not possible if the table is not empty.
- Added the required column `authorId` to the `Schema` table without a default value. This is not possible if the table is not empty.
- Added the required column `originalId` to the `Schema` table without a default value. This is not possible if the table is not empty.
- Added the required column `name` to the `Tag` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE `Schema` DROP FOREIGN KEY `Schema_gameSystemId_fkey`;
-- DropForeignKey
ALTER TABLE `Tag` DROP FOREIGN KEY `Tag_publicationId_fkey`;
-- AlterTable
ALTER TABLE `GameSystem` ADD COLUMN `authorId` VARCHAR(191) NOT NULL;
-- AlterTable
ALTER TABLE `Publication` ADD COLUMN `authorId` VARCHAR(191) NOT NULL;
-- AlterTable
ALTER TABLE `Schema` ADD COLUMN `authorId` VARCHAR(191) NOT NULL,
ADD COLUMN `originalId` VARCHAR(191) NOT NULL,
MODIFY `gameSystemId` VARCHAR(191) NULL;
-- AlterTable
ALTER TABLE `Tag` ADD COLUMN `name` VARCHAR(191) NOT NULL,
MODIFY `publicationId` VARCHAR(191) NULL;
-- CreateTable
CREATE TABLE `TagsOnPublications` (
`publicationId` VARCHAR(191) NOT NULL,
`tagId` VARCHAR(191) NOT NULL,
PRIMARY KEY (`publicationId`, `tagId`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `TagsOnTags` (
`parentTagId` VARCHAR(191) NOT NULL,
`childTagId` VARCHAR(191) NOT NULL,
PRIMARY KEY (`parentTagId`, `childTagId`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `User` (
`id` VARCHAR(191) NOT NULL,
`username` VARCHAR(191) NOT NULL,
`email` VARCHAR(191) NOT NULL,
UNIQUE INDEX `User_email_key`(`email`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- AddForeignKey
ALTER TABLE `GameSystem` ADD CONSTRAINT `GameSystem_authorId_fkey` FOREIGN KEY (`authorId`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `Schema` ADD CONSTRAINT `Schema_gameSystemId_fkey` FOREIGN KEY (`gameSystemId`) REFERENCES `GameSystem`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `Schema` ADD CONSTRAINT `Schema_authorId_fkey` FOREIGN KEY (`authorId`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `Publication` ADD CONSTRAINT `Publication_authorId_fkey` FOREIGN KEY (`authorId`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `TagsOnPublications` ADD CONSTRAINT `TagsOnPublications_publicationId_fkey` FOREIGN KEY (`publicationId`) REFERENCES `Publication`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `TagsOnPublications` ADD CONSTRAINT `TagsOnPublications_tagId_fkey` FOREIGN KEY (`tagId`) REFERENCES `Tag`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `Tag` ADD CONSTRAINT `Tag_publicationId_fkey` FOREIGN KEY (`publicationId`) REFERENCES `Publication`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `TagsOnTags` ADD CONSTRAINT `TagsOnTags_parentTagId_fkey` FOREIGN KEY (`parentTagId`) REFERENCES `Tag`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `TagsOnTags` ADD CONSTRAINT `TagsOnTags_childTagId_fkey` FOREIGN KEY (`childTagId`) REFERENCES `Tag`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1,71 @@
/*
Warnings:
- A unique constraint covering the columns `[username]` on the table `User` will be added. If there are existing duplicate values, this will fail.
- Added the required column `updatedAt` to the `User` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE `User` ADD COLUMN `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
ADD COLUMN `emailVerified` DATETIME(3) NULL,
ADD COLUMN `image` VARCHAR(191) NULL,
ADD COLUMN `name` VARCHAR(191) NULL,
ADD COLUMN `updatedAt` DATETIME(3) NOT NULL,
MODIFY `username` VARCHAR(191) NULL,
MODIFY `email` VARCHAR(191) NULL;
-- CreateTable
CREATE TABLE `Account` (
`id` VARCHAR(191) NOT NULL,
`userId` VARCHAR(191) NOT NULL,
`type` VARCHAR(191) NOT NULL,
`provider` VARCHAR(191) NOT NULL,
`providerAccountId` VARCHAR(191) NOT NULL,
`refresh_token` TEXT NULL,
`access_token` TEXT NULL,
`expires_at` INTEGER NULL,
`token_type` VARCHAR(191) NULL,
`scope` VARCHAR(191) NULL,
`id_token` TEXT NULL,
`session_state` VARCHAR(191) NULL,
`refresh_token_expires_in` INTEGER NULL,
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updatedAt` DATETIME(3) NOT NULL,
UNIQUE INDEX `Account_userId_key`(`userId`),
INDEX `Account_userId_idx`(`userId`),
UNIQUE INDEX `Account_provider_providerAccountId_key`(`provider`, `providerAccountId`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `Session` (
`id` VARCHAR(191) NOT NULL,
`sessionToken` VARCHAR(191) NOT NULL,
`userId` VARCHAR(191) NOT NULL,
`expires` DATETIME(3) NOT NULL,
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updatedAt` DATETIME(3) NOT NULL,
UNIQUE INDEX `Session_sessionToken_key`(`sessionToken`),
INDEX `Session_userId_idx`(`userId`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `VerificationToken` (
`identifier` VARCHAR(191) NOT NULL,
`token` VARCHAR(191) NOT NULL,
`expires` DATETIME(3) NOT NULL,
UNIQUE INDEX `VerificationToken_identifier_token_key`(`identifier`, `token`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateIndex
CREATE UNIQUE INDEX `User_username_key` ON `User`(`username`);
-- AddForeignKey
ALTER TABLE `Account` ADD CONSTRAINT `Account_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `Session` ADD CONSTRAINT `Session_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE `User` ADD COLUMN `passwordHash` VARCHAR(191) NULL;

View File

@ -1,177 +0,0 @@
-- CreateTable
CREATE TABLE "GameSystem" (
"id" TEXT NOT NULL,
"authorId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"created" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "GameSystem_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Schema" (
"id" TEXT NOT NULL,
"gameSystemId" TEXT,
"authorId" TEXT NOT NULL,
"originalId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"schema" JSONB NOT NULL,
"types" JSONB NOT NULL,
"version" INTEGER NOT NULL,
CONSTRAINT "Schema_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Publication" (
"id" TEXT NOT NULL,
"schemaId" TEXT NOT NULL,
"authorId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"data" JSONB NOT NULL,
CONSTRAINT "Publication_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TagsOnPublications" (
"publicationId" TEXT NOT NULL,
"tagId" TEXT NOT NULL,
CONSTRAINT "TagsOnPublications_pkey" PRIMARY KEY ("publicationId","tagId")
);
-- CreateTable
CREATE TABLE "Tag" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"publicationId" TEXT,
CONSTRAINT "Tag_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "TagsOnTags" (
"parentTagId" TEXT NOT NULL,
"childTagId" TEXT NOT NULL,
CONSTRAINT "TagsOnTags_pkey" PRIMARY KEY ("parentTagId","childTagId")
);
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"name" TEXT,
"username" TEXT,
"email" TEXT,
"emailVerified" TIMESTAMP(3),
"passwordHash" TEXT,
"image" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Account" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"type" TEXT NOT NULL,
"provider" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"refresh_token" TEXT,
"access_token" TEXT,
"expires_at" INTEGER,
"token_type" TEXT,
"scope" TEXT,
"id_token" TEXT,
"session_state" TEXT,
"refresh_token_expires_in" INTEGER,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Account_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Session" (
"id" TEXT NOT NULL,
"sessionToken" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"expires" TIMESTAMP(3) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "VerificationToken" (
"identifier" TEXT NOT NULL,
"token" TEXT NOT NULL,
"expires" TIMESTAMP(3) NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "GameSystem_name_key" ON "GameSystem"("name");
-- CreateIndex
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "Account_userId_key" ON "Account"("userId");
-- CreateIndex
CREATE INDEX "Account_userId_idx" ON "Account"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
-- CreateIndex
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
-- CreateIndex
CREATE INDEX "Session_userId_idx" ON "Session"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");
-- AddForeignKey
ALTER TABLE "GameSystem" ADD CONSTRAINT "GameSystem_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Schema" ADD CONSTRAINT "Schema_gameSystemId_fkey" FOREIGN KEY ("gameSystemId") REFERENCES "GameSystem"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Schema" ADD CONSTRAINT "Schema_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Publication" ADD CONSTRAINT "Publication_schemaId_fkey" FOREIGN KEY ("schemaId") REFERENCES "Schema"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Publication" ADD CONSTRAINT "Publication_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TagsOnPublications" ADD CONSTRAINT "TagsOnPublications_publicationId_fkey" FOREIGN KEY ("publicationId") REFERENCES "Publication"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TagsOnPublications" ADD CONSTRAINT "TagsOnPublications_tagId_fkey" FOREIGN KEY ("tagId") REFERENCES "Tag"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Tag" ADD CONSTRAINT "Tag_publicationId_fkey" FOREIGN KEY ("publicationId") REFERENCES "Publication"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TagsOnTags" ADD CONSTRAINT "TagsOnTags_parentTagId_fkey" FOREIGN KEY ("parentTagId") REFERENCES "Tag"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "TagsOnTags" ADD CONSTRAINT "TagsOnTags_childTagId_fkey" FOREIGN KEY ("childTagId") REFERENCES "Tag"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -1,2 +0,0 @@
-- AlterTable
ALTER TABLE "Schema" ALTER COLUMN "originalId" DROP NOT NULL;

View File

@ -1,3 +1,3 @@
# Please do not edit this file manually # Please do not edit this file manually
# It should be added in your version-control system (i.e. Git) # It should be added in your version-control system (i.e. Git)
provider = "postgresql" provider = "mysql"

View File

@ -9,7 +9,7 @@ generator client {
} }
datasource db { datasource db {
provider = "postgresql" provider = "mysql"
url = env("DATABASE_URL") url = env("DATABASE_URL")
} }
@ -31,7 +31,7 @@ model Schema {
author User @relation(fields: [authorId], references: [id]) author User @relation(fields: [authorId], references: [id])
authorId String authorId String
originalId String? originalId String
name String name String
schema Json schema Json
types Json types Json

View File

@ -51,6 +51,6 @@ const config: Config = {
}, },
}, },
}, },
plugins: [require("@tailwindcss/container-queries")], plugins: [],
}; };
export default config; export default config;

View File

@ -38,7 +38,6 @@
"node_modules" "node_modules"
], ],
"files": [ "files": [
"global.d.ts", "global.d.ts"
"./components/mdeditor/TextEditor.tsx"
] ]
} }

View File

@ -1,54 +0,0 @@
import { prisma } from "@/prisma/prismaClient";
export async function isEmailVerified(id: string) {
const user = await prisma.user.findUnique({
where: { id },
select: {
emailVerified: true,
email: true,
accounts: { select: { provider: true, access_token: true } },
},
});
if (!user) return false;
if (user?.emailVerified) return true;
const discordAccount = user.accounts.find((a) => a.provider === "discord");
if (user && discordAccount?.access_token) {
const dcUser = await getDiscordUserInfo(discordAccount.access_token);
if (!dcUser.verified) return false;
prisma.user.update({ where: { id }, data: { emailVerified: new Date() } });
return true;
}
}
async function getDiscordUserInfo(accessToken: string): Promise<{
id: string;
username: string;
discriminator: string;
avatar: string;
verified: boolean;
email: string;
flags: number;
banner: string;
accent_color: number;
premium_type: number;
public_flags: number;
avatar_decoration_data: {
sku_id: string;
asset: string;
};
}> {
try {
const response = await fetch("https://discord.com/api/users/@me", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return await response.json();
} catch (error) {
console.error("Error fetching user info from Discord:", error);
throw error;
}
}