Skip to content

Commit

Permalink
use bun with trpc
Browse files Browse the repository at this point in the history
  • Loading branch information
Sacramentix committed Apr 1, 2024
1 parent 1b6154b commit f4e6491
Show file tree
Hide file tree
Showing 21 changed files with 765 additions and 148 deletions.
6 changes: 1 addition & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
.env
/local
/npm
/types
/npm/node_modules
/npm/src
/npm/.npmignore
/node_modules
7 changes: 7 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.env
/local
/node_modules
dockerfile
fly.toml
.vscode
.github
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# db.wikiadventu.re

To install dependencies:

```bash
bun install
```

To run:

```bash
bun run api/app.ts
```

This project was created using `bun init` in bun v1.0.27. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
64 changes: 40 additions & 24 deletions api/app.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Hono, hc } from "https://deno.land/x/[email protected]/mod.ts";
import { cors } from "https://deno.land/x/[email protected]/middleware.ts";
import { z } from "https://esm.sh/[email protected]";
import { zValidator } from "https://esm.sh/@hono/[email protected]";
import { serve } from "https://deno.land/[email protected]/http/server.ts";
import { type Context, Hono } from "hono";
import { cors } from "hono/cors";
import { getAccount } from "./surreal/query/getAccountOrCreate/index.ts";
import { getSixDegreeAccount, six_degree_achivements_id } from "./surreal/query/getAccountOrCreate/six_degree.ts";
import { achieve } from "./surreal/query/achieve.ts";
import { guardOrySession } from "./guard/orySession.ts";
import { initTRPC } from "@trpc/server";
import { z } from "zod";
import { getLangFromHeaders } from "./utils/lang.ts";
import { trpcServer } from "@hono/trpc-server";

const app = new Hono();

Expand All @@ -18,27 +18,43 @@ app.use("/*", cors({
credentials: true
}));

const t = initTRPC.context<Context>().create()

const route =
app
.get('/get-account', async c => {
const { user } = await guardOrySession(c);
return c.jsonT(await getAccount(user));
})
.get('/get-account/six-degree', async c => {
const { user } = await guardOrySession(c);
return c.jsonT(await getSixDegreeAccount(user, getLangFromHeaders(c.req)));
})
.post("/achieve/:achievement" , zValidator('param', z.object({
achievement: z.enum(six_degree_achivements_id)
})) as any, async c => {
const { achievement } = await c.req.param();
const { user } = await guardOrySession(c);
return c.jsonT(await achieve(user, achievement));
const publicProcedure = t.procedure
const router = t.router


export const AccountProcedure = t.procedure.use( async (opts) => {
opts.ctx
return opts.next({
ctx: {
...(await guardOrySession(opts.ctx as Context))
},
});
});

const trpcRouter = router({
account: AccountProcedure.query( async ({ ctx }) => {
return await getAccount(ctx.user);
}),
sixDegree: router({
account: AccountProcedure.query( async ({ ctx }) => {
return await getSixDegreeAccount(ctx.user, getLangFromHeaders(ctx.req));
}),
}),
achieve: AccountProcedure
.input( z.object({ achievement: z.enum(six_degree_achivements_id) }))
.query( async ({ ctx, input }) => {
return await achieve(ctx.user, input.achievement);
})
});

app.use('/*', trpcServer({router: trpcRouter}));

const port = 9009;
serve(app.fetch, { port: port});
export default {
port,
fetch: app.fetch,
}

export type AppType = typeof route;

export type Db_wikiadventu_re_trpc_router = typeof trpcRouter
4 changes: 0 additions & 4 deletions api/client/index.ts

This file was deleted.

7 changes: 3 additions & 4 deletions api/env/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { type ZodError, z } from "https://deno.land/x/[email protected]/mod.ts";
import "https://deno.land/[email protected]/dotenv/load.ts";
import { type ZodError, z } from "zod";

const envSchema = z.object({
SURREAL_URL: z.string().url(),
Expand All @@ -23,15 +22,15 @@ const envSchema = z.object({
export const env = Object.freeze(
(() => {
try {
return envSchema.parse(Deno.env.toObject());
return envSchema.parse(process.env);
} catch(error) {
const e = error as ZodError;
console.error(
"Env config is incorrect: \n",
e.errors.map(v=>` - ${v.path.join(".")} field ${v.message}`).join("\n"),
"\nMake sure to fill the env with all the required fields."
);
Deno.exit(1);
process.exit(1);
}
})());

Expand Down
38 changes: 33 additions & 5 deletions api/guard/oryJwt.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { verify } from "https://deno.land/x/[email protected]/mod.ts";
import { HTTPException } from "https://deno.land/x/hono@v3.3.1/http-exception.ts";
import type { Context } from "https://deno.land/x/hono@v3.3.1/mod.ts";
import { decode } from "jsonwebtoken";
import { HTTPException } from "hono/http-exception";
import type { Context } from "hono";
import { env } from "../env/index.ts";

const cryptoKey = await crypto.subtle.importKey("jwk",env.ORY_JWT, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256'},false,["verify"]);
Expand All @@ -24,8 +24,36 @@ export async function guardOryJwt(c:Context) {
const jwt = bearer.split("Bearer ")[1];
if (jwt == null) throw new HTTPException(401, { message: "There is no valid jwt in Authorization bearer header." });

return await verify(jwt, cryptoKey)
return await verifyJwt(jwt, cryptoKey)
.catch(e=>{
throw new HTTPException(401, { message: "The jwt provided in Authorization bearer header is invalid." })
throw new HTTPException(401, { message: "The jwt provided in Authorization bearer header is invalid or expired." })
}) as OryJwt;
}


async function verifyJwt(jwt: string, cryptoKey: CryptoKey) {
// Decode the JWT without verifying
const decodedJwt = decode(jwt, { complete: true });

if (decodedJwt == null) throw new Error("Invalid or expired jwt.");

// Get the signature from the JWT
const signature = decodedJwt.signature;

// Get the data from the JWT
const data = decodedJwt.header + "." + decodedJwt.payload;

// Verify the JWT
const isVerified = await crypto.subtle.verify(
{
name: "RSASSA-PKCS1-v1_5",
},
cryptoKey, //from your key import function
new TextEncoder().encode(signature), //ArrayBuffer of the signature
new TextEncoder().encode(data) //ArrayBuffer of the data
);

if (!isVerified) throw new Error("Invalid or expired jwt.");
// If the JWT is verified, return the decoded JWT
return decodedJwt?.payload;
}
28 changes: 14 additions & 14 deletions api/guard/orySession.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { verify } from "https://deno.land/x/[email protected]/mod.ts";
import { HTTPException } from "https://deno.land/x/[email protected]/http-exception.ts";
import type { Context } from "https://deno.land/x/[email protected]/mod.ts";
import { getCookie } from "https://deno.land/x/[email protected]/middleware.ts";

import { env } from "../env/index.ts";
import { Session } from "https://deno.land/x/[email protected]/index.ts";
import type { Session } from "@ory/client-fetch";
import { TRPCError } from "@trpc/server";
import type { Context } from "hono";
import { getCookie } from "hono/cookie";

export async function guardOrySession(c:Context) {
export async function guardOrySession(c:Context<any,any,{}>) {
const ory_session_cookie = Object.entries(getCookie(c)??{}).find(([k,v])=>k.startsWith("ory_session"));
if (ory_session_cookie == null) throw new HTTPException(401, { message: "The user is not authenticated" });
const ory_session:Session = await fetch(env.ORY_BASE_PATH+"/sessions/whoami",{
headers: c.req.headers,
if (ory_session_cookie == null) throw new TRPCError({ code: "UNAUTHORIZED", message: "The user is not authenticated.", cause: "ory_session cookie missing." });
const ory_session = await fetch(env.ORY_BASE_PATH+"/sessions/whoami",{
headers: c.req.raw.headers,
})
.then(r=>r.json())
.catch(e=>{throw new HTTPException(401, { message: "Could not validate the ory session" })});
.then(r=>r.json() as unknown as Session)
.catch(e=>{throw new TRPCError({ code: "UNAUTHORIZED", message: "Could not validate the session cookie.", cause: "ory_session cookie is invalid." })});
const user:OryUser = {
id: ory_session.identity.id,
name: ory_session.identity.traits.username,
email: ory_session.identity.traits.email
id: ory_session.identity?.id ?? "",
name: ory_session.identity?.traits.username,
email: ory_session.identity?.traits.email
};
return { ory_session, user };
}
Expand Down
11 changes: 5 additions & 6 deletions api/ory/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { FrontendApi, IdentityApi } from "https://deno.land/x/[email protected]/index.ts";
import { createConfiguration } from "https://deno.land/x/[email protected]/configuration.ts";
import { Configuration, FrontendApi, IdentityApi } from "@ory/client-fetch";
import { env } from "../env/index.ts";
import { ServerConfiguration } from "https://deno.land/x/[email protected]/servers.ts";

// export const oryConfig = createConfiguration({

Expand All @@ -13,10 +11,11 @@ import { ServerConfiguration } from "https://deno.land/x/sacramentix_ory_client@
// }
// }
// },
// });
// });/

// export const oryId = new IdentityApi(oryConfig);

export const ory = new FrontendApi(createConfiguration({
baseServer: new ServerConfiguration(env.ORY_BASE_PATH,{})
export const ory = new FrontendApi(new Configuration({
basePath: env.ORY_BASE_PATH
// baseServer: new ServerConfiguration(env.ORY_BASE_PATH,{})
}));
40 changes: 4 additions & 36 deletions api/surreal/db.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import Surreal from "https://deno.land/x/surrealdb@v0.8.0/mod.ts";
import { Surreal } from "surrealdb.node";
import { env } from "../env/index.ts";

export const db = new Surreal(env.SURREAL_URL);
export const db = new Surreal();

await db.connect(env.SURREAL_URL);

await db.signin({
user: env.SURREAL_USER,
Expand All @@ -12,37 +14,3 @@ await db.use({
ns: "account",
db: "account"
});

// await db.query(/* surrealql */`
// DEFINE TABLE people SCHEMALESS;
// DEFINE TABLE country SCHEMALESS;

// DEFINE TABLE liveIn SCHEMALESS;

// DEFINE INDEX unique_country_live
// ON TABLE liveIn
// COLUMNS in, out UNIQUE;

// CREATE people:sacra;
// CREATE people:benji;
// CREATE people:thibat;

// CREATE country:france;
// CREATE country:demacia;
// CREATE country:zoo;

// RELATE person:sacra->liveIn->country:france;
// RELATE person:thibat->liveIn->country:demacia;
// RELATE person:benji->liveIn->country:zoo;

// RELATE person:benji->liveIn->country:zoo;
// RELATE person:benji->liveIn->country:demacia;

// SELECT * from liveIn;

// REMOVE TABLE people;
// REMOVE TABLE country;
// REMOVE TABLE liveIn;
// REMOVE INDEX unique_country_live ON TABLE liveIn;

// `).then(console.log)
2 changes: 1 addition & 1 deletion api/surreal/init/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { env } from "../../env/index.ts";
import { db } from "../db.ts";
import jwkToPem from "https://esm.sh/jwk-to-pem@2.0.5";
import jwkToPem from "jwk-to-pem";
import { six_degree_achievements_creation_query } from "./sixdegree/Achievement.ts";

const publicKey = jwkToPem(env.ORY_JWT);
Expand Down
4 changes: 2 additions & 2 deletions api/surreal/query/achieve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import type { OryUser } from "../../guard/orySession.ts";
import { db } from "../db.ts";

export async function achieve(user:OryUser, achievement:string) {
const queryResult = await db.query<[never,AchieveError|{}]>(/* surrealql */`
const queryResult = await db.query(/* surrealql */`
RELATE type::thing("user", $id)->achieve->type::thing("achievement", $achievement)
`,
{ id: user.id, achievement }
);
) as [ never, AchieveError | {} ];
return queryResult[1];
}

Expand Down
8 changes: 6 additions & 2 deletions api/surreal/query/getAccountOrCreate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@ import type { OryUser } from "../../../guard/orySession.ts";
import { db } from "../../db.ts";

export async function getAccount(user:OryUser) {
const queryResult = await db.query<[never, Account]>(/* surrealql */`
const queryResult = await db.query(/* surrealql */`
LET $user = fn::getUserOrCreate( type::thing("user", $id) , $name , $email );
RETURN (SELECT meta::id(id) as id, name, email FROM $user)[0];
`,
user
)
) as [never, {result: Account}];
return queryResult[1].result!;
}

export type Account = {
/**
* Description for id.
* @minLength 10
*/
id: string,
name: string,
email: string
Expand Down
8 changes: 4 additions & 4 deletions api/surreal/query/getAccountOrCreate/six_degree.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { OryUser } from "../../../guard/orySession.ts";
import { Lang } from "../../../utils/lang.ts";
import type { Lang } from "../../../utils/lang.ts";
import { db } from "../../db.ts";
import { Account } from "./index.ts";
import type { Account } from "./index.ts";

export async function getSixDegreeAccount(user:OryUser, lang:Lang) {
const langFallback = lang == "en" ? `[lang:en]` : `[lang:${lang},lang:en]`;
const queryResult = await db.query<[never, never, never, SixDegreeAccount]>(/* surrealql */`
const queryResult = await db.query(/* surrealql */`
-- LET $user = fn::getUserOrCreate( $id , $name , $email );
-- LET $all_achievement = SELECT VALUE fn::langFallback(->achievement_content, ${langFallback}) FROM achievement WHERE game_tag == game_tag:six_degree;
-- LET $user_achievement = SELECT title, description, meta::id(out) as lang, meta::id(in) as id, (SELECT VALUE date FROM $uid->achieve WHERE out = $parent.in)[0] as achieved FROM $all_achievement;
Expand All @@ -22,7 +22,7 @@ export async function getSixDegreeAccount(user:OryUser, lang:Lang) {
)[0];
`,
user
)
) as [never, never, never, {result: SixDegreeAccount}]
return queryResult[3].result!;
}

Expand Down
4 changes: 2 additions & 2 deletions api/utils/lang.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HonoRequest } from "https://deno.land/x/hono@v3.3.1/mod.ts";
import type { HonoRequest } from "hono";

export const supportedLang = [
"en",
Expand All @@ -8,6 +8,6 @@ export const supportedLang = [
export type Lang = typeof supportedLang[number];

export function getLangFromHeaders(req:HonoRequest<any,any>):Lang {
const langHeaders = req.headers.get("Accept-Language");
const langHeaders = req.header("Accept-Language");
return supportedLang.includes(langHeaders as any) ? langHeaders as Lang : "en";
}
Binary file added bun.lockb
Binary file not shown.
Loading

1 comment on commit f4e6491

@deno-deploy
Copy link

@deno-deploy deno-deploy bot commented on f4e6491 Apr 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failed to deploy:

Relative import path "@hono/trpc-server" not prefixed with / or ./ or ../

Please sign in to comment.