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
4 changes: 2 additions & 2 deletions apps/api/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const env = createEnv({
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
VITE_FALLBACK_WEB_URL: z
.string({
description:
"The URL of the frontend. DO NOT ADD A TRAILING SLASH",
Expand All @@ -15,7 +15,7 @@ export const env = createEnv({
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.
// TODO(https://github.com/acmutsa/Fallback/issues/14): add these back once the oauth stuff is implemented.
// GOOGLE_CLIENT_ID: z.string(),
// GOOGLE_CLIENT_SECRET: z.string(),
// DISCORD_CLIENT_ID: z.string(),
Expand Down
4 changes: 3 additions & 1 deletion apps/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
userhandler,
logHandler,
healthHandler,
teamHandler,
} from "./routes";
import { generalCorsPolicy, betterAuthCorsPolicy } from "./lib/functions/cors";
import { HonoBetterAuth } from "./lib/functions";
Expand All @@ -31,7 +32,8 @@ export const api = HonoBetterAuth()
.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.
.route("/api/auth/*", authHandler)
.route("/team", teamHandler);

///

Expand Down
23 changes: 11 additions & 12 deletions apps/api/src/lib/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "db"; // your drizzle instance
import { APP_NAME, AUTH_CONFIG } from "shared/constants";
import { env } from "../env";

export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "sqlite",
debugLogs: true,
}),
trustedOrigins: [env.VITE_FALLBACK_WEB_URL],
databaseHooks: {
user: {
create: {
// used in order to break up the first and last name into separate fields
before: async (user) => {
console.log("Creating user. Raw inputs are: ", 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(" ");
Expand Down Expand Up @@ -40,29 +43,25 @@ export const auth = betterAuth({
},
lastSeen: {
type: "date",
required: true,
defaultValue: Date.now(),
required: false,
input: false,
},
siteRole: {
type: "string",
defaultValue: "USER",
input: false,
},
// role: {
// type: "string",
// defaultValue: "user",
// validator: {
// input: z.enum(["user", "admin"]),
// output: z.enum(["user", "admin"]),
// },
// },
},
},
advanced: {
cookiePrefix: APP_NAME,
cookiePrefix: APP_NAME.toLocaleLowerCase(),
},
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
// 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
// https://www.better-auth.com/docs/concepts/database#extending-core-schema:~:text=Example%3A%20Mapping%20Profile%20to%20User%20For%20firstName%20and%20lastName
// socialProviders: {
// google: {
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/lib/functions/cors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { env } from "../../env";
* General CORS policy for the API. Will run on every request, but others can be specified for individual routes.
*/
export const generalCorsPolicy = cors({
origin: env.FALLBACK_WEB_URL,
origin: env.VITE_FALLBACK_WEB_URL,
allowHeaders: [
"Content-Type",
"Authorization",
Expand All @@ -21,7 +21,7 @@ export const generalCorsPolicy = cors({
* CORS policy specifically for the Better Auth routes.
*/
export const betterAuthCorsPolicy = cors({
origin: env.FALLBACK_WEB_URL,
origin: env.VITE_FALLBACK_WEB_URL,
allowHeaders: ["Content-Type", "Authorization"],
allowMethods: ["POST", "GET", "OPTIONS"],
exposeHeaders: ["Content-Length"],
Expand Down
3 changes: 1 addition & 2 deletions apps/api/src/lib/functions/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import type { Context, Next } from "hono";
import { auth } from "../auth";

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

// 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
// TODO: Make this type safe
export async function setUserSessionContextMiddleware(c: Context, next: Next) {
const session = await auth.api.getSession({ headers: c.req.raw.headers });

Expand Down
10 changes: 9 additions & 1 deletion apps/api/src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,17 @@ import authHandler from "./auth";
import backupHandler from "./backup";
import logHandler from "./log";
import userhandler from "./user";
import teamHandler from "./team";

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

export { healthHandler, authHandler, backupHandler, logHandler, userhandler };
export {
healthHandler,
authHandler,
backupHandler,
logHandler,
userhandler,
teamHandler,
};
30 changes: 30 additions & 0 deletions apps/api/src/routes/team.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { zValidator } from "@hono/zod-validator";
import { HonoBetterAuth } from "../lib/functions";
import { db, getUserTeamsQuery } from "db";
import { joinTeamSchema } from "shared/zod";

const teamHandler = HonoBetterAuth()
.get("/", async (c) => {
const user = c.get("user");
if (!user) {
return c.json({ error: "Unauthorized" }, 401);
}
const userTeams = await getUserTeamsQuery(user.id);
return c.json({ data: userTeams }, 200);
})
.post("/join", zValidator("param", joinTeamSchema), async (c) => {
// First, try to join by invite code
// 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
// 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.
const inv = c.req.param("inv");
const teamId = c.req.param("teamId");
const user = c.get("user");

if (inv) {
// const result
}

return c.json({ message: "invite_code_success" }, 200);
});

export default teamHandler;
2 changes: 1 addition & 1 deletion apps/api/src/routes/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ const userhandler = HonoBetterAuth()
}
return c.json({ user }, 200);
})
// This needs a permission check. Only admins of the site should be able to see ths endpoint
.get(
"/:userId",
zValidator(
"query",
z.object({
// TODO: Tighten up a little bit
userId: z.string().min(1, "User ID is required"),
}),
),
Expand Down
2 changes: 1 addition & 1 deletion apps/web/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"tsx": true,
"tailwind": {
"config": "",
"css": "src/styles/globals.css",
"css": "src/styles.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
Expand Down
8 changes: 8 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@
"test": "vitest run"
},
"dependencies": {
"@daveyplate/better-auth-tanstack": "^1.3.6",
"@daveyplate/better-auth-ui": "^3.1.10",
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tooltip": "^1.2.8",
"@tailwindcss/vite": "^4.1.11",
"@tanstack/react-query": "^5.87.4",
"@tanstack/react-router": "^1.130.2",
Expand Down
177 changes: 177 additions & 0 deletions apps/web/src/components/shared/AppSidebar/app-sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
"use client";

import * as React from "react";
import {
AudioWaveform,
BookOpen,
Bot,
Command,
Frame,
GalleryVerticalEnd,
Map,
PieChart,
Settings2,
SquareTerminal,
} from "lucide-react";

import { NavMain } from "@/components/shared/AppSidebar/nav-main";
import { NavTeams } from "@/components/shared/AppSidebar/nav-teams";
import { NavUser } from "@/components/shared/AppSidebar/nav-user";
import { TeamSwitcher } from "@/components/shared/AppSidebar/team-switcher";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarRail,
} from "@/components/ui/sidebar";
import { authClient } from "@/lib/auth-client";

// This is sample data.
const data = {
user: {
name: "shadcn",
email: "m@example.com",
avatar: "/avatars/shadcn.jpg",
},
teams: [
{
name: "Acme Inc",
logo: GalleryVerticalEnd,
plan: "Enterprise",
},
{
name: "Acme Corp.",
logo: AudioWaveform,
plan: "Startup",
},
{
name: "Evil Corp.",
logo: Command,
plan: "Free",
},
],
navMain: [
{
title: "Playground",
url: "#",
icon: SquareTerminal,
isActive: true,
items: [
{
title: "History",
url: "#",
},
{
title: "Starred",
url: "#",
},
{
title: "Settings",
url: "#",
},
],
},
{
title: "Models",
url: "#",
icon: Bot,
items: [
{
title: "Genesis",
url: "#",
},
{
title: "Explorer",
url: "#",
},
{
title: "Quantum",
url: "#",
},
],
},
{
title: "Documentation",
url: "#",
icon: BookOpen,
items: [
{
title: "Introduction",
url: "#",
},
{
title: "Get Started",
url: "#",
},
{
title: "Tutorials",
url: "#",
},
{
title: "Changelog",
url: "#",
},
],
},
{
title: "Settings",
url: "#",
icon: Settings2,
items: [
{
title: "General",
url: "#",
},
{
title: "Team",
url: "#",
},
{
title: "Billing",
url: "#",
},
{
title: "Limits",
url: "#",
},
],
},
],
projects: [
{
name: "Design Engineering",
url: "#",
icon: Frame,
},
{
name: "Sales & Marketing",
url: "#",
icon: PieChart,
},
{
name: "Travel",
url: "#",
icon: Map,
},
],
};

// Add the type for the user here as well
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
// const {data, isPending} = authClient.useSession()
return (
<Sidebar collapsible="icon" {...props}>
<SidebarHeader>
{/* <TeamSwitcher teams={data.teams} /> */}
<h1>Some Header</h1>
</SidebarHeader>
<SidebarContent>
{/* <NavMain items={data.navMain} /> */}
<NavTeams />
</SidebarContent>
<SidebarFooter>{/* <NavUser user={data?.user} /> */}</SidebarFooter>
<SidebarRail />
</Sidebar>
);
}
Loading