Compare commits
32 Commits
d125687536
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
cba647ca27
|
|||
|
f6b18d3f82
|
|||
|
d660fd12fa
|
|||
|
38f769ef67
|
|||
|
b152175499
|
|||
|
e0655a56d7
|
|||
|
3d6de91dc8
|
|||
|
b28aae201a
|
|||
|
51f52c0a1e
|
|||
|
decd9a6945
|
|||
|
bb69b33481
|
|||
|
a7eb7d5037
|
|||
|
96129c1fe9
|
|||
|
b990b04902
|
|||
|
577d97cfcd
|
|||
|
23f37df217
|
|||
|
98a65043c9
|
|||
|
4934f799f5
|
|||
|
2a69ff0f3e
|
|||
|
64ea2e9524
|
|||
|
a29bac1eb2
|
|||
|
9a54c4712e
|
|||
|
ca130dc2b8
|
|||
|
5c739bcfa4
|
|||
|
7e8d8dc523
|
|||
|
60a7753247
|
|||
|
d54649893c
|
|||
|
0f6ec77dc0
|
|||
|
99f9531d32
|
|||
|
3c37fbf59b
|
|||
|
c1a019a461
|
|||
|
5c4abf24bb
|
@@ -4,6 +4,8 @@ Dockerfile
|
||||
minio
|
||||
node_modules
|
||||
sqlite.db
|
||||
sqlite.db-wal
|
||||
sqlite.db-shm
|
||||
.DS_Store
|
||||
dist
|
||||
.env
|
||||
|
||||
10
.eslintrc.json
Normal file
10
.eslintrc.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": [
|
||||
"next/core-web-vitals",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"prefer-const": "error"
|
||||
}
|
||||
}
|
||||
@@ -22,4 +22,13 @@ jobs:
|
||||
platforms: linux/amd64
|
||||
tags: gitea.cognizata.com/atapy/sorvor: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"
|
||||
command_timeout: 30h
|
||||
- run: echo "🍏 This job's status is ${{ job.status }}."
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -9,3 +9,8 @@ caddy/config/caddy
|
||||
testaction.secret
|
||||
caddy/logs
|
||||
.next
|
||||
sqlite.db-shm
|
||||
sqlite.db-wal
|
||||
user.json
|
||||
user-p.json
|
||||
user-c.json
|
||||
|
||||
@@ -24,3 +24,6 @@ tasks:
|
||||
- node -r @swc-node/register src/app.ts
|
||||
env:
|
||||
NODE_ENV: production
|
||||
update-server:
|
||||
cmds:
|
||||
- ssh -t sorvor-p "docker compose pull && docker compose up -d"
|
||||
|
||||
114
addMetadata.ts
114
addMetadata.ts
@@ -1,8 +1,29 @@
|
||||
import { db } from "./src/db";
|
||||
import { group, opinion, zone, province } from "./src/schema";
|
||||
import {
|
||||
group,
|
||||
opinion,
|
||||
zone,
|
||||
province,
|
||||
user,
|
||||
userToSelection,
|
||||
} from "./src/schema";
|
||||
import { Groups, Opinions, Provinces, Districts } from "./initialData";
|
||||
import { createBucket, createClient } from "./src/minio";
|
||||
import { Config } from "./src/config";
|
||||
import ud from "./user-c.json";
|
||||
|
||||
const user_data: UserData[] = ud;
|
||||
console.log(ud.length);
|
||||
|
||||
for (const user of user_data) {
|
||||
let thisName = `${user.first_name} ${user.last_name}`;
|
||||
if (
|
||||
user_data.filter((u) => `${u.first_name} ${u.last_name}` == thisName)
|
||||
.length != 1
|
||||
) {
|
||||
console.log(`duplicate name ${user}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
@@ -35,9 +56,100 @@ async function main() {
|
||||
province: district.province_code,
|
||||
}));
|
||||
await db.insert(zone).values(zoneValues);
|
||||
await create_user();
|
||||
await create_relation();
|
||||
|
||||
const allUser = await db.query.user.findMany({
|
||||
with: {
|
||||
userToSelection: { with: { selection: true } },
|
||||
},
|
||||
});
|
||||
// for (const u of allUser) {
|
||||
// console.log(
|
||||
// u.firstName,
|
||||
// u.lastName,
|
||||
// u.userToSelection.map(
|
||||
// (t) => `${t.selection?.firstName} ${t.selection?.lastName}`,
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
console.log("Done");
|
||||
}
|
||||
|
||||
type UserData = {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
job_code: number;
|
||||
selection: string[];
|
||||
province: string;
|
||||
rank: number;
|
||||
};
|
||||
|
||||
async function create_user() {
|
||||
const provinces = await db.query.province.findMany({});
|
||||
const district = await db.query.zone.findMany({});
|
||||
for (const newUser of user_data) {
|
||||
let isSelectionFound = true;
|
||||
for (const selection of newUser.selection) {
|
||||
const isFound = user_data.findIndex(
|
||||
(p) => `${p.first_name} ${p.last_name}` == selection,
|
||||
);
|
||||
if (isFound == -1) {
|
||||
isSelectionFound = false;
|
||||
}
|
||||
}
|
||||
if (!isSelectionFound) {
|
||||
console.log(newUser.province, newUser, isSelectionFound);
|
||||
} else {
|
||||
await db.insert(user).values({
|
||||
group: newUser.job_code,
|
||||
firstName: newUser.first_name,
|
||||
lastName: newUser.last_name,
|
||||
title: "",
|
||||
cid: "0000000000000",
|
||||
phone: "0000000000",
|
||||
age: 0,
|
||||
job: "",
|
||||
education: "",
|
||||
zone: 1001,
|
||||
rank: newUser.rank,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function create_relation() {
|
||||
const allUser = await db.query.user.findMany({
|
||||
with: {
|
||||
zone: { with: { province: true } },
|
||||
},
|
||||
});
|
||||
for (const u of allUser) {
|
||||
let thisUsers = user_data.filter(
|
||||
(raw) => raw.first_name == u.firstName && raw.last_name == u.lastName,
|
||||
);
|
||||
if (thisUsers.length !== 1) {
|
||||
console.log("duplicated users", thisUsers);
|
||||
return;
|
||||
}
|
||||
const rawUser = thisUsers[0];
|
||||
const selections = allUser.filter(
|
||||
(target) =>
|
||||
rawUser.selection.includes(`${target.firstName} ${target.lastName}`) &&
|
||||
target.zone.province.id == u.zone.province.id,
|
||||
);
|
||||
if (selections.length == 0) {
|
||||
console.log("selection not found", selections);
|
||||
return;
|
||||
}
|
||||
for (const selection of selections) {
|
||||
await db
|
||||
.insert(userToSelection)
|
||||
.values({ userId: u.id, targetId: selection.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function setupBucket() {
|
||||
const BucketPolicy = {
|
||||
Version: "2012-10-17",
|
||||
|
||||
@@ -1,28 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { LocationContext } from "@/components/locationContenxt";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { LocationContext } from "@/components/locationContext";
|
||||
import { useContext, useState } from "react";
|
||||
import Grouping from "./Grouping";
|
||||
import { unique } from "drizzle-orm/mysql-core";
|
||||
import { Group, JobCategory, updateGroups } from "./action";
|
||||
|
||||
type Props = {
|
||||
allJobs: JobCategory[];
|
||||
};
|
||||
|
||||
export type JobCategory = {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type Group = {
|
||||
id: number;
|
||||
jobs: number[];
|
||||
};
|
||||
export default function GroupCreator({ allJobs }: Props) {
|
||||
let locationContext = useContext(LocationContext);
|
||||
let [usedJobs, setUsedJobs] = useState<number[]>([]);
|
||||
let [groups, setGroup] = useState<Group[]>(
|
||||
[...Array(4).keys()].map((i) => ({ id: i + 1, jobs: [] }))
|
||||
const locationContext = useContext(LocationContext);
|
||||
const [usedJobs, setUsedJobs] = useState<number[]>([]);
|
||||
const [groups, setGroup] = useState<Group[]>(
|
||||
[...Array(4).keys()].map((i) => ({ id: i + 1, jobs: [] })),
|
||||
);
|
||||
function useJob(id: number) {
|
||||
setUsedJobs((u) => [...u, id]);
|
||||
@@ -37,13 +28,26 @@ export default function GroupCreator({ allJobs }: Props) {
|
||||
groups.map((g) => {
|
||||
if (g.id != id) return g;
|
||||
else return { id, jobs };
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log(groups);
|
||||
}, [groups]);
|
||||
async function submit() {
|
||||
if (
|
||||
locationContext?.zone[0] == undefined ||
|
||||
locationContext?.province[0] == undefined
|
||||
) {
|
||||
alert("ยังไม่ได้เลือกพื้นที่");
|
||||
return;
|
||||
}
|
||||
await updateGroups(
|
||||
locationContext.province[0],
|
||||
locationContext.zone[0],
|
||||
groups,
|
||||
);
|
||||
|
||||
alert("อัพเดทสำเร็จ");
|
||||
}
|
||||
|
||||
if (
|
||||
locationContext?.zone[0] == undefined ||
|
||||
@@ -66,7 +70,9 @@ export default function GroupCreator({ allJobs }: Props) {
|
||||
))}
|
||||
|
||||
<div className="flex justify-center">
|
||||
<button className="bg-green-300 rounded-md p-2">ยืนยัน</button>
|
||||
<button className="rounded-md bg-green-300 p-2" onClick={submit}>
|
||||
ยืนยัน
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { JobCategory } from "./GroupCreator";
|
||||
import { useState } from "react";
|
||||
import { JobCategory } from "./action";
|
||||
|
||||
type Props = {
|
||||
availableJobs: JobCategory[];
|
||||
@@ -14,23 +14,24 @@ export default function Grouping({
|
||||
removeJob,
|
||||
updateGroup,
|
||||
}: Props) {
|
||||
let [selectedJob, setSelectedJob] = useState<JobCategory[]>([]);
|
||||
const [selectedJob, setSelectedJob] = useState<JobCategory[]>([]);
|
||||
function addJob(id: string) {
|
||||
let _id = parseInt(id);
|
||||
let job = availableJobs.find((j) => j.id == _id);
|
||||
const _id = parseInt(id);
|
||||
const job = availableJobs.find((j) => j.id == _id);
|
||||
if (job == undefined) return;
|
||||
setSelectedJob((old) => [...old, job]);
|
||||
const newSelectedJob = [...selectedJob, job];
|
||||
setSelectedJob(newSelectedJob);
|
||||
selectJob(_id);
|
||||
updateGroup(newSelectedJob.map((j) => j.id));
|
||||
}
|
||||
function removeJobFromGroup(id: number) {
|
||||
setSelectedJob((old) => old.filter((j) => j.id != id));
|
||||
const newSelectedJob = selectedJob.filter((j) => j.id != id);
|
||||
setSelectedJob(newSelectedJob);
|
||||
removeJob(id);
|
||||
updateGroup(newSelectedJob.map((j) => j.id));
|
||||
}
|
||||
useEffect(() => {
|
||||
updateGroup(selectedJob.map((j) => j.id));
|
||||
}, [selectedJob]);
|
||||
return (
|
||||
<div className="flex flex-col gap-2 m-2 p-2 border-black rounded-md shadow-md border w-full">
|
||||
<div className="m-2 flex w-full flex-col gap-2 rounded-md border border-black p-2 shadow-md">
|
||||
{selectedJob.map((j) => (
|
||||
<div className="flex justify-between gap-2 p-2" key={j.id}>
|
||||
<p>{j.name}</p>
|
||||
|
||||
20
app/grouping/action.ts
Normal file
20
app/grouping/action.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
"use server";
|
||||
|
||||
export type JobCategory = {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type Group = {
|
||||
id: number;
|
||||
jobs: number[];
|
||||
};
|
||||
|
||||
//TODO: submit group
|
||||
export async function updateGroups(
|
||||
province: number,
|
||||
zone: number,
|
||||
groups: Group[],
|
||||
) {
|
||||
console.log({ province, zone, groups });
|
||||
}
|
||||
@@ -1,13 +1,20 @@
|
||||
import { db } from "@/src/db";
|
||||
import LocationSelector from "../../components/LocationSelector";
|
||||
import LocationContextProvider from "@/components/locationContenxt";
|
||||
import LocationContextProvider from "@/components/locationContext";
|
||||
import GroupCreator from "./GroupCreator";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { user } from "@/src/schema";
|
||||
|
||||
export default async function Page() {
|
||||
let provinces = await db.query.province
|
||||
const provinces = await db.query.province
|
||||
.findMany({ with: { zones: true } })
|
||||
.execute();
|
||||
let jobList = await db.query.group.findMany().execute();
|
||||
const jobList = await db.query.group.findMany().execute();
|
||||
const r = await db.query.user
|
||||
.findMany({ columns: { id: true }, where: eq(user.verified, true) })
|
||||
.execute()
|
||||
.then((v) => v.length);
|
||||
console.log(r);
|
||||
return (
|
||||
<LocationContextProvider>
|
||||
<LocationSelector provinces={provinces} />
|
||||
|
||||
@@ -5,16 +5,17 @@ type Props = {
|
||||
};
|
||||
|
||||
export default function IdComponent({ updateIdList }: Props) {
|
||||
let [idSet, setIdSet] = useState<Set<string>>(new Set());
|
||||
let onValidId = (id: string) => {
|
||||
setIdSet((prev) => new Set(prev.add(id)));
|
||||
const [idSet, setIdSet] = useState<Set<string>>(new Set());
|
||||
const onValidId = (id: string) => {
|
||||
const newSet = new Set(idSet.add(id));
|
||||
setIdSet(newSet);
|
||||
updateIdList([...newSet]);
|
||||
};
|
||||
let removeCid = (id: string) => {
|
||||
setIdSet((prev) => new Set([...prev].filter((x) => x !== id)));
|
||||
const removeCid = (id: string) => {
|
||||
const newSet = new Set([...idSet].filter((x) => x !== id));
|
||||
setIdSet(newSet);
|
||||
updateIdList([...newSet]);
|
||||
};
|
||||
useEffect(() => {
|
||||
updateIdList([...idSet]);
|
||||
}, [idSet]);
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
<div className="flex flex-col gap-2">
|
||||
@@ -37,7 +38,7 @@ function FixedId({ cid, removeCid }: FixedIdProps) {
|
||||
<div className="flex gap-2">
|
||||
<input type="text" className="border-2" disabled value={cid} />
|
||||
<button
|
||||
className="bg-red-300 p-2 rounded-md"
|
||||
className="rounded-md bg-red-300 p-2"
|
||||
onClick={() => removeCid(cid)}
|
||||
>
|
||||
ลบ
|
||||
@@ -51,17 +52,17 @@ type SingleIdProps = {
|
||||
};
|
||||
|
||||
function SingleIdComponent({ onValidId }: SingleIdProps) {
|
||||
let [isValid, setIsValid] = useState(false);
|
||||
let [cid, setCid] = useState("");
|
||||
const [isValid, setIsValid] = useState(false);
|
||||
const [cid, setCid] = useState("");
|
||||
useEffect(() => {
|
||||
let isValidId = isValidThaiID(cid);
|
||||
const isValidId = isValidThaiID(cid);
|
||||
setIsValid(isValidId);
|
||||
if (isValidId) {
|
||||
onValidId(cid);
|
||||
|
||||
setCid("");
|
||||
}
|
||||
}, [cid]);
|
||||
}, [cid, onValidId]);
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
|
||||
13
app/inside/action.ts
Normal file
13
app/inside/action.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
"use server";
|
||||
import { db } from "@/src/db";
|
||||
import { user } from "@/src/schema";
|
||||
import { inArray } from "drizzle-orm";
|
||||
|
||||
export async function saveUser(cids: string[]) {
|
||||
const rs = await db
|
||||
.update(user)
|
||||
.set({ verified: true })
|
||||
.where(inArray(user.cid, cids))
|
||||
.execute();
|
||||
return rs;
|
||||
}
|
||||
@@ -1,18 +1,20 @@
|
||||
"use client";
|
||||
import { useState } from "react";
|
||||
import { saveUser } from "./action";
|
||||
import IdComponent from "./IdComponent";
|
||||
|
||||
export default function Page() {
|
||||
let [idList, setIdList] = useState<string[]>([]);
|
||||
function submit() {
|
||||
console.log(idList);
|
||||
const [idList, setIdList] = useState<string[]>([]);
|
||||
async function submit() {
|
||||
const rs = await saveUser(idList);
|
||||
alert(`อัพเดทสำเร็จ ${rs.changes} คน`);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<IdComponent updateIdList={(cids) => setIdList(cids)} />
|
||||
<p className="flex justify-center gap-4 mt-2 items-center">
|
||||
<p className="mt-2 flex items-center justify-center gap-4">
|
||||
Total: {idList.length}{" "}
|
||||
<button className="bg-green-200 p-2 rounded-md" onClick={submit}>
|
||||
<button className="rounded-md bg-green-200 p-2" onClick={submit}>
|
||||
Submit
|
||||
</button>
|
||||
</p>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export default function Page() {
|
||||
return <h1>Hello, Next.js!</h1>;
|
||||
return <h1>Hello!</h1>;
|
||||
}
|
||||
|
||||
47
app/total/TotalSetter.tsx
Normal file
47
app/total/TotalSetter.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
"use client";
|
||||
|
||||
import { LocationContext } from "@/components/locationContext";
|
||||
import { useContext, useState } from "react";
|
||||
import { updateZone } from "./action";
|
||||
|
||||
export default function TotalSetter() {
|
||||
const locationContext = useContext(LocationContext);
|
||||
const [total, setTotal] = useState<number>(0);
|
||||
async function submit() {
|
||||
if (
|
||||
locationContext?.zone[0] == undefined ||
|
||||
locationContext?.province[0] == undefined
|
||||
) {
|
||||
alert("ยังไม่ได้เลือกพื้นที่");
|
||||
return;
|
||||
}
|
||||
await updateZone(
|
||||
locationContext.province[0],
|
||||
locationContext.zone[0],
|
||||
total,
|
||||
);
|
||||
alert(`อัพเดทสำเร็จ`);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div className="flex">
|
||||
จำนวนทั้งหมด:{" "}
|
||||
<input
|
||||
value={total}
|
||||
className="rounded-md border-2"
|
||||
type="number"
|
||||
onChange={(e) =>
|
||||
setTotal(
|
||||
e.currentTarget.value !== ""
|
||||
? parseInt(e.currentTarget.value)
|
||||
: 0,
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<button className="rounded-md bg-green-300 p-2" onClick={submit}>
|
||||
ยืนยัน
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
20
app/total/action.ts
Normal file
20
app/total/action.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
"use server";
|
||||
|
||||
export type JobCategory = {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type Group = {
|
||||
id: number;
|
||||
jobs: number[];
|
||||
};
|
||||
|
||||
//TODO: submit group
|
||||
export async function updateZone(
|
||||
province: number,
|
||||
zone: number,
|
||||
total: number,
|
||||
) {
|
||||
console.log({ province, zone, total });
|
||||
}
|
||||
16
app/total/page.tsx
Normal file
16
app/total/page.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { db } from "@/src/db";
|
||||
import LocationSelector from "../../components/LocationSelector";
|
||||
import LocationContextProvider from "@/components/locationContext";
|
||||
import TotalSetter from "./TotalSetter";
|
||||
|
||||
export default async function Page() {
|
||||
const provinces = await db.query.province
|
||||
.findMany({ with: { zones: true } })
|
||||
.execute();
|
||||
return (
|
||||
<LocationContextProvider>
|
||||
<LocationSelector provinces={provinces} />
|
||||
<TotalSetter />
|
||||
</LocationContextProvider>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { LocationContext } from "../locationContenxt";
|
||||
import { LocationContext } from "../locationContext";
|
||||
|
||||
type Props = {
|
||||
provinces: Province[];
|
||||
@@ -17,20 +17,20 @@ type Zone = {
|
||||
province: number;
|
||||
};
|
||||
export default function LocationSelector({ provinces }: Props) {
|
||||
let [provinceId, setProvinceId] = useState<number | undefined>(undefined);
|
||||
let [amphurList, setAmphurList] = useState<Zone[] | undefined>(undefined);
|
||||
let [amphurId, setAmphurId] = useState<number | undefined>(undefined);
|
||||
const [provinceId, setProvinceId] = useState<number | undefined>(undefined);
|
||||
const [amphurList, setAmphurList] = useState<Zone[] | undefined>(undefined);
|
||||
const [amphurId, setAmphurId] = useState<number | undefined>(undefined);
|
||||
const locationContext = useContext(LocationContext);
|
||||
function setProvince(_id: string) {
|
||||
let id = parseInt(_id);
|
||||
const id = parseInt(_id);
|
||||
setProvinceId(id);
|
||||
let province = provinces.find((p) => p.id == id);
|
||||
const province = provinces.find((p) => p.id == id);
|
||||
if (province == undefined) return;
|
||||
setAmphurList(province.zones);
|
||||
setAmphurId(undefined);
|
||||
}
|
||||
function setAmphur(_id: string) {
|
||||
let id = parseInt(_id);
|
||||
const id = parseInt(_id);
|
||||
setAmphurId(id);
|
||||
}
|
||||
useEffect(() => {
|
||||
@@ -43,7 +43,7 @@ export default function LocationSelector({ provinces }: Props) {
|
||||
|
||||
locationContext.zone[1](amphurId);
|
||||
locationContext.province[1](provinceId);
|
||||
}, [amphurId]);
|
||||
}, [amphurId, locationContext, provinceId]);
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex gap-2">
|
||||
|
||||
@@ -13,7 +13,7 @@ type LocationContextType = {
|
||||
};
|
||||
|
||||
export const LocationContext = createContext<LocationContextType | undefined>(
|
||||
undefined
|
||||
undefined,
|
||||
);
|
||||
|
||||
export default function LocationContextProvider({
|
||||
@@ -21,8 +21,8 @@ export default function LocationContextProvider({
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
}) {
|
||||
let zone = useState<number | undefined>(undefined);
|
||||
let province = useState<number | undefined>(undefined);
|
||||
const zone = useState<number | undefined>(undefined);
|
||||
const province = useState<number | undefined>(undefined);
|
||||
|
||||
return (
|
||||
<LocationContext.Provider value={{ zone, province }}>
|
||||
19
compose.yml
19
compose.yml
@@ -9,8 +9,20 @@ services:
|
||||
- 3001:3001
|
||||
volumes:
|
||||
- ./sqlite.db:/app/sqlite.db
|
||||
- ./sqlite.db-shm:/app/sqlite.db-shm
|
||||
- ./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
|
||||
@@ -48,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:
|
||||
|
||||
@@ -3,16 +3,17 @@ CREATE TABLE `groups` (
|
||||
`name` text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `image_to_user` (
|
||||
`user_id` integer PRIMARY KEY NOT NULL,
|
||||
`image_name` text NOT NULL,
|
||||
`created_on` integer DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `opinions` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`type` text DEFAULT '3Choice' NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `phone_tokens` (
|
||||
`phone` text PRIMARY KEY NOT NULL,
|
||||
`token` text NOT NULL,
|
||||
`created_on` integer DEFAULT CURRENT_TIMESTAMP
|
||||
`type` text DEFAULT '5Choice' NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `provinces` (
|
||||
@@ -24,6 +25,7 @@ CREATE TABLE `users` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`firstName` text NOT NULL,
|
||||
`lastName` text NOT NULL,
|
||||
`registerno` text,
|
||||
`title` text NOT NULL,
|
||||
`cid` text(13) NOT NULL,
|
||||
`age` integer NOT NULL,
|
||||
@@ -33,6 +35,7 @@ CREATE TABLE `users` (
|
||||
`twitter` text,
|
||||
`tiktok` text,
|
||||
`other_social` text,
|
||||
`image` text,
|
||||
`email` text,
|
||||
`job` text NOT NULL,
|
||||
`education` text NOT NULL,
|
||||
@@ -40,6 +43,7 @@ CREATE TABLE `users` (
|
||||
`reason` text,
|
||||
`group_id` integer NOT NULL,
|
||||
`zone_id` integer NOT NULL,
|
||||
`verified` integer DEFAULT false NOT NULL,
|
||||
FOREIGN KEY (`group_id`) REFERENCES `groups`(`id`) ON UPDATE no action ON DELETE no action,
|
||||
FOREIGN KEY (`zone_id`) REFERENCES `zones`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
||||
@@ -47,23 +51,30 @@ CREATE TABLE `users` (
|
||||
CREATE TABLE `user_opinions` (
|
||||
`user_id` integer NOT NULL,
|
||||
`opinion_id` integer NOT NULL,
|
||||
`choice` text DEFAULT 'deciding',
|
||||
`choice` text DEFAULT 'ignore',
|
||||
PRIMARY KEY(`opinion_id`, `user_id`),
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action,
|
||||
FOREIGN KEY (`opinion_id`) REFERENCES `opinions`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `user_to_selection` (
|
||||
`user_id` integer,
|
||||
`target_id` integer,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade,
|
||||
FOREIGN KEY (`target_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `zones` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`province_id` integer NOT NULL,
|
||||
`total` integer DEFAULT 0 NOT NULL,
|
||||
FOREIGN KEY (`province_id`) REFERENCES `provinces`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `groups_name_unique` ON `groups` (`name`);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `opinions_name_unique` ON `opinions` (`name`);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `provinces_name_unique` ON `provinces` (`name`);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `users_cid_unique` ON `users` (`cid`);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `users_phone_unique` ON `users` (`phone`);--> statement-breakpoint
|
||||
CREATE INDEX `phone_idx` ON `users` (`phone`);--> statement-breakpoint
|
||||
CREATE INDEX `image_idx` ON `users` (`image`);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `zones_name_province_id_unique` ON `zones` (`name`,`province_id`);
|
||||
@@ -1,10 +0,0 @@
|
||||
CREATE TABLE `image_to_user` (
|
||||
`user_id` integer PRIMARY KEY NOT NULL,
|
||||
`image_name` text NOT NULL,
|
||||
`created_on` integer DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
||||
--> statement-breakpoint
|
||||
DROP TABLE `phone_tokens`;--> statement-breakpoint
|
||||
ALTER TABLE users ADD `image` text;--> statement-breakpoint
|
||||
CREATE INDEX `image_idx` ON `users` (`image`);
|
||||
1
drizzle/0001_last_hiroim.sql
Normal file
1
drizzle/0001_last_hiroim.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE users ADD `rank` integer DEFAULT 9999;
|
||||
@@ -1 +0,0 @@
|
||||
ALTER TABLE users ADD `verified` integer DEFAULT false NOT NULL;
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "sqlite",
|
||||
"id": "58f80520-7300-4bc4-943d-87568666e42d",
|
||||
"id": "b46f7266-bc92-4a23-b12d-d162624db70a",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"groups": {
|
||||
@@ -35,6 +35,51 @@
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"image_to_user": {
|
||||
"name": "image_to_user",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"image_name": {
|
||||
"name": "image_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_on": {
|
||||
"name": "created_on",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "CURRENT_TIMESTAMP"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"image_to_user_user_id_users_id_fk": {
|
||||
"name": "image_to_user_user_id_users_id_fk",
|
||||
"tableFrom": "image_to_user",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"opinions": {
|
||||
"name": "opinions",
|
||||
"columns": {
|
||||
@@ -58,7 +103,7 @@
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'3Choice'"
|
||||
"default": "'5Choice'"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
@@ -74,37 +119,6 @@
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"phone_tokens": {
|
||||
"name": "phone_tokens",
|
||||
"columns": {
|
||||
"phone": {
|
||||
"name": "phone",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"token": {
|
||||
"name": "token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_on": {
|
||||
"name": "created_on",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "CURRENT_TIMESTAMP"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"provinces": {
|
||||
"name": "provinces",
|
||||
"columns": {
|
||||
@@ -160,6 +174,13 @@
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"registerno": {
|
||||
"name": "registerno",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
@@ -223,6 +244,13 @@
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"image": {
|
||||
"name": "image",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
@@ -271,29 +299,30 @@
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"verified": {
|
||||
"name": "verified",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"users_cid_unique": {
|
||||
"name": "users_cid_unique",
|
||||
"columns": [
|
||||
"cid"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"users_phone_unique": {
|
||||
"name": "users_phone_unique",
|
||||
"columns": [
|
||||
"phone"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"phone_idx": {
|
||||
"name": "phone_idx",
|
||||
"columns": [
|
||||
"phone"
|
||||
],
|
||||
"isUnique": false
|
||||
},
|
||||
"image_idx": {
|
||||
"name": "image_idx",
|
||||
"columns": [
|
||||
"image"
|
||||
],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
@@ -350,7 +379,7 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "'deciding'"
|
||||
"default": "'ignore'"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
@@ -393,6 +422,56 @@
|
||||
},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"user_to_selection": {
|
||||
"name": "user_to_selection",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"target_id": {
|
||||
"name": "target_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_to_selection_user_id_users_id_fk": {
|
||||
"name": "user_to_selection_user_id_users_id_fk",
|
||||
"tableFrom": "user_to_selection",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"user_to_selection_target_id_users_id_fk": {
|
||||
"name": "user_to_selection_target_id_users_id_fk",
|
||||
"tableFrom": "user_to_selection",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"target_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"zones": {
|
||||
"name": "zones",
|
||||
"columns": {
|
||||
@@ -416,6 +495,14 @@
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"total": {
|
||||
"name": "total",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "sqlite",
|
||||
"id": "2cf2acb7-cc98-4f28-8ead-5916b87b7683",
|
||||
"prevId": "58f80520-7300-4bc4-943d-87568666e42d",
|
||||
"id": "d7a1e002-36b8-4c25-b935-c826f0a2aaab",
|
||||
"prevId": "b46f7266-bc92-4a23-b12d-d162624db70a",
|
||||
"tables": {
|
||||
"groups": {
|
||||
"name": "groups",
|
||||
@@ -103,7 +103,7 @@
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'3Choice'"
|
||||
"default": "'5Choice'"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
@@ -174,6 +174,13 @@
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"registerno": {
|
||||
"name": "registerno",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
@@ -292,23 +299,25 @@
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"verified": {
|
||||
"name": "verified",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
},
|
||||
"rank": {
|
||||
"name": "rank",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 9999
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"users_cid_unique": {
|
||||
"name": "users_cid_unique",
|
||||
"columns": [
|
||||
"cid"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"users_phone_unique": {
|
||||
"name": "users_phone_unique",
|
||||
"columns": [
|
||||
"phone"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"phone_idx": {
|
||||
"name": "phone_idx",
|
||||
"columns": [
|
||||
@@ -378,7 +387,7 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "'deciding'"
|
||||
"default": "'ignore'"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
@@ -421,6 +430,56 @@
|
||||
},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"user_to_selection": {
|
||||
"name": "user_to_selection",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"target_id": {
|
||||
"name": "target_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_to_selection_user_id_users_id_fk": {
|
||||
"name": "user_to_selection_user_id_users_id_fk",
|
||||
"tableFrom": "user_to_selection",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"user_to_selection_target_id_users_id_fk": {
|
||||
"name": "user_to_selection_target_id_users_id_fk",
|
||||
"tableFrom": "user_to_selection",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"target_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"zones": {
|
||||
"name": "zones",
|
||||
"columns": {
|
||||
@@ -444,6 +503,14 @@
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"total": {
|
||||
"name": "total",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
|
||||
@@ -1,492 +0,0 @@
|
||||
{
|
||||
"version": "5",
|
||||
"dialect": "sqlite",
|
||||
"id": "cb28043a-c451-41dc-a5cc-14a1a74c756d",
|
||||
"prevId": "2cf2acb7-cc98-4f28-8ead-5916b87b7683",
|
||||
"tables": {
|
||||
"groups": {
|
||||
"name": "groups",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"groups_name_unique": {
|
||||
"name": "groups_name_unique",
|
||||
"columns": [
|
||||
"name"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"image_to_user": {
|
||||
"name": "image_to_user",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"image_name": {
|
||||
"name": "image_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_on": {
|
||||
"name": "created_on",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "CURRENT_TIMESTAMP"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"image_to_user_user_id_users_id_fk": {
|
||||
"name": "image_to_user_user_id_users_id_fk",
|
||||
"tableFrom": "image_to_user",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"opinions": {
|
||||
"name": "opinions",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'5Choice'"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"opinions_name_unique": {
|
||||
"name": "opinions_name_unique",
|
||||
"columns": [
|
||||
"name"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"provinces": {
|
||||
"name": "provinces",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"provinces_name_unique": {
|
||||
"name": "provinces_name_unique",
|
||||
"columns": [
|
||||
"name"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"users": {
|
||||
"name": "users",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"firstName": {
|
||||
"name": "firstName",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"lastName": {
|
||||
"name": "lastName",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"cid": {
|
||||
"name": "cid",
|
||||
"type": "text(13)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"age": {
|
||||
"name": "age",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"phone": {
|
||||
"name": "phone",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"public_phone": {
|
||||
"name": "public_phone",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"facebook": {
|
||||
"name": "facebook",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"twitter": {
|
||||
"name": "twitter",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"tiktok": {
|
||||
"name": "tiktok",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"other_social": {
|
||||
"name": "other_social",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"image": {
|
||||
"name": "image",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"job": {
|
||||
"name": "job",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"education": {
|
||||
"name": "education",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"vision": {
|
||||
"name": "vision",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"reason": {
|
||||
"name": "reason",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"group_id": {
|
||||
"name": "group_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"zone_id": {
|
||||
"name": "zone_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"verified": {
|
||||
"name": "verified",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"users_cid_unique": {
|
||||
"name": "users_cid_unique",
|
||||
"columns": [
|
||||
"cid"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"users_phone_unique": {
|
||||
"name": "users_phone_unique",
|
||||
"columns": [
|
||||
"phone"
|
||||
],
|
||||
"isUnique": true
|
||||
},
|
||||
"phone_idx": {
|
||||
"name": "phone_idx",
|
||||
"columns": [
|
||||
"phone"
|
||||
],
|
||||
"isUnique": false
|
||||
},
|
||||
"image_idx": {
|
||||
"name": "image_idx",
|
||||
"columns": [
|
||||
"image"
|
||||
],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"users_group_id_groups_id_fk": {
|
||||
"name": "users_group_id_groups_id_fk",
|
||||
"tableFrom": "users",
|
||||
"tableTo": "groups",
|
||||
"columnsFrom": [
|
||||
"group_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"users_zone_id_zones_id_fk": {
|
||||
"name": "users_zone_id_zones_id_fk",
|
||||
"tableFrom": "users",
|
||||
"tableTo": "zones",
|
||||
"columnsFrom": [
|
||||
"zone_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"user_opinions": {
|
||||
"name": "user_opinions",
|
||||
"columns": {
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"opinion_id": {
|
||||
"name": "opinion_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"choice": {
|
||||
"name": "choice",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "'ignore'"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"user_opinions_user_id_users_id_fk": {
|
||||
"name": "user_opinions_user_id_users_id_fk",
|
||||
"tableFrom": "user_opinions",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"user_opinions_opinion_id_opinions_id_fk": {
|
||||
"name": "user_opinions_opinion_id_opinions_id_fk",
|
||||
"tableFrom": "user_opinions",
|
||||
"tableTo": "opinions",
|
||||
"columnsFrom": [
|
||||
"opinion_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"user_opinions_user_id_opinion_id_pk": {
|
||||
"columns": [
|
||||
"opinion_id",
|
||||
"user_id"
|
||||
],
|
||||
"name": "user_opinions_user_id_opinion_id_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {}
|
||||
},
|
||||
"zones": {
|
||||
"name": "zones",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"province_id": {
|
||||
"name": "province_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"zones_name_province_id_unique": {
|
||||
"name": "zones_name_province_id_unique",
|
||||
"columns": [
|
||||
"name",
|
||||
"province_id"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"zones_province_id_provinces_id_fk": {
|
||||
"name": "zones_province_id_provinces_id_fk",
|
||||
"tableFrom": "zones",
|
||||
"tableTo": "provinces",
|
||||
"columnsFrom": [
|
||||
"province_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
}
|
||||
}
|
||||
@@ -5,22 +5,15 @@
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "5",
|
||||
"when": 1713548458041,
|
||||
"tag": "0000_right_nebula",
|
||||
"when": 1717814164478,
|
||||
"tag": "0000_tired_impossible_man",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "5",
|
||||
"when": 1713599233997,
|
||||
"tag": "0001_chilly_bullseye",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "5",
|
||||
"when": 1715319087385,
|
||||
"tag": "0002_gigantic_sentry",
|
||||
"when": 1717817049392,
|
||||
"tag": "0001_last_hiroim",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
|
||||
148
initialData.ts
148
initialData.ts
@@ -1,23 +1,23 @@
|
||||
export const Groups = [
|
||||
"กลุ่มบริหารราชการแผ่นดินและความมั่นคง",
|
||||
"กลุ่มกฎหมายและกระบวนการยุติธรรม",
|
||||
"กลุ่มการศึกษา",
|
||||
"กลุ่มสาธารณสุข",
|
||||
"กลุ่มทำนา ทำไร่",
|
||||
"กลุ่มทำสวน ป่าไม้ ประมง เลี้ยงสัตว์",
|
||||
"กลุ่มลูกจ้าง ผู้ใช้แรงงาน",
|
||||
"กลุ่มผู้ประกอบกิจการ SMEs",
|
||||
"กลุ่มผู้ประกอบกิจการอื่น",
|
||||
"กลุ่มผู้ประกอบอุตสาหกรรม",
|
||||
"กลุ่มสิ่งแวดล้อม อสังหาริมทรัพย์ พลังงาน",
|
||||
"กลุ่มท่องเที่ยว โรงแรม",
|
||||
"กลุ่มวิทยาศาสตร์ เทคโนโลยี",
|
||||
"กลุ่มศิลปะ ดนตรี บันเทิง กีฬา",
|
||||
"กลุ่มประชาสังคม",
|
||||
"กลุ่มสื่อสารมวลชน นักเขียน",
|
||||
"กลุ่มอาชีพอิสระ",
|
||||
"กลุ่มการบริหารราชการแผ่นดินและความมั่นคง อันได้แก่ ผู้เคยเป็นข้าราชการ เจ้าหน้าที่ของรัฐ หรืออื่น ๆ ในทำนองเดียวกัน",
|
||||
"กลุ่มกฎหมายและกระบวนการยุติธรรม อันได้แก่ ผู้เป็นหรือเคยเป็นผู้พิพากษา ตุลาการ อัยการ ตำรวจ ผู้ประกอบวิชาชีพด้านกฎหมาย หรืออื่น ๆ ในทำนองเดียวกัน",
|
||||
"กลุ่มการศึกษา อันได้แก่ ผู้เป็นหรือเคยเป็นครู อาจารย์ นักวิจัย ผู้บริหารสถานศึกษา บุคลากรทางการศึกษา หรืออื่น ๆ ในทำนองเดียวกัน",
|
||||
"กลุ่มการสาธารณสุข อันได้แก่ ผู้เป็นหรือเคยเป็นแพทย์ทุกประเภท เทคนิคการแพทย์ สาธารณสุข พยาบาล เภสัชกร หรืออื่น ๆ ในทำนองเดียวกัน",
|
||||
"กลุ่มอาชีพทำนา ปลูกพืชล้มลุก หรืออื่น ๆ ในทำนองเดียวกัน",
|
||||
"กลุ่มอาชีพทำสวน ป่าไม้ ปศุสัตว์ ประมง หรืออื่น ๆ ในทำนองเดียวกัน",
|
||||
"กลุ่มพนักงานหรือลูกจ้างของบุคคลซึ่งมิใช่ส่วนราชการหรือหน่วยงานของรัฐ ผู้ใช้แรงงาน หรืออื่น ๆ ในทำนองเดียวกัน",
|
||||
"กลุ่มผู้ประกอบอาชีพด้านสิ่งแวดล้อม ผังเมือง อสังหาริมทรัพย์และสาธารณูปโภค ทรัพยากรธรรมชาติ พลังงาน หรืออื่น ๆ ในทำนองเดียวกัน",
|
||||
"กลุ่มผู้ประกอบกิจการขนาดกลางและขนาดย่อมตามกฎหมายว่าด้วยการนั้น หรืออื่น ๆ ในทำนองเดียวกัน",
|
||||
"กลุ่มผู้ประกอบกิจการอื่นนอกจากกิจการตาม (๙)",
|
||||
"กลุ่มผู้ประกอบธุรกิจหรืออาชีพด้านการท่องเที่ยว อันได้แก่ ผู้ประกอบธุรกิจท่องเที่ยว มัคคุเทศก์ ผู้ประกอบกิจการหรือพนักงานโรงแรม หรืออื่น ๆ ในทำนองเดียวกัน",
|
||||
"กลุ่มผู้ประกอบอุตสาหกรรม หรืออื่น ๆ ในทำนองเดียวกัน",
|
||||
"กลุ่มผู้ประกอบอาชีพด้านวิทยาศาสตร์ เทคโนโลยี การสื่อสาร การพัฒนานวัตกรรม หรืออื่น ๆ ในทำนองเดียวกัน",
|
||||
"กลุ่มสตรี",
|
||||
"กลุ่มผู้สูงอายุ คนพิการ ชาติพันธุ์ กลุ่มอัตลักษณ์อื่น",
|
||||
"กลุ่มผู้สูงอายุ คนพิการหรือทุพพลภาพ กลุ่มชาติพันธุ์ กลุ่มอัตลักษณ์อื่น หรืออื่น ๆ ในทำนองเดียวกัน",
|
||||
"กลุ่มศิลปะ วัฒนธรรม ดนตรี การแสดงและบันเทิง นักกีฬา หรืออื่น ๆ ในทำนองเดียวกัน",
|
||||
"กลุ่มประชาสังคม กลุ่มองค์กรสาธารณประโยชน์ หรืออื่น ๆ ในทำนองเดียวกัน",
|
||||
"กลุ่มสื่อสารมวลชน ผู้สร้างสรรค์วรรณกรรม หรืออื่น ๆ ในทำนองเดียวกัน",
|
||||
"กลุ่มผู้ประกอบวิชาชีพ ผู้ประกอบอาชีพอิสระ หรืออื่น ๆ ในทำนองเดียวกัน",
|
||||
"กลุ่มอื่นๆ",
|
||||
];
|
||||
|
||||
@@ -233,361 +233,361 @@ export const Provinces = [
|
||||
},
|
||||
{
|
||||
code: 38,
|
||||
name_th: "บึงกาฬ",
|
||||
name_th_short: "นธ",
|
||||
name_en: "buogkan",
|
||||
geography_id: 3,
|
||||
},
|
||||
{
|
||||
code: 39,
|
||||
name_th: "หนองบัวลำภู",
|
||||
name_th_short: "บก",
|
||||
name_en: "Nong Bua Lam Phu",
|
||||
geography_id: 3,
|
||||
},
|
||||
{
|
||||
code: 39,
|
||||
code: 40,
|
||||
name_th: "ขอนแก่น",
|
||||
name_th_short: "นภ",
|
||||
name_en: "Khon Kaen",
|
||||
geography_id: 3,
|
||||
},
|
||||
{
|
||||
code: 40,
|
||||
code: 41,
|
||||
name_th: "อุดรธานี",
|
||||
name_th_short: "ขก",
|
||||
name_en: "Udon Thani",
|
||||
geography_id: 3,
|
||||
},
|
||||
{
|
||||
code: 41,
|
||||
code: 42,
|
||||
name_th: "เลย",
|
||||
name_th_short: "อธ",
|
||||
name_en: "Loei",
|
||||
geography_id: 3,
|
||||
},
|
||||
{
|
||||
code: 42,
|
||||
code: 43,
|
||||
name_th: "หนองคาย",
|
||||
name_th_short: "เลย",
|
||||
name_en: "Nong Khai",
|
||||
geography_id: 3,
|
||||
},
|
||||
{
|
||||
code: 43,
|
||||
code: 44,
|
||||
name_th: "มหาสารคาม",
|
||||
name_th_short: "นค",
|
||||
name_en: "Maha Sarakham",
|
||||
geography_id: 3,
|
||||
},
|
||||
{
|
||||
code: 44,
|
||||
code: 45,
|
||||
name_th: "ร้อยเอ็ด",
|
||||
name_th_short: "มค",
|
||||
name_en: "Roi Et",
|
||||
geography_id: 3,
|
||||
},
|
||||
{
|
||||
code: 45,
|
||||
code: 46,
|
||||
name_th: "กาฬสินธุ์",
|
||||
name_th_short: "รอ",
|
||||
name_en: "Kalasin",
|
||||
geography_id: 3,
|
||||
},
|
||||
{
|
||||
code: 46,
|
||||
code: 47,
|
||||
name_th: "สกลนคร",
|
||||
name_th_short: "กส",
|
||||
name_en: "Sakon Nakhon",
|
||||
geography_id: 3,
|
||||
},
|
||||
{
|
||||
code: 47,
|
||||
code: 48,
|
||||
name_th: "นครพนม",
|
||||
name_th_short: "สน",
|
||||
name_en: "Nakhon Phanom",
|
||||
geography_id: 3,
|
||||
},
|
||||
{
|
||||
code: 48,
|
||||
code: 49,
|
||||
name_th: "มุกดาหาร",
|
||||
name_th_short: "นพ",
|
||||
name_en: "Mukdahan",
|
||||
geography_id: 3,
|
||||
},
|
||||
{
|
||||
code: 49,
|
||||
code: 50,
|
||||
name_th: "เชียงใหม่",
|
||||
name_th_short: "มห",
|
||||
name_en: "Chiang Mai",
|
||||
geography_id: 1,
|
||||
},
|
||||
{
|
||||
code: 50,
|
||||
code: 51,
|
||||
name_th: "ลำพูน",
|
||||
name_th_short: "ชม",
|
||||
name_en: "Lamphun",
|
||||
geography_id: 1,
|
||||
},
|
||||
{
|
||||
code: 51,
|
||||
code: 52,
|
||||
name_th: "ลำปาง",
|
||||
name_th_short: "ลพ",
|
||||
name_en: "Lampang",
|
||||
geography_id: 1,
|
||||
},
|
||||
{
|
||||
code: 52,
|
||||
code: 53,
|
||||
name_th: "อุตรดิตถ์",
|
||||
name_th_short: "ลป",
|
||||
name_en: "Uttaradit",
|
||||
geography_id: 1,
|
||||
},
|
||||
{
|
||||
code: 53,
|
||||
code: 54,
|
||||
name_th: "แพร่",
|
||||
name_th_short: "อด",
|
||||
name_en: "Phrae",
|
||||
geography_id: 1,
|
||||
},
|
||||
{
|
||||
code: 54,
|
||||
code: 55,
|
||||
name_th: "น่าน",
|
||||
name_th_short: "พร",
|
||||
name_en: "Nan",
|
||||
geography_id: 1,
|
||||
},
|
||||
{
|
||||
code: 55,
|
||||
code: 56,
|
||||
name_th: "พะเยา",
|
||||
name_th_short: "นน",
|
||||
name_en: "Phayao",
|
||||
geography_id: 1,
|
||||
},
|
||||
{
|
||||
code: 56,
|
||||
code: 57,
|
||||
name_th: "เชียงราย",
|
||||
name_th_short: "พย",
|
||||
name_en: "Chiang Rai",
|
||||
geography_id: 1,
|
||||
},
|
||||
{
|
||||
code: 57,
|
||||
code: 58,
|
||||
name_th: "แม่ฮ่องสอน",
|
||||
name_th_short: "ชร",
|
||||
name_en: "Mae Hong Son",
|
||||
geography_id: 1,
|
||||
},
|
||||
{
|
||||
code: 58,
|
||||
code: 60,
|
||||
name_th: "นครสวรรค์",
|
||||
name_th_short: "มส",
|
||||
name_en: "Nakhon Sawan",
|
||||
geography_id: 2,
|
||||
},
|
||||
{
|
||||
code: 60,
|
||||
code: 61,
|
||||
name_th: "อุทัยธานี",
|
||||
name_th_short: "นว",
|
||||
name_en: "Uthai Thani",
|
||||
geography_id: 2,
|
||||
},
|
||||
{
|
||||
code: 61,
|
||||
code: 62,
|
||||
name_th: "กำแพงเพชร",
|
||||
name_th_short: "อน",
|
||||
name_en: "Kamphaeng Phet",
|
||||
geography_id: 2,
|
||||
},
|
||||
{
|
||||
code: 62,
|
||||
code: 63,
|
||||
name_th: "ตาก",
|
||||
name_th_short: "กพ",
|
||||
name_en: "Tak",
|
||||
geography_id: 4,
|
||||
},
|
||||
{
|
||||
code: 63,
|
||||
code: 64,
|
||||
name_th: "สุโขทัย",
|
||||
name_th_short: "ตก",
|
||||
name_en: "Sukhothai",
|
||||
geography_id: 2,
|
||||
},
|
||||
{
|
||||
code: 64,
|
||||
code: 65,
|
||||
name_th: "พิษณุโลก",
|
||||
name_th_short: "สท",
|
||||
name_en: "Phitsanulok",
|
||||
geography_id: 2,
|
||||
},
|
||||
{
|
||||
code: 65,
|
||||
code: 66,
|
||||
name_th: "พิจิตร",
|
||||
name_th_short: "พล",
|
||||
name_en: "Phichit",
|
||||
geography_id: 2,
|
||||
},
|
||||
{
|
||||
code: 66,
|
||||
code: 67,
|
||||
name_th: "เพชรบูรณ์",
|
||||
name_th_short: "พจ",
|
||||
name_en: "Phetchabun",
|
||||
geography_id: 2,
|
||||
},
|
||||
{
|
||||
code: 67,
|
||||
code: 70,
|
||||
name_th: "ราชบุรี",
|
||||
name_th_short: "พช",
|
||||
name_en: "Ratchaburi",
|
||||
geography_id: 4,
|
||||
},
|
||||
{
|
||||
code: 70,
|
||||
code: 71,
|
||||
name_th: "กาญจนบุรี",
|
||||
name_th_short: "รบ",
|
||||
name_en: "Kanchanaburi",
|
||||
geography_id: 4,
|
||||
},
|
||||
{
|
||||
code: 71,
|
||||
code: 72,
|
||||
name_th: "สุพรรณบุรี",
|
||||
name_th_short: "กจ",
|
||||
name_en: "Suphan Buri",
|
||||
geography_id: 2,
|
||||
},
|
||||
{
|
||||
code: 72,
|
||||
code: 73,
|
||||
name_th: "นครปฐม",
|
||||
name_th_short: "สพ",
|
||||
name_en: "Nakhon Pathom",
|
||||
geography_id: 2,
|
||||
},
|
||||
{
|
||||
code: 73,
|
||||
code: 74,
|
||||
name_th: "สมุทรสาคร",
|
||||
name_th_short: "นป",
|
||||
name_en: "Samut Sakhon",
|
||||
geography_id: 2,
|
||||
},
|
||||
{
|
||||
code: 74,
|
||||
code: 75,
|
||||
name_th: "สมุทรสงคราม",
|
||||
name_th_short: "สค",
|
||||
name_en: "Samut Songkhram",
|
||||
geography_id: 2,
|
||||
},
|
||||
{
|
||||
code: 75,
|
||||
code: 76,
|
||||
name_th: "เพชรบุรี",
|
||||
name_th_short: "สส",
|
||||
name_en: "Phetchaburi",
|
||||
geography_id: 4,
|
||||
},
|
||||
{
|
||||
code: 76,
|
||||
code: 77,
|
||||
name_th: "ประจวบคีรีขันธ์",
|
||||
name_th_short: "พบ",
|
||||
name_en: "Prachuap Khiri Khan",
|
||||
geography_id: 4,
|
||||
},
|
||||
{
|
||||
code: 77,
|
||||
code: 80,
|
||||
name_th: "นครศรีธรรมราช",
|
||||
name_th_short: "ปข",
|
||||
name_en: "Nakhon Si Thammarat",
|
||||
geography_id: 6,
|
||||
},
|
||||
{
|
||||
code: 80,
|
||||
code: 81,
|
||||
name_th: "กระบี่",
|
||||
name_th_short: "นศ",
|
||||
name_en: "Krabi",
|
||||
geography_id: 6,
|
||||
},
|
||||
{
|
||||
code: 81,
|
||||
code: 82,
|
||||
name_th: "พังงา",
|
||||
name_th_short: "กบ",
|
||||
name_en: "Phangnga",
|
||||
geography_id: 6,
|
||||
},
|
||||
{
|
||||
code: 82,
|
||||
code: 83,
|
||||
name_th: "ภูเก็ต",
|
||||
name_th_short: "พง",
|
||||
name_en: "Phuket",
|
||||
geography_id: 6,
|
||||
},
|
||||
{
|
||||
code: 83,
|
||||
code: 84,
|
||||
name_th: "สุราษฎร์ธานี",
|
||||
name_th_short: "ภก",
|
||||
name_en: "Surat Thani",
|
||||
geography_id: 6,
|
||||
},
|
||||
{
|
||||
code: 84,
|
||||
code: 85,
|
||||
name_th: "ระนอง",
|
||||
name_th_short: "สฎ",
|
||||
name_en: "Ranong",
|
||||
geography_id: 6,
|
||||
},
|
||||
{
|
||||
code: 85,
|
||||
code: 86,
|
||||
name_th: "ชุมพร",
|
||||
name_th_short: "รน",
|
||||
name_en: "Chumphon",
|
||||
geography_id: 6,
|
||||
},
|
||||
{
|
||||
code: 86,
|
||||
code: 90,
|
||||
name_th: "สงขลา",
|
||||
name_th_short: "ชพ",
|
||||
name_en: "Songkhla",
|
||||
geography_id: 6,
|
||||
},
|
||||
{
|
||||
code: 90,
|
||||
code: 91,
|
||||
name_th: "สตูล",
|
||||
name_th_short: "สข",
|
||||
name_en: "Satun",
|
||||
geography_id: 6,
|
||||
},
|
||||
{
|
||||
code: 91,
|
||||
code: 92,
|
||||
name_th: "ตรัง",
|
||||
name_th_short: "สต",
|
||||
name_en: "Trang",
|
||||
geography_id: 6,
|
||||
},
|
||||
{
|
||||
code: 92,
|
||||
code: 93,
|
||||
name_th: "พัทลุง",
|
||||
name_th_short: "ตง",
|
||||
name_en: "Phatthalung",
|
||||
geography_id: 6,
|
||||
},
|
||||
{
|
||||
code: 93,
|
||||
code: 94,
|
||||
name_th: "ปัตตานี",
|
||||
name_th_short: "พท",
|
||||
name_en: "Pattani",
|
||||
geography_id: 6,
|
||||
},
|
||||
{
|
||||
code: 94,
|
||||
code: 95,
|
||||
name_th: "ยะลา",
|
||||
name_th_short: "ปน",
|
||||
name_en: "Yala",
|
||||
geography_id: 6,
|
||||
},
|
||||
{
|
||||
code: 95,
|
||||
code: 96,
|
||||
name_th: "นราธิวาส",
|
||||
name_th_short: "ยล",
|
||||
name_en: "Narathiwat",
|
||||
geography_id: 6,
|
||||
},
|
||||
{
|
||||
code: 96,
|
||||
name_th: "บึงกาฬ",
|
||||
name_th_short: "นธ",
|
||||
name_en: "buogkan",
|
||||
geography_id: 3,
|
||||
},
|
||||
];
|
||||
export const Districts = [
|
||||
{
|
||||
|
||||
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 #Only needed if public is there
|
||||
|
||||
# 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",
|
||||
};
|
||||
12
package.json
12
package.json
@@ -7,8 +7,10 @@
|
||||
"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",
|
||||
"initialize_data": "node -r @swc-node/register addMetadata.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
@@ -32,6 +34,7 @@
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.2.0",
|
||||
"@swc-node/register": "^1.9.0",
|
||||
"@swc/cli": "^0.3.12",
|
||||
"@swc/core": "^1.4.16",
|
||||
@@ -41,10 +44,17 @@
|
||||
"@types/jsonwebtoken": "^9.0.6",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"drizzle-kit": "^0.20.14",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.3",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"nodemon": "^3.1.0",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-tailwindcss": "^0.5.14",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.5"
|
||||
"typescript": "^5.4.5",
|
||||
"typescript-eslint": "^7.9.0"
|
||||
}
|
||||
}
|
||||
|
||||
1906
pnpm-lock.yaml
generated
1906
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
6
prettier.config.mjs
Normal file
6
prettier.config.mjs
Normal file
@@ -0,0 +1,6 @@
|
||||
/** @type {import("prettier").Config} */
|
||||
const config = {
|
||||
plugins: ["prettier-plugin-tailwindcss"],
|
||||
};
|
||||
|
||||
export default config;
|
||||
92
src/adminRoute.ts
Normal file
92
src/adminRoute.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { router, publicProcedure } from "./trpc";
|
||||
import { db } from "./db";
|
||||
import { user, userOpinion } from "./schema";
|
||||
import { count, eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
|
||||
export const adminRoute = router({
|
||||
totalUser: publicProcedure.query(async () => {
|
||||
const rs = await db
|
||||
.select({ zone: user.zone, value: count(user.id) })
|
||||
.from(user)
|
||||
.groupBy(user.zone)
|
||||
.execute();
|
||||
const zones = await db.query.zone
|
||||
.findMany({ with: { province: true } })
|
||||
.execute();
|
||||
zones.sort((a, b) => a.province.id - b.province.id);
|
||||
const summary = zones.map((z) => {
|
||||
const num = rs.find((user) => user.zone == z.id)?.value ?? 0;
|
||||
return {
|
||||
count: num,
|
||||
zone: z.name,
|
||||
province: z.province.name,
|
||||
};
|
||||
});
|
||||
return summary;
|
||||
}),
|
||||
totalUserDeep: publicProcedure
|
||||
.input(z.object({ code: z.string().optional() }))
|
||||
.query(async ({ input }) => {
|
||||
const users = await db
|
||||
.select({
|
||||
zone: user.zone,
|
||||
cid: user.cid,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
phone: user.phone,
|
||||
})
|
||||
.from(user)
|
||||
.execute();
|
||||
const zones = await db.query.zone
|
||||
.findMany({ with: { province: true } })
|
||||
.execute();
|
||||
zones.sort((a, b) => a.province.id - b.province.id);
|
||||
let rs = [];
|
||||
for (const zone of zones) {
|
||||
const zoneUser = users.filter((u) => u.zone == zone.id);
|
||||
if (zoneUser.length == 0) continue;
|
||||
const total = zoneUser.length;
|
||||
let userDescription: string;
|
||||
if (input.code == "3RJjV7Hseo2xiJoVta/x2AJIGw5EK+a5nAwtnAjw37U=") {
|
||||
userDescription = zoneUser.reduce(
|
||||
(acc, n) =>
|
||||
acc + `${n.firstName} ${n.lastName}: ${n.cid}|${n.phone}\n`,
|
||||
"",
|
||||
);
|
||||
} else {
|
||||
userDescription = zoneUser.reduce(
|
||||
(acc, n) => acc + `${n.firstName} ${n.lastName}: ${n.cid}\n`,
|
||||
"",
|
||||
);
|
||||
}
|
||||
rs.push({
|
||||
province: zone.province.name,
|
||||
zone: zone.name,
|
||||
total,
|
||||
users: userDescription,
|
||||
});
|
||||
}
|
||||
return rs;
|
||||
}),
|
||||
|
||||
removeUser: publicProcedure
|
||||
.input(z.object({ cid: z.string(), key: z.string() }))
|
||||
.mutation(async ({ input }) => {
|
||||
if (input.key !== "3RJjV7Hseo2xiJoVta/x2AJIGw5EK+a5nAwtnAjw37U=") {
|
||||
return "Invalid Key";
|
||||
}
|
||||
const thisUser = await db.query.user
|
||||
.findFirst({ where: eq(user.cid, input.cid) })
|
||||
.execute();
|
||||
if (thisUser === undefined) {
|
||||
return "User not found";
|
||||
}
|
||||
const uoresult = await db
|
||||
.delete(userOpinion)
|
||||
.where(eq(userOpinion.userId, thisUser.id))
|
||||
.execute();
|
||||
const rs = await db.delete(user).where(eq(user.cid, input.cid)).execute();
|
||||
return { useropinion: uoresult, rs };
|
||||
}),
|
||||
});
|
||||
@@ -4,10 +4,12 @@ import { userRoute } from "./userRoute";
|
||||
import { runPlayground } from "./playgroud";
|
||||
import cors from "cors";
|
||||
import { infoRoute } from "./infoRoute";
|
||||
import { adminRoute } from "./adminRoute";
|
||||
|
||||
export const appRouter = router({
|
||||
user: userRoute,
|
||||
info: infoRoute,
|
||||
OjTBXE4m1xAULqhbxj3yiQ: adminRoute,
|
||||
});
|
||||
export type AppRouter = typeof appRouter;
|
||||
|
||||
|
||||
@@ -4,5 +4,6 @@ import Database from "better-sqlite3";
|
||||
import * as schema from "./schema";
|
||||
|
||||
const sqlite = new Database("sqlite.db");
|
||||
sqlite.pragma("journal_mode = WAL");
|
||||
export const db = drizzle(sqlite, { schema });
|
||||
migrate(db, { migrationsFolder: "drizzle" });
|
||||
|
||||
@@ -16,18 +16,18 @@ export function createClient() {
|
||||
export async function createUploadImageUrl(
|
||||
mc: minio.Client,
|
||||
objectName: string,
|
||||
contentType: string
|
||||
contentType: string,
|
||||
) {
|
||||
let policy = mc.newPostPolicy();
|
||||
const policy = mc.newPostPolicy();
|
||||
policy.setKey(objectName);
|
||||
policy.setBucket(Config.bucketName);
|
||||
let expires = new Date();
|
||||
const expires = new Date();
|
||||
expires.setSeconds(30 * 60);
|
||||
policy.setExpires(expires);
|
||||
policy.setContentType(contentType);
|
||||
policy.setContentDisposition(`attachment; filename="${objectName}"`);
|
||||
policy.setContentLengthRange(1, 3 * 1024 * 1024);
|
||||
let rs = await mc.presignedPostPolicy(policy);
|
||||
const rs = await mc.presignedPostPolicy(policy);
|
||||
return rs;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export const runPlayground = async (appRouter: AppRouter) => {
|
||||
trpcApiEndpoint,
|
||||
playgroundEndpoint,
|
||||
router: appRouter,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
app.listen(3001, () => {
|
||||
|
||||
@@ -15,10 +15,11 @@ export const user = sqliteTable(
|
||||
id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }),
|
||||
firstName: text("firstName").notNull(),
|
||||
lastName: text("lastName").notNull(),
|
||||
registerno: text("registerno"),
|
||||
title: text("title").notNull(),
|
||||
cid: text("cid", { length: 13 }).notNull().unique(),
|
||||
cid: text("cid", { length: 13 }).notNull(),
|
||||
age: integer("age").notNull(),
|
||||
phone: text("phone").unique().notNull(),
|
||||
phone: text("phone").notNull(),
|
||||
public_phone: text("public_phone"),
|
||||
facebook: text("facebook"),
|
||||
twitter: text("twitter"),
|
||||
@@ -37,11 +38,12 @@ export const user = sqliteTable(
|
||||
.notNull()
|
||||
.references(() => zone.id),
|
||||
verified: integer("verified", { mode: "boolean" }).notNull().default(false),
|
||||
rank: integer("rank").default(9999),
|
||||
},
|
||||
(t) => ({
|
||||
phone_idx: index("phone_idx").on(t.phone),
|
||||
image_idx: index("image_idx").on(t.image),
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
export const userRelation = relations(user, ({ many, one }) => ({
|
||||
@@ -54,6 +56,7 @@ export const userRelation = relations(user, ({ many, one }) => ({
|
||||
fields: [user.zone],
|
||||
references: [zone.id],
|
||||
}),
|
||||
userToSelection: many(userToSelection, { relationName: "userRelation" }),
|
||||
}));
|
||||
|
||||
//----------------Group
|
||||
@@ -98,7 +101,7 @@ export const userOpinion = sqliteTable(
|
||||
},
|
||||
(t) => ({
|
||||
pk: primaryKey({ columns: [t.userId, t.opinionId] }),
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
export const userOpinionRelation = relations(userOpinion, ({ one }) => ({
|
||||
@@ -117,8 +120,9 @@ export const zone = sqliteTable(
|
||||
province: integer("province_id")
|
||||
.notNull()
|
||||
.references(() => province.id),
|
||||
total: integer("total").notNull().default(0),
|
||||
},
|
||||
(t) => ({ unique_name_province: unique().on(t.name, t.province) })
|
||||
(t) => ({ unique_name_province: unique().on(t.name, t.province) }),
|
||||
);
|
||||
export const zoneRelation = relations(zone, ({ one }) => ({
|
||||
province: one(province, {
|
||||
@@ -144,6 +148,29 @@ export const imageToUser = sqliteTable("image_to_user", {
|
||||
.references(() => user.id),
|
||||
imageName: text("image_name").notNull(),
|
||||
createdOn: integer("created_on", { mode: "timestamp" }).default(
|
||||
sql`CURRENT_TIMESTAMP`
|
||||
sql`CURRENT_TIMESTAMP`,
|
||||
),
|
||||
});
|
||||
|
||||
export const userToSelection = sqliteTable("user_to_selection", {
|
||||
userId: integer("user_id").references(() => user.id, { onDelete: "cascade" }),
|
||||
targetId: integer("target_id").references(() => user.id, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
});
|
||||
|
||||
export const userToSelectionRelation = relations(
|
||||
userToSelection,
|
||||
({ one }) => ({
|
||||
user: one(user, {
|
||||
fields: [userToSelection.userId],
|
||||
references: [user.id],
|
||||
relationName: "userRelation",
|
||||
}),
|
||||
selection: one(user, {
|
||||
fields: [userToSelection.targetId],
|
||||
references: [user.id],
|
||||
relationName: "selectionRelation",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -42,7 +42,7 @@ export const createContext = async (opts: CreateHTTPContextOptions) => {
|
||||
const bearerToken = authorizationHeader.split(" ")[1];
|
||||
const phone = await verifyToken(bearerToken);
|
||||
if (phone !== null) {
|
||||
let user = await db.query.user.findFirst({
|
||||
const user = await db.query.user.findFirst({
|
||||
where: (user, { eq }) => eq(user.phone, phone),
|
||||
});
|
||||
return {
|
||||
@@ -59,7 +59,7 @@ export const createContext = async (opts: CreateHTTPContextOptions) => {
|
||||
|
||||
async function verifyToken(token: string): Promise<string | null> {
|
||||
try {
|
||||
let rs = await new Promise((resolve, reject) => {
|
||||
const rs = await new Promise((resolve, reject) => {
|
||||
jwt.verify(token, Config.jwt_secret, (err, decoded) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
@@ -68,7 +68,7 @@ async function verifyToken(token: string): Promise<string | null> {
|
||||
}
|
||||
});
|
||||
});
|
||||
let data = z
|
||||
const data = z
|
||||
.object({
|
||||
phone: z.string(),
|
||||
})
|
||||
|
||||
185
src/userRoute.ts
185
src/userRoute.ts
@@ -1,13 +1,6 @@
|
||||
import { router, publicProcedure, protectedProcedure } from "./trpc";
|
||||
import { db } from "./db";
|
||||
import {
|
||||
imageToUser,
|
||||
opinion,
|
||||
province,
|
||||
user,
|
||||
userOpinion,
|
||||
zone,
|
||||
} from "./schema";
|
||||
import { imageToUser, opinion, user, userOpinion, zone } from "./schema";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { z } from "zod";
|
||||
import { SQL, and, count, eq, inArray, sql } from "drizzle-orm";
|
||||
@@ -19,7 +12,7 @@ import { createClient, createUploadImageUrl } from "./minio";
|
||||
const userInsertSchema = createInsertSchema(user, {
|
||||
cid: (schema) =>
|
||||
schema.cid.length(13).refine(isValidThaiID, { message: "Invalid Thai ID" }),
|
||||
});
|
||||
}).omit({ verified: true });
|
||||
|
||||
const userUpdateSchema = userInsertSchema
|
||||
.omit({ id: true, cid: true, phone: true })
|
||||
@@ -27,7 +20,6 @@ const userUpdateSchema = userInsertSchema
|
||||
const opinionInsertSchema = createInsertSchema(userOpinion)
|
||||
.omit({
|
||||
userId: true,
|
||||
verified: true,
|
||||
})
|
||||
.array()
|
||||
.default([]);
|
||||
@@ -38,6 +30,7 @@ const opinionUpdateSchema = createInsertSchema(userOpinion)
|
||||
verified: true,
|
||||
})
|
||||
.required({ opinionId: true });
|
||||
|
||||
type OpinionInsertSchema = z.infer<typeof opinionInsertSchema>;
|
||||
type UserInsertSchema = z.infer<typeof userInsertSchema>;
|
||||
type UserUpdateSchema = z.infer<typeof userUpdateSchema>;
|
||||
@@ -47,20 +40,20 @@ export const userRoute = router({
|
||||
.input(
|
||||
userInsertSchema.omit({ id: true }).extend({
|
||||
opinions: opinionInsertSchema,
|
||||
})
|
||||
}),
|
||||
)
|
||||
.mutation(
|
||||
async ({ input }) => await createUser({ ...input }, input.opinions)
|
||||
async ({ input }) => await createUser({ ...input }, input.opinions),
|
||||
),
|
||||
// changeImage: protectedProcedure
|
||||
updateUser: protectedProcedure
|
||||
.input(userUpdateSchema)
|
||||
.mutation(async ({ input, ctx }) => await updateUser(ctx.user.id, input)),
|
||||
getUser: protectedProcedure
|
||||
getUser: publicProcedure
|
||||
.input(z.object({ userId: z.number() }))
|
||||
.mutation(async ({ input }) => await getUser(input.userId, false)),
|
||||
getSelf: protectedProcedure.mutation(
|
||||
async ({ ctx }) => await getUser(ctx.user.id, true)
|
||||
async ({ ctx }) => await getUser(ctx.user.id, true),
|
||||
),
|
||||
login: publicProcedure
|
||||
.input(z.object({ cid: z.string(), phone: z.string() }))
|
||||
@@ -69,7 +62,7 @@ export const userRoute = router({
|
||||
.input(opinionUpdateSchema)
|
||||
.mutation(
|
||||
async ({ input, ctx }) =>
|
||||
await changeOpinion(input.opinionId, ctx.user.id, input.choice)
|
||||
await changeOpinion(input.opinionId, ctx.user.id, input.choice),
|
||||
),
|
||||
requestChangeImage: protectedProcedure
|
||||
.input(z.object({ imageName: z.string(), contentType: z.string() }))
|
||||
@@ -78,22 +71,23 @@ export const userRoute = router({
|
||||
await requestChangeImage(
|
||||
ctx.user.id,
|
||||
input.imageName,
|
||||
input.contentType
|
||||
)
|
||||
input.contentType,
|
||||
),
|
||||
),
|
||||
confirmChangeImage: protectedProcedure.mutation(
|
||||
async ({ ctx }) => await confirmChangeImage(ctx.user.id, ctx.user.image)
|
||||
async ({ ctx }) => await confirmChangeImage(ctx.user.id, ctx.user.image),
|
||||
),
|
||||
getAllUser: protectedProcedure
|
||||
getAllUser: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
offset: z.number().default(0),
|
||||
limit: z.number().max(50).default(10),
|
||||
limit: z.number().max(1000).default(1000),
|
||||
group: z.number().optional(),
|
||||
zone: z.number().optional(),
|
||||
opinionCount: z.number().default(3),
|
||||
province: z.number().optional(),
|
||||
})
|
||||
userId: z.number().optional(),
|
||||
}),
|
||||
)
|
||||
.query(
|
||||
async ({ input }) =>
|
||||
@@ -103,33 +97,34 @@ export const userRoute = router({
|
||||
input.opinionCount,
|
||||
input.group,
|
||||
input.zone,
|
||||
input.province
|
||||
)
|
||||
input.province,
|
||||
input.userId,
|
||||
),
|
||||
),
|
||||
getAllUserCount: protectedProcedure
|
||||
getAllUserCount: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
group: z.number().optional(),
|
||||
zone: z.number().optional(),
|
||||
province: z.number().optional(),
|
||||
})
|
||||
}),
|
||||
)
|
||||
.query(
|
||||
async ({ input }) =>
|
||||
await getAllUserCount(input.group, input.zone, input.province)
|
||||
await getAllUserCount(input.group, input.zone, input.province),
|
||||
),
|
||||
});
|
||||
|
||||
async function getAllUserCount(
|
||||
group?: number,
|
||||
zoneId?: number,
|
||||
provinceId?: number
|
||||
provinceId?: number,
|
||||
) {
|
||||
let zoneIds: number[] = await getZone(provinceId);
|
||||
const zoneIds: number[] = await getZone(provinceId);
|
||||
if (provinceId && zoneIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
let conditions: SQL[] = [];
|
||||
const conditions: SQL[] = [];
|
||||
if (group !== undefined) {
|
||||
conditions.push(eq(user.group, group));
|
||||
}
|
||||
@@ -152,12 +147,62 @@ async function getAllUser(
|
||||
opinionLimit: number,
|
||||
group?: number,
|
||||
zoneId?: number,
|
||||
provinceId?: number
|
||||
provinceId?: number,
|
||||
userId?: number,
|
||||
) {
|
||||
let zoneIds: number[] = await getZone(provinceId);
|
||||
const zoneIds: number[] = await getZone(provinceId);
|
||||
if (provinceId && zoneIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
let thisUser =
|
||||
userId == undefined
|
||||
? undefined
|
||||
: await db.query.user.findFirst({
|
||||
where: eq(user.id, userId),
|
||||
with: {
|
||||
userToSelection: {
|
||||
with: {
|
||||
selection: {
|
||||
with: {
|
||||
group: true,
|
||||
opinions: {
|
||||
limit: opinionLimit,
|
||||
},
|
||||
zone: {
|
||||
with: { province: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const topTen = await db.query.user.findMany({
|
||||
with: {
|
||||
group: true,
|
||||
opinions: {
|
||||
limit: opinionLimit,
|
||||
},
|
||||
zone: {
|
||||
with: { province: true },
|
||||
},
|
||||
},
|
||||
limit: 10,
|
||||
orderBy: user.rank,
|
||||
where: (user, { eq, and }) => {
|
||||
const conditions: SQL[] = [];
|
||||
if (group !== undefined) {
|
||||
conditions.push(eq(user.group, group));
|
||||
}
|
||||
if (zoneId !== undefined) {
|
||||
conditions.push(eq(user.zone, zoneId));
|
||||
}
|
||||
if (zoneIds.length > 0) {
|
||||
conditions.push(inArray(user.zone, zoneIds));
|
||||
}
|
||||
return and(...conditions);
|
||||
},
|
||||
});
|
||||
let users = await db.query.user.findMany({
|
||||
with: {
|
||||
group: true,
|
||||
@@ -172,7 +217,7 @@ async function getAllUser(
|
||||
offset,
|
||||
orderBy: sql`random()`,
|
||||
where: (user, { eq, and }) => {
|
||||
let conditions: SQL[] = [];
|
||||
const conditions: SQL[] = [];
|
||||
if (group !== undefined) {
|
||||
conditions.push(eq(user.group, group));
|
||||
}
|
||||
@@ -185,17 +230,43 @@ async function getAllUser(
|
||||
return and(...conditions);
|
||||
},
|
||||
});
|
||||
let resultUser: typeof users;
|
||||
if (thisUser && thisUser.group == group) {
|
||||
const selections = thisUser.userToSelection.map((v) => v.selection);
|
||||
let validSelection: typeof users = [];
|
||||
for (const sl of selections) {
|
||||
if (sl !== null) {
|
||||
validSelection.push(sl);
|
||||
}
|
||||
}
|
||||
resultUser = [
|
||||
...validSelection,
|
||||
...users.filter(
|
||||
(u) => validSelection.filter((v) => v.id == u.id).length == 0,
|
||||
),
|
||||
];
|
||||
resultUser = randomArray(1, 10, resultUser);
|
||||
} else {
|
||||
resultUser = [
|
||||
...topTen,
|
||||
...users.filter((u) => topTen.filter((v) => v.id == u.id).length == 0),
|
||||
];
|
||||
resultUser = randomArray(0, 5, resultUser);
|
||||
resultUser = randomArray(5, 10, resultUser);
|
||||
}
|
||||
|
||||
return users.map((u) => ({
|
||||
...u,
|
||||
phone: hidePhone(u.phone),
|
||||
verified: true,
|
||||
image: u.image ? `${Config.minioPublicBucketEndpoint}${u.image}` : null,
|
||||
}));
|
||||
return resultUser
|
||||
.map((u) => ({
|
||||
...u,
|
||||
phone: hidePhone(u.phone),
|
||||
verified: true,
|
||||
image: u.image ? `${Config.minioPublicBucketEndpoint}${u.image}` : null,
|
||||
}))
|
||||
.slice(0, limit);
|
||||
}
|
||||
|
||||
async function getUser(userId: number, showPhone: boolean) {
|
||||
let user = await db.query.user.findFirst({
|
||||
const user = await db.query.user.findFirst({
|
||||
where: (user, { eq }) => eq(user.id, userId),
|
||||
with: {
|
||||
group: true,
|
||||
@@ -221,13 +292,13 @@ async function getUser(userId: number, showPhone: boolean) {
|
||||
|
||||
async function createUser(
|
||||
newUser: UserInsertSchema,
|
||||
opinions: OpinionInsertSchema
|
||||
opinions: OpinionInsertSchema,
|
||||
) {
|
||||
try {
|
||||
let result = (
|
||||
const result = (
|
||||
await db.insert(user).values(newUser).returning({ id: user.id })
|
||||
)[0];
|
||||
for (let op of opinions) {
|
||||
for (const op of opinions) {
|
||||
await db.insert(userOpinion).values({ ...op, userId: result.id });
|
||||
}
|
||||
return { token: createJWT(newUser.phone) };
|
||||
@@ -248,7 +319,7 @@ async function updateUser(userId: number, update: UserUpdateSchema) {
|
||||
}
|
||||
|
||||
async function login(cid: string, phone: string) {
|
||||
let user = await db.query.user.findFirst({
|
||||
const user = await db.query.user.findFirst({
|
||||
where: (user, { and, eq }) => and(eq(user.cid, cid), eq(user.phone, phone)),
|
||||
});
|
||||
if (user === undefined) {
|
||||
@@ -264,10 +335,10 @@ async function login(cid: string, phone: string) {
|
||||
async function changeOpinion(
|
||||
opinionId: number,
|
||||
userId: number,
|
||||
opinionChoice: OpinionInsertSchema[0]["choice"]
|
||||
opinionChoice: OpinionInsertSchema[0]["choice"],
|
||||
) {
|
||||
try {
|
||||
let thisOpinion = await db
|
||||
const thisOpinion = await db
|
||||
.select()
|
||||
.from(opinion)
|
||||
.where(eq(opinion.id, opinionId))
|
||||
@@ -308,7 +379,7 @@ async function changeOpinion(
|
||||
async function requestChangeImage(
|
||||
userId: number,
|
||||
imageName: string,
|
||||
contentType: string
|
||||
contentType: string,
|
||||
) {
|
||||
const mc = createClient();
|
||||
// Check if the image is valid
|
||||
@@ -328,11 +399,11 @@ async function requestChangeImage(
|
||||
});
|
||||
}
|
||||
// Create a unique image name
|
||||
let tryCount = 0;
|
||||
const tryCount = 0;
|
||||
let objectName: string | null = null;
|
||||
while (tryCount < 3) {
|
||||
let imageName = `${generateRandomString()}.${extension}`;
|
||||
let ok = await db
|
||||
const imageName = `${generateRandomString()}.${extension}`;
|
||||
const ok = await db
|
||||
.select({ value: count(user.image) })
|
||||
.from(user)
|
||||
.where(eq(user.image, imageName))
|
||||
@@ -356,14 +427,14 @@ async function requestChangeImage(
|
||||
target: [imageToUser.userId],
|
||||
set: { imageName: objectName },
|
||||
});
|
||||
let request = await createUploadImageUrl(mc, objectName, contentType);
|
||||
const request = await createUploadImageUrl(mc, objectName, contentType);
|
||||
request.postURL = Config.minioPublicBucketEndpoint;
|
||||
return request;
|
||||
}
|
||||
|
||||
async function confirmChangeImage(userId: number, oldImage: string | null) {
|
||||
const mc = createClient();
|
||||
let rs = await db
|
||||
const rs = await db
|
||||
.select({ imageName: imageToUser.imageName })
|
||||
.from(imageToUser)
|
||||
.where(eq(imageToUser.userId, userId));
|
||||
@@ -373,7 +444,7 @@ async function confirmChangeImage(userId: number, oldImage: string | null) {
|
||||
code: "BAD_REQUEST",
|
||||
});
|
||||
}
|
||||
let imageName = rs[0].imageName;
|
||||
const imageName = rs[0].imageName;
|
||||
const isImageExist = await mc
|
||||
.statObject(Config.bucketName, imageName)
|
||||
.then(() => true)
|
||||
@@ -384,7 +455,7 @@ async function confirmChangeImage(userId: number, oldImage: string | null) {
|
||||
code: "BAD_REQUEST",
|
||||
});
|
||||
}
|
||||
const promises: Promise<any>[] = [];
|
||||
const promises: Promise<void>[] = [];
|
||||
if (oldImage) {
|
||||
promises.push(mc.removeObject(Config.bucketName, oldImage).catch(() => {}));
|
||||
}
|
||||
@@ -437,3 +508,11 @@ async function getZone(province?: number) {
|
||||
.then((queryResult) => queryResult.map((v) => v.id));
|
||||
return zoneIds;
|
||||
}
|
||||
|
||||
function randomArray<T>(from: number, to: number, arr: T[]): T[] {
|
||||
for (let i = Math.min(arr.length - 1, from); i < to; i++) {
|
||||
const j = Math.floor(Math.random() * (i - from)) + from;
|
||||
[arr[i], arr[j]] = [arr[j], arr[i]];
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "resolveJsonModule": true /* Enable importing .json files. */,
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
/* JavaScript Support */
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user