Skip to content

Commit

Permalink
feat(golinks): update API docs and use @slack/oauth for all things bo…
Browse files Browse the repository at this point in the history
…t tokens

A lot of implementation details involve using Slack SDK packages,
among other chores. To simplify bot token storage, we use
a KV namespace for that in the moment.

Relates to andreijiroh-dev/personal-launchpad#4

Signed-off-by: Andrei Jiroh Halili <[email protected]>
ajhalili2006 committed Jul 26, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent b5d52a9 commit 01b8683
Showing 11 changed files with 1,086 additions and 405 deletions.
8 changes: 8 additions & 0 deletions apps/golinks-v2/package.json
Original file line number Diff line number Diff line change
@@ -24,15 +24,23 @@
"dependencies": {
"@prisma/adapter-d1": "^5.17.0",
"@prisma/client": "^5.17.0",
"@slack/oauth": "^3.0.0",
"@slack/web-api": "^7.3.1",
"chanfana": "^2.0.2",
"cross-fetch": "^4.0.0",
"date-fns": "^3.6.0",
"hono": "^4.5.1",
"jose": "^5.6.3",
"jsonwebtoken": "^9.0.2",
"url-search-params": "^1.1.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20240718.0",
"@types/jsonwebtoken": "^9",
"@types/node": "^20.14.11",
"@types/service-worker-mock": "^2.0.4",
"@types/url-search-params": "^1",
"prisma": "^5.17.0",
"ts-node": "^10.9.2",
"typescript": "^5.5.3",
91 changes: 61 additions & 30 deletions apps/golinks-v2/src/api/golinks.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { OpenAPIRoute, Num, Bool, Str, contentJson } from "chanfana";
import { z } from "zod";
import { GoLinks } from "types";
import { addGoLink, getGoLinks, updateGoLink } from "lib/db";
import { adminApiKey, userApiKey } from "lib/constants";
import { addGoLink, getGoLinks, getLink, updateGoLink } from "lib/db";
import { Context } from "hono";
import { generateSlug } from "lib/utils";

@@ -85,7 +84,7 @@ export class GoLinkList extends OpenAPIRoute {
schema = {
tags: ["golinks"],
summary: "List all golinks",
description: "Accessing this API route does not require authenication, although we also added it for higher API limits.",
description: "Accessing this API route does not require authenication, although we also added it for higher API limits.",
request: {
query: z.object({
page: Num({
@@ -99,14 +98,14 @@ export class GoLinkList extends OpenAPIRoute {
}),
}),
},
security: [
{
adminApiKey: []
},
{
userApiKey: []
}
],
security: [
{
adminApiKey: [],
},
{
userApiKey: [],
},
],
responses: {
"200": {
description: "Returns a list of golinks",
@@ -143,8 +142,9 @@ export class GoLinkUpdate extends OpenAPIRoute {
tags: ["golinks"],
parameters: [
{
name: "golink",
name: "slug",
in: "path",
description: "Slug name of the golink"
},
],
request: {
@@ -163,35 +163,66 @@ export class GoLinkUpdate extends OpenAPIRoute {
{
adminApiKey: [],
},
{
userApiKey: []
}
{
userApiKey: [],
},
],
responses: {
"200": {
description: "Shows the updated information about a golink",
content: {
"application/json": {
schema: GoLinks,
schema: z.object({
ok: Bool({default: true}),
result: GoLinks
}),
},
},
},
},
};
async handle(c) {
const data = await this.getValidatedData<typeof this.schema>();
const { slug, newSlug, targetUrl } = data.body
try {
const result = await updateGoLink(c.env.golinks, slug, targetUrl, "golinks", newSlug)
return c.json({
ok: true,
result
})
} catch(error) {
return c.json({
async handle(c: Context) {
const data = await this.getValidatedData<typeof this.schema>();
const { newSlug, targetUrl } = data.body;
const { slug } = c.req.param()
try {
const result = await updateGoLink(c.env.golinks, slug, targetUrl, "golinks", newSlug);
return c.json({
ok: true,
result,
});
} catch (error) {
return c.json({
ok: false,
error
error,
});
}
}
}

export class GoLinkInfo extends OpenAPIRoute {
schema = {
tags: ["golinks"],
summary: "Get an information about a golink",
parameters: [
{
name: "slug",
in: "path",
description: "Slug name of the golink",
},
],
responses: {
"200": {

}
}
}
}
};
async handle(context: Context) {
const { slug } = context.req.param();
const result = await getLink(context.env.golinks, slug, "golinks");
if (!result) {
return context.json({ ok: false, error: "Not found" }, 404);
}
return context.json({ ok: true, result });
}
}
2 changes: 2 additions & 0 deletions apps/golinks-v2/src/api/meta.ts
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import { z } from "zod";

export class CommitHash extends OpenAPIRoute {
schema = {
tags: ["meta"],
description: "Get the latest commit hash",
responses: {
"200": {
@@ -34,6 +35,7 @@ export class CommitHash extends OpenAPIRoute {

export class PingPong extends OpenAPIRoute {
schema = {
tags: ["meta"],
description: "Ping the API service if it's up.",
responses: {
"200": {
351 changes: 224 additions & 127 deletions apps/golinks-v2/src/api/slack.ts

Large diffs are not rendered by default.

116 changes: 116 additions & 0 deletions apps/golinks-v2/src/api/wikilinks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { OpenAPIRoute, Num, Bool, Str, contentJson } from "chanfana";
import { z } from "zod";
import { GoLinks } from "types";
import { addGoLink, getGoLinks, updateGoLink } from "lib/db";
import { Context } from "hono";

export class WikiLinkCreate extends OpenAPIRoute {
schema = {
tags: ["wikilinks"],
summary: "Create a golink-styled wikilink for `wiki.andreijiroh.xyz/go/*` and `andreijiroh.xyz/go/*`",
request: {
body: {
content: {
"application/json": {
schema: z.object({
slug: Str({ required: true }),
targetUrl: Str({ required: true, example: "https://github.com/integrations/slack" }),
}),
},
},
},
},
security: [
{
adminApiKey: [],
},
],
};
async handle(context) {
const data = await this.getValidatedData<typeof this.schema>();
const linkToCreate = data.body;
console.log(`[golinks-api] received body for link creation ${JSON.stringify(linkToCreate)}`);
try {
const result = await addGoLink(context.env.golink, linkToCreate.slug, linkToCreate.targetUrl, "wikilinks");
if (result) {
return context.json({
ok: true,
result,
});
} else {
return context.json(
{
ok: false,
error: "Something gone wrong while handling this request.",
},
400,
);
}
} catch (error) {
console.error(error);
return context.json(
{
ok: false,
error: "Internal server error",
},
500,
);
}
}
}

export class WikiLinkList extends OpenAPIRoute {
schema = {
tags: ["wikilinks"],
summary: "List all golink-styled wikilinks",
description: "Accessing this API route does not require authenication, although we also added it for higher API limits.",
request: {
query: z.object({
page: Num({
description: "Page number",
default: 0,
required: false,
}),
isActive: Bool({
description: "Filter by is_active status",
required: false,
}),
}),
},
security: [
{
adminApiKey: [],
},
{
userApiKey: [],
},
],
responses: {
"200": {
description: "Returns a list of golinks",
content: {
"application/json": {
schema: z.object({
series: z.object({
success: Bool(),
result: GoLinks.array(),
}),
}),
},
},
},
},
};

async handle(c) {
const data = await this.getValidatedData<typeof this.schema>();
const { page, isActive } = data.query;

const links = await getGoLinks(c.env.golinks, page !== undefined ? page : 0, isActive, "wikilinks");

return {
success: true,
result: links,
};
}
}
145 changes: 98 additions & 47 deletions apps/golinks-v2/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GoLinkCreate, GoLinkList, GoLinkUpdate } from "api/golinks";
import { GoLinkCreate, GoLinkInfo, GoLinkList, GoLinkUpdate } from "api/golinks";
import { fromHono } from "chanfana";
import { Context, Hono } from "hono";
import { cors } from "hono/cors";
@@ -13,16 +13,27 @@ import {
discordServerNotFound,
golinkNotFound,
tags,
userApiKey,
userApiKey,
} from "lib/constants";
import { DiscordInviteLinkCreate, DiscordInviteLinkList } from "api/discord";
import { adminApiKeyAuth } from "lib/auth";
import { adminApiKeyAuth, slackAppInstaller } from "lib/auth";
import { DeprecatedGoLinkPage } from "pages/deprecated-link";
import { CommitHash, PingPong } from "api/meta";
import { prettyJSON } from "hono/pretty-json";
import { generateNewIssueUrl, handleOldUrls } from "lib/utils";
import { handleSlackCommand, handleSlackInteractivity, slackOAuth, slackOAuthCallback } from "api/slack";
import {
debugApiGetSlackBotToken,
debugApiTestSlackBotToken,
handleSlackCommand,
handleSlackInteractivity,
slackOAuth,
slackOAuthCallback,
} from "api/slack";
import { githubAuth } from "api/github";
import * as jose from "jose";
import { IncomingMessage, ServerResponse } from "node:http";
import { InstallationQuery } from "@slack/oauth";
import { WikiLinkCreate } from "api/wikilinks";

// Start a Hono app
const app = new Hono<{ Bindings: EnvBindings }>();
@@ -42,6 +53,7 @@ app.use(
}),
);
app.use("/api/*", adminApiKeyAuth);
app.use("/debug", adminApiKeyAuth);
app.use("/*", async (c, next) => await handleOldUrls(c, next));

// Setup OpenAPI registry
@@ -67,18 +79,24 @@ const openapi = fromHono(app, {
});

openapi.registry.registerComponent("securitySchemes", "adminApiKey", adminApiKey);
openapi.registry.registerComponent("securitySchemes", "userApiKey", userApiKey)
openapi.registry.registerComponent("securitySchemes", "userApiKey", userApiKey);

// Register OpenAPI endpoints in this section
openapi.get("/api/links", GoLinkList);
openapi.post("/api/links", GoLinkCreate);
openapi.put("/api/links/:slug", GoLinkUpdate)
openapi.get("/api/links/:slug", GoLinkInfo)
openapi.put("/api/links/:slug", GoLinkUpdate);
// category:wikilinks
openapi.post("/api/wikilinks", WikiLinkCreate)
// category:discord-invites
openapi.get("/api/discord-invites", DiscordInviteLinkList);
openapi.post("/api/discord-invites", DiscordInviteLinkCreate);
// category:meta
openapi.get("/api/ping", PingPong);
openapi.get("/api/commit", CommitHash);
// category: debug
openapi.get("/api/debug/slack/bot-token", debugApiGetSlackBotToken);
openapi.get("/api/debug/slack/auth-test", debugApiTestSlackBotToken);

// Undocumented API endpoints: Slack integration
app.post("/api/slack/slash-commands/:command", async (c) => handleSlackCommand(c));
@@ -109,18 +127,72 @@ app.get("/workers", (c) => {
return c.redirect(`${origin}/workers/dashboard`);
});

/* Old /edit/* stuff */
app.get("/edit", (c) => {
return c.redirect("/workers/edit");
});

app.get("/landing/deprecated", (c) => {
const params = c.req.query();

if (!c.req.param()) {
return c.newResponse("This is unexpected request for this route", 400);
}

return c.html(<DeprecatedGoLinkPage golink={params.golink} reason={params.reason} />);
});

app.get("/feedback/:type", (c) => {
const { type } = c.req.param();
const { url } = c.req.query();
return c.redirect(generateNewIssueUrl(type, "golinks", url));
});

app.get("/api/debug/bindings", (context) => {
console.log(context.env);
return context.json(context.env);
});
app.get("/api/debug/jwt", async (c) => {
const { token } = c.req.query();
const secret = new TextEncoder().encode(c.env.JWT_SIGNING_KEY);
const payload = {
slack_team_id: "T1234",
slack_user_id: "U1234",
slack_enterprise_id: "E1234",
slack_enterprise_install: true,
example_jwt: true,
};

if (token == null) {
const exampleToken = await new jose.SignJWT(payload)
.setProtectedHeader({ alg: "HS256" })
.setAudience("challenge_1234abcd")
.setIssuer(c.env.BASE_URL)
.setIssuedAt()
.setExpirationTime("15 minutes")
.sign(secret);
return c.json({ ok: true, result: exampleToken });
}

const result = await jose.jwtVerify(token, secret, {
issuer: c.env.BASE_URL,
clockTolerance: 30,
});
return c.json({ ok: true, result });
});

app.get("/:link", async (c) => {
try {
const { link } = c.req.param();
console.log(`[redirector]: incoming request with path - ${link}`);
const result = await getLink(c.env.golinks, link);
console.log(`[redirector]: resulting data - ${JSON.stringify(result)}`);
if (!result) {
return c.newResponse(golinkNotFound(c.req.url), 404)
}
if (!result) {
return c.newResponse(golinkNotFound(c.req.url), 404);
}
return c.redirect(result.targetUrl);
} catch (error) {
console.error(`[redirector]: error`, error)
console.error(`[redirector]: error`, error);
return c.newResponse(golinkNotFound(c.req.url), 500);
}
});
@@ -129,50 +201,29 @@ app.get("/discord/:inviteCode", async (c) => {
const { inviteCode } = c.req.param();
console.log(`[redirector]: incoming request with path - /discord/${inviteCode}`);
const result = await getDiscordInvite(c.env.golinks, inviteCode);
if (!result) {
return c.newResponse(discordServerNotFound(c.req.url), 404)
}
if (!result) {
return c.newResponse(discordServerNotFound(c.req.url), 404);
}
return c.redirect(`https://discord.gg/${result.inviteCode}`);
} catch (error) {
console.error(`[redirector]: error`, error)
console.error(`[redirector]: error`, error);
return c.newResponse(discordServerNotFound(c.req.url), 500);
}
});
app.get("/go/:link", async (c) => {
try {
const { link } = c.req.param();
console.log(`[redirector]: incoming request with path - ${link}`);
const result = await getLink(c.env.golinks, link, "wikilinks");
console.log(`[redirector]: resulting data - ${JSON.stringify(result)}`);
if (!result) {
return c.newResponse(golinkNotFound(c.req.url), 404)
}
return c.redirect(result.targetUrl);
} catch (error) {
console.error(`[redirector]: error`, error)
return c.newResponse(golinkNotFound(c.req.url), 500);
}
})

/* Old /edit/* stuff */
app.get("/edit", (c) => {
return c.redirect("/workers/edit");
});

app.get("/landing/deprecated", (c) => {
const params = c.req.query();

if (!c.req.param()) {
return c.newResponse("This is unexpected request for this route", 400);
try {
const { link } = c.req.param();
console.log(`[redirector]: incoming request with path - ${link}`);
const result = await getLink(c.env.golinks, link, "wikilinks");
console.log(`[redirector]: resulting data - ${JSON.stringify(result)}`);
if (!result) {
return c.newResponse(golinkNotFound(c.req.url), 404);
}
return c.redirect(result.targetUrl);
} catch (error) {
console.error(`[redirector]: error`, error);
return c.newResponse(golinkNotFound(c.req.url), 500);
}

return c.html(<DeprecatedGoLinkPage golink={params.golink} reason={params.reason} />);
});

app.get("/feedback/:type", (c) => {
const { type } = c.req.param();
const { url } = c.req.query();
return c.redirect(generateNewIssueUrl(type, "golinks", url));
});

// Export the Hono app
67 changes: 57 additions & 10 deletions apps/golinks-v2/src/lib/auth.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,71 @@
import { PrismaD1 } from "@prisma/adapter-d1";
import { PrismaClient } from "@prisma/client";
import { Context, Next } from "hono";
import { EnvBindings, Env } from "types";
import { EnvBindings } from "types";
import { generateSlug } from "./utils";
import { add } from "date-fns";
import { error } from "console";
import { InstallProvider } from "@slack/oauth";

export const slackAppInstaller = (env: EnvBindings) =>
new InstallProvider({
clientId: env.SLACK_OAUTH_ID,
clientSecret: env.SLACK_OAUTH_SECRET,
stateSecret: env.SLACK_OAUTH_STATE_SECRET,
installationStore: {
storeInstallation: async (installation) => {
if (installation.isEnterpriseInstall) {
// support for org wide app installation
return await env.slackBotTokens.put(installation.enterprise.id, JSON.stringify(installation));
} else {
// single team app installation
return await env.slackBotTokens.put(installation.team.id, JSON.stringify(installation));
}
throw new Error("Failed saving installation data to installationStore");
},
fetchInstallation: async (query) => {
if (query.isEnterpriseInstall && query.enterpriseId !== undefined) {
return await env.slackBotTokens.get(query.enterpriseId, "json");
}
if (query.teamId !== undefined) {
return await env.slackBotTokens.get(query.teamId, "json");
}
throw new Error("Failed fetching installation");
},
deleteInstallation: async (query) => {
if (query.isEnterpriseInstall && query.enterpriseId !== undefined) {
// org wide app installation deletion
return await env.slackBotTokens.delete(query.enterpriseId);
}
if (query.teamId !== undefined) {
// single team app installation deletion
return await env.slackBotTokens.delete(query.teamId);
}
throw new Error("Failed to delete installation");
},
},
});

export async function adminApiKeyAuth(c: Context, next: Next) {
if (c.req.method == "GET" || c.req.method == "HEAD") {
return await next();
}
const adminApiKey = c.env.ADMIN_KEY;
const apiKeyHeader = c.req.header("X-Golinks-Admin-Key");

if (c.req.path.startsWith("/api/slack")) {
return await next();
} else if (c.req.path.startsWith("/debug") || c.req.path.startsWith("/api/debug")) {
if (c.env.DEPLOY_ENV == "development") {
return await next();
}
}

const adminApiKey = c.env.ADMIN_KEY;
const apiKeyHeader = c.req.header("X-Golinks-Admin-Key");
console.debug(`[auth] ${adminApiKey}:${apiKeyHeader}`);

if (c.req.method == "GET" || c.req.method == "HEAD") {
if (!c.req.path.startsWith("/api/debug")) {
return await next();
}
}

if (!apiKeyHeader || apiKeyHeader !== adminApiKey) {
return c.json(
{
@@ -43,7 +90,7 @@ export async function slackOAuthExchange(payload: object) {
return api;
}

export async function lookupBotToken(db: EnvBindings<Env>["golinks"], teamId: string, is_enterprise_install?: boolean) {
export async function lookupBotToken(db: EnvBindings["golinks"], teamId: string, is_enterprise_install?: boolean) {
const adapter = new PrismaD1(db);
const prisma = new PrismaClient({ adapter });
try {
@@ -59,7 +106,7 @@ export async function lookupBotToken(db: EnvBindings<Env>["golinks"], teamId: st
}
}

export async function addBotToken(db: EnvBindings<Env>["golinks"], teamId: string, token: string, enterprise_install?: boolean) {
export async function addBotToken(db: EnvBindings["golinks"], teamId: string, token: string, enterprise_install?: boolean) {
const adapter = new PrismaD1(db);
const prisma = new PrismaClient({ adapter });
try {
@@ -76,7 +123,7 @@ export async function addBotToken(db: EnvBindings<Env>["golinks"], teamId: strin
}
}

export async function updateBotToken(db: EnvBindings<Env>["golinks"], teamId: string, token: string, enterprise_install?: boolean) {
export async function updateBotToken(db: EnvBindings["golinks"], teamId: string, token: string, enterprise_install?: boolean) {
const adapter = new PrismaD1(db);
const prisma = new PrismaClient({ adapter });
try {
@@ -122,7 +169,7 @@ export async function getUserInfoAndGenerateTicket(token: string, context: Conte
}
}

export async function addNewChallenge(db: EnvBindings<Env>["golinks"], challenge, metadata) {
export async function addNewChallenge(db: EnvBindings["golinks"], challenge, metadata) {
const adapter = new PrismaD1(db);
const prisma = new PrismaClient({ adapter });
try {
168 changes: 84 additions & 84 deletions apps/golinks-v2/src/lib/db.ts
Original file line number Diff line number Diff line change
@@ -193,109 +193,109 @@ export async function updateGoLink(
}

export async function deprecateGoLink(db: EnvBindings<Env>["golinks"], slug: string, reason: string, type: "golinks" | "wikilinks" | null) {
const adapter = new PrismaD1(db);
const prisma = new PrismaClient({ adapter });

try {
if (type == "wikilinks") {
const result = prisma.wikiLinks.update({
where: {
slug,
},
data: {
is_active: false,
deactivation_reason: reason,
},
});
return result;
} else {
const result = prisma.goLink.update({
where: {
slug,
},
data: {
is_active: false,
deactivation_reason: reason,
},
});
return result;
}
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(`[prisma-client] known client error: ${error.code} - ${error.message}`);

if (error.code === "P2002") {
return Promise.reject(new Error("A Discord invite code with that slug already exists."));
}

return Promise.reject(new Error("A error occurred while querying the database."));
} else {
console.error(`[prisma-client]- Unexpected error`, error);
return Promise.reject(new Error("An unexpected error occurred."));
const adapter = new PrismaD1(db);
const prisma = new PrismaClient({ adapter });

try {
if (type == "wikilinks") {
const result = prisma.wikiLinks.update({
where: {
slug,
},
data: {
is_active: false,
deactivation_reason: reason,
},
});
return result;
} else {
const result = prisma.goLink.update({
where: {
slug,
},
data: {
is_active: false,
deactivation_reason: reason,
},
});
return result;
}
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(`[prisma-client] known client error: ${error.code} - ${error.message}`);

if (error.code === "P2002") {
return Promise.reject(new Error("A Discord invite code with that slug already exists."));
}

return Promise.reject(new Error("A error occurred while querying the database."));
} else {
console.error(`[prisma-client]- Unexpected error`, error);
return Promise.reject(new Error("An unexpected error occurred."));
}
};
}
}

export async function undeprecateGoLink(db: EnvBindings<Env>["golinks"], slug: string, type: "golinks" | "wikilinks" | null) {
const adapter = new PrismaD1(db);
const prisma = new PrismaClient({ adapter });

try {
if (type == "wikilinks") {
const result = prisma.wikiLinks.update({
where: {
slug,
},
data: {
is_active: true,
deactivation_reason: null,
},
});
return result;
} else {
const result = prisma.goLink.update({
where: {
slug,
},
data: {
is_active: true,
deactivation_reason: null,
},
});
return result;
}
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(`[prisma-client] known client error: ${error.code} - ${error.message}`);

if (error.code === "P2002") {
return Promise.reject(new Error("A Discord invite code with that slug already exists."));
}

return Promise.reject(new Error("A error occurred while querying the database."));
} else {
console.error(`[prisma-client]- Unexpected error`, error);
return Promise.reject(new Error("An unexpected error occurred."));
const adapter = new PrismaD1(db);
const prisma = new PrismaClient({ adapter });

try {
if (type == "wikilinks") {
const result = prisma.wikiLinks.update({
where: {
slug,
},
data: {
is_active: true,
deactivation_reason: null,
},
});
return result;
} else {
const result = prisma.goLink.update({
where: {
slug,
},
data: {
is_active: true,
deactivation_reason: null,
},
});
return result;
}
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(`[prisma-client] known client error: ${error.code} - ${error.message}`);

if (error.code === "P2002") {
return Promise.reject(new Error("A Discord invite code with that slug already exists."));
}

return Promise.reject(new Error("A error occurred while querying the database."));
} else {
console.error(`[prisma-client]- Unexpected error`, error);
return Promise.reject(new Error("An unexpected error occurred."));
}
};
}
}

export async function deleteGoLink(db: EnvBindings<Env>["golinks"], slug: string, type: "golinks" | "wikilinks" | null) {
const adapter = new PrismaD1(db);
const prisma = new PrismaClient({ adapter });

try {
if (type == "wikilinks") {
const result = prisma.wikiLinks.delete({
const result = prisma.wikiLinks.delete({
where: { slug },
});
return result;
} else {
const result = prisma.goLink.delete({
} else {
const result = prisma.goLink.delete({
where: { slug },
});
return result;
}
}
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(`[prisma-client] known client error: ${error.code} - ${error.message}`);
4 changes: 3 additions & 1 deletion apps/golinks-v2/src/types.ts
Original file line number Diff line number Diff line change
@@ -41,7 +41,9 @@ export interface EnvBindings {
golinks: D1Database;
ADMIN_KEY: string;
BASE_URL: string;
JWT_SIGNING_KEY: string
JWT_SIGNING_KEY: string;
SLACK_OAUTH_STATE_SECRET: string;
slackBotTokens: KVNamespace;
}

/*type EnvBindings = {
11 changes: 10 additions & 1 deletion apps/golinks-v2/wrangler.toml
Original file line number Diff line number Diff line change
@@ -13,21 +13,27 @@ compatibility_date = "2024-07-12" # TODO: Update this once a month
account_id = "cf0bd808c6a294fd8c4d8f6d2cdeca05"

placement = { mode = "smart" }
compatibility_flags = [ "nodejs_compat" ]
node_compat = true

# Please do not leak your secrets here.
vars = { DEPLOY_ENV = "development", ADMIN_KEY = "gostg_localdev-null", BASE_URL = "https://stellapent-cier.fawn-cod.ts.net" }

d1_databases = [
{ binding = "golinks", database_name = "golinks-db-preview", database_id = "0b415a64-cc61-4c2b-a7c8-2d223f18e996", migrations_dir = "migrations" },
]
kv_namespaces = [
{ binding = "slackBotTokens", id = "24a1f8bb9d434e97a490c43b74435e64" }
]

[env.staging]
workers_dev = true
vars = { DEPLOY_ENV = "staging", BASE_URL = "https://staging.go-next.andreijiroh.xyz" }
d1_databases = [
{ binding = "golinks", database_name = "golinks-db-preview", database_id = "0b415a64-cc61-4c2b-a7c8-2d223f18e996", migrations_dir = "migrations" },
]
kv_namespaces = [
{ binding = "slackBotTokens", id = "24a1f8bb9d434e97a490c43b74435e64" }
]
routes = [
{ custom_domain = true, pattern = "staging.go-next.andreijiroh.xyz", zone_name = "andreijiroh.xyz" },
{ custom_domain = true, pattern = "go-next.andreijiroh.xyz", zone_name = "andreijiroh.xyz" },
@@ -39,6 +45,9 @@ vars = { DEPLOY_ENV = "production", BASE_URL = "https://go.andreijiroh.xyz" }
d1_databases = [
{ binding = "golinks", database_name = "golinks-db", database_id = "63e746f4-40de-4f8c-a3e5-7969cde040b3", migrations_dir = "migrations" },
]
kv_namespaces = [
{ binding = "slackBotTokens", id = "21b6d6aaacbf4f04b52a66a9ed11fcd3" }
]
routes = [
{ pattern = "go.andreijiroh.xyz", zone_name = "andreijiroh.xyz", custom_domain = true },
# Inspired by @flanican's website on adding /go/ links to
528 changes: 423 additions & 105 deletions yarn.lock

Large diffs are not rendered by default.

0 comments on commit 01b8683

Please sign in to comment.