From 23f37df21729e3d6fd1fb9e52171c62d30f40dff Mon Sep 17 00:00:00 2001 From: Thanu Poptiphueng Date: Mon, 20 May 2024 16:48:06 +0700 Subject: [PATCH] added sorvor admin page --- .dockerignore | 2 + .gitea/workflows/backend-admin.yaml | 34 +++++++++++++++ app/grouping/Grouping.tsx | 2 +- app/grouping/page.tsx | 2 +- app/inside/action.ts | 2 +- app/inside/page.tsx | 2 +- app/page.tsx | 2 +- app/total/page.tsx | 8 ---- compose.yml | 17 +++++--- next.Dockerfile | 68 +++++++++++++++++++++++++++++ next.config.js | 3 ++ package.json | 1 + src/adminRoute.ts | 14 +++--- tsconfig.json | 8 +++- 14 files changed, 137 insertions(+), 28 deletions(-) create mode 100644 .gitea/workflows/backend-admin.yaml create mode 100644 next.Dockerfile create mode 100644 next.config.js diff --git a/.dockerignore b/.dockerignore index 78b2e40..a6c4fd6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,6 +4,8 @@ Dockerfile minio node_modules sqlite.db +sqlite.db-wal +sqlite.db-shm .DS_Store dist .env diff --git a/.gitea/workflows/backend-admin.yaml b/.gitea/workflows/backend-admin.yaml new file mode 100644 index 0000000..30dd6f5 --- /dev/null +++ b/.gitea/workflows/backend-admin.yaml @@ -0,0 +1,34 @@ +name: backend-admin-action +run-name: ${{ gitea.actor }} is building docker image 🚀 +on: [push] + +jobs: + build-image: + runs-on: ubuntu-20.04 + steps: + - name: Check out repository code + uses: actions/checkout@v3 + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + registry: gitea.cognizata.com + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + file: next.Dockerfile + push: true + platforms: linux/amd64 + tags: gitea.cognizata.com/atapy/sorvor-admin:latest + target: app + - name: update server + uses: https://github.com/appleboy/ssh-action@v1.0.3 + with: + host: 46.102.174.196 + username: root + key: ${{ secrets.SORVOR_KEY }} + port: 22 + script: "docker compose pull && docker compose up -d" + - run: echo "🍏 This job's status is ${{ job.status }}." diff --git a/app/grouping/Grouping.tsx b/app/grouping/Grouping.tsx index b15d405..ba5f226 100644 --- a/app/grouping/Grouping.tsx +++ b/app/grouping/Grouping.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useState } from "react"; import { JobCategory } from "./action"; type Props = { diff --git a/app/grouping/page.tsx b/app/grouping/page.tsx index 84c9420..f51bce6 100644 --- a/app/grouping/page.tsx +++ b/app/grouping/page.tsx @@ -10,7 +10,7 @@ export default async function Page() { .findMany({ with: { zones: true } }) .execute(); const jobList = await db.query.group.findMany().execute(); - let r = await db.query.user + const r = await db.query.user .findMany({ columns: { id: true }, where: eq(user.verified, true) }) .execute() .then((v) => v.length); diff --git a/app/inside/action.ts b/app/inside/action.ts index f163eae..29f5244 100644 --- a/app/inside/action.ts +++ b/app/inside/action.ts @@ -4,7 +4,7 @@ import { user } from "@/src/schema"; import { inArray } from "drizzle-orm"; export async function saveUser(cids: string[]) { - let rs = await db + const rs = await db .update(user) .set({ verified: true }) .where(inArray(user.cid, cids)) diff --git a/app/inside/page.tsx b/app/inside/page.tsx index df0d01c..fdd7beb 100644 --- a/app/inside/page.tsx +++ b/app/inside/page.tsx @@ -6,7 +6,7 @@ import IdComponent from "./IdComponent"; export default function Page() { const [idList, setIdList] = useState([]); async function submit() { - let rs = await saveUser(idList); + const rs = await saveUser(idList); alert(`อัพเดทสำเร็จ ${rs.changes} คน`); } return ( diff --git a/app/page.tsx b/app/page.tsx index e85d509..0fc036d 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,3 +1,3 @@ export default function Page() { - return

Hello, Next.js!

; + return

Hello!

; } diff --git a/app/total/page.tsx b/app/total/page.tsx index d639c8b..b001190 100644 --- a/app/total/page.tsx +++ b/app/total/page.tsx @@ -2,19 +2,11 @@ import { db } from "@/src/db"; import LocationSelector from "../../components/LocationSelector"; import LocationContextProvider from "@/components/locationContext"; import TotalSetter from "./TotalSetter"; -import { eq } from "drizzle-orm"; -import { user } from "@/src/schema"; export default async function Page() { const provinces = await db.query.province .findMany({ with: { zones: true } }) .execute(); - const jobList = await db.query.group.findMany().execute(); - let r = await db.query.user - .findMany({ columns: { id: true }, where: eq(user.verified, true) }) - .execute() - .then((v) => v.length); - console.log(r); return ( diff --git a/compose.yml b/compose.yml index 1c3447e..88c05aa 100644 --- a/compose.yml +++ b/compose.yml @@ -13,6 +13,16 @@ services: - ./sqlite.db-wal:/app/sqlite.db-wal env_file: - .env + + admin: + build: + dockerfile: ./next.Dockerfile + ports: + - 3003:3000 + volumes: + - ./sqlite.db:/app/sqlite.db + - ./sqlite.db-shm:/app/sqlite.db-shm + - ./sqlite.db-wal:/app/sqlite.db-wal frontend: restart: always image: gitea.cognizata.com/atapy/sorvor-front @@ -50,13 +60,6 @@ services: - ./caddy/config/:/config - ./caddy/logs/:/var/log/caddy/ - sqliteweb: - image: tomdesinto/sqliteweb - ports: - - 8080:8080 - volumes: - - ./sqlite.db:/db/sqlite.db - command: sqlite.db crowdsec: image: crowdsecurity/crowdsec environment: diff --git a/next.Dockerfile b/next.Dockerfile new file mode 100644 index 0000000..5e36301 --- /dev/null +++ b/next.Dockerfile @@ -0,0 +1,68 @@ +FROM node:18-alpine AS base + +# Install dependencies only when needed +FROM base AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Install dependencies based on the preferred package manager +COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ +RUN \ + if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ + elif [ -f package-lock.json ]; then npm ci; \ + elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \ + else echo "Lockfile not found." && exit 1; \ + fi + + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Next.js collects completely anonymous telemetry data about general usage. +# Learn more here: https://nextjs.org/telemetry +# Uncomment the following line in case you want to disable telemetry during the build. +# ENV NEXT_TELEMETRY_DISABLED 1 + +RUN \ + if [ -f yarn.lock ]; then yarn run next-build; \ + elif [ -f package-lock.json ]; then npm run next-build; \ + elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm run next-build; \ + else echo "Lockfile not found." && exit 1; \ + fi + +# Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV production +# Uncomment the following line in case you want to disable telemetry during runtime. +# ENV NEXT_TELEMETRY_DISABLED 1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public + +# Set the correct permission for prerender cache +RUN mkdir .next +RUN chown nextjs:nodejs .next + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +COPY --from=builder --chown=nextjs:nodejs /app/drizzle ./drizzle + +USER nextjs + +EXPOSE 3000 + +ENV PORT 3000 + +# server.js is created by next build from the standalone output +# https://nextjs.org/docs/pages/api-reference/next-config-js/output +CMD HOSTNAME="0.0.0.0" node server.js diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000..5c2c44f --- /dev/null +++ b/next.config.js @@ -0,0 +1,3 @@ +module.exports = { + output: "standalone", +}; diff --git a/package.json b/package.json index 24e8bc2..cef596c 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "test": "echo \"Error: no test specified\" && exit 1", "dev": "nodemon --exec ts-node --swc src/app.ts", "next-dev": "next dev", + "next-build": "next build", "start": "node dist/src/app.js", "build": "swc src -d dist", "lint": "next lint", diff --git a/src/adminRoute.ts b/src/adminRoute.ts index 742b312..1f1137b 100644 --- a/src/adminRoute.ts +++ b/src/adminRoute.ts @@ -6,17 +6,17 @@ import { z } from "zod"; export const adminRoute = router({ totalUser: publicProcedure.query(async () => { - let rs = await db + const rs = await db .select({ zone: user.zone, value: count(user.id) }) .from(user) .groupBy(user.zone) .execute(); - let zones = await db.query.zone + const zones = await db.query.zone .findMany({ with: { province: true } }) .execute(); zones.sort((a, b) => a.province.id - b.province.id); - let summary = zones.map((z) => { - let num = rs.find((user) => user.zone == z.id)?.value ?? 0; + const summary = zones.map((z) => { + const num = rs.find((user) => user.zone == z.id)?.value ?? 0; return { count: num, zone: z.name, @@ -32,17 +32,17 @@ export const adminRoute = router({ if (input.key !== "3RJjV7Hseo2xiJoVta/x2AJIGw5EK+a5nAwtnAjw37U=") { return "Invalid Key"; } - let thisUser = await db.query.user + const thisUser = await db.query.user .findFirst({ where: eq(user.cid, input.cid) }) .execute(); if (thisUser === undefined) { return "User not found"; } - let uoresult = await db + const uoresult = await db .delete(userOpinion) .where(eq(userOpinion.userId, thisUser.id)) .execute(); - let rs = await db.delete(user).where(eq(user.cid, input.cid)).execute(); + const rs = await db.delete(user).where(eq(user.cid, input.cid)).execute(); return { useropinion: uoresult, rs }; }), }); diff --git a/tsconfig.json b/tsconfig.json index a48ede5..16a0ffd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -109,6 +109,12 @@ } ] }, - "include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"], + "include": [ + "next-env.d.ts", + ".next/types/**/*.ts", + "**/*.ts", + "**/*.tsx", + "next.config.js" + ], "exclude": ["node_modules"] }