abdellah0l-stack 1.0.1

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.
Files changed (57) hide show
  1. package/README.md +50 -0
  2. package/bin/cli.js +218 -0
  3. package/package.json +30 -0
  4. package/template/README.md +61 -0
  5. package/template/components.json +22 -0
  6. package/template/drizzle.config.ts +13 -0
  7. package/template/eslint.config.mjs +25 -0
  8. package/template/next.config.ts +114 -0
  9. package/template/package.json +62 -0
  10. package/template/postcss.config.mjs +7 -0
  11. package/template/public/file.svg +1 -0
  12. package/template/public/globe.svg +1 -0
  13. package/template/public/next.svg +1 -0
  14. package/template/public/vercel.svg +1 -0
  15. package/template/public/window.svg +1 -0
  16. package/template/src/app/api/v1/auth/[...all]/route.ts +5 -0
  17. package/template/src/app/api/v1/trpc/[trpc]/route.ts +13 -0
  18. package/template/src/app/api/v1/uploadthing/core.ts +50 -0
  19. package/template/src/app/api/v1/uploadthing/route.ts +11 -0
  20. package/template/src/app/favicon.ico +0 -0
  21. package/template/src/app/globals.css +121 -0
  22. package/template/src/app/layout.tsx +50 -0
  23. package/template/src/app/page.tsx +58 -0
  24. package/template/src/components/loading-spinner.tsx +18 -0
  25. package/template/src/components/navigation.tsx +54 -0
  26. package/template/src/components/query-provider.tsx +27 -0
  27. package/template/src/components/ui/badge.tsx +46 -0
  28. package/template/src/components/ui/button.tsx +58 -0
  29. package/template/src/components/ui/card.tsx +92 -0
  30. package/template/src/components/ui/dialog.tsx +143 -0
  31. package/template/src/components/ui/input.tsx +21 -0
  32. package/template/src/components/ui/label.tsx +26 -0
  33. package/template/src/components/ui/select.tsx +185 -0
  34. package/template/src/components/ui/tabs.tsx +55 -0
  35. package/template/src/components/ui/textarea.tsx +23 -0
  36. package/template/src/data/env/client.ts +7 -0
  37. package/template/src/data/env/server.ts +13 -0
  38. package/template/src/drizzle/db.ts +6 -0
  39. package/template/src/drizzle/schema/app-schema.ts +31 -0
  40. package/template/src/drizzle/schema/auth-schema.ts +55 -0
  41. package/template/src/drizzle/schema/index.ts +14 -0
  42. package/template/src/hooks/use-auth.ts +32 -0
  43. package/template/src/hooks/use-debounce.ts +18 -0
  44. package/template/src/lib/arcjet.ts +45 -0
  45. package/template/src/lib/auth-client.ts +7 -0
  46. package/template/src/lib/auth.ts +50 -0
  47. package/template/src/lib/use-mobile.ts +29 -0
  48. package/template/src/lib/utils.ts +6 -0
  49. package/template/src/middleware.ts +14 -0
  50. package/template/src/server/index.ts +13 -0
  51. package/template/src/server/routers/posts.ts +93 -0
  52. package/template/src/server/routers/users.ts +56 -0
  53. package/template/src/server/trpc.ts +38 -0
  54. package/template/src/types/index.ts +10 -0
  55. package/template/src/utils/trpc.ts +5 -0
  56. package/template/src/utils/uploadthing.ts +10 -0
  57. package/template/tsconfig.json +42 -0
@@ -0,0 +1,143 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as DialogPrimitive from "@radix-ui/react-dialog"
5
+ import { XIcon } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ function Dialog({
10
+ ...props
11
+ }: React.ComponentProps<typeof DialogPrimitive.Root>) {
12
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />
13
+ }
14
+
15
+ function DialogTrigger({
16
+ ...props
17
+ }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
18
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
19
+ }
20
+
21
+ function DialogPortal({
22
+ ...props
23
+ }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
24
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
25
+ }
26
+
27
+ function DialogClose({
28
+ ...props
29
+ }: React.ComponentProps<typeof DialogPrimitive.Close>) {
30
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
31
+ }
32
+
33
+ function DialogOverlay({
34
+ className,
35
+ ...props
36
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
37
+ return (
38
+ <DialogPrimitive.Overlay
39
+ data-slot="dialog-overlay"
40
+ className={cn(
41
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
42
+ className
43
+ )}
44
+ {...props}
45
+ />
46
+ )
47
+ }
48
+
49
+ function DialogContent({
50
+ className,
51
+ children,
52
+ showCloseButton = true,
53
+ ...props
54
+ }: React.ComponentProps<typeof DialogPrimitive.Content> & {
55
+ showCloseButton?: boolean
56
+ }) {
57
+ return (
58
+ <DialogPortal data-slot="dialog-portal">
59
+ <DialogOverlay />
60
+ <DialogPrimitive.Content
61
+ data-slot="dialog-content"
62
+ className={cn(
63
+ "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
64
+ className
65
+ )}
66
+ {...props}
67
+ >
68
+ {children}
69
+ {showCloseButton && (
70
+ <DialogPrimitive.Close
71
+ data-slot="dialog-close"
72
+ className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
73
+ >
74
+ <XIcon />
75
+ <span className="sr-only">Close</span>
76
+ </DialogPrimitive.Close>
77
+ )}
78
+ </DialogPrimitive.Content>
79
+ </DialogPortal>
80
+ )
81
+ }
82
+
83
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
84
+ return (
85
+ <div
86
+ data-slot="dialog-header"
87
+ className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
88
+ {...props}
89
+ />
90
+ )
91
+ }
92
+
93
+ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
94
+ return (
95
+ <div
96
+ data-slot="dialog-footer"
97
+ className={cn(
98
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
99
+ className
100
+ )}
101
+ {...props}
102
+ />
103
+ )
104
+ }
105
+
106
+ function DialogTitle({
107
+ className,
108
+ ...props
109
+ }: React.ComponentProps<typeof DialogPrimitive.Title>) {
110
+ return (
111
+ <DialogPrimitive.Title
112
+ data-slot="dialog-title"
113
+ className={cn("text-lg leading-none font-semibold", className)}
114
+ {...props}
115
+ />
116
+ )
117
+ }
118
+
119
+ function DialogDescription({
120
+ className,
121
+ ...props
122
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
123
+ return (
124
+ <DialogPrimitive.Description
125
+ data-slot="dialog-description"
126
+ className={cn("text-muted-foreground text-sm", className)}
127
+ {...props}
128
+ />
129
+ )
130
+ }
131
+
132
+ export {
133
+ Dialog,
134
+ DialogClose,
135
+ DialogContent,
136
+ DialogDescription,
137
+ DialogFooter,
138
+ DialogHeader,
139
+ DialogOverlay,
140
+ DialogPortal,
141
+ DialogTitle,
142
+ DialogTrigger,
143
+ }
@@ -0,0 +1,21 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
6
+ return (
7
+ <input
8
+ type={type}
9
+ data-slot="input"
10
+ className={cn(
11
+ "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
12
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
13
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
14
+ className
15
+ )}
16
+ {...props}
17
+ />
18
+ )
19
+ }
20
+
21
+ export { Input }
@@ -0,0 +1,26 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as LabelPrimitive from "@radix-ui/react-label"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const labelVariants = cva(
10
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11
+ )
12
+
13
+ const Label = React.forwardRef<
14
+ React.ElementRef<typeof LabelPrimitive.Root>,
15
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
16
+ VariantProps<typeof labelVariants>
17
+ >(({ className, ...props }, ref) => (
18
+ <LabelPrimitive.Root
19
+ ref={ref}
20
+ className={cn(labelVariants(), className)}
21
+ {...props}
22
+ />
23
+ ))
24
+ Label.displayName = LabelPrimitive.Root.displayName
25
+
26
+ export { Label }
@@ -0,0 +1,185 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SelectPrimitive from "@radix-ui/react-select"
5
+ import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ function Select({
10
+ ...props
11
+ }: React.ComponentProps<typeof SelectPrimitive.Root>) {
12
+ return <SelectPrimitive.Root data-slot="select" {...props} />
13
+ }
14
+
15
+ function SelectGroup({
16
+ ...props
17
+ }: React.ComponentProps<typeof SelectPrimitive.Group>) {
18
+ return <SelectPrimitive.Group data-slot="select-group" {...props} />
19
+ }
20
+
21
+ function SelectValue({
22
+ ...props
23
+ }: React.ComponentProps<typeof SelectPrimitive.Value>) {
24
+ return <SelectPrimitive.Value data-slot="select-value" {...props} />
25
+ }
26
+
27
+ function SelectTrigger({
28
+ className,
29
+ size = "default",
30
+ children,
31
+ ...props
32
+ }: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
33
+ size?: "sm" | "default"
34
+ }) {
35
+ return (
36
+ <SelectPrimitive.Trigger
37
+ data-slot="select-trigger"
38
+ data-size={size}
39
+ className={cn(
40
+ "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
41
+ className
42
+ )}
43
+ {...props}
44
+ >
45
+ {children}
46
+ <SelectPrimitive.Icon asChild>
47
+ <ChevronDownIcon className="size-4 opacity-50" />
48
+ </SelectPrimitive.Icon>
49
+ </SelectPrimitive.Trigger>
50
+ )
51
+ }
52
+
53
+ function SelectContent({
54
+ className,
55
+ children,
56
+ position = "popper",
57
+ ...props
58
+ }: React.ComponentProps<typeof SelectPrimitive.Content>) {
59
+ return (
60
+ <SelectPrimitive.Portal>
61
+ <SelectPrimitive.Content
62
+ data-slot="select-content"
63
+ className={cn(
64
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
65
+ position === "popper" &&
66
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
67
+ className
68
+ )}
69
+ position={position}
70
+ {...props}
71
+ >
72
+ <SelectScrollUpButton />
73
+ <SelectPrimitive.Viewport
74
+ className={cn(
75
+ "p-1",
76
+ position === "popper" &&
77
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
78
+ )}
79
+ >
80
+ {children}
81
+ </SelectPrimitive.Viewport>
82
+ <SelectScrollDownButton />
83
+ </SelectPrimitive.Content>
84
+ </SelectPrimitive.Portal>
85
+ )
86
+ }
87
+
88
+ function SelectLabel({
89
+ className,
90
+ ...props
91
+ }: React.ComponentProps<typeof SelectPrimitive.Label>) {
92
+ return (
93
+ <SelectPrimitive.Label
94
+ data-slot="select-label"
95
+ className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
96
+ {...props}
97
+ />
98
+ )
99
+ }
100
+
101
+ function SelectItem({
102
+ className,
103
+ children,
104
+ ...props
105
+ }: React.ComponentProps<typeof SelectPrimitive.Item>) {
106
+ return (
107
+ <SelectPrimitive.Item
108
+ data-slot="select-item"
109
+ className={cn(
110
+ "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
111
+ className
112
+ )}
113
+ {...props}
114
+ >
115
+ <span className="absolute right-2 flex size-3.5 items-center justify-center">
116
+ <SelectPrimitive.ItemIndicator>
117
+ <CheckIcon className="size-4" />
118
+ </SelectPrimitive.ItemIndicator>
119
+ </span>
120
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
121
+ </SelectPrimitive.Item>
122
+ )
123
+ }
124
+
125
+ function SelectSeparator({
126
+ className,
127
+ ...props
128
+ }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
129
+ return (
130
+ <SelectPrimitive.Separator
131
+ data-slot="select-separator"
132
+ className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
133
+ {...props}
134
+ />
135
+ )
136
+ }
137
+
138
+ function SelectScrollUpButton({
139
+ className,
140
+ ...props
141
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
142
+ return (
143
+ <SelectPrimitive.ScrollUpButton
144
+ data-slot="select-scroll-up-button"
145
+ className={cn(
146
+ "flex cursor-default items-center justify-center py-1",
147
+ className
148
+ )}
149
+ {...props}
150
+ >
151
+ <ChevronUpIcon className="size-4" />
152
+ </SelectPrimitive.ScrollUpButton>
153
+ )
154
+ }
155
+
156
+ function SelectScrollDownButton({
157
+ className,
158
+ ...props
159
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
160
+ return (
161
+ <SelectPrimitive.ScrollDownButton
162
+ data-slot="select-scroll-down-button"
163
+ className={cn(
164
+ "flex cursor-default items-center justify-center py-1",
165
+ className
166
+ )}
167
+ {...props}
168
+ >
169
+ <ChevronDownIcon className="size-4" />
170
+ </SelectPrimitive.ScrollDownButton>
171
+ )
172
+ }
173
+
174
+ export {
175
+ Select,
176
+ SelectContent,
177
+ SelectGroup,
178
+ SelectItem,
179
+ SelectLabel,
180
+ SelectScrollDownButton,
181
+ SelectScrollUpButton,
182
+ SelectSeparator,
183
+ SelectTrigger,
184
+ SelectValue,
185
+ }
@@ -0,0 +1,55 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as TabsPrimitive from "@radix-ui/react-tabs"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Tabs = TabsPrimitive.Root
9
+
10
+ const TabsList = React.forwardRef<
11
+ React.ElementRef<typeof TabsPrimitive.List>,
12
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
13
+ >(({ className, ...props }, ref) => (
14
+ <TabsPrimitive.List
15
+ ref={ref}
16
+ className={cn(
17
+ "inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
18
+ className
19
+ )}
20
+ {...props}
21
+ />
22
+ ))
23
+ TabsList.displayName = TabsPrimitive.List.displayName
24
+
25
+ const TabsTrigger = React.forwardRef<
26
+ React.ElementRef<typeof TabsPrimitive.Trigger>,
27
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
28
+ >(({ className, ...props }, ref) => (
29
+ <TabsPrimitive.Trigger
30
+ ref={ref}
31
+ className={cn(
32
+ "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
33
+ className
34
+ )}
35
+ {...props}
36
+ />
37
+ ))
38
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
39
+
40
+ const TabsContent = React.forwardRef<
41
+ React.ElementRef<typeof TabsPrimitive.Content>,
42
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
43
+ >(({ className, ...props }, ref) => (
44
+ <TabsPrimitive.Content
45
+ ref={ref}
46
+ className={cn(
47
+ "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
48
+ className
49
+ )}
50
+ {...props}
51
+ />
52
+ ))
53
+ TabsContent.displayName = TabsPrimitive.Content.displayName
54
+
55
+ export { Tabs, TabsList, TabsTrigger, TabsContent }
@@ -0,0 +1,23 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>
6
+
7
+ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
8
+ ({ className, ...props }, ref) => {
9
+ return (
10
+ <textarea
11
+ className={cn(
12
+ "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
13
+ className
14
+ )}
15
+ ref={ref}
16
+ {...props}
17
+ />
18
+ )
19
+ }
20
+ )
21
+ Textarea.displayName = "Textarea"
22
+
23
+ export { Textarea }
@@ -0,0 +1,7 @@
1
+ import { createEnv } from "@t3-oss/env-nextjs";
2
+
3
+ export const env = createEnv({
4
+ client: {}, // client-side environment variables
5
+ emptyStringAsUndefined: true, // treat empty strings as undefined
6
+ experimental__runtimeEnv: {} // no process.env on the client
7
+ });
@@ -0,0 +1,13 @@
1
+ import { createEnv } from "@t3-oss/env-nextjs";
2
+ import { z } from "zod";
3
+
4
+ export const env = createEnv({
5
+ server: {
6
+ ARCJET_KEY: z.string().optional(),
7
+ DATABASE_URL: z.string().url(),
8
+ AI_GATEWAY_API_KEY: z.string().min(1),
9
+ }, // server-side environment variables
10
+ emptyStringAsUndefined: true, // treat empty strings as undefined
11
+ experimental__runtimeEnv: process.env, // use process.env at runtime
12
+ skipValidation: false, // do not skip validation
13
+ });
@@ -0,0 +1,6 @@
1
+ import { env } from "../data/env/server";
2
+ import { drizzle } from "drizzle-orm/node-postgres";
3
+ import { schema } from "./schema";
4
+
5
+ // the main database instance u will use throughout your app
6
+ export const db = drizzle(env.DATABASE_URL, { schema });
@@ -0,0 +1,31 @@
1
+ import {
2
+ pgTable,
3
+ uuid,
4
+ varchar,
5
+ text,
6
+ timestamp,
7
+ index,
8
+ } from "drizzle-orm/pg-core";
9
+ import { user } from "./auth-schema";
10
+
11
+ // ========== EXAMPLE: POSTS ==========
12
+ // This is an example table. Replace with your own schema.
13
+ export const posts = pgTable(
14
+ "posts",
15
+ {
16
+ id: uuid("id").defaultRandom().primaryKey(),
17
+ title: varchar("title", { length: 255 }).notNull(),
18
+ content: text("content").notNull(),
19
+ userId: text("user_id")
20
+ .notNull()
21
+ .references(() => user.id, { onDelete: "cascade", onUpdate: "cascade" }),
22
+ createdAt: timestamp("created_at").defaultNow().notNull(),
23
+ updatedAt: timestamp("updated_at")
24
+ .defaultNow()
25
+ .$onUpdate(() => new Date())
26
+ .notNull(),
27
+ },
28
+ (table) => ({
29
+ userIdx: index("idx_posts_user_id").on(table.userId),
30
+ }),
31
+ );
@@ -0,0 +1,55 @@
1
+ import { pgTable, text, timestamp, varchar, boolean } from "drizzle-orm/pg-core";
2
+
3
+ // you can ofc customize the schema as you want depending on your auth needs
4
+ export const user = pgTable("user", {
5
+ id: text("id").primaryKey(),
6
+ email: varchar("email", { length: 50 }).notNull().unique(),
7
+ emailVerified: boolean("email_verified").default(false).notNull(),
8
+ name: varchar("name", { length: 50 }).notNull(),
9
+ image: varchar("image", { length: 255 }),
10
+ bio: varchar("bio", { length: 255 }),
11
+ createdAt: timestamp("created_at").defaultNow().notNull(),
12
+ updatedAt: timestamp("updated_at")
13
+ .defaultNow()
14
+ .$onUpdate(() => new Date())
15
+ .notNull(),
16
+ });
17
+
18
+ // this table is mandatory if you have to deal with sessions and JWTs and tokens and all that
19
+ export const session = pgTable("session", {
20
+ id: text("id").primaryKey(),
21
+ expiresAt: timestamp("expires_at").notNull(),
22
+ token: text("token").notNull().unique(),
23
+ createdAt: timestamp("created_at").defaultNow().notNull(),
24
+ updatedAt: timestamp("updated_at")
25
+ .defaultNow()
26
+ .$onUpdate(() => /* @__PURE__ */ new Date())
27
+ .notNull(),
28
+ ipAddress: text("ip_address"),
29
+ userAgent: text("user_agent"),
30
+ userId: text("user_id")
31
+ .notNull()
32
+ .references(() => user.id, { onDelete: "cascade" }),
33
+ });
34
+
35
+ // delete this table if u don't need OAuth providers
36
+ export const account = pgTable("account", {
37
+ id: text("id").primaryKey(),
38
+ accountId: text("account_id").notNull(),
39
+ providerId: text("provider_id").notNull(),
40
+ userId: text("user_id")
41
+ .notNull()
42
+ .references(() => user.id, { onDelete: "cascade" }),
43
+ accessToken: text("access_token"),
44
+ refreshToken: text("refresh_token"),
45
+ idToken: text("id_token"),
46
+ accessTokenExpiresAt: timestamp("access_token_expires_at"),
47
+ refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
48
+ scope: text("scope"),
49
+ password: text("password"),
50
+ createdAt: timestamp("created_at").defaultNow().notNull(),
51
+ updatedAt: timestamp("updated_at")
52
+ .defaultNow()
53
+ .$onUpdate(() => /* @__PURE__ */ new Date())
54
+ .notNull(),
55
+ });
@@ -0,0 +1,14 @@
1
+
2
+
3
+ // this file aggregates all the database schemas for easier import elsewhere
4
+ import * as app from "./app-schema"
5
+
6
+ import * as auth from "./auth-schema"
7
+
8
+ export const schema = {
9
+ ...app,
10
+ ...auth,
11
+ }
12
+
13
+ export * from "./app-schema"
14
+ export * from "./auth-schema"
@@ -0,0 +1,32 @@
1
+ 'use client'
2
+
3
+ import { authClient } from "@/lib/auth-client";
4
+
5
+ interface User {
6
+ id: string
7
+ name: string
8
+ email: string
9
+ image?: string
10
+ }
11
+
12
+ interface AuthState {
13
+ user: User | null
14
+ isLoading: boolean
15
+ isSignedIn: boolean
16
+ }
17
+
18
+ // a custom hook to access authentication state in your components
19
+ export function useAuth(): AuthState {
20
+ const { data: session, isPending: isLoading } = authClient.useSession();
21
+
22
+ return {
23
+ user: session?.user ? {
24
+ id: session.user.id,
25
+ name: session.user.name,
26
+ email: session.user.email,
27
+ image: session.user.image || undefined,
28
+ } : null,
29
+ isLoading,
30
+ isSignedIn: !!session,
31
+ };
32
+ }
@@ -0,0 +1,18 @@
1
+ import { useState, useEffect } from 'react'
2
+
3
+ // a custom hook that debounces a value by a specified delay for better performance and UX
4
+ export function useDebounce<T>(value: T, delay: number): T {
5
+ const [debouncedValue, setDebouncedValue] = useState<T>(value)
6
+
7
+ useEffect(() => {
8
+ const handler = setTimeout(() => {
9
+ setDebouncedValue(value)
10
+ }, delay)
11
+
12
+ return () => {
13
+ clearTimeout(handler)
14
+ }
15
+ }, [value, delay])
16
+
17
+ return debouncedValue
18
+ }
@@ -0,0 +1,45 @@
1
+ import arcjet, { shield, detectBot, slidingWindow } from "@arcjet/next";
2
+ import { env } from "@/data/env/server";
3
+
4
+ // initialize Arcjet with rules for security and rate limiting
5
+
6
+ const ARCJET_KEY = env.ARCJET_KEY!;
7
+ if (!ARCJET_KEY) {
8
+ throw new Error("ARCJET_KEY environment variable is required");
9
+ }
10
+
11
+ // Arcjet instance to be used in API routes and server-side functions
12
+ export const aj = arcjet({
13
+ key: ARCJET_KEY,
14
+ rules: [
15
+ shield({ mode: "LIVE" }), // the job of this rule is to block malicious requests like SQLi, XSS, etc. (mode: "LIVE" means that it will actually block them)
16
+ detectBot({
17
+ mode: "LIVE", // mode: "LIVE" means that it will actually enforce the rate limit
18
+ allow: ["CATEGORY:SEARCH_ENGINE", "CATEGORY:PREVIEW"], // allow good bots like Googlebot
19
+ }),
20
+ slidingWindow({
21
+ mode: "LIVE", // mode: "LIVE" means that it will actually enforce the rate limit
22
+ interval: "1m", // 1 minute
23
+ max: 50, // max 50 requests per interval
24
+ characteristics: ["userId"], // identify users by their userId
25
+ }), // this rule limits the number of requests to prevent abuse
26
+ ],
27
+ });
28
+
29
+ // u can create more Arcjet instances with different rules if needed
30
+ // example: a stricter instance for pfp uploads
31
+ export function ProfilePhotoLimiter() {
32
+ return arcjet({
33
+ key: ARCJET_KEY,
34
+ rules: [
35
+ shield({ mode: "LIVE" }),
36
+ slidingWindow({
37
+ mode: "LIVE",
38
+ interval: "7d",
39
+ max: 2,
40
+ characteristics: ["userId"],
41
+ }),
42
+ ],
43
+ });
44
+ }
45
+