added sorvor admin page
This commit is contained in:
@@ -4,6 +4,8 @@ Dockerfile
|
|||||||
minio
|
minio
|
||||||
node_modules
|
node_modules
|
||||||
sqlite.db
|
sqlite.db
|
||||||
|
sqlite.db-wal
|
||||||
|
sqlite.db-shm
|
||||||
.DS_Store
|
.DS_Store
|
||||||
dist
|
dist
|
||||||
.env
|
.env
|
||||||
|
|||||||
34
.gitea/workflows/backend-admin.yaml
Normal file
34
.gitea/workflows/backend-admin.yaml
Normal file
@@ -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 }}."
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useState } from "react";
|
||||||
import { JobCategory } from "./action";
|
import { JobCategory } from "./action";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export default async function Page() {
|
|||||||
.findMany({ with: { zones: true } })
|
.findMany({ with: { zones: true } })
|
||||||
.execute();
|
.execute();
|
||||||
const jobList = await db.query.group.findMany().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) })
|
.findMany({ columns: { id: true }, where: eq(user.verified, true) })
|
||||||
.execute()
|
.execute()
|
||||||
.then((v) => v.length);
|
.then((v) => v.length);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { user } from "@/src/schema";
|
|||||||
import { inArray } from "drizzle-orm";
|
import { inArray } from "drizzle-orm";
|
||||||
|
|
||||||
export async function saveUser(cids: string[]) {
|
export async function saveUser(cids: string[]) {
|
||||||
let rs = await db
|
const rs = await db
|
||||||
.update(user)
|
.update(user)
|
||||||
.set({ verified: true })
|
.set({ verified: true })
|
||||||
.where(inArray(user.cid, cids))
|
.where(inArray(user.cid, cids))
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import IdComponent from "./IdComponent";
|
|||||||
export default function Page() {
|
export default function Page() {
|
||||||
const [idList, setIdList] = useState<string[]>([]);
|
const [idList, setIdList] = useState<string[]>([]);
|
||||||
async function submit() {
|
async function submit() {
|
||||||
let rs = await saveUser(idList);
|
const rs = await saveUser(idList);
|
||||||
alert(`อัพเดทสำเร็จ ${rs.changes} คน`);
|
alert(`อัพเดทสำเร็จ ${rs.changes} คน`);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export default function Page() {
|
export default function Page() {
|
||||||
return <h1>Hello, Next.js!</h1>;
|
return <h1>Hello!</h1>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,11 @@ import { db } from "@/src/db";
|
|||||||
import LocationSelector from "../../components/LocationSelector";
|
import LocationSelector from "../../components/LocationSelector";
|
||||||
import LocationContextProvider from "@/components/locationContext";
|
import LocationContextProvider from "@/components/locationContext";
|
||||||
import TotalSetter from "./TotalSetter";
|
import TotalSetter from "./TotalSetter";
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { user } from "@/src/schema";
|
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const provinces = await db.query.province
|
const provinces = await db.query.province
|
||||||
.findMany({ with: { zones: true } })
|
.findMany({ with: { zones: true } })
|
||||||
.execute();
|
.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 (
|
return (
|
||||||
<LocationContextProvider>
|
<LocationContextProvider>
|
||||||
<LocationSelector provinces={provinces} />
|
<LocationSelector provinces={provinces} />
|
||||||
|
|||||||
17
compose.yml
17
compose.yml
@@ -13,6 +13,16 @@ services:
|
|||||||
- ./sqlite.db-wal:/app/sqlite.db-wal
|
- ./sqlite.db-wal:/app/sqlite.db-wal
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .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:
|
frontend:
|
||||||
restart: always
|
restart: always
|
||||||
image: gitea.cognizata.com/atapy/sorvor-front
|
image: gitea.cognizata.com/atapy/sorvor-front
|
||||||
@@ -50,13 +60,6 @@ services:
|
|||||||
- ./caddy/config/:/config
|
- ./caddy/config/:/config
|
||||||
- ./caddy/logs/:/var/log/caddy/
|
- ./caddy/logs/:/var/log/caddy/
|
||||||
|
|
||||||
sqliteweb:
|
|
||||||
image: tomdesinto/sqliteweb
|
|
||||||
ports:
|
|
||||||
- 8080:8080
|
|
||||||
volumes:
|
|
||||||
- ./sqlite.db:/db/sqlite.db
|
|
||||||
command: sqlite.db
|
|
||||||
crowdsec:
|
crowdsec:
|
||||||
image: crowdsecurity/crowdsec
|
image: crowdsecurity/crowdsec
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
68
next.Dockerfile
Normal file
68
next.Dockerfile
Normal file
@@ -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
|
||||||
3
next.config.js
Normal file
3
next.config.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
output: "standalone",
|
||||||
|
};
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"dev": "nodemon --exec ts-node --swc src/app.ts",
|
"dev": "nodemon --exec ts-node --swc src/app.ts",
|
||||||
"next-dev": "next dev",
|
"next-dev": "next dev",
|
||||||
|
"next-build": "next build",
|
||||||
"start": "node dist/src/app.js",
|
"start": "node dist/src/app.js",
|
||||||
"build": "swc src -d dist",
|
"build": "swc src -d dist",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
|
|||||||
@@ -6,17 +6,17 @@ import { z } from "zod";
|
|||||||
|
|
||||||
export const adminRoute = router({
|
export const adminRoute = router({
|
||||||
totalUser: publicProcedure.query(async () => {
|
totalUser: publicProcedure.query(async () => {
|
||||||
let rs = await db
|
const rs = await db
|
||||||
.select({ zone: user.zone, value: count(user.id) })
|
.select({ zone: user.zone, value: count(user.id) })
|
||||||
.from(user)
|
.from(user)
|
||||||
.groupBy(user.zone)
|
.groupBy(user.zone)
|
||||||
.execute();
|
.execute();
|
||||||
let zones = await db.query.zone
|
const zones = await db.query.zone
|
||||||
.findMany({ with: { province: true } })
|
.findMany({ with: { province: true } })
|
||||||
.execute();
|
.execute();
|
||||||
zones.sort((a, b) => a.province.id - b.province.id);
|
zones.sort((a, b) => a.province.id - b.province.id);
|
||||||
let summary = zones.map((z) => {
|
const summary = zones.map((z) => {
|
||||||
let num = rs.find((user) => user.zone == z.id)?.value ?? 0;
|
const num = rs.find((user) => user.zone == z.id)?.value ?? 0;
|
||||||
return {
|
return {
|
||||||
count: num,
|
count: num,
|
||||||
zone: z.name,
|
zone: z.name,
|
||||||
@@ -32,17 +32,17 @@ export const adminRoute = router({
|
|||||||
if (input.key !== "3RJjV7Hseo2xiJoVta/x2AJIGw5EK+a5nAwtnAjw37U=") {
|
if (input.key !== "3RJjV7Hseo2xiJoVta/x2AJIGw5EK+a5nAwtnAjw37U=") {
|
||||||
return "Invalid Key";
|
return "Invalid Key";
|
||||||
}
|
}
|
||||||
let thisUser = await db.query.user
|
const thisUser = await db.query.user
|
||||||
.findFirst({ where: eq(user.cid, input.cid) })
|
.findFirst({ where: eq(user.cid, input.cid) })
|
||||||
.execute();
|
.execute();
|
||||||
if (thisUser === undefined) {
|
if (thisUser === undefined) {
|
||||||
return "User not found";
|
return "User not found";
|
||||||
}
|
}
|
||||||
let uoresult = await db
|
const uoresult = await db
|
||||||
.delete(userOpinion)
|
.delete(userOpinion)
|
||||||
.where(eq(userOpinion.userId, thisUser.id))
|
.where(eq(userOpinion.userId, thisUser.id))
|
||||||
.execute();
|
.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 };
|
return { useropinion: uoresult, rs };
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user