Compare commits

..

30 Commits

Author SHA1 Message Date
cba647ca27 randomize result
All checks were successful
backend-action / build-image (push) Successful in 11m53s
2024-06-24 15:16:47 +07:00
f6b18d3f82 apply limit to resulting array
All checks were successful
backend-action / build-image (push) Successful in 6m21s
2024-06-24 00:23:42 +07:00
d660fd12fa update province user code 2024-06-24 00:23:16 +07:00
38f769ef67 remove admin workflow
All checks were successful
backend-action / build-image (push) Successful in 10m41s
2024-06-08 10:52:58 +07:00
b152175499 change filter method
Some checks failed
backend-admin-action / build-image (push) Failing after 41s
backend-action / build-image (push) Has been cancelled
2024-06-08 10:51:53 +07:00
e0655a56d7 update top three to match filter condition
Some checks failed
backend-admin-action / build-image (push) Failing after 45s
backend-action / build-image (push) Successful in 13m59s
2024-06-08 10:32:37 +07:00
3d6de91dc8 added locking mechanism
Some checks failed
backend-admin-action / build-image (push) Failing after 38s
backend-action / build-image (push) Successful in 7m35s
2024-06-08 10:04:49 +07:00
b28aae201a update db schema + populate allUser 2024-06-08 09:44:53 +07:00
51f52c0a1e make search api public
Some checks failed
backend-admin-action / build-image (push) Failing after 2m33s
backend-action / build-image (push) Successful in 17m31s
2024-06-06 12:15:09 +07:00
decd9a6945 increase timeout
Some checks failed
backend-admin-action / build-image (push) Failing after 38s
backend-action / build-image (push) Successful in 1h53m1s
2024-05-27 11:30:13 +07:00
bb69b33481 increase timeout for cicd
Some checks failed
backend-admin-action / build-image (push) Failing after 42s
backend-action / build-image (push) Has been cancelled
2024-05-27 10:57:08 +07:00
a7eb7d5037 show phone number in api
Some checks failed
backend-admin-action / build-image (push) Failing after 52s
backend-action / build-image (push) Failing after 11m23s
2024-05-27 10:42:43 +07:00
96129c1fe9 added totalUserDeep
Some checks failed
backend-admin-action / build-image (push) Failing after 2m8s
backend-action / build-image (push) Successful in 3m22s
2024-05-25 08:43:12 +07:00
b990b04902 remove public folder clone
Some checks failed
backend-admin-action / build-image (push) Failing after 11m5s
backend-action / build-image (push) Failing after 11m38s
2024-05-20 17:06:32 +07:00
577d97cfcd remove target from admin workflow
Some checks failed
backend-action / build-image (push) Failing after 8s
backend-admin-action / build-image (push) Failing after 7s
2024-05-20 16:51:13 +07:00
23f37df217 added sorvor admin page
Some checks failed
backend-admin-action / build-image (push) Failing after 8s
backend-action / build-image (push) Failing after 1m40s
2024-05-20 16:48:06 +07:00
98a65043c9 add total to zone
Some checks failed
backend-action / build-image (push) Failing after 10m40s
2024-05-20 15:26:18 +07:00
4934f799f5 disable user verification change
All checks were successful
backend-action / build-image (push) Successful in 6m24s
2024-05-20 14:49:44 +07:00
2a69ff0f3e fix remove bug
All checks were successful
backend-action / build-image (push) Successful in 28s
2024-05-20 14:11:50 +07:00
64ea2e9524 add removation
All checks were successful
backend-action / build-image (push) Successful in 8m1s
2024-05-20 13:50:14 +07:00
a29bac1eb2 redo summary
All checks were successful
backend-action / build-image (push) Successful in 7m29s
2024-05-20 12:57:40 +07:00
9a54c4712e return summary
Some checks failed
backend-action / build-image (push) Has been cancelled
2024-05-20 12:56:36 +07:00
ca130dc2b8 update admin route
Some checks failed
backend-action / build-image (push) Has been cancelled
2024-05-20 12:34:54 +07:00
5c739bcfa4 fix initial data + workflow
All checks were successful
backend-action / build-image (push) Successful in 3m40s
2024-05-18 10:04:39 +07:00
7e8d8dc523 fix key typo
Some checks failed
backend-action / build-image (push) Has been cancelled
2024-05-18 09:57:47 +07:00
60a7753247 rename to key
Some checks failed
backend-action / build-image (push) Failing after 1m6s
2024-05-18 09:53:57 +07:00
d54649893c added update to server script
Some checks failed
backend-action / build-image (push) Failing after 1m8s
2024-05-18 09:51:27 +07:00
0f6ec77dc0 update to wal mode
All checks were successful
backend-action / build-image (push) Successful in 58s
2024-05-18 09:40:43 +07:00
99f9531d32 added registrationno field
All checks were successful
backend-action / build-image (push) Successful in 1m6s
2024-05-17 15:55:34 +07:00
3c37fbf59b temporary server action for group and inside 2024-05-17 15:33:15 +07:00
35 changed files with 927 additions and 713 deletions

View File

@@ -4,6 +4,8 @@ Dockerfile
minio
node_modules
sqlite.db
sqlite.db-wal
sqlite.db-shm
.DS_Store
dist
.env

View File

@@ -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
View File

@@ -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

View File

@@ -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"

View File

@@ -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",

View File

@@ -3,20 +3,12 @@
import { LocationContext } from "@/components/locationContext";
import { useContext, useState } from "react";
import Grouping from "./Grouping";
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) {
const locationContext = useContext(LocationContext);
const [usedJobs, setUsedJobs] = useState<number[]>([]);
@@ -40,7 +32,22 @@ export default function GroupCreator({ allJobs }: Props) {
);
}
function submit() {} //TODO! submit group
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 ||

View File

@@ -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[];
@@ -19,16 +19,17 @@ export default function Grouping({
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, updateGroup]);
return (
<div className="m-2 flex w-full flex-col gap-2 rounded-md border border-black p-2 shadow-md">
{selectedJob.map((j) => (

20
app/grouping/action.ts Normal file
View 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 });
}

View File

@@ -2,12 +2,19 @@ import { db } from "@/src/db";
import LocationSelector from "../../components/LocationSelector";
import LocationContextProvider from "@/components/locationContext";
import GroupCreator from "./GroupCreator";
import { eq } from "drizzle-orm";
import { user } from "@/src/schema";
export default async function Page() {
const provinces = await db.query.province
.findMany({ with: { zones: true } })
.execute();
const jobList = await db.query.group.findMany().execute();
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} />

View File

@@ -7,14 +7,15 @@ type Props = {
export default function IdComponent({ updateIdList }: Props) {
const [idSet, setIdSet] = useState<Set<string>>(new Set());
const onValidId = (id: string) => {
setIdSet((prev) => new Set(prev.add(id)));
const newSet = new Set(idSet.add(id));
setIdSet(newSet);
updateIdList([...newSet]);
};
const removeCid = (id: string) => {
setIdSet((prev) => new Set([...prev].filter((x) => x !== id)));
const newSet = new Set([...idSet].filter((x) => x !== id));
setIdSet(newSet);
updateIdList([...newSet]);
};
useEffect(() => {
updateIdList([...idSet]);
}, [idSet, updateIdList]);
return (
<div className="flex justify-center">
<div className="flex flex-col gap-2">

13
app/inside/action.ts Normal file
View 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;
}

View File

@@ -1,10 +1,14 @@
"use client";
import { useState } from "react";
import { saveUser } from "./action";
import IdComponent from "./IdComponent";
export default function Page() {
const [idList, setIdList] = useState<string[]>([]);
function submit() {} //TODO! submit inside user
async function submit() {
const rs = await saveUser(idList);
alert(`อัพเดทสำเร็จ ${rs.changes} คน`);
}
return (
<div>
<IdComponent updateIdList={(cids) => setIdList(cids)} />

View File

@@ -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
View 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
View 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
View 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>
);
}

View File

@@ -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:

View File

@@ -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`);

View File

@@ -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`);

View File

@@ -0,0 +1 @@
ALTER TABLE users ADD `rank` integer DEFAULT 9999;

View File

@@ -1 +0,0 @@
ALTER TABLE users ADD `verified` integer DEFAULT false NOT NULL;

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {}
}
}

View File

@@ -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
}
]

View File

@@ -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
View 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
View File

@@ -0,0 +1,3 @@
module.exports = {
output: "standalone",
};

View File

@@ -7,6 +7,7 @@
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon --exec ts-node --swc src/app.ts",
"next-dev": "next dev",
"next-build": "next build",
"start": "node dist/src/app.js",
"build": "swc src -d dist",
"lint": "next lint",

92
src/adminRoute.ts Normal file
View 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 };
}),
});

View File

@@ -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;

View File

@@ -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" });

View File

@@ -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,6 +38,7 @@ 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),
@@ -54,6 +56,7 @@ export const userRelation = relations(user, ({ many, one }) => ({
fields: [user.zone],
references: [zone.id],
}),
userToSelection: many(userToSelection, { relationName: "userRelation" }),
}));
//----------------Group
@@ -117,6 +120,7 @@ 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) }),
);
@@ -147,3 +151,26 @@ export const imageToUser = sqliteTable("image_to_user", {
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",
}),
}),
);

View File

@@ -12,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 })
@@ -20,7 +20,6 @@ const userUpdateSchema = userInsertSchema
const opinionInsertSchema = createInsertSchema(userOpinion)
.omit({
userId: true,
verified: true,
})
.array()
.default([]);
@@ -31,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>;
@@ -49,7 +49,7 @@ export const userRoute = router({
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(
@@ -77,15 +77,16 @@ export const userRoute = router({
confirmChangeImage: protectedProcedure.mutation(
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(
@@ -97,9 +98,10 @@ export const userRoute = router({
input.group,
input.zone,
input.province,
input.userId,
),
),
getAllUserCount: protectedProcedure
getAllUserCount: publicProcedure
.input(
z.object({
group: z.number().optional(),
@@ -146,12 +148,62 @@ async function getAllUser(
group?: number,
zoneId?: number,
provinceId?: number,
userId?: number,
) {
const zoneIds: number[] = await getZone(provinceId);
if (provinceId && zoneIds.length === 0) {
return [];
}
const users = await db.query.user.findMany({
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,
opinions: {
@@ -178,13 +230,39 @@ 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) {
@@ -430,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;
}

View File

@@ -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"]
}