ta_data_mas 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -0
- package/apps/api/.prettierrc +4 -0
- package/apps/api/README.md +98 -0
- package/apps/api/drizzle/0000_right_drax.sql +87 -0
- package/apps/api/drizzle/meta/0000_snapshot.json +588 -0
- package/apps/api/drizzle/meta/_journal.json +13 -0
- package/apps/api/drizzle.config.ts +12 -0
- package/apps/api/eslint.config.mjs +35 -0
- package/apps/api/nest-cli.json +8 -0
- package/apps/api/package.json +99 -0
- package/apps/api/src/app.controller.spec.ts +25 -0
- package/apps/api/src/app.controller.ts +9 -0
- package/apps/api/src/app.module.ts +37 -0
- package/apps/api/src/app.service.ts +8 -0
- package/apps/api/src/auth/auth.controller.ts +113 -0
- package/apps/api/src/auth/auth.module.ts +15 -0
- package/apps/api/src/auth/auth.service.ts +309 -0
- package/apps/api/src/auth/decorators/current-user-decorator.ts +10 -0
- package/apps/api/src/auth/decorators/require-org-role-decorator.ts +7 -0
- package/apps/api/src/auth/dto/login.dto.ts +10 -0
- package/apps/api/src/auth/dto/register.dto.ts +14 -0
- package/apps/api/src/auth/guards/jwt-auth-guard.ts +40 -0
- package/apps/api/src/auth/guards/org-role.guards.ts +60 -0
- package/apps/api/src/db/db.module.ts +9 -0
- package/apps/api/src/db/drizzle.service.ts +16 -0
- package/apps/api/src/db/schema/index.ts +6 -0
- package/apps/api/src/db/schema/organizations.ts +30 -0
- package/apps/api/src/db/schema/projects.ts +26 -0
- package/apps/api/src/db/schema/query-history.ts +16 -0
- package/apps/api/src/db/schema/storage-buckets.ts +17 -0
- package/apps/api/src/db/schema/storage-objects.ts +18 -0
- package/apps/api/src/db/schema/users.ts +14 -0
- package/apps/api/src/main.ts +51 -0
- package/apps/api/src/members/dto/invite-member-dto.ts +6 -0
- package/apps/api/src/members/dto/update-role.dto.ts +8 -0
- package/apps/api/src/members/invite.service.ts +147 -0
- package/apps/api/src/members/members.controller.ts +56 -0
- package/apps/api/src/members/members.module.ts +13 -0
- package/apps/api/src/members/members.service.ts +85 -0
- package/apps/api/src/orgs/dto/create-org.dto.ts +8 -0
- package/apps/api/src/orgs/orgs.controller.ts +27 -0
- package/apps/api/src/orgs/orgs.module.ts +12 -0
- package/apps/api/src/orgs/orgs.service.ts +86 -0
- package/apps/api/src/project-api/project-api.controller.ts +95 -0
- package/apps/api/src/project-api/project-api.module.ts +12 -0
- package/apps/api/src/project-api/project-api.service.ts +196 -0
- package/apps/api/src/project-api/project-key.guard.ts +45 -0
- package/apps/api/src/project-api/query-parser.ts +129 -0
- package/apps/api/src/project-auth/dto/magic-link.dto.ts +7 -0
- package/apps/api/src/project-auth/dto/signin.dto.ts +10 -0
- package/apps/api/src/project-auth/dto/signup.dto.ts +11 -0
- package/apps/api/src/project-auth/project-auth-dashboard.controller.ts +35 -0
- package/apps/api/src/project-auth/project-auth.controller.ts +111 -0
- package/apps/api/src/project-auth/project-auth.module.ts +13 -0
- package/apps/api/src/project-auth/project-auth.service.ts +440 -0
- package/apps/api/src/projects/dto/create-project.dto.ts +9 -0
- package/apps/api/src/projects/projects.controller.ts +34 -0
- package/apps/api/src/projects/projects.module.ts +12 -0
- package/apps/api/src/projects/projects.service.ts +136 -0
- package/apps/api/src/realtime/realtime-socket.type.ts +14 -0
- package/apps/api/src/realtime/realtime.controller.ts +63 -0
- package/apps/api/src/realtime/realtime.gateway.ts +150 -0
- package/apps/api/src/realtime/realtime.module.ts +14 -0
- package/apps/api/src/realtime/realtime.service.ts +101 -0
- package/apps/api/src/realtime/trigger.service.ts +95 -0
- package/apps/api/src/sql-editor/dto/execute-query.dto.ts +7 -0
- package/apps/api/src/sql-editor/sql-editor.controller.ts +28 -0
- package/apps/api/src/sql-editor/sql-editor.module.ts +12 -0
- package/apps/api/src/sql-editor/sql-editor.service.ts +154 -0
- package/apps/api/src/storage/project-storage.controller.ts +140 -0
- package/apps/api/src/storage/storage.controller.ts +118 -0
- package/apps/api/src/storage/storage.module.ts +15 -0
- package/apps/api/src/storage/storage.service.ts +191 -0
- package/apps/api/src/storage/uploadthing.ts +23 -0
- package/apps/api/src/table-editor/dto/alter-table.dto.ts +15 -0
- package/apps/api/src/table-editor/dto/create-table.dto.ts +54 -0
- package/apps/api/src/table-editor/table-editor.controller.ts +117 -0
- package/apps/api/src/table-editor/table-editor.module.ts +12 -0
- package/apps/api/src/table-editor/table-editor.service.ts +366 -0
- package/apps/api/test/app.e2e-spec.ts +29 -0
- package/apps/api/test/jest-e2e.json +9 -0
- package/apps/api/tsconfig.build.json +4 -0
- package/apps/api/tsconfig.json +25 -0
- package/apps/web/.env.example +12 -0
- package/apps/web/AGENTS.md +9 -0
- package/apps/web/CLAUDE.md +1 -0
- package/apps/web/README.md +36 -0
- package/apps/web/components.json +25 -0
- package/apps/web/eslint.config.mjs +18 -0
- package/apps/web/next.config.ts +11 -0
- package/apps/web/package.json +48 -0
- package/apps/web/postcss.config.mjs +7 -0
- package/apps/web/public/file.svg +1 -0
- package/apps/web/public/globe.svg +1 -0
- package/apps/web/public/logo.svg +4 -0
- package/apps/web/public/next.svg +1 -0
- package/apps/web/public/vercel.svg +1 -0
- package/apps/web/public/window.svg +1 -0
- package/apps/web/src/app/(dashboard)/layout.tsx +33 -0
- package/apps/web/src/app/(dashboard)/organizations/(list)/layout.tsx +7 -0
- package/apps/web/src/app/(dashboard)/organizations/(list)/new/page.tsx +26 -0
- package/apps/web/src/app/(dashboard)/organizations/(list)/page.tsx +44 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/api/page.tsx +13 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/auth/layout.tsx +19 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/auth/page.tsx +10 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/auth/providers/page.tsx +19 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/auth/url-configuration/page.tsx +24 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/auth/users/page.tsx +13 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/database/page.tsx +29 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/realtime/page.tsx +28 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/sql/page.tsx +21 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/[projectSlug]/storage/page.tsx +28 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/layout.tsx +20 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/projects/page.tsx +62 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/settings/members/page.tsx +28 -0
- package/apps/web/src/app/(dashboard)/organizations/[slug]/settings/page.tsx +10 -0
- package/apps/web/src/app/(dashboard)/settings/page.tsx +12 -0
- package/apps/web/src/app/dashboard/page.tsx +14 -0
- package/apps/web/src/app/favicon.ico +0 -0
- package/apps/web/src/app/globals.css +115 -0
- package/apps/web/src/app/layout.tsx +36 -0
- package/apps/web/src/app/login/page.tsx +199 -0
- package/apps/web/src/app/page.tsx +100 -0
- package/apps/web/src/app/register/page.tsx +218 -0
- package/apps/web/src/components/app-sidebar.tsx +301 -0
- package/apps/web/src/components/navbar.tsx +63 -0
- package/apps/web/src/components/topNav.tsx +68 -0
- package/apps/web/src/components/ui/avatar.tsx +112 -0
- package/apps/web/src/components/ui/badge.tsx +48 -0
- package/apps/web/src/components/ui/button.tsx +66 -0
- package/apps/web/src/components/ui/card.tsx +102 -0
- package/apps/web/src/components/ui/checkbox.tsx +32 -0
- package/apps/web/src/components/ui/dialog.tsx +168 -0
- package/apps/web/src/components/ui/dropdown-menu.tsx +269 -0
- package/apps/web/src/components/ui/field.tsx +238 -0
- package/apps/web/src/components/ui/input.tsx +19 -0
- package/apps/web/src/components/ui/label.tsx +24 -0
- package/apps/web/src/components/ui/resizable.tsx +50 -0
- package/apps/web/src/components/ui/scroll-area.tsx +55 -0
- package/apps/web/src/components/ui/select.tsx +192 -0
- package/apps/web/src/components/ui/separator.tsx +28 -0
- package/apps/web/src/components/ui/sheet.tsx +147 -0
- package/apps/web/src/components/ui/sidebar.tsx +703 -0
- package/apps/web/src/components/ui/skeleton.tsx +13 -0
- package/apps/web/src/components/ui/switch.tsx +33 -0
- package/apps/web/src/components/ui/table.tsx +116 -0
- package/apps/web/src/components/ui/tooltip.tsx +57 -0
- package/apps/web/src/features/api-docs/api-docs-client.tsx +227 -0
- package/apps/web/src/features/api-docs/api-docs-helpers.server.ts +86 -0
- package/apps/web/src/features/auth/action.ts +67 -0
- package/apps/web/src/features/auth/client.schema.ts +13 -0
- package/apps/web/src/features/auth/constants.ts +6 -0
- package/apps/web/src/features/auth/cookies.ts +86 -0
- package/apps/web/src/features/auth/server.schema.ts +11 -0
- package/apps/web/src/features/auth/sign-out.ts +17 -0
- package/apps/web/src/features/members/action.ts +66 -0
- package/apps/web/src/features/members/client.schema.ts +11 -0
- package/apps/web/src/features/members/constants.ts +8 -0
- package/apps/web/src/features/members/members-client.tsx +198 -0
- package/apps/web/src/features/members/members-helpers.server.ts +21 -0
- package/apps/web/src/features/members/server.schema.ts +19 -0
- package/apps/web/src/features/organization/action.ts +45 -0
- package/apps/web/src/features/organization/client.schema.ts +5 -0
- package/apps/web/src/features/organization/constants.tsx +3 -0
- package/apps/web/src/features/organization/create-org-form.tsx +66 -0
- package/apps/web/src/features/organization/organization-helpers.server.ts +35 -0
- package/apps/web/src/features/organization/server.schema.ts +10 -0
- package/apps/web/src/features/project-auth/project-auth-action.ts +78 -0
- package/apps/web/src/features/project-auth/project-auth-client.schema.ts +18 -0
- package/apps/web/src/features/project-auth/project-auth-constants.ts +8 -0
- package/apps/web/src/features/project-auth/project-auth-helpers.server.ts +39 -0
- package/apps/web/src/features/project-auth/project-auth-providers.tsx +222 -0
- package/apps/web/src/features/project-auth/project-auth-server.schema.ts +22 -0
- package/apps/web/src/features/project-auth/project-auth-shell.tsx +106 -0
- package/apps/web/src/features/project-auth/project-auth-url-config.tsx +151 -0
- package/apps/web/src/features/project-auth/project-auth-users.tsx +138 -0
- package/apps/web/src/features/projects/action.ts +46 -0
- package/apps/web/src/features/projects/client.schema.ts +9 -0
- package/apps/web/src/features/projects/constants.ts +6 -0
- package/apps/web/src/features/projects/create-project-modal.tsx +117 -0
- package/apps/web/src/features/projects/project-helpers.server.ts +38 -0
- package/apps/web/src/features/projects/server.schema.ts +10 -0
- package/apps/web/src/features/realtime/realtime-client.tsx +294 -0
- package/apps/web/src/features/realtime/realtime-helpers.server.ts +40 -0
- package/apps/web/src/features/sql-editor/sql-editor-client.tsx +324 -0
- package/apps/web/src/features/sql-editor/sql-editor-helpers.server.ts +22 -0
- package/apps/web/src/features/storage/storage-client.tsx +500 -0
- package/apps/web/src/features/storage/storage-helpers.server.ts +36 -0
- package/apps/web/src/features/table-editor/action.ts +97 -0
- package/apps/web/src/features/table-editor/add-column-dialog.tsx +184 -0
- package/apps/web/src/features/table-editor/client.schema.ts +36 -0
- package/apps/web/src/features/table-editor/create-table-drawer.tsx +542 -0
- package/apps/web/src/features/table-editor/server.schema.ts +41 -0
- package/apps/web/src/features/table-editor/table-editor-client.tsx +737 -0
- package/apps/web/src/features/table-editor/table-editor-helpers.server.ts +39 -0
- package/apps/web/src/hooks/use-mobile.ts +19 -0
- package/apps/web/src/lib/axios.ts +8 -0
- package/apps/web/src/lib/utils.ts +6 -0
- package/apps/web/src/proxy.ts +72 -0
- package/apps/web/src/server-utils/utils.ts +10 -0
- package/apps/web/tsconfig.json +34 -0
- package/package.json +22 -0
- package/packages/apiDatabase-js/package.json +39 -0
- package/packages/apiDatabase-js/src/auth/index.ts +121 -0
- package/packages/apiDatabase-js/src/client.ts +25 -0
- package/packages/apiDatabase-js/src/db/index.ts +15 -0
- package/packages/apiDatabase-js/src/db/query-builder.ts +194 -0
- package/packages/apiDatabase-js/src/index.ts +16 -0
- package/packages/apiDatabase-js/src/realtime/index.ts +84 -0
- package/packages/apiDatabase-js/src/storage/index.ts +130 -0
- package/packages/apiDatabase-js/tsconfig.json +13 -0
- package/packages/apiDatabase-js/tsdown.config.ts +20 -0
- package/packages/constants/package.json +6 -0
- package/packages/constants/src/index.ts +76 -0
- package/packages/tsconfig.json +13 -0
- package/packages/tsdown.config.ts +20 -0
- package/packages/types/package.json +6 -0
- package/packages/types/src/index.ts +254 -0
- package/pnpm-workspace.yaml +10 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Switch as SwitchPrimitive } from "radix-ui"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
function Switch({
|
|
9
|
+
className,
|
|
10
|
+
size = "default",
|
|
11
|
+
...props
|
|
12
|
+
}: React.ComponentProps<typeof SwitchPrimitive.Root> & {
|
|
13
|
+
size?: "sm" | "default"
|
|
14
|
+
}) {
|
|
15
|
+
return (
|
|
16
|
+
<SwitchPrimitive.Root
|
|
17
|
+
data-slot="switch"
|
|
18
|
+
data-size={size}
|
|
19
|
+
className={cn(
|
|
20
|
+
"peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50",
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
>
|
|
25
|
+
<SwitchPrimitive.Thumb
|
|
26
|
+
data-slot="switch-thumb"
|
|
27
|
+
className="pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] dark:data-checked:bg-primary-foreground group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-unchecked:bg-foreground"
|
|
28
|
+
/>
|
|
29
|
+
</SwitchPrimitive.Root>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { Switch }
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
data-slot="table-container"
|
|
11
|
+
className="relative w-full overflow-x-auto"
|
|
12
|
+
>
|
|
13
|
+
<table
|
|
14
|
+
data-slot="table"
|
|
15
|
+
className={cn("w-full caption-bottom text-sm", className)}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
</div>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
|
23
|
+
return (
|
|
24
|
+
<thead
|
|
25
|
+
data-slot="table-header"
|
|
26
|
+
className={cn("[&_tr]:border-b", className)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
|
33
|
+
return (
|
|
34
|
+
<tbody
|
|
35
|
+
data-slot="table-body"
|
|
36
|
+
className={cn("[&_tr:last-child]:border-0", className)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
|
43
|
+
return (
|
|
44
|
+
<tfoot
|
|
45
|
+
data-slot="table-footer"
|
|
46
|
+
className={cn(
|
|
47
|
+
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
|
48
|
+
className
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
|
56
|
+
return (
|
|
57
|
+
<tr
|
|
58
|
+
data-slot="table-row"
|
|
59
|
+
className={cn(
|
|
60
|
+
"border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted",
|
|
61
|
+
className
|
|
62
|
+
)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
69
|
+
return (
|
|
70
|
+
<th
|
|
71
|
+
data-slot="table-head"
|
|
72
|
+
className={cn(
|
|
73
|
+
"h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0",
|
|
74
|
+
className
|
|
75
|
+
)}
|
|
76
|
+
{...props}
|
|
77
|
+
/>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
82
|
+
return (
|
|
83
|
+
<td
|
|
84
|
+
data-slot="table-cell"
|
|
85
|
+
className={cn(
|
|
86
|
+
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0",
|
|
87
|
+
className
|
|
88
|
+
)}
|
|
89
|
+
{...props}
|
|
90
|
+
/>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function TableCaption({
|
|
95
|
+
className,
|
|
96
|
+
...props
|
|
97
|
+
}: React.ComponentProps<"caption">) {
|
|
98
|
+
return (
|
|
99
|
+
<caption
|
|
100
|
+
data-slot="table-caption"
|
|
101
|
+
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export {
|
|
108
|
+
Table,
|
|
109
|
+
TableHeader,
|
|
110
|
+
TableBody,
|
|
111
|
+
TableFooter,
|
|
112
|
+
TableHead,
|
|
113
|
+
TableRow,
|
|
114
|
+
TableCell,
|
|
115
|
+
TableCaption,
|
|
116
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Tooltip as TooltipPrimitive } from "radix-ui"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
function TooltipProvider({
|
|
9
|
+
delayDuration = 0,
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
|
12
|
+
return (
|
|
13
|
+
<TooltipPrimitive.Provider
|
|
14
|
+
data-slot="tooltip-provider"
|
|
15
|
+
delayDuration={delayDuration}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function Tooltip({
|
|
22
|
+
...props
|
|
23
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
24
|
+
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function TooltipTrigger({
|
|
28
|
+
...props
|
|
29
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
30
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function TooltipContent({
|
|
34
|
+
className,
|
|
35
|
+
sideOffset = 0,
|
|
36
|
+
children,
|
|
37
|
+
...props
|
|
38
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
39
|
+
return (
|
|
40
|
+
<TooltipPrimitive.Portal>
|
|
41
|
+
<TooltipPrimitive.Content
|
|
42
|
+
data-slot="tooltip-content"
|
|
43
|
+
sideOffset={sideOffset}
|
|
44
|
+
className={cn(
|
|
45
|
+
"z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
46
|
+
className
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
>
|
|
50
|
+
{children}
|
|
51
|
+
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />
|
|
52
|
+
</TooltipPrimitive.Content>
|
|
53
|
+
</TooltipPrimitive.Portal>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Eye, EyeOff, Copy, Check } from "lucide-react";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import type { ProjectApiDocs } from "@apiDatabase/types";
|
|
7
|
+
|
|
8
|
+
export function ApiDocsClient({ docs }: { docs: ProjectApiDocs }) {
|
|
9
|
+
const [showAnonKey, setShowAnonKey] = useState(false);
|
|
10
|
+
const [showServiceKey, setShowServiceKey] = useState(false);
|
|
11
|
+
const [copiedKey, setCopiedKey] = useState<string | null>(null);
|
|
12
|
+
|
|
13
|
+
const copyToClipboard = async (text: string, key: string) => {
|
|
14
|
+
await navigator.clipboard.writeText(text);
|
|
15
|
+
setCopiedKey(key);
|
|
16
|
+
setTimeout(() => setCopiedKey(null), 2000);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const methodColors: Record<string, string> = {
|
|
20
|
+
GET: "bg-blue-100 text-blue-700",
|
|
21
|
+
POST: "bg-green-100 text-green-700",
|
|
22
|
+
PATCH: "bg-yellow-100 text-yellow-700",
|
|
23
|
+
DELETE: "bg-red-100 text-red-700",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className="mx-auto w-full min-w-0 max-w-4xl space-y-8 overflow-x-hidden">
|
|
28
|
+
<div>
|
|
29
|
+
<h1 className="text-2xl font-medium">API</h1>
|
|
30
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
31
|
+
Auto-generated REST endpoints for your project tables
|
|
32
|
+
</p>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div className="space-y-4">
|
|
36
|
+
<h2 className="text-sm font-medium uppercase tracking-wider text-muted-foreground">
|
|
37
|
+
API Keys
|
|
38
|
+
</h2>
|
|
39
|
+
|
|
40
|
+
<div className="space-y-1 rounded-xl border border-border p-4">
|
|
41
|
+
<p className="text-xs text-muted-foreground">Project URL</p>
|
|
42
|
+
<div className="flex min-w-0 items-center justify-between gap-2">
|
|
43
|
+
<code className="min-w-0 break-all font-mono text-sm">
|
|
44
|
+
{docs.projectUrl}
|
|
45
|
+
</code>
|
|
46
|
+
<Button
|
|
47
|
+
variant="ghost"
|
|
48
|
+
size="icon"
|
|
49
|
+
className="h-7 w-7 shrink-0"
|
|
50
|
+
onClick={() => void copyToClipboard(docs.projectUrl, "url")}
|
|
51
|
+
>
|
|
52
|
+
{copiedKey === "url" ? <Check size={12} /> : <Copy size={12} />}
|
|
53
|
+
</Button>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div className="space-y-1 rounded-xl border border-border p-4">
|
|
58
|
+
<div className="flex items-center justify-between gap-2">
|
|
59
|
+
<p className="text-xs text-muted-foreground">Anon Key</p>
|
|
60
|
+
<p className="shrink-0 text-xs text-muted-foreground">
|
|
61
|
+
Read-only (GET)
|
|
62
|
+
</p>
|
|
63
|
+
</div>
|
|
64
|
+
<div className="flex min-w-0 items-center justify-between gap-2">
|
|
65
|
+
<code className="min-w-0 break-all font-mono text-sm">
|
|
66
|
+
{showAnonKey ? docs.anonKey : "•".repeat(40)}
|
|
67
|
+
</code>
|
|
68
|
+
<div className="flex shrink-0 gap-1">
|
|
69
|
+
<Button
|
|
70
|
+
variant="ghost"
|
|
71
|
+
size="icon"
|
|
72
|
+
className="h-7 w-7"
|
|
73
|
+
onClick={() => setShowAnonKey((v) => !v)}
|
|
74
|
+
>
|
|
75
|
+
{showAnonKey ? <EyeOff size={12} /> : <Eye size={12} />}
|
|
76
|
+
</Button>
|
|
77
|
+
<Button
|
|
78
|
+
variant="ghost"
|
|
79
|
+
size="icon"
|
|
80
|
+
className="h-7 w-7"
|
|
81
|
+
onClick={() => void copyToClipboard(docs.anonKey, "anon")}
|
|
82
|
+
>
|
|
83
|
+
{copiedKey === "anon" ? (
|
|
84
|
+
<Check size={12} />
|
|
85
|
+
) : (
|
|
86
|
+
<Copy size={12} />
|
|
87
|
+
)}
|
|
88
|
+
</Button>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div className="space-y-1 rounded-xl border border-border p-4">
|
|
94
|
+
<div className="flex items-center justify-between gap-2">
|
|
95
|
+
<p className="text-xs text-muted-foreground">Service Role Key</p>
|
|
96
|
+
<p className="shrink-0 text-xs text-red-500">
|
|
97
|
+
Full access — server only
|
|
98
|
+
</p>
|
|
99
|
+
</div>
|
|
100
|
+
<div className="flex min-w-0 items-center justify-between gap-2">
|
|
101
|
+
<code className="min-w-0 break-all font-mono text-sm">
|
|
102
|
+
{showServiceKey ? docs.serviceRoleKey : "•".repeat(40)}
|
|
103
|
+
</code>
|
|
104
|
+
<div className="flex shrink-0 gap-1">
|
|
105
|
+
<Button
|
|
106
|
+
variant="ghost"
|
|
107
|
+
size="icon"
|
|
108
|
+
className="h-7 w-7"
|
|
109
|
+
onClick={() => setShowServiceKey((v) => !v)}
|
|
110
|
+
>
|
|
111
|
+
{showServiceKey ? <EyeOff size={12} /> : <Eye size={12} />}
|
|
112
|
+
</Button>
|
|
113
|
+
<Button
|
|
114
|
+
variant="ghost"
|
|
115
|
+
size="icon"
|
|
116
|
+
className="h-7 w-7"
|
|
117
|
+
onClick={() =>
|
|
118
|
+
void copyToClipboard(docs.serviceRoleKey, "service")
|
|
119
|
+
}
|
|
120
|
+
>
|
|
121
|
+
{copiedKey === "service" ? (
|
|
122
|
+
<Check size={12} />
|
|
123
|
+
) : (
|
|
124
|
+
<Copy size={12} />
|
|
125
|
+
)}
|
|
126
|
+
</Button>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
{docs.tables.length === 0 ? (
|
|
133
|
+
<div className="flex flex-col items-center justify-center rounded-xl border border-dashed border-border py-16 text-center">
|
|
134
|
+
<p className="text-sm text-muted-foreground">No tables yet</p>
|
|
135
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
136
|
+
Create a table in the Table Editor to see its endpoints here
|
|
137
|
+
</p>
|
|
138
|
+
</div>
|
|
139
|
+
) : (
|
|
140
|
+
<div className="space-y-8">
|
|
141
|
+
<h2 className="text-sm font-medium uppercase tracking-wider text-muted-foreground">
|
|
142
|
+
Endpoints
|
|
143
|
+
</h2>
|
|
144
|
+
{docs.tables.map((table) => (
|
|
145
|
+
<div key={table.name} className="min-w-0 space-y-3">
|
|
146
|
+
<h3 className="text-base font-medium">{table.name}</h3>
|
|
147
|
+
<div className="space-y-2">
|
|
148
|
+
{table.endpoints.map((endpoint, i) => (
|
|
149
|
+
<EndpointCard
|
|
150
|
+
key={i}
|
|
151
|
+
endpoint={endpoint}
|
|
152
|
+
methodColors={methodColors}
|
|
153
|
+
onCopy={copyToClipboard}
|
|
154
|
+
copiedKey={copiedKey}
|
|
155
|
+
/>
|
|
156
|
+
))}
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
))}
|
|
160
|
+
</div>
|
|
161
|
+
)}
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function EndpointCard({
|
|
167
|
+
endpoint,
|
|
168
|
+
methodColors,
|
|
169
|
+
onCopy,
|
|
170
|
+
copiedKey,
|
|
171
|
+
}: {
|
|
172
|
+
endpoint: {
|
|
173
|
+
method: string;
|
|
174
|
+
path: string;
|
|
175
|
+
description: string;
|
|
176
|
+
example: string;
|
|
177
|
+
};
|
|
178
|
+
methodColors: Record<string, string>;
|
|
179
|
+
onCopy: (text: string, key: string) => Promise<void>;
|
|
180
|
+
copiedKey: string | null;
|
|
181
|
+
}) {
|
|
182
|
+
const [expanded, setExpanded] = useState(false);
|
|
183
|
+
const copyKey = `${endpoint.method}-${endpoint.path}`;
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<div
|
|
187
|
+
className="min-w-0 cursor-pointer overflow-hidden rounded-xl border border-border"
|
|
188
|
+
onClick={() => setExpanded((v) => !v)}
|
|
189
|
+
>
|
|
190
|
+
<div className="flex min-w-0 items-start gap-3 px-4 py-3 transition-colors hover:bg-accent/50">
|
|
191
|
+
<span
|
|
192
|
+
className={`mt-0.5 shrink-0 rounded px-2 py-0.5 font-mono text-xs font-bold ${methodColors[endpoint.method]}`}
|
|
193
|
+
>
|
|
194
|
+
{endpoint.method}
|
|
195
|
+
</span>
|
|
196
|
+
<div className="min-w-0 flex-1 space-y-1">
|
|
197
|
+
<code className="block break-all font-mono text-sm">
|
|
198
|
+
{endpoint.path}
|
|
199
|
+
</code>
|
|
200
|
+
<p className="text-xs text-muted-foreground">
|
|
201
|
+
{endpoint.description}
|
|
202
|
+
</p>
|
|
203
|
+
</div>
|
|
204
|
+
</div>
|
|
205
|
+
{expanded && (
|
|
206
|
+
<div className="border-t border-border bg-muted/30 px-4 py-3">
|
|
207
|
+
<div className="flex min-w-0 items-start justify-between gap-2">
|
|
208
|
+
<pre className="min-w-0 flex-1 overflow-x-auto whitespace-pre-wrap break-all font-mono text-xs text-muted-foreground">
|
|
209
|
+
{endpoint.example}
|
|
210
|
+
</pre>
|
|
211
|
+
<Button
|
|
212
|
+
variant="ghost"
|
|
213
|
+
size="icon"
|
|
214
|
+
className="h-7 w-7 shrink-0"
|
|
215
|
+
onClick={(e) => {
|
|
216
|
+
e.stopPropagation();
|
|
217
|
+
void onCopy(endpoint.example, copyKey);
|
|
218
|
+
}}
|
|
219
|
+
>
|
|
220
|
+
{copiedKey === copyKey ? <Check size={12} /> : <Copy size={12} />}
|
|
221
|
+
</Button>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
)}
|
|
225
|
+
</div>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { redirect } from "next/navigation";
|
|
2
|
+
import apiClient from "@/lib/axios";
|
|
3
|
+
import { retrieveTokenFromCookie } from "@/server-utils/utils";
|
|
4
|
+
import { COOKIE_KEYS } from "@apiDatabase/constants";
|
|
5
|
+
import type {
|
|
6
|
+
ProjectApiDocs,
|
|
7
|
+
ProjectApiEndpoint,
|
|
8
|
+
ProjectBySlugResponse,
|
|
9
|
+
} from "@apiDatabase/types";
|
|
10
|
+
|
|
11
|
+
function buildEndpoints(
|
|
12
|
+
projectUrl: string,
|
|
13
|
+
tableName: string,
|
|
14
|
+
): ProjectApiEndpoint[] {
|
|
15
|
+
const base = `${projectUrl}/rest/${tableName}`;
|
|
16
|
+
|
|
17
|
+
return [
|
|
18
|
+
{
|
|
19
|
+
method: "GET",
|
|
20
|
+
path: base,
|
|
21
|
+
description: `Fetch rows from ${tableName}. Supports filters, ordering, pagination. Anon key OK.`,
|
|
22
|
+
example: `curl "${base}?limit=10&order=created_at.desc" \\\n -H "Authorization: Bearer <anon_key>"`,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
method: "GET",
|
|
26
|
+
path: `${base}?col=eq.value`,
|
|
27
|
+
description:
|
|
28
|
+
"Filter rows. Operators: eq, neq, gt, gte, lt, lte, like, ilike, is.",
|
|
29
|
+
example: `curl "${base}?name=eq.Shirt&price=lt.100" \\\n -H "Authorization: Bearer <anon_key>"`,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
method: "POST",
|
|
33
|
+
path: base,
|
|
34
|
+
description: `Insert a row into ${tableName}. Requires service role key.`,
|
|
35
|
+
example: `curl -X POST "${base}" \\\n -H "Authorization: Bearer <service_role_key>" \\\n -H "Content-Type: application/json" \\\n -d '{"name":"T-Shirt"}'`,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
method: "PATCH",
|
|
39
|
+
path: `${base}/:id`,
|
|
40
|
+
description: `Update a row by primary key in ${tableName}. Requires service role key.`,
|
|
41
|
+
example: `curl -X PATCH "${base}/1" \\\n -H "Authorization: Bearer <service_role_key>" \\\n -H "Content-Type: application/json" \\\n -d '{"name":"Hoodie"}'`,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
method: "DELETE",
|
|
45
|
+
path: `${base}/:id`,
|
|
46
|
+
description: `Delete a row by primary key from ${tableName}. Requires service role key.`,
|
|
47
|
+
example: `curl -X DELETE "${base}/1" \\\n -H "Authorization: Bearer <service_role_key>"`,
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function retrieveApiDocsFromApi(
|
|
53
|
+
orgSlug: string,
|
|
54
|
+
projectSlug: string,
|
|
55
|
+
): Promise<ProjectApiDocs> {
|
|
56
|
+
const token = await retrieveTokenFromCookie();
|
|
57
|
+
const headers = { Cookie: `${COOKIE_KEYS.ACCESS_TOKEN}=${token}` };
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const [projectRes, tablesRes] = await Promise.all([
|
|
61
|
+
apiClient.get<ProjectBySlugResponse>(
|
|
62
|
+
`/orgs/${orgSlug}/projects/${projectSlug}`,
|
|
63
|
+
{ headers },
|
|
64
|
+
),
|
|
65
|
+
apiClient.get<string[]>(
|
|
66
|
+
`/orgs/${orgSlug}/projects/${projectSlug}/tables`,
|
|
67
|
+
{ headers },
|
|
68
|
+
),
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
const project = projectRes.data.projects;
|
|
72
|
+
const tableNames = tablesRes.data;
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
projectUrl: project.projectUrl,
|
|
76
|
+
anonKey: project.anonKey,
|
|
77
|
+
serviceRoleKey: project.serviceRoleKey,
|
|
78
|
+
tables: tableNames.map((name) => ({
|
|
79
|
+
name,
|
|
80
|
+
endpoints: buildEndpoints(project.projectUrl, name),
|
|
81
|
+
})),
|
|
82
|
+
};
|
|
83
|
+
} catch {
|
|
84
|
+
redirect(`/organizations/${orgSlug}/projects`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
|
|
3
|
+
import { redirect } from "next/navigation";
|
|
4
|
+
import { authServerSchema } from "./server.schema";
|
|
5
|
+
import { AUTH_INTENT } from "./constants";
|
|
6
|
+
import { applyAuthCookiesFromResponse } from "./cookies";
|
|
7
|
+
|
|
8
|
+
export type AuthActionState = {
|
|
9
|
+
error?: string;
|
|
10
|
+
fieldErrors?: Record<string, string[]>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export async function authAction(
|
|
14
|
+
_prev: AuthActionState,
|
|
15
|
+
formData: FormData,
|
|
16
|
+
): Promise<AuthActionState> {
|
|
17
|
+
const raw = Object.fromEntries(formData);
|
|
18
|
+
const parsed = authServerSchema.safeParse(raw);
|
|
19
|
+
|
|
20
|
+
if (!parsed.success) {
|
|
21
|
+
return {
|
|
22
|
+
fieldErrors: parsed.error.flatten().fieldErrors as Record<
|
|
23
|
+
string,
|
|
24
|
+
string[]
|
|
25
|
+
>,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { intent, ...data } = parsed.data;
|
|
30
|
+
|
|
31
|
+
let res: Response;
|
|
32
|
+
|
|
33
|
+
switch (intent) {
|
|
34
|
+
case AUTH_INTENT.LOGIN: {
|
|
35
|
+
res = await fetch(`${process.env.API_URL}/auth/login`, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: { "Content-Type": "application/json" },
|
|
38
|
+
body: JSON.stringify(data),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
const body = await res.json();
|
|
43
|
+
return { error: body.message ?? "Login failed" };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
case AUTH_INTENT.REGISTER: {
|
|
50
|
+
res = await fetch(`${process.env.API_URL}/auth/register`, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: { "Content-Type": "application/json" },
|
|
53
|
+
body: JSON.stringify(data),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
const body = await res.json();
|
|
58
|
+
return { error: body.message ?? "Registration failed" };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await applyAuthCookiesFromResponse(res!);
|
|
66
|
+
redirect("/dashboard");
|
|
67
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { LoginInput, RegisterInput } from "@apiDatabase/types";
|
|
3
|
+
|
|
4
|
+
export const loginSchema = z.object({
|
|
5
|
+
email: z.string().email("Invalid email"),
|
|
6
|
+
password: z.string().min(1, "Password is required"),
|
|
7
|
+
}) satisfies z.ZodType<LoginInput>;
|
|
8
|
+
|
|
9
|
+
export const registerSchema = z.object({
|
|
10
|
+
name: z.string().min(1, "Name is required"),
|
|
11
|
+
email: z.string().email("Invalid email"),
|
|
12
|
+
password: z.string().min(8, "Password must be at least 8 characters"),
|
|
13
|
+
}) satisfies z.ZodType<RegisterInput>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { COOKIE_KEYS } from "@apiDatabase/constants";
|
|
2
|
+
import { cookies } from "next/headers";
|
|
3
|
+
import type { NextResponse } from "next/server";
|
|
4
|
+
|
|
5
|
+
type ParsedCookie = {
|
|
6
|
+
name: string;
|
|
7
|
+
value: string;
|
|
8
|
+
options: {
|
|
9
|
+
path: string;
|
|
10
|
+
maxAge?: number;
|
|
11
|
+
httpOnly: boolean;
|
|
12
|
+
secure: boolean;
|
|
13
|
+
sameSite: "lax" | "strict" | "none";
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function parseSetCookieHeader(setCookie: string): ParsedCookie {
|
|
18
|
+
const parts = setCookie.split(";").map((part) => part.trim());
|
|
19
|
+
const [nameValue, ...attributes] = parts;
|
|
20
|
+
|
|
21
|
+
const separatorIndex = nameValue.indexOf("=");
|
|
22
|
+
const name = nameValue.slice(0, separatorIndex);
|
|
23
|
+
const value = nameValue.slice(separatorIndex + 1);
|
|
24
|
+
|
|
25
|
+
const options: ParsedCookie["options"] = {
|
|
26
|
+
path: "/",
|
|
27
|
+
httpOnly: false,
|
|
28
|
+
secure: false,
|
|
29
|
+
sameSite: "lax",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
for (const attribute of attributes) {
|
|
33
|
+
const separator = attribute.indexOf("=");
|
|
34
|
+
const key = (
|
|
35
|
+
separator === -1 ? attribute : attribute.slice(0, separator)
|
|
36
|
+
).toLowerCase();
|
|
37
|
+
const val = separator === -1 ? "" : attribute.slice(separator + 1);
|
|
38
|
+
|
|
39
|
+
switch (key) {
|
|
40
|
+
case "path":
|
|
41
|
+
options.path = val;
|
|
42
|
+
break;
|
|
43
|
+
case "max-age":
|
|
44
|
+
options.maxAge = Number.parseInt(val, 10);
|
|
45
|
+
break;
|
|
46
|
+
case "httponly":
|
|
47
|
+
options.httpOnly = true;
|
|
48
|
+
break;
|
|
49
|
+
case "secure":
|
|
50
|
+
options.secure = true;
|
|
51
|
+
break;
|
|
52
|
+
case "samesite":
|
|
53
|
+
options.sameSite =
|
|
54
|
+
val.toLowerCase() as ParsedCookie["options"]["sameSite"];
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { name, value, options };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isAuthCookie(name: string) {
|
|
63
|
+
return (
|
|
64
|
+
name === COOKIE_KEYS.ACCESS_TOKEN || name === COOKIE_KEYS.REFRESH_TOKEN
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function applyAuthCookiesFromResponse(response: Response) {
|
|
69
|
+
const cookieStore = await cookies();
|
|
70
|
+
for (const header of response.headers.getSetCookie()) {
|
|
71
|
+
const parsed = parseSetCookieHeader(header);
|
|
72
|
+
if (!isAuthCookie(parsed.name)) continue;
|
|
73
|
+
cookieStore.set(parsed.name, parsed.value, parsed.options);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function applyAuthCookiesToResponse(
|
|
78
|
+
response: NextResponse,
|
|
79
|
+
setCookieHeaders: string[],
|
|
80
|
+
) {
|
|
81
|
+
for (const header of setCookieHeaders) {
|
|
82
|
+
const parsed = parseSetCookieHeader(header);
|
|
83
|
+
if (!isAuthCookie(parsed.name)) continue;
|
|
84
|
+
response.cookies.set(parsed.name, parsed.value, parsed.options);
|
|
85
|
+
}
|
|
86
|
+
}
|