init
This commit is contained in:
16
src/server/api/root.ts
Normal file
16
src/server/api/root.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { exampleRouter } from "./routers/example";
|
||||
import { authRouter } from "./routers/auth";
|
||||
import { databaseRouter } from "./routers/database";
|
||||
import { lineageRouter } from "./routers/lineage";
|
||||
import { miscRouter } from "./routers/misc";
|
||||
import { createTRPCRouter } from "./utils";
|
||||
|
||||
export const appRouter = createTRPCRouter({
|
||||
example: exampleRouter,
|
||||
auth: authRouter,
|
||||
database: databaseRouter,
|
||||
lineage: lineageRouter,
|
||||
misc: miscRouter
|
||||
});
|
||||
|
||||
export type AppRouter = typeof appRouter;
|
||||
34
src/server/api/routers/auth.ts
Normal file
34
src/server/api/routers/auth.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { createTRPCRouter, publicProcedure } from "../utils";
|
||||
import { z } from "zod";
|
||||
|
||||
export const authRouter = createTRPCRouter({
|
||||
// GitHub callback route
|
||||
githubCallback: publicProcedure
|
||||
.query(async () => {
|
||||
// Implementation for GitHub OAuth callback
|
||||
return { message: "GitHub callback endpoint" };
|
||||
}),
|
||||
|
||||
// Google callback route
|
||||
googleCallback: publicProcedure
|
||||
.query(async () => {
|
||||
// Implementation for Google OAuth callback
|
||||
return { message: "Google callback endpoint" };
|
||||
}),
|
||||
|
||||
// Email login route
|
||||
emailLogin: publicProcedure
|
||||
.input(z.object({ email: z.string().email() }))
|
||||
.mutation(async ({ input }) => {
|
||||
// Implementation for email login
|
||||
return { message: `Email login initiated for ${input.email}` };
|
||||
}),
|
||||
|
||||
// Email verification route
|
||||
emailVerification: publicProcedure
|
||||
.input(z.object({ email: z.string().email() }))
|
||||
.query(async ({ input }) => {
|
||||
// Implementation for email verification
|
||||
return { message: `Email verification requested for ${input.email}` };
|
||||
}),
|
||||
});
|
||||
101
src/server/api/routers/database.ts
Normal file
101
src/server/api/routers/database.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { createTRPCRouter, publicProcedure } from "../utils";
|
||||
import { z } from "zod";
|
||||
|
||||
export const databaseRouter = createTRPCRouter({
|
||||
// Comment reactions routes
|
||||
getCommentReactions: publicProcedure
|
||||
.input(z.object({ commentId: z.string() }))
|
||||
.query(({ input }) => {
|
||||
// Implementation for getting comment reactions
|
||||
return { commentId: input.commentId, reactions: [] };
|
||||
}),
|
||||
|
||||
postCommentReaction: publicProcedure
|
||||
.input(z.object({
|
||||
commentId: z.string(),
|
||||
reactionType: z.string()
|
||||
}))
|
||||
.mutation(({ input }) => {
|
||||
// Implementation for posting comment reaction
|
||||
return { success: true, commentId: input.commentId };
|
||||
}),
|
||||
|
||||
deleteCommentReaction: publicProcedure
|
||||
.input(z.object({
|
||||
commentId: z.string(),
|
||||
reactionType: z.string()
|
||||
}))
|
||||
.mutation(({ input }) => {
|
||||
// Implementation for deleting comment reaction
|
||||
return { success: true, commentId: input.commentId };
|
||||
}),
|
||||
|
||||
// Comments routes
|
||||
getComments: publicProcedure
|
||||
.input(z.object({ postId: z.string() }))
|
||||
.query(({ input }) => {
|
||||
// Implementation for getting comments
|
||||
return { postId: input.postId, comments: [] };
|
||||
}),
|
||||
|
||||
// Post manipulation routes
|
||||
getPosts: publicProcedure
|
||||
.input(z.object({
|
||||
limit: z.number().optional(),
|
||||
offset: z.number().optional()
|
||||
}))
|
||||
.query(({ input }) => {
|
||||
// Implementation for getting posts
|
||||
return { posts: [], total: 0 };
|
||||
}),
|
||||
|
||||
createPost: publicProcedure
|
||||
.input(z.object({
|
||||
title: z.string(),
|
||||
content: z.string()
|
||||
}))
|
||||
.mutation(({ input }) => {
|
||||
// Implementation for creating post
|
||||
return { success: true, post: { id: "1", ...input } };
|
||||
}),
|
||||
|
||||
updatePost: publicProcedure
|
||||
.input(z.object({
|
||||
id: z.string(),
|
||||
title: z.string().optional(),
|
||||
content: z.string().optional()
|
||||
}))
|
||||
.mutation(({ input }) => {
|
||||
// Implementation for updating post
|
||||
return { success: true, postId: input.id };
|
||||
}),
|
||||
|
||||
deletePost: publicProcedure
|
||||
.input(z.object({ id: z.string() }))
|
||||
.mutation(({ input }) => {
|
||||
// Implementation for deleting post
|
||||
return { success: true, postId: input.id };
|
||||
}),
|
||||
|
||||
// Post likes routes
|
||||
getPostLikes: publicProcedure
|
||||
.input(z.object({ postId: z.string() }))
|
||||
.query(({ input }) => {
|
||||
// Implementation for getting post likes
|
||||
return { postId: input.postId, likes: [] };
|
||||
}),
|
||||
|
||||
likePost: publicProcedure
|
||||
.input(z.object({ postId: z.string() }))
|
||||
.mutation(({ input }) => {
|
||||
// Implementation for liking post
|
||||
return { success: true, postId: input.postId };
|
||||
}),
|
||||
|
||||
unlikePost: publicProcedure
|
||||
.input(z.object({ postId: z.string() }))
|
||||
.mutation(({ input }) => {
|
||||
// Implementation for unliking post
|
||||
return { success: true, postId: input.postId };
|
||||
}),
|
||||
});
|
||||
11
src/server/api/routers/example.ts
Normal file
11
src/server/api/routers/example.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { wrap } from "@typeschema/valibot";
|
||||
import { string } from "valibot";
|
||||
import { createTRPCRouter, publicProcedure } from "../utils";
|
||||
|
||||
export const exampleRouter = createTRPCRouter({
|
||||
hello: publicProcedure
|
||||
.input(wrap(string()))
|
||||
.query(({ input }) => {
|
||||
return `Hello ${input}!`;
|
||||
})
|
||||
});
|
||||
124
src/server/api/routers/lineage.ts
Normal file
124
src/server/api/routers/lineage.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import { createTRPCRouter, publicProcedure } from "../utils";
|
||||
import { z } from "zod";
|
||||
|
||||
export const lineageRouter = createTRPCRouter({
|
||||
// Database management routes (GET)
|
||||
databaseManagement: publicProcedure
|
||||
.query(async () => {
|
||||
// Implementation for database management
|
||||
return { message: "Database management endpoint" };
|
||||
}),
|
||||
|
||||
// Analytics route (GET)
|
||||
analytics: publicProcedure
|
||||
.query(async () => {
|
||||
// Implementation for analytics
|
||||
return { message: "Analytics endpoint" };
|
||||
}),
|
||||
|
||||
// Apple authentication routes (GET)
|
||||
appleAuth: publicProcedure
|
||||
.query(async () => {
|
||||
// Implementation for Apple authentication
|
||||
return { message: "Apple authentication endpoint" };
|
||||
}),
|
||||
|
||||
// Email login/registration/verification routes (GET/POST)
|
||||
emailLogin: publicProcedure
|
||||
.input(z.object({ email: z.string().email(), password: z.string() }))
|
||||
.mutation(async ({ input }) => {
|
||||
// Implementation for email login
|
||||
return { message: `Email login for ${input.email}` };
|
||||
}),
|
||||
|
||||
emailRegister: publicProcedure
|
||||
.input(z.object({ email: z.string().email(), password: z.string() }))
|
||||
.mutation(async ({ input }) => {
|
||||
// Implementation for email registration
|
||||
return { message: `Email registration for ${input.email}` };
|
||||
}),
|
||||
|
||||
emailVerify: publicProcedure
|
||||
.input(z.object({ token: z.string() }))
|
||||
.mutation(async ({ input }) => {
|
||||
// Implementation for email verification
|
||||
return { message: "Email verification endpoint" };
|
||||
}),
|
||||
|
||||
// Google registration route (POST)
|
||||
googleRegister: publicProcedure
|
||||
.input(z.object({
|
||||
googleId: z.string(),
|
||||
email: z.string().email(),
|
||||
name: z.string()
|
||||
}))
|
||||
.mutation(async ({ input }) => {
|
||||
// Implementation for Google registration
|
||||
return { message: `Google registration for ${input.email}` };
|
||||
}),
|
||||
|
||||
// JSON service routes (GET - attacks, conditions, dungeons, enemies, items, misc)
|
||||
attacks: publicProcedure
|
||||
.query(async () => {
|
||||
// Implementation for attacks data
|
||||
return { message: "Attacks data" };
|
||||
}),
|
||||
|
||||
conditions: publicProcedure
|
||||
.query(async () => {
|
||||
// Implementation for conditions data
|
||||
return { message: "Conditions data" };
|
||||
}),
|
||||
|
||||
dungeons: publicProcedure
|
||||
.query(async () => {
|
||||
// Implementation for dungeons data
|
||||
return { message: "Dungeons data" };
|
||||
}),
|
||||
|
||||
enemies: publicProcedure
|
||||
.query(async () => {
|
||||
// Implementation for enemies data
|
||||
return { message: "Enemies data" };
|
||||
}),
|
||||
|
||||
items: publicProcedure
|
||||
.query(async () => {
|
||||
// Implementation for items data
|
||||
return { message: "Items data" };
|
||||
}),
|
||||
|
||||
misc: publicProcedure
|
||||
.query(async () => {
|
||||
// Implementation for miscellaneous data
|
||||
return { message: "Miscellaneous data" };
|
||||
}),
|
||||
|
||||
// Offline secret route (GET)
|
||||
offlineSecret: publicProcedure
|
||||
.query(async () => {
|
||||
// Implementation for offline secret
|
||||
return { message: "Offline secret endpoint" };
|
||||
}),
|
||||
|
||||
// PvP routes (GET/POST)
|
||||
pvpGet: publicProcedure
|
||||
.query(async () => {
|
||||
// Implementation for PvP GET
|
||||
return { message: "PvP GET endpoint" };
|
||||
}),
|
||||
|
||||
pvpPost: publicProcedure
|
||||
.input(z.object({ player1: z.string(), player2: z.string() }))
|
||||
.mutation(async ({ input }) => {
|
||||
// Implementation for PvP POST
|
||||
return { message: `PvP battle between ${input.player1} and ${input.player2}` };
|
||||
}),
|
||||
|
||||
// Tokens route (GET)
|
||||
tokens: publicProcedure
|
||||
.query(async () => {
|
||||
// Implementation for tokens
|
||||
return { message: "Tokens endpoint" };
|
||||
}),
|
||||
});
|
||||
34
src/server/api/routers/misc.ts
Normal file
34
src/server/api/routers/misc.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { createTRPCRouter, publicProcedure } from "../utils";
|
||||
import { z } from "zod";
|
||||
|
||||
export const miscRouter = createTRPCRouter({
|
||||
// Downloads endpoint (GET)
|
||||
downloads: publicProcedure
|
||||
.query(async () => {
|
||||
// Implementation for downloads logic would go here
|
||||
return { message: "Downloads endpoint" };
|
||||
}),
|
||||
|
||||
// S3 operations (DELETE/GET)
|
||||
s3Delete: publicProcedure
|
||||
.input(z.object({ key: z.string() }))
|
||||
.mutation(async ({ input }) => {
|
||||
// Implementation for S3 delete logic would go here
|
||||
return { message: `Deleted S3 object with key: ${input.key}` };
|
||||
}),
|
||||
|
||||
s3Get: publicProcedure
|
||||
.input(z.object({ key: z.string() }))
|
||||
.query(async ({ input }) => {
|
||||
// Implementation for S3 get logic would go here
|
||||
return { message: `Retrieved S3 object with key: ${input.key}` };
|
||||
}),
|
||||
|
||||
// Password hashing endpoint (POST)
|
||||
hashPassword: publicProcedure
|
||||
.input(z.object({ password: z.string() }))
|
||||
.mutation(async ({ input }) => {
|
||||
// Implementation for password hashing logic would go here
|
||||
return { message: "Password hashed successfully" };
|
||||
}),
|
||||
});
|
||||
6
src/server/api/utils.ts
Normal file
6
src/server/api/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { initTRPC } from "@trpc/server";
|
||||
|
||||
export const t = initTRPC.create();
|
||||
|
||||
export const createTRPCRouter = t.router;
|
||||
export const publicProcedure = t.procedure;
|
||||
249
src/server/utils.ts
Normal file
249
src/server/utils.ts
Normal file
@@ -0,0 +1,249 @@
|
||||
import jwt, { JwtPayload } from "jsonwebtoken";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
export const LINEAGE_JWT_EXPIRY = "14d";
|
||||
|
||||
export async function getPrivilegeLevel(): Promise<
|
||||
"anonymous" | "admin" | "user"
|
||||
> {
|
||||
try {
|
||||
const userIDToken = (await cookies()).get("userIDToken");
|
||||
|
||||
if (userIDToken) {
|
||||
const decoded = await new Promise<JwtPayload | undefined>((resolve) => {
|
||||
jwt.verify(
|
||||
userIDToken.value,
|
||||
env.JWT_SECRET_KEY,
|
||||
async (err, decoded) => {
|
||||
if (err) {
|
||||
console.log("Failed to authenticate token.");
|
||||
(await cookies()).set({
|
||||
name: "userIDToken",
|
||||
value: "",
|
||||
maxAge: 0,
|
||||
expires: new Date("2016-10-05"),
|
||||
});
|
||||
resolve(undefined);
|
||||
} else {
|
||||
resolve(decoded as JwtPayload);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
if (decoded) {
|
||||
return decoded.id === env.ADMIN_ID ? "admin" : "user";
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
return "anonymous";
|
||||
}
|
||||
return "anonymous";
|
||||
}
|
||||
export async function getUserID(): Promise<string | null> {
|
||||
try {
|
||||
const userIDToken = (await cookies()).get("userIDToken");
|
||||
|
||||
if (userIDToken) {
|
||||
const decoded = await new Promise<JwtPayload | undefined>((resolve) => {
|
||||
jwt.verify(
|
||||
userIDToken.value,
|
||||
env.JWT_SECRET_KEY,
|
||||
async (err, decoded) => {
|
||||
if (err) {
|
||||
console.log("Failed to authenticate token.");
|
||||
(await cookies()).set({
|
||||
name: "userIDToken",
|
||||
value: "",
|
||||
maxAge: 0,
|
||||
expires: new Date("2016-10-05"),
|
||||
});
|
||||
resolve(undefined);
|
||||
} else {
|
||||
resolve(decoded as JwtPayload);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
if (decoded) {
|
||||
return decoded.id;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
import { createClient, Row } from "@libsql/client/web";
|
||||
import { env } from "@/env.mjs";
|
||||
|
||||
// Turso
|
||||
export function ConnectionFactory() {
|
||||
const config = {
|
||||
url: env.TURSO_DB_URL,
|
||||
authToken: env.TURSO_DB_TOKEN,
|
||||
};
|
||||
|
||||
const conn = createClient(config);
|
||||
return conn;
|
||||
}
|
||||
|
||||
export function LineageConnectionFactory() {
|
||||
const config = {
|
||||
url: env.TURSO_LINEAGE_URL,
|
||||
authToken: env.TURSO_LINEAGE_TOKEN,
|
||||
};
|
||||
|
||||
const conn = createClient(config);
|
||||
return conn;
|
||||
}
|
||||
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { createClient as createAPIClient } from "@tursodatabase/api";
|
||||
import { checkPassword } from "./api/passwordHashing";
|
||||
import { OAuth2Client } from "google-auth-library";
|
||||
|
||||
export async function LineageDBInit() {
|
||||
const turso = createAPIClient({
|
||||
org: "mikefreno",
|
||||
token: env.TURSO_DB_API_TOKEN,
|
||||
});
|
||||
|
||||
const db_name = uuid();
|
||||
const db = await turso.databases.create(db_name, { group: "default" });
|
||||
|
||||
const token = await turso.databases.createToken(db_name, {
|
||||
authorization: "full-access",
|
||||
});
|
||||
|
||||
const conn = PerUserDBConnectionFactory(db.name, token.jwt);
|
||||
await conn.execute(`
|
||||
CREATE TABLE checkpoints (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
last_updated TEXT NOT NULL,
|
||||
player_age INTEGER NOT NULL,
|
||||
player_data TEXT,
|
||||
time_data TEXT,
|
||||
dungeon_data TEXT,
|
||||
character_data TEXT,
|
||||
shops_data TEXT
|
||||
)
|
||||
`);
|
||||
|
||||
return { token: token.jwt, dbName: db.name };
|
||||
}
|
||||
|
||||
export function PerUserDBConnectionFactory(dbName: string, token: string) {
|
||||
const config = {
|
||||
url: `libsql://${dbName}-mikefreno.turso.io`,
|
||||
authToken: token,
|
||||
};
|
||||
const conn = createClient(config);
|
||||
return conn;
|
||||
}
|
||||
|
||||
export async function dumpAndSendDB({
|
||||
dbName,
|
||||
dbToken,
|
||||
sendTarget,
|
||||
}: {
|
||||
dbName: string;
|
||||
dbToken: string;
|
||||
sendTarget: string;
|
||||
}): Promise<{
|
||||
success: boolean;
|
||||
reason?: string;
|
||||
}> {
|
||||
const res = await fetch(`https://${dbName}-mikefreno.turso.io/dump`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${dbToken}`,
|
||||
},
|
||||
});
|
||||
if (!res.ok) {
|
||||
console.error(res);
|
||||
return { success: false, reason: "bad dump request response" };
|
||||
}
|
||||
const text = await res.text();
|
||||
const base64Content = Buffer.from(text, "utf-8").toString("base64");
|
||||
|
||||
const apiKey = env.SENDINBLUE_KEY as string;
|
||||
const apiUrl = "https://api.brevo.com/v3/smtp/email";
|
||||
|
||||
const emailPayload = {
|
||||
sender: {
|
||||
name: "no_reply@freno.me",
|
||||
email: "no_reply@freno.me",
|
||||
},
|
||||
to: [
|
||||
{
|
||||
email: sendTarget,
|
||||
},
|
||||
],
|
||||
subject: "Your Lineage Database Dump",
|
||||
htmlContent:
|
||||
"<html><body><p>Please find the attached database dump. This contains the state of your person remote Lineage remote saves. Should you ever return to Lineage, you can upload this file to reinstate the saves you had.</p></body></html>",
|
||||
attachment: [
|
||||
{
|
||||
content: base64Content,
|
||||
name: "database_dump.txt",
|
||||
},
|
||||
],
|
||||
};
|
||||
const sendRes = await fetch(apiUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"api-key": apiKey,
|
||||
"content-type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(emailPayload),
|
||||
});
|
||||
|
||||
if (!sendRes.ok) {
|
||||
return { success: false, reason: "email send failure" };
|
||||
} else {
|
||||
return { success: true };
|
||||
}
|
||||
}
|
||||
|
||||
export async function validateLineageRequest({
|
||||
auth_token,
|
||||
userRow,
|
||||
}: {
|
||||
auth_token: string;
|
||||
userRow: Row;
|
||||
}): Promise<boolean> {
|
||||
const { provider, email } = userRow;
|
||||
if (provider === "email") {
|
||||
const decoded = jwt.verify(
|
||||
auth_token,
|
||||
env.JWT_SECRET_KEY,
|
||||
) as jwt.JwtPayload;
|
||||
if (email !== decoded.email) {
|
||||
return false;
|
||||
}
|
||||
} else if (provider == "apple") {
|
||||
const { apple_user_string } = userRow;
|
||||
if (apple_user_string !== auth_token) {
|
||||
return false;
|
||||
}
|
||||
} else if (provider == "google") {
|
||||
const CLIENT_ID = env.NEXT_PUBLIC_GOOGLE_CLIENT_ID_MAGIC_DELVE;
|
||||
const client = new OAuth2Client(CLIENT_ID);
|
||||
const ticket = await client.verifyIdToken({
|
||||
idToken: auth_token,
|
||||
audience: CLIENT_ID,
|
||||
});
|
||||
if (ticket.getPayload()?.email !== email) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user