Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/prettier.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# .github/workflows/prettier.yml

name: Prettier Check

on: [pull_request]

jobs:
prettier-check:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: "20"

- name: Install pnpm
run: |
npm install -g pnpm

- name: Install dependencies
run: pnpm install

- name: Run Prettier check
run: pnpm run format-check
9 changes: 9 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Ignore artifacts:
build
coverage
packages/db/drizzle/meta/**
pnpm-lock.yaml
pnpm-workspace.yaml
**/package.json
*.json
web/src/components/ui
8 changes: 8 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"semi": true,
"singleQuote": false,
"jsxSingleQuote": false,
"printWidth": 80,
"tabWidth": 4,
"useTabs": true
}
59 changes: 31 additions & 28 deletions apps/api/src/env.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
import { createEnv } from "@t3-oss/env-core";
import { z } from "zod";
export const env = createEnv({
server: {
CLOUDFLARE_ACCOUNT_ID: z.string({
description:
"Account ID for the Cloudflare account. Note that this ID should be the same one the bucket is hosted in.",
}),
FALLBACK_WEB_URL:z.string({
description:"The URL of the frontend. DO NOT ADD A TRAILING SLASH"
}).url(),
R2_ACCESS_KEY_ID: z.string(),
R2_SECRET_ACCESS_KEY: z.string(),
BETTER_AUTH_SECRET: z.string(),
// TODO: add these back once the oauth stuff is implemented.
// GOOGLE_CLIENT_ID: z.string(),
// GOOGLE_CLIENT_SECRET: z.string(),
// DISCORD_CLIENT_ID: z.string(),
// DISCORD_CLIENT_SECRET: z.string(),
// GITHUB_CLIENT_ID: z.string(),
// GITHUB_CLIENT_SECRET: z.string(),
// LINEAR_CLIENT_ID: z.string(),
server: {
CLOUDFLARE_ACCOUNT_ID: z.string({
description:
"Account ID for the Cloudflare account. Note that this ID should be the same one the bucket is hosted in.",
}),
FALLBACK_WEB_URL: z
.string({
description:
"The URL of the frontend. DO NOT ADD A TRAILING SLASH",
})
.url(),
R2_ACCESS_KEY_ID: z.string(),
R2_SECRET_ACCESS_KEY: z.string(),
BETTER_AUTH_SECRET: z.string(),
// TODO: add these back once the oauth stuff is implemented.
// GOOGLE_CLIENT_ID: z.string(),
// GOOGLE_CLIENT_SECRET: z.string(),
// DISCORD_CLIENT_ID: z.string(),
// DISCORD_CLIENT_SECRET: z.string(),
// GITHUB_CLIENT_ID: z.string(),
// GITHUB_CLIENT_SECRET: z.string(),
// LINEAR_CLIENT_ID: z.string(),
// LINEAR_CLIENT_SECRET: z.string(),
},
onValidationError: (issues) => {
console.log("all process variables:", process.env);
console.error("❌ Invalid environment variables:", issues);
throw new Error("Invalid environment variables");
},
runtimeEnv: process.env,
emptyStringAsUndefined: true,
});
},
onValidationError: (issues) => {
console.log("all process variables:", process.env);
console.error("❌ Invalid environment variables:", issues);
throw new Error("Invalid environment variables");
},
runtimeEnv: process.env,
emptyStringAsUndefined: true,
});
53 changes: 31 additions & 22 deletions apps/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,56 @@ import {
type ScheduledController,
type ExecutionContext,
} from "@cloudflare/workers-types";
import { authHandler, backupHandler, userhandler, logHandler, healthHandler } from "./routes";
import {
authHandler,
backupHandler,
userhandler,
logHandler,
healthHandler,
} from "./routes";
import { generalCorsPolicy, betterAuthCorsPolicy } from "./lib/functions/cors";
import { HonoBetterAuth } from "./lib/functions";
import { setUserSessionContextMiddleware, authenticatedMiddleware } from "./lib/functions/middleware";

import {
setUserSessionContextMiddleware,
authenticatedMiddleware,
} from "./lib/functions/middleware";

interface Env {}

// api stuff
// api stuff
export const api = HonoBetterAuth()
.use(
"*",
generalCorsPolicy, // see if we can get rid of this one maybe later?
betterAuthCorsPolicy,
async (c, next) => setUserSessionContextMiddleware(c,next),
async (c, next) => authenticatedMiddleware(c,next)
)
.route("/health", healthHandler)
.route("/log", logHandler)
.route("/backup", backupHandler)
.route("/user", userhandler);

.use(
"*",
generalCorsPolicy, // see if we can get rid of this one maybe later?
betterAuthCorsPolicy,
async (c, next) => setUserSessionContextMiddleware(c, next),
async (c, next) => authenticatedMiddleware(c, next),
)
.route("/health", healthHandler)
.route("/log", logHandler)
.route("/backup", backupHandler)
.route("/user", userhandler)
.route("/api/auth/*", authHandler); //TODO: Ensure that this is the correct route segment to start requests from.

///

// cron stuff
/**
* The basic logic for running a cron job in Cloudflare Workers. Will be updated to be more specific later.
*/
const cron = async (
controller: ScheduledController,
_: Env,
ctx: ExecutionContext
controller: ScheduledController,
_: Env,
ctx: ExecutionContext,
) => {
// NOTE: controller.cron is what we will use to check what jobs need to be running
// ctx.waitUntil(doBackup());
// ctx.waitUntil(doBackup());
};

export default {
fetch: api.fetch,
scheduled: cron,
};


// Special type only exported for the web client
export type ApiType = typeof api;
export type ApiType = typeof api;
183 changes: 91 additions & 92 deletions apps/api/src/lib/auth.ts
Original file line number Diff line number Diff line change
@@ -1,98 +1,97 @@
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "db"; // your drizzle instance
import { env } from "../env";
import {APP_NAME, AUTH_CONFIG} from "shared/constants"
import { APP_NAME, AUTH_CONFIG } from "shared/constants";

export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "sqlite",
debugLogs: true,
}),
databaseHooks: {
user: {
create: {
// used in order to break up the first and last name into separate fields
before: async (user) => {
// split the name into first and last name (name object is mapped to the first name by the config)
const [firstName, ...rest] = user.name.split(" ");
const lastName = rest.join(" ");
return {
data: { ...user, firstName, lastName },
};
},
},
},
},
user: {
// this maps the default "name" field to the "firstName" field in the database
fields: {
name: "firstName",
},
// this declares the extra fields that are not in the default user schema that better auth creates, but are in the database
additionalFields: {
firstName: {
type: "string",
defaultValue: "",
},
lastName: {
type: "string",
defaultValue: "",
},
lastSeen: {
type: "date",
required: true,
defaultValue: Date.now(),
input: false,
},
// role: {
// type: "string",
// defaultValue: "user",
// validator: {
// input: z.enum(["user", "admin"]),
// output: z.enum(["user", "admin"]),
// },
// },
},
},
advanced: {
cookiePrefix: APP_NAME,
},
emailAndPassword: {
enabled: AUTH_CONFIG.emailAndPassword.enabled,
minPasswordLength: AUTH_CONFIG.emailAndPassword.minPasswordLength,
maxPasswordLength: AUTH_CONFIG.emailAndPassword.maxPasswordLength,
},
// TODO: Reference the following link to see if it is easier to have the social provider's returned values map to first and last name instead
// https://www.better-auth.com/docs/concepts/database#extending-core-schema:~:text=Example%3A%20Mapping%20Profile%20to%20User%20For%20firstName%20and%20lastName
// socialProviders: {
// google: {
// clientId: env.GOOGLE_CLIENT_ID,
// clientSecret: env.GOOGLE_CLIENT_SECRET,
// },
// discord: {
// clientId: env.DISCORD_CLIENT_ID,
// clientSecret: env.DISCORD_CLIENT_SECRET,
// },
// github: {
// clientId: env.GITHUB_CLIENT_ID,
// clientSecret: env.GITHUB_CLIENT_SECRET,
// },
// linear: {
// clientId: env.LINEAR_CLIENT_ID,
// clientSecret: env.LINEAR_CLIENT_SECRET,
// },
// },
rateLimit: {
window: 10, // time window in seconds
max: 100, // max requests in the window
},
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days
updateAge: 60 * 60 * 24, // 1 day (every 1 day the session expiration is updated)
cookieCache: {
enabled: true,
maxAge: 5 * 60,
},
},
database: drizzleAdapter(db, {
provider: "sqlite",
debugLogs: true,
}),
databaseHooks: {
user: {
create: {
// used in order to break up the first and last name into separate fields
before: async (user) => {
// split the name into first and last name (name object is mapped to the first name by the config)
const [firstName, ...rest] = user.name.split(" ");
const lastName = rest.join(" ");
return {
data: { ...user, firstName, lastName },
};
},
},
},
},
user: {
// this maps the default "name" field to the "firstName" field in the database
fields: {
name: "firstName",
},
// this declares the extra fields that are not in the default user schema that better auth creates, but are in the database
additionalFields: {
firstName: {
type: "string",
defaultValue: "",
},
lastName: {
type: "string",
defaultValue: "",
},
lastSeen: {
type: "date",
required: true,
defaultValue: Date.now(),
input: false,
},
// role: {
// type: "string",
// defaultValue: "user",
// validator: {
// input: z.enum(["user", "admin"]),
// output: z.enum(["user", "admin"]),
// },
// },
},
},
advanced: {
cookiePrefix: APP_NAME,
},
emailAndPassword: {
enabled: AUTH_CONFIG.emailAndPassword.enabled,
minPasswordLength: AUTH_CONFIG.emailAndPassword.minPasswordLength,
maxPasswordLength: AUTH_CONFIG.emailAndPassword.maxPasswordLength,
},
// TODO: Reference the following link to see if it is easier to have the social provider's returned values map to first and last name instead
// https://www.better-auth.com/docs/concepts/database#extending-core-schema:~:text=Example%3A%20Mapping%20Profile%20to%20User%20For%20firstName%20and%20lastName
// socialProviders: {
// google: {
// clientId: env.GOOGLE_CLIENT_ID,
// clientSecret: env.GOOGLE_CLIENT_SECRET,
// },
// discord: {
// clientId: env.DISCORD_CLIENT_ID,
// clientSecret: env.DISCORD_CLIENT_SECRET,
// },
// github: {
// clientId: env.GITHUB_CLIENT_ID,
// clientSecret: env.GITHUB_CLIENT_SECRET,
// },
// linear: {
// clientId: env.LINEAR_CLIENT_ID,
// clientSecret: env.LINEAR_CLIENT_SECRET,
// },
// },
rateLimit: {
window: 10, // time window in seconds
max: 100, // max requests in the window
},
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days
updateAge: 60 * 60 * 24, // 1 day (every 1 day the session expiration is updated)
cookieCache: {
enabled: true,
maxAge: 5 * 60,
},
},
});
Loading