Skip to content

Commit 2ad2a49

Browse files
Adds Better auth UI, other changes, and API team join enhancements (#23)
1 parent bbb6768 commit 2ad2a49

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+5673
-452
lines changed

apps/api/src/env.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const env = createEnv({
66
description:
77
"Account ID for the Cloudflare account. Note that this ID should be the same one the bucket is hosted in.",
88
}),
9-
FALLBACK_WEB_URL: z
9+
VITE_FALLBACK_WEB_URL: z
1010
.string({
1111
description:
1212
"The URL of the frontend. DO NOT ADD A TRAILING SLASH",
@@ -15,7 +15,7 @@ export const env = createEnv({
1515
R2_ACCESS_KEY_ID: z.string(),
1616
R2_SECRET_ACCESS_KEY: z.string(),
1717
BETTER_AUTH_SECRET: z.string(),
18-
// TODO: add these back once the oauth stuff is implemented.
18+
// TODO(https://github.com/acmutsa/Fallback/issues/14): add these back once the oauth stuff is implemented.
1919
// GOOGLE_CLIENT_ID: z.string(),
2020
// GOOGLE_CLIENT_SECRET: z.string(),
2121
// DISCORD_CLIENT_ID: z.string(),

apps/api/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
userhandler,
99
logHandler,
1010
healthHandler,
11+
teamHandler,
1112
} from "./routes";
1213
import { generalCorsPolicy, betterAuthCorsPolicy } from "./lib/functions/cors";
1314
import { HonoBetterAuth } from "./lib/functions";
@@ -31,7 +32,8 @@ export const api = HonoBetterAuth()
3132
.route("/log", logHandler)
3233
.route("/backup", backupHandler)
3334
.route("/user", userhandler)
34-
.route("/api/auth/*", authHandler); //TODO: Ensure that this is the correct route segment to start requests from.
35+
.route("/api/auth/*", authHandler)
36+
.route("/team", teamHandler);
3537

3638
///
3739

apps/api/src/lib/auth.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@ import { betterAuth } from "better-auth";
22
import { drizzleAdapter } from "better-auth/adapters/drizzle";
33
import { db } from "db"; // your drizzle instance
44
import { APP_NAME, AUTH_CONFIG } from "shared/constants";
5+
import { env } from "../env";
56

67
export const auth = betterAuth({
78
database: drizzleAdapter(db, {
89
provider: "sqlite",
910
debugLogs: true,
1011
}),
12+
trustedOrigins: [env.VITE_FALLBACK_WEB_URL],
1113
databaseHooks: {
1214
user: {
1315
create: {
1416
// used in order to break up the first and last name into separate fields
1517
before: async (user) => {
18+
console.log("Creating user. Raw inputs are: ", user);
1619
// split the name into first and last name (name object is mapped to the first name by the config)
1720
const [firstName, ...rest] = user.name.split(" ");
1821
const lastName = rest.join(" ");
@@ -40,29 +43,25 @@ export const auth = betterAuth({
4043
},
4144
lastSeen: {
4245
type: "date",
43-
required: true,
44-
defaultValue: Date.now(),
46+
required: false,
47+
input: false,
48+
},
49+
siteRole: {
50+
type: "string",
51+
defaultValue: "USER",
4552
input: false,
4653
},
47-
// role: {
48-
// type: "string",
49-
// defaultValue: "user",
50-
// validator: {
51-
// input: z.enum(["user", "admin"]),
52-
// output: z.enum(["user", "admin"]),
53-
// },
54-
// },
5554
},
5655
},
5756
advanced: {
58-
cookiePrefix: APP_NAME,
57+
cookiePrefix: APP_NAME.toLocaleLowerCase(),
5958
},
6059
emailAndPassword: {
6160
enabled: AUTH_CONFIG.emailAndPassword.enabled,
6261
minPasswordLength: AUTH_CONFIG.emailAndPassword.minPasswordLength,
6362
maxPasswordLength: AUTH_CONFIG.emailAndPassword.maxPasswordLength,
6463
},
65-
// 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
64+
// TODO(https://github.com/acmutsa/Fallback/issues/14): 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
6665
// https://www.better-auth.com/docs/concepts/database#extending-core-schema:~:text=Example%3A%20Mapping%20Profile%20to%20User%20For%20firstName%20and%20lastName
6766
// socialProviders: {
6867
// google: {

apps/api/src/lib/functions/cors.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { env } from "../../env";
55
* General CORS policy for the API. Will run on every request, but others can be specified for individual routes.
66
*/
77
export const generalCorsPolicy = cors({
8-
origin: env.FALLBACK_WEB_URL,
8+
origin: env.VITE_FALLBACK_WEB_URL,
99
allowHeaders: [
1010
"Content-Type",
1111
"Authorization",
@@ -21,7 +21,7 @@ export const generalCorsPolicy = cors({
2121
* CORS policy specifically for the Better Auth routes.
2222
*/
2323
export const betterAuthCorsPolicy = cors({
24-
origin: env.FALLBACK_WEB_URL,
24+
origin: env.VITE_FALLBACK_WEB_URL,
2525
allowHeaders: ["Content-Type", "Authorization"],
2626
allowMethods: ["POST", "GET", "OPTIONS"],
2727
exposeHeaders: ["Content-Length"],

apps/api/src/lib/functions/middleware.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ import type { Context, Next } from "hono";
22
import { auth } from "../auth";
33

44
export const MIDDLEWARE_PUBLIC_ROUTES = ["/health", "/api/auth"];
5+
//TODO(https://github.com/acmutsa/Fallback/issues/16): Make these function's context types safe
56

6-
// We need to grab the specific type of context here as we know for the middleware it will always be the same as the overall api
7-
// TODO: Make this type safe
87
export async function setUserSessionContextMiddleware(c: Context, next: Next) {
98
const session = await auth.api.getSession({ headers: c.req.raw.headers });
109

apps/api/src/routes/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,17 @@ import authHandler from "./auth";
33
import backupHandler from "./backup";
44
import logHandler from "./log";
55
import userhandler from "./user";
6+
import teamHandler from "./team";
67

78
const healthHandler = HonoBetterAuth().get("/", (c) => {
89
return c.json({ status: "It's alive!" }, 200);
910
});
1011

11-
export { healthHandler, authHandler, backupHandler, logHandler, userhandler };
12+
export {
13+
healthHandler,
14+
authHandler,
15+
backupHandler,
16+
logHandler,
17+
userhandler,
18+
teamHandler,
19+
};

apps/api/src/routes/team.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { zValidator } from "@hono/zod-validator";
2+
import { HonoBetterAuth } from "../lib/functions";
3+
import { db, getUserTeamsQuery } from "db";
4+
import { joinTeamSchema } from "shared/zod";
5+
6+
const teamHandler = HonoBetterAuth()
7+
.get("/", async (c) => {
8+
const user = c.get("user");
9+
if (!user) {
10+
return c.json({ error: "Unauthorized" }, 401);
11+
}
12+
const userTeams = await getUserTeamsQuery(user.id);
13+
return c.json({ data: userTeams }, 200);
14+
})
15+
.post("/join", zValidator("param", joinTeamSchema), async (c) => {
16+
// First, try to join by invite code
17+
// If no invite code, we will try to join by team ID only if the team allows it. If not, we will render our card regardless but include an error message notifying
18+
// The user that the team is private and requires an invite code. The server will send back the name of the team as well in this case.
19+
const inv = c.req.param("inv");
20+
const teamId = c.req.param("teamId");
21+
const user = c.get("user");
22+
23+
if (inv) {
24+
// const result
25+
}
26+
27+
return c.json({ message: "invite_code_success" }, 200);
28+
});
29+
30+
export default teamHandler;

apps/api/src/routes/user.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ const userhandler = HonoBetterAuth()
1212
}
1313
return c.json({ user }, 200);
1414
})
15+
// This needs a permission check. Only admins of the site should be able to see ths endpoint
1516
.get(
1617
"/:userId",
1718
zValidator(
1819
"query",
1920
z.object({
20-
// TODO: Tighten up a little bit
2121
userId: z.string().min(1, "User ID is required"),
2222
}),
2323
),

apps/web/components.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"tsx": true,
66
"tailwind": {
77
"config": "",
8-
"css": "src/styles/globals.css",
8+
"css": "src/styles.css",
99
"baseColor": "neutral",
1010
"cssVariables": true,
1111
"prefix": ""

apps/web/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@
1010
"test": "vitest run"
1111
},
1212
"dependencies": {
13+
"@daveyplate/better-auth-tanstack": "^1.3.6",
14+
"@daveyplate/better-auth-ui": "^3.1.10",
15+
"@radix-ui/react-avatar": "^1.1.10",
16+
"@radix-ui/react-collapsible": "^1.1.12",
17+
"@radix-ui/react-dialog": "^1.1.15",
18+
"@radix-ui/react-dropdown-menu": "^2.1.16",
19+
"@radix-ui/react-separator": "^1.1.7",
1320
"@radix-ui/react-slot": "^1.2.3",
21+
"@radix-ui/react-tooltip": "^1.2.8",
1422
"@tailwindcss/vite": "^4.1.11",
1523
"@tanstack/react-query": "^5.87.4",
1624
"@tanstack/react-router": "^1.130.2",

0 commit comments

Comments
 (0)