student table optimization adn docker testing
This commit is contained in:
18
.dockerignore
Normal file
18
.dockerignore
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
README.md
|
||||||
|
.next
|
||||||
|
.git
|
||||||
|
.pnp
|
||||||
|
.pnp.js
|
||||||
|
.turbo
|
||||||
|
.vercel
|
||||||
|
.next/
|
||||||
|
out/
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
npm-debug.log*
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
29
Dockerfile
Normal file
29
Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
FROM node:22-alpine AS base
|
||||||
|
ENV PNPM_HOME="/pnpm"
|
||||||
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
|
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||||
|
|
||||||
|
FROM base AS builder
|
||||||
|
RUN apk update && apk add --no-cache libc6-compat
|
||||||
|
WORKDIR /app
|
||||||
|
RUN pnpm add -g turbo@^2
|
||||||
|
COPY . .
|
||||||
|
RUN turbo prune admin --docker
|
||||||
|
|
||||||
|
FROM base AS installer
|
||||||
|
RUN apk update && apk add --no-cache libc6-compat
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /app/out/json/ .
|
||||||
|
RUN pnpm fetch --frozen-lockfile
|
||||||
|
RUN pnpm install --offline --frozen-lockfile --prod=false
|
||||||
|
COPY --from=builder /app/out/full/ .
|
||||||
|
RUN pnpm turbo run build
|
||||||
|
|
||||||
|
FROM base AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs
|
||||||
|
USER nextjs
|
||||||
|
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
|
||||||
|
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
|
||||||
|
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
|
||||||
|
CMD node apps/web/server.js
|
||||||
29
apps/admin/Dockerfile
Normal file
29
apps/admin/Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
FROM node:22-alpine AS base
|
||||||
|
ENV PNPM_HOME="/pnpm"
|
||||||
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
|
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||||
|
|
||||||
|
FROM base AS builder
|
||||||
|
RUN apk update && apk add --no-cache libc6-compat
|
||||||
|
WORKDIR /app
|
||||||
|
RUN pnpm add -g turbo@^2
|
||||||
|
COPY . .
|
||||||
|
RUN turbo prune admin --docker
|
||||||
|
|
||||||
|
FROM base AS installer
|
||||||
|
RUN apk update && apk add --no-cache libc6-compat
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /app/out/json/ .
|
||||||
|
RUN pnpm fetch --frozen-lockfile
|
||||||
|
RUN pnpm install --offline --frozen-lockfile --prod=false
|
||||||
|
COPY --from=builder /app/out/full/ .
|
||||||
|
RUN pnpm turbo run build
|
||||||
|
|
||||||
|
FROM base AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs
|
||||||
|
USER nextjs
|
||||||
|
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
|
||||||
|
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
|
||||||
|
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
|
||||||
|
CMD node apps/web/server.js
|
||||||
@@ -1,19 +1,13 @@
|
|||||||
import Login from '@/components/login';
|
|
||||||
import Studs from '@/components/studs';
|
import Studs from '@/components/studs';
|
||||||
import { db, admins } from '@workspace/db';
|
import { db, students } from '@workspace/db';
|
||||||
import { auth, signIn, signOut } from '@/auth';
|
import { auth, signOut } from '@/auth';
|
||||||
|
|
||||||
async function getStudents() {
|
async function getStudents() {
|
||||||
'use server';
|
'use server';
|
||||||
const s = await db.select().from(admins);
|
const s = await db.select().from(students);
|
||||||
console.log(s);
|
console.log(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function logIn() {
|
|
||||||
'use server';
|
|
||||||
await signIn('google');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function logOut() {
|
async function logOut() {
|
||||||
'use server';
|
'use server';
|
||||||
await signOut();
|
await signOut();
|
||||||
@@ -25,7 +19,6 @@ export default async function Page() {
|
|||||||
<div className="flex items-center justify-center min-h-svh">
|
<div className="flex items-center justify-center min-h-svh">
|
||||||
<div className="flex flex-col items-center justify-center gap-4">
|
<div className="flex flex-col items-center justify-center gap-4">
|
||||||
<h1 className="text-2xl font-bold">Hello admin {session?.user?.name}</h1>
|
<h1 className="text-2xl font-bold">Hello admin {session?.user?.name}</h1>
|
||||||
{!session?.user && <Login action={logIn} />}
|
|
||||||
<Studs action={getStudents} logOut={logOut} />
|
<Studs action={getStudents} logOut={logOut} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,20 +6,39 @@ const studentSelectSchema = createSelectSchema(students);
|
|||||||
export type Student = z.infer<typeof studentSelectSchema>;
|
export type Student = z.infer<typeof studentSelectSchema>;
|
||||||
|
|
||||||
export const columns: ColumnDef<Student>[] = [
|
export const columns: ColumnDef<Student>[] = [
|
||||||
{
|
|
||||||
accessorKey: 'id',
|
|
||||||
header: 'ID',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
accessorKey: 'firstName',
|
accessorKey: 'firstName',
|
||||||
header: 'First Name',
|
header: 'First Name',
|
||||||
|
filterFn: 'includesString',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'lastName',
|
accessorKey: 'lastName',
|
||||||
header: 'Last Name',
|
header: 'Last Name',
|
||||||
|
filterFn: 'includesString',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'rollNumber',
|
||||||
|
header: 'Roll Number',
|
||||||
|
filterFn: 'includesString',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'email',
|
accessorKey: 'email',
|
||||||
header: 'Email',
|
header: 'Email',
|
||||||
|
filterFn: 'includesString',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'yearOfGraduation',
|
||||||
|
header: 'Year of Graduation',
|
||||||
|
filterFn: 'includesString',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'degree',
|
||||||
|
header: 'Degree',
|
||||||
|
filterFn: 'includesString',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'branch',
|
||||||
|
header: 'Branch',
|
||||||
|
filterFn: 'includesString',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
useReactTable,
|
||||||
|
} from '@tanstack/react-table';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
@@ -21,6 +27,7 @@ export function DataTable<TData, TValue>({ columns, data }: DataTableProps<TData
|
|||||||
data,
|
data,
|
||||||
columns,
|
columns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
30
apps/admin/app/(main)/students/error.tsx
Normal file
30
apps/admin/app/(main)/students/error.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { Button } from '@workspace/ui/components/button';
|
||||||
|
|
||||||
|
export default function Error({
|
||||||
|
error,
|
||||||
|
reset,
|
||||||
|
}: {
|
||||||
|
error: Error & { digest?: string };
|
||||||
|
reset: () => void;
|
||||||
|
}) {
|
||||||
|
useEffect(() => {
|
||||||
|
console.error('Students page error:', error);
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto py-10">
|
||||||
|
<div className="flex flex-col items-center justify-center space-y-4 p-8">
|
||||||
|
<h2 className="text-2xl font-semibold">Something went wrong!</h2>
|
||||||
|
<p className="text-muted-foreground text-center">
|
||||||
|
Failed to load students data. Please try again.
|
||||||
|
</p>
|
||||||
|
<Button onClick={reset} variant="outline">
|
||||||
|
Try again
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
63
apps/admin/app/(main)/students/loading.tsx
Normal file
63
apps/admin/app/(main)/students/loading.tsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { Skeleton } from '@workspace/ui/components/skeleton';
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from '@workspace/ui/components/table';
|
||||||
|
|
||||||
|
export default function Loading() {
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto py-10">
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Header skeleton */}
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h1 className="text-2xl font-semibold tracking-tight">Students</h1>
|
||||||
|
<Skeleton className="h-4 w-24" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Table skeleton */}
|
||||||
|
<div className="rounded-md border">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>
|
||||||
|
<Skeleton className="h-4 w-8" />
|
||||||
|
</TableHead>
|
||||||
|
<TableHead>
|
||||||
|
<Skeleton className="h-4 w-20" />
|
||||||
|
</TableHead>
|
||||||
|
<TableHead>
|
||||||
|
<Skeleton className="h-4 w-20" />
|
||||||
|
</TableHead>
|
||||||
|
<TableHead>
|
||||||
|
<Skeleton className="h-4 w-32" />
|
||||||
|
</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{Array.from({ length: 8 }).map((_, index) => (
|
||||||
|
<TableRow key={index}>
|
||||||
|
<TableCell>
|
||||||
|
<Skeleton className="h-4 w-8" />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Skeleton className="h-4 w-24" />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Skeleton className="h-4 w-24" />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Skeleton className="h-4 w-40" />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,16 +3,32 @@ import { DataTable } from './data-table';
|
|||||||
import { db, students } from '@workspace/db';
|
import { db, students } from '@workspace/db';
|
||||||
|
|
||||||
async function getData(): Promise<Student[]> {
|
async function getData(): Promise<Student[]> {
|
||||||
const data = db.select().from(students);
|
const data = await db.select().from(students);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function DemoPage() {
|
async function StudentsTable() {
|
||||||
const data = await getData();
|
const data = await getData();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto py-10">
|
<div className="container mx-auto py-10">
|
||||||
<DataTable columns={columns} data={data} />
|
<div className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h1 className="text-2xl font-semibold tracking-tight">Students</h1>
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
{data.length} {data.length === 1 ? 'student' : 'students'} total
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DataTable columns={columns} data={data} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function StudentsPage() {
|
||||||
|
return (
|
||||||
|
<StudentsTable />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dynamic = 'force-dynamic';
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const __dirname = path.resolve();
|
||||||
|
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
transpilePackages: ['@workspace/ui', '@workspace/db'],
|
transpilePackages: ['@workspace/ui', '@workspace/db'],
|
||||||
|
output: 'standalone',
|
||||||
|
outputFileTracingRoot: path.join(__dirname, '../../'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@tailwindcss/postcss": "^4.0.8",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"@workspace/db": "workspace:*",
|
"@workspace/db": "workspace:*",
|
||||||
"@workspace/ui": "workspace:*",
|
"@workspace/ui": "workspace:*",
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
"tailwindcss": "^4.0.8",
|
||||||
"zod": "^3.25.67"
|
"zod": "^3.25.67"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
38
apps/student/Dockerfile
Normal file
38
apps/student/Dockerfile
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
FROM node:22-alpine AS base
|
||||||
|
|
||||||
|
FROM base AS builder
|
||||||
|
RUN apk update
|
||||||
|
RUN apk add --no-cache libc6-compat
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN corepack enable pnpm && pnpm add -g turbo@^2
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN turbo prune student --docker
|
||||||
|
|
||||||
|
FROM base AS installer
|
||||||
|
RUN apk update
|
||||||
|
RUN apk add --no-cache libc6-compat
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=builder /app/out/json/ .
|
||||||
|
RUN yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
COPY --from=builder /app/out/full/ .
|
||||||
|
RUN pnpm turbo run build
|
||||||
|
|
||||||
|
FROM base AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Don't run production as root
|
||||||
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
|
RUN adduser --system --uid 1001 nextjs
|
||||||
|
USER nextjs
|
||||||
|
|
||||||
|
# Automatically leverage output traces to reduce image size
|
||||||
|
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||||
|
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
|
||||||
|
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
|
||||||
|
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
|
||||||
|
|
||||||
|
CMD node apps/web/server.js
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const __dirname = path.resolve();
|
||||||
|
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
transpilePackages: ['@workspace/ui', '@workspace/db'],
|
transpilePackages: ['@workspace/ui', '@workspace/db'],
|
||||||
|
output: 'standalone',
|
||||||
|
outputFileTracingRoot: path.join(__dirname, '../../'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"build": "turbo build",
|
"build": "turbo build",
|
||||||
"dev": "turbo dev",
|
"dev": "turbo dev",
|
||||||
"lint": "turbo lint",
|
"lint": "turbo lint",
|
||||||
|
"start": "turbo start",
|
||||||
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
|
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
|
||||||
"db:check": "pnpm --filter @workspace/db check",
|
"db:check": "pnpm --filter @workspace/db check",
|
||||||
"db:generate": "pnpm --filter @workspace/db generate",
|
"db:generate": "pnpm --filter @workspace/db generate",
|
||||||
|
|||||||
12
packages/ui/src/components/skeleton.tsx
Normal file
12
packages/ui/src/components/skeleton.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { cn } from '@workspace/ui/lib/utils';
|
||||||
|
|
||||||
|
function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn('animate-pulse rounded-md bg-muted', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Skeleton };
|
||||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -26,6 +26,9 @@ importers:
|
|||||||
|
|
||||||
apps/admin:
|
apps/admin:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@tailwindcss/postcss':
|
||||||
|
specifier: ^4.0.8
|
||||||
|
version: 4.1.11
|
||||||
'@tanstack/react-table':
|
'@tanstack/react-table':
|
||||||
specifier: ^8.21.3
|
specifier: ^8.21.3
|
||||||
version: 8.21.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
version: 8.21.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
@@ -56,6 +59,9 @@ importers:
|
|||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^19.1.0
|
specifier: ^19.1.0
|
||||||
version: 19.1.0(react@19.1.0)
|
version: 19.1.0(react@19.1.0)
|
||||||
|
tailwindcss:
|
||||||
|
specifier: ^4.0.8
|
||||||
|
version: 4.1.11
|
||||||
zod:
|
zod:
|
||||||
specifier: ^3.25.67
|
specifier: ^3.25.67
|
||||||
version: 3.25.67
|
version: 3.25.67
|
||||||
|
|||||||
@@ -16,6 +16,9 @@
|
|||||||
"dev": {
|
"dev": {
|
||||||
"cache": false,
|
"cache": false,
|
||||||
"persistent": true
|
"persistent": true
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"persistent": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user