Compare commits
3 Commits
6ab7dd630e
...
afa56b1b16
| Author | SHA1 | Date | |
|---|---|---|---|
| afa56b1b16 | |||
| cbf3bbd5bd | |||
| 3fa13c0386 |
@@ -8,3 +8,4 @@ export {
|
||||
export * as Models from "./src/models";
|
||||
export * from "./src/providers/metadata";
|
||||
export * from "./src/services/extensions/baseExtension";
|
||||
export type { AppRouter } from "./src/trpc";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { betterAuth } from "better-auth";
|
||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
import { admin, username } from "better-auth/plugins";
|
||||
import { reactStartCookies } from "better-auth/react-start";
|
||||
import { database } from "../db";
|
||||
import * as schema from "../db/schema/auth";
|
||||
import {
|
||||
@@ -27,16 +28,17 @@ const auth = betterAuth({
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
username(),
|
||||
admin({
|
||||
ac: accessControl,
|
||||
roles: {
|
||||
adminRole,
|
||||
moderatorRole,
|
||||
userRole,
|
||||
admin: adminRole,
|
||||
moderator: moderatorRole,
|
||||
user: userRole,
|
||||
},
|
||||
defaultRole: "user",
|
||||
}),
|
||||
username(),
|
||||
reactStartCookies(),
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
@@ -72,10 +72,11 @@ export class OnboardService extends BaseService {
|
||||
|
||||
public async completeOnboarding(
|
||||
body: OnboardConfigurationData,
|
||||
headers: Headers,
|
||||
): Promise<void> {
|
||||
try {
|
||||
// Create user account
|
||||
await auth.api.signUpEmail({
|
||||
const createdAccount = await auth.api.signUpEmail({
|
||||
body: {
|
||||
email: body.account.email,
|
||||
name: body.account.username,
|
||||
@@ -83,6 +84,13 @@ export class OnboardService extends BaseService {
|
||||
username: body.account.username,
|
||||
},
|
||||
});
|
||||
await auth.api.setRole({
|
||||
body: {
|
||||
role: "admin",
|
||||
userId: createdAccount.user.id,
|
||||
},
|
||||
headers: headers,
|
||||
});
|
||||
|
||||
// Save libraries with language settings
|
||||
if (body.libraries && body.libraries.length > 0) {
|
||||
|
||||
@@ -131,7 +131,10 @@ export const onboardRouter = t.router({
|
||||
completeOnboarding: onboardProcedure
|
||||
.input(OnboardConfigurationData)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await ctx.onboardService.completeOnboarding(input);
|
||||
await ctx.onboardService.completeOnboarding(
|
||||
input,
|
||||
ctx.requestContext.req.raw.headers,
|
||||
);
|
||||
return { success: true, completed: true };
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -18,45 +18,45 @@
|
||||
"@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.8",
|
||||
"@tanstack/react-form": "^1.0.5",
|
||||
"@tanstack/react-query": "^5.85.5",
|
||||
"@tanstack/react-router": "^1.121.0-alpha.27",
|
||||
"@tanstack/react-router-with-query": "^1.121.0",
|
||||
"@tanstack/react-start": "^1.121.0-alpha.27",
|
||||
"@tanstack/router-plugin": "^1.121.0",
|
||||
"@trpc/client": "^11.5.0",
|
||||
"@trpc/server": "^11.5.0",
|
||||
"@trpc/tanstack-react-query": "^11.5.0",
|
||||
"better-auth": "^1.3.10",
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
"@tanstack/react-form": "^1.23.5",
|
||||
"@tanstack/react-query": "^5.90.2",
|
||||
"@tanstack/react-router": "^1.132.37",
|
||||
"@tanstack/react-router-with-query": "^1.130.17",
|
||||
"@tanstack/react-start": "^1.132.38",
|
||||
"@tanstack/router-plugin": "^1.132.37",
|
||||
"@trpc/client": "^11.6.0",
|
||||
"@trpc/server": "^11.6.0",
|
||||
"@trpc/tanstack-react-query": "^11.6.0",
|
||||
"better-auth": "^1.3.26",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"embla-carousel-react": "^8.6.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.544.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"radix-ui": "^1.4.2",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"sonner": "^2.0.3",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss": "^4.1.3",
|
||||
"tw-animate-css": "^1.2.5",
|
||||
"tailwindcss": "^4.1.14",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"zod": "^4.0.2"
|
||||
"zod": "^4.1.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tanstack/react-query-devtools": "^5.85.5",
|
||||
"@tanstack/react-router-devtools": "^1.121.0-alpha.27",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@tanstack/react-query-devtools": "^5.90.2",
|
||||
"@tanstack/react-router-devtools": "^1.132.37",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/react": "~19.1.10",
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@vitejs/plugin-react": "^5.0.1",
|
||||
"jsdom": "^26.0.0",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^7.0.2",
|
||||
"web-vitals": "^5.0.3"
|
||||
"@types/react": "~19.1.17",
|
||||
"@types/react-dom": "^19.2.0",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"jsdom": "^26.1.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.1.9",
|
||||
"web-vitals": "^5.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,10 +120,6 @@ function HeaderBar() {
|
||||
const routerState = useRouterState();
|
||||
const pathname = routerState.location.pathname;
|
||||
|
||||
if (pathname.endsWith("dashboard") || pathname.endsWith("dashboard/")) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-50 flex items-center justify-between border-border border-b bg-muted/95 px-5 py-3 shadow-sm backdrop-blur supports-[backdrop-filter]:bg-muted/80">
|
||||
<div className="flex items-center gap-3">
|
||||
|
||||
0
apps/web/src/functions/auth.ts
Normal file
0
apps/web/src/functions/auth.ts
Normal file
@@ -2,6 +2,7 @@ import { adminRole, moderatorRole, statement, userRole } from "@nontara/server";
|
||||
import { adminClient, usernameClient } from "better-auth/client/plugins";
|
||||
import { createAccessControl } from "better-auth/plugins/access";
|
||||
import { createAuthClient } from "better-auth/react";
|
||||
import { reactStartCookies } from "better-auth/react-start";
|
||||
|
||||
const ac = createAccessControl(statement);
|
||||
|
||||
@@ -17,5 +18,6 @@ export const authClient = createAuthClient({
|
||||
user: userRole,
|
||||
},
|
||||
}),
|
||||
reactStartCookies(),
|
||||
],
|
||||
});
|
||||
|
||||
20
apps/web/src/middlewares/auth.ts
Normal file
20
apps/web/src/middlewares/auth.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { createMiddleware } from "@tanstack/react-start";
|
||||
import { getRequestHeaders } from "@tanstack/react-start/server";
|
||||
import { authClient } from "../lib/auth-client";
|
||||
|
||||
export const authMiddleware = createMiddleware().server(async ({ next }) => {
|
||||
const auth = await authClient.getSession({
|
||||
fetchOptions: {
|
||||
headers: getRequestHeaders(),
|
||||
},
|
||||
});
|
||||
const isAuthenticated = typeof auth.data?.user.id === "string";
|
||||
|
||||
return await next({
|
||||
context: {
|
||||
user: auth.data?.user,
|
||||
isAuthenticated,
|
||||
isUserBanned: auth.data?.user.banned ?? false,
|
||||
},
|
||||
});
|
||||
});
|
||||
23
apps/web/src/middlewares/dashboard.ts
Normal file
23
apps/web/src/middlewares/dashboard.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { createMiddleware } from "@tanstack/react-start";
|
||||
import { getRequestHeaders } from "@tanstack/react-start/server";
|
||||
import { authClient } from "../lib/auth-client";
|
||||
import { authMiddleware } from "./auth";
|
||||
|
||||
export const dashboardMiddleware = createMiddleware()
|
||||
.middleware([authMiddleware])
|
||||
.server(async ({ next, context }) => {
|
||||
const hasPermissions = await authClient.admin.hasPermission({
|
||||
fetchOptions: {
|
||||
headers: getRequestHeaders(),
|
||||
},
|
||||
permissions: {
|
||||
dashboard: ["access"],
|
||||
},
|
||||
});
|
||||
return await next({
|
||||
context: {
|
||||
...context,
|
||||
hasPermissions: hasPermissions.data?.success ?? false,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -418,3 +418,12 @@ const rootRouteChildren: RootRouteChildren = {
|
||||
export const routeTree = rootRouteImport
|
||||
._addFileChildren(rootRouteChildren)
|
||||
._addFileTypes<FileRouteTypes>()
|
||||
|
||||
import type { getRouter } from './router.tsx'
|
||||
import type { createStart } from '@tanstack/react-start'
|
||||
declare module '@tanstack/react-start' {
|
||||
interface Register {
|
||||
ssr: true
|
||||
router: Awaited<ReturnType<typeof getRouter>>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createRouter as createTanStackRouter } from "@tanstack/react-router";
|
||||
import Loader from "./components/loader";
|
||||
import "./index.css";
|
||||
import type { AppRouter } from "@nontara/server";
|
||||
import {
|
||||
QueryCache,
|
||||
QueryClient,
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
import { createTRPCClient, httpBatchLink } from "@trpc/client";
|
||||
import { createTRPCOptionsProxy } from "@trpc/tanstack-react-query";
|
||||
import { toast } from "sonner";
|
||||
import type { AppRouter } from "../../server/src/routers";
|
||||
import { routeTree } from "./routeTree.gen";
|
||||
import { TRPCProvider } from "./utils/trpc";
|
||||
|
||||
@@ -48,11 +48,12 @@ const trpc = createTRPCOptionsProxy({
|
||||
queryClient: queryClient,
|
||||
});
|
||||
|
||||
export const createRouter = () => {
|
||||
export const getRouter = () => {
|
||||
const router = createTanStackRouter({
|
||||
routeTree,
|
||||
scrollRestoration: true,
|
||||
defaultPreloadStaleTime: 0,
|
||||
defaultPreload: "intent",
|
||||
context: { trpc, queryClient },
|
||||
defaultPendingComponent: () => <Loader />,
|
||||
defaultNotFoundComponent: () => <div>Not Found</div>,
|
||||
@@ -69,6 +70,6 @@ export const createRouter = () => {
|
||||
|
||||
declare module "@tanstack/react-router" {
|
||||
interface Register {
|
||||
router: ReturnType<typeof createRouter>;
|
||||
router: ReturnType<typeof getRouter>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import type { AppRouter } from "@nontara/server";
|
||||
import type { QueryClient } from "@tanstack/react-query";
|
||||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||
// import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
||||
import {
|
||||
createRootRouteWithContext,
|
||||
HeadContent,
|
||||
Outlet,
|
||||
Scripts,
|
||||
} from "@tanstack/react-router";
|
||||
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
|
||||
// import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
|
||||
import type { TRPCOptionsProxy } from "@trpc/tanstack-react-query";
|
||||
import { ThemeProvider } from "next-themes";
|
||||
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import type { AppRouter } from "../../../server/src/trpc";
|
||||
import appCss from "../index.css?url";
|
||||
export interface RouterAppContext {
|
||||
trpc: TRPCOptionsProxy<AppRouter>;
|
||||
@@ -45,7 +45,7 @@ export const Route = createRootRouteWithContext<RouterAppContext>()({
|
||||
|
||||
function RootDocument() {
|
||||
return (
|
||||
<html lang="en">
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<head>
|
||||
<HeadContent />
|
||||
</head>
|
||||
@@ -64,8 +64,8 @@ function RootDocument() {
|
||||
</ScrollArea>
|
||||
<Toaster richColors />
|
||||
</ThemeProvider>
|
||||
<TanStackRouterDevtools position="bottom-left" />
|
||||
<ReactQueryDevtools position="bottom" buttonPosition="bottom-right" />
|
||||
{/* <TanStackRouterDevtools position="bottom-left" /> */}
|
||||
{/* <ReactQueryDevtools position="bottom" buttonPosition="bottom-right" /> */}
|
||||
<Scripts />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -74,9 +74,13 @@ export const Route = createFileRoute("/dashboard")({
|
||||
dashboard: ["access"],
|
||||
},
|
||||
});
|
||||
if (!hasPermission)
|
||||
return {
|
||||
hasAccess: false,
|
||||
};
|
||||
|
||||
return {
|
||||
hasAccess: hasPermission ?? false,
|
||||
hasAccess: hasPermission.success ?? false,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router";
|
||||
|
||||
import Header from "@/components/header";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { authMiddleware } from "@/middlewares/auth";
|
||||
|
||||
export const Route = createFileRoute("/home")({
|
||||
component: RouteComponent,
|
||||
beforeLoad: async () => {
|
||||
const session = await authClient.getSession();
|
||||
|
||||
if (!session) {
|
||||
server: {
|
||||
middleware: [authMiddleware],
|
||||
},
|
||||
beforeLoad: async (ctx) => {
|
||||
if (!ctx.serverContext) {
|
||||
throw redirect({
|
||||
to: "/login",
|
||||
params: {
|
||||
reason: "UNAUTHENTICATED",
|
||||
},
|
||||
});
|
||||
}
|
||||
if (!ctx.serverContext.user) {
|
||||
throw redirect({
|
||||
to: "/login",
|
||||
});
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { authMiddleware } from "@/middlewares/auth";
|
||||
import { isOnboardingNeeded } from "@/utils/functions";
|
||||
import { useTRPC } from "@/utils/trpc";
|
||||
|
||||
export const Route = createFileRoute("/")({
|
||||
component: HomeComponent,
|
||||
beforeLoad: async () => {
|
||||
server: {
|
||||
middleware: [authMiddleware],
|
||||
},
|
||||
beforeLoad: async (ctx) => {
|
||||
// Check if onboarding is needed first
|
||||
const needsOnboarding = await isOnboardingNeeded();
|
||||
if (needsOnboarding.needsOnboarding) {
|
||||
@@ -16,10 +19,7 @@ export const Route = createFileRoute("/")({
|
||||
}
|
||||
|
||||
// Check authentication status
|
||||
const session = await authClient.getSession();
|
||||
|
||||
// Redirect based on auth status
|
||||
if (session) {
|
||||
if (ctx.serverContext?.isAuthenticated) {
|
||||
// User is logged in, redirect to home
|
||||
throw redirect({
|
||||
to: "/home",
|
||||
|
||||
Reference in New Issue
Block a user