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,7 @@
1
+ import { createAuthClient } from "better-auth/react"
2
+
3
+ // authentication client to manage user sessions and auth state
4
+ export const authClient = createAuthClient({
5
+ baseURL: "http://localhost:3000/api/auth",
6
+ });
7
+
@@ -0,0 +1,50 @@
1
+ import { betterAuth } from "better-auth";
2
+ import { drizzleAdapter } from "better-auth/adapters/drizzle";
3
+ import { db } from "@/drizzle/db";
4
+ import { nextCookies } from "better-auth/next-js";
5
+ import { headers } from "next/headers";
6
+
7
+ // authentication instance to be used in API routes and server-side functions
8
+ export const auth = betterAuth({
9
+ basePath: "/api/v1/auth",
10
+ database: drizzleAdapter(db, {
11
+ provider: "pg",
12
+ }),
13
+ emailAndPassword: {
14
+ enabled: true,
15
+ },
16
+ socialProviders: {
17
+ github: {
18
+ clientId: process.env.GITHUB_CLIENT_ID as string || "",
19
+ clientSecret: process.env.GITHUB_CLIENT_SECRET as string || "",
20
+ },
21
+ google: {
22
+ clientId: process.env.GOOGLE_CLIENT_ID as string || "",
23
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET as string || "",
24
+ },
25
+ },
26
+ session: {
27
+ cookieCache: {
28
+ enabled: true,
29
+ maxAge: 60 * 5, // 5 minutes
30
+ }
31
+ },
32
+ plugins: [nextCookies()],
33
+ });
34
+
35
+ // get full session data
36
+ export async function getSession() {
37
+ try {
38
+ const headersList = await headers();
39
+
40
+ // Try to get session using the built-in better-auth session handling
41
+ const session = await auth.api.getSession({
42
+ headers: headersList,
43
+ });
44
+
45
+ return session;
46
+ } catch (error) {
47
+ console.error('Error getting session:', error);
48
+ return null;
49
+ }
50
+ }
@@ -0,0 +1,29 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect } from "react";
4
+
5
+ // a custom hook to determine if the current device is mobile based on window width
6
+ export function useMobile() {
7
+ const [isMobile, setIsMobile] = useState(false);
8
+
9
+ useEffect(() => {
10
+
11
+ const checkIsMobile = () => {
12
+ setIsMobile(window.innerWidth < 768);
13
+ };
14
+
15
+
16
+ checkIsMobile();
17
+
18
+
19
+ window.addEventListener("resize", checkIsMobile);
20
+
21
+
22
+ return () => {
23
+ window.removeEventListener("resize", checkIsMobile);
24
+ };
25
+ }, []);
26
+
27
+ return isMobile;
28
+ }
29
+
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
@@ -0,0 +1,14 @@
1
+ import { NextResponse, type NextRequest } from "next/server"
2
+
3
+ // middleware function that runs on every request matching the specified matcher
4
+
5
+ export async function middleware(request: NextRequest) {
6
+ return NextResponse.next();
7
+ }
8
+
9
+ export const config = {
10
+ matcher: [
11
+ // match all paths except for static files and API routes
12
+ "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
13
+ ],
14
+ }
@@ -0,0 +1,13 @@
1
+ import { router } from "./trpc";
2
+ import { postsRouter } from "./routers/posts";
3
+ import { usersRouter } from "./routers/users";
4
+
5
+ // main application router combining all individual routers
6
+ export const appRouter = router({
7
+ // example routers
8
+ posts: postsRouter,
9
+ users: usersRouter,
10
+ });
11
+
12
+ // export the type definition of the app router
13
+ export type AppRouter = typeof appRouter;
@@ -0,0 +1,93 @@
1
+ import { z } from "zod";
2
+ import { router, protectedProcedure, publicProcedure } from "../trpc";
3
+ import { db } from "@/drizzle/db";
4
+ import { posts } from "@/drizzle/schema";
5
+ import { eq, desc } from "drizzle-orm";
6
+
7
+ // this is an example router file for managing blog posts
8
+
9
+ export const postsRouter = router({
10
+ // Get all posts
11
+ list: publicProcedure.query(async () => {
12
+ return await db.select().from(posts).orderBy(desc(posts.createdAt));
13
+ }),
14
+
15
+ // Get single post by ID
16
+ byId: publicProcedure
17
+ .input(z.object({ id: z.string().uuid() }))
18
+ .query(async ({ input }) => {
19
+ const result = await db
20
+ .select()
21
+ .from(posts)
22
+ .where(eq(posts.id, input.id));
23
+ return result[0] ?? null;
24
+ }),
25
+
26
+ // Create a new post (protected)
27
+ create: protectedProcedure
28
+ .input(
29
+ z.object({
30
+ title: z.string().min(1).max(255),
31
+ content: z.string().min(1),
32
+ }),
33
+ )
34
+ .mutation(async ({ ctx, input }) => {
35
+ const [post] = await db
36
+ .insert(posts)
37
+ .values({
38
+ title: input.title,
39
+ content: input.content,
40
+ userId: ctx.user.id,
41
+ })
42
+ .returning();
43
+ return post;
44
+ }),
45
+
46
+ // Update a post (protected, owner only)
47
+ update: protectedProcedure
48
+ .input(
49
+ z.object({
50
+ id: z.string().uuid(),
51
+ title: z.string().min(1).max(255).optional(),
52
+ content: z.string().min(1).optional(),
53
+ }),
54
+ )
55
+ .mutation(async ({ ctx, input }) => {
56
+ const [existing] = await db
57
+ .select()
58
+ .from(posts)
59
+ .where(eq(posts.id, input.id));
60
+
61
+ if (!existing || existing.userId !== ctx.user.id) {
62
+ throw new Error("Not authorized");
63
+ }
64
+
65
+ const [updated] = await db
66
+ .update(posts)
67
+ .set({
68
+ ...(input.title && { title: input.title }),
69
+ ...(input.content && { content: input.content }),
70
+ })
71
+ .where(eq(posts.id, input.id))
72
+ .returning();
73
+
74
+ return updated;
75
+ }),
76
+
77
+ // Delete a post (protected, owner only)
78
+ delete: protectedProcedure
79
+ .input(z.object({ id: z.string().uuid() }))
80
+ .mutation(async ({ ctx, input }) => {
81
+ const [existing] = await db
82
+ .select()
83
+ .from(posts)
84
+ .where(eq(posts.id, input.id));
85
+
86
+ if (!existing || existing.userId !== ctx.user.id) {
87
+ throw new Error("Not authorized");
88
+ }
89
+
90
+ await db.delete(posts).where(eq(posts.id, input.id));
91
+ return { success: true };
92
+ }),
93
+ });
@@ -0,0 +1,56 @@
1
+ import { z } from "zod";
2
+ import { router, publicProcedure, protectedProcedure } from "../trpc";
3
+ import { db } from "@/drizzle/db";
4
+ import { user } from "@/drizzle/schema/auth-schema";
5
+ import { eq } from "drizzle-orm";
6
+
7
+ // this is an example router file for managing users
8
+ // the ctx in protectedProcedure contains the authenticated user's session info
9
+ // and the input is actually a parameter passed to the procedure from the client-side
10
+
11
+ export const usersRouter = router({
12
+ // Get current user profile
13
+ me: protectedProcedure.query(async ({ ctx }) => {
14
+ const [profile] = await db
15
+ .select()
16
+ .from(user)
17
+ .where(eq(user.id, ctx.user.id));
18
+ return profile ?? null;
19
+ }),
20
+
21
+ // Get user by ID (public)
22
+ byId: publicProcedure
23
+ .input(z.object({ id: z.string() }))
24
+ .query(async ({ input }) => {
25
+ const [profile] = await db
26
+ .select({
27
+ id: user.id,
28
+ name: user.name,
29
+ image: user.image,
30
+ createdAt: user.createdAt,
31
+ })
32
+ .from(user)
33
+ .where(eq(user.id, input.id));
34
+ return profile ?? null;
35
+ }),
36
+
37
+ // Update current user profile
38
+ update: protectedProcedure
39
+ .input(
40
+ z.object({
41
+ name: z.string().min(1).max(100).optional(),
42
+ image: z.string().url().optional(),
43
+ }),
44
+ )
45
+ .mutation(async ({ ctx, input }) => {
46
+ const [updated] = await db
47
+ .update(user)
48
+ .set({
49
+ ...(input.name && { name: input.name }),
50
+ ...(input.image && { image: input.image }),
51
+ })
52
+ .where(eq(user.id, ctx.user.id))
53
+ .returning();
54
+ return updated;
55
+ }),
56
+ });
@@ -0,0 +1,38 @@
1
+ import { initTRPC, TRPCError } from '@trpc/server';
2
+ import { getSession } from '@/lib/auth';
3
+ import { aj } from '@/lib/arcjet';
4
+
5
+ // create the tRPC context which includes session data and request info
6
+ export const createTRPCContext = async (opts: { req: Request }) => {
7
+ const session = await getSession();
8
+ return {
9
+ session,
10
+ req: opts.req,
11
+ arcjet: aj,
12
+ };
13
+ };
14
+
15
+ // initialize tRPC with the created context
16
+ const t = initTRPC.context<typeof createTRPCContext>().create();
17
+
18
+ // create the main router and procedures for tRPC
19
+ export const router = t.router;
20
+
21
+ // public procedure that does not require authentication
22
+ export const publicProcedure = t.procedure;
23
+
24
+ // protected procedure that requires the user to be logged in
25
+ export const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
26
+ if (!ctx.session || !ctx.session.user) {
27
+ throw new TRPCError({
28
+ code: 'UNAUTHORIZED',
29
+ message: 'You must be logged in to access this feature.'
30
+ });
31
+ }
32
+
33
+ return next({
34
+ ctx: {
35
+ session: { ...ctx.session, user: ctx.session.user },
36
+ },
37
+ });
38
+ });
@@ -0,0 +1,10 @@
1
+ // Add ur custom types here
2
+
3
+ export interface Post {
4
+ id: string;
5
+ title: string;
6
+ content: string;
7
+ userId: string;
8
+ createdAt: Date;
9
+ updatedAt: Date;
10
+ }
@@ -0,0 +1,5 @@
1
+ import { createTRPCReact } from '@trpc/react-query';
2
+ import type { AppRouter } from '@/server';
3
+
4
+ // create a tRPC React hook for the AppRouter, the job of this file is to provide type-safe hooks for tRPC in React components
5
+ export const trpc = createTRPCReact<AppRouter>();
@@ -0,0 +1,10 @@
1
+ import {
2
+ generateUploadButton,
3
+ } from "@uploadthing/react";
4
+
5
+ import type { OurFileRouter } from "../app/api/v1/uploadthing/core";
6
+
7
+ // UploadButton component for uploading files using Uploadthing
8
+ export const UploadButton = generateUploadButton<OurFileRouter>({
9
+ url: "/api/v1/uploadthing",
10
+ });
@@ -0,0 +1,42 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": [
5
+ "dom",
6
+ "dom.iterable",
7
+ "esnext"
8
+ ],
9
+ "allowJs": true,
10
+ "skipLibCheck": true,
11
+ "strict": true,
12
+ "noEmit": true,
13
+ "esModuleInterop": true,
14
+ "module": "esnext",
15
+ "moduleResolution": "bundler",
16
+ "resolveJsonModule": true,
17
+ "isolatedModules": true,
18
+ "jsx": "react-jsx",
19
+ "incremental": true,
20
+ "plugins": [
21
+ {
22
+ "name": "next"
23
+ }
24
+ ],
25
+ "paths": {
26
+ "@/*": [
27
+ "./src/*"
28
+ ]
29
+ }
30
+ },
31
+ "include": [
32
+ "next-env.d.ts",
33
+ "**/*.ts",
34
+ "**/*.tsx",
35
+ ".next/types/**/*.ts",
36
+ "**/*.mts",
37
+ ".next/dev/types/**/*.ts"
38
+ ],
39
+ "exclude": [
40
+ "node_modules"
41
+ ]
42
+ }