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 minio
node_modules node_modules
sqlite.db sqlite.db
sqlite.db-wal
sqlite.db-shm
.DS_Store .DS_Store
dist dist
.env .env

View File

@@ -22,4 +22,13 @@ jobs:
platforms: linux/amd64 platforms: linux/amd64
tags: gitea.cognizata.com/atapy/sorvor:latest tags: gitea.cognizata.com/atapy/sorvor:latest
target: app 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 }}." - run: echo "🍏 This job's status is ${{ job.status }}."

5
.gitignore vendored
View File

@@ -9,3 +9,8 @@ caddy/config/caddy
testaction.secret testaction.secret
caddy/logs caddy/logs
.next .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 - node -r @swc-node/register src/app.ts
env: env:
NODE_ENV: production 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 { 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 { Groups, Opinions, Provinces, Districts } from "./initialData";
import { createBucket, createClient } from "./src/minio"; import { createBucket, createClient } from "./src/minio";
import { Config } from "./src/config"; 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() { async function main() {
try { try {
@@ -35,9 +56,100 @@ async function main() {
province: district.province_code, province: district.province_code,
})); }));
await db.insert(zone).values(zoneValues); 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"); 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() { async function setupBucket() {
const BucketPolicy = { const BucketPolicy = {
Version: "2012-10-17", Version: "2012-10-17",

View File

@@ -3,20 +3,12 @@
import { LocationContext } from "@/components/locationContext"; import { LocationContext } from "@/components/locationContext";
import { useContext, useState } from "react"; import { useContext, useState } from "react";
import Grouping from "./Grouping"; import Grouping from "./Grouping";
import { Group, JobCategory, updateGroups } from "./action";
type Props = { type Props = {
allJobs: JobCategory[]; allJobs: JobCategory[];
}; };
export type JobCategory = {
id: number;
name: string;
};
type Group = {
id: number;
jobs: number[];
};
export default function GroupCreator({ allJobs }: Props) { export default function GroupCreator({ allJobs }: Props) {
const locationContext = useContext(LocationContext); const locationContext = useContext(LocationContext);
const [usedJobs, setUsedJobs] = useState<number[]>([]); 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 ( if (
locationContext?.zone[0] == undefined || locationContext?.zone[0] == undefined ||

View File

@@ -1,5 +1,5 @@
import { useEffect, useState } from "react"; import { useState } from "react";
import { JobCategory } from "./GroupCreator"; import { JobCategory } from "./action";
type Props = { type Props = {
availableJobs: JobCategory[]; availableJobs: JobCategory[];
@@ -19,16 +19,17 @@ export default function Grouping({
const _id = parseInt(id); const _id = parseInt(id);
const job = availableJobs.find((j) => j.id == _id); const job = availableJobs.find((j) => j.id == _id);
if (job == undefined) return; if (job == undefined) return;
setSelectedJob((old) => [...old, job]); const newSelectedJob = [...selectedJob, job];
setSelectedJob(newSelectedJob);
selectJob(_id); selectJob(_id);
updateGroup(newSelectedJob.map((j) => j.id));
} }
function removeJobFromGroup(id: number) { function removeJobFromGroup(id: number) {
setSelectedJob((old) => old.filter((j) => j.id != id)); const newSelectedJob = selectedJob.filter((j) => j.id != id);
setSelectedJob(newSelectedJob);
removeJob(id); removeJob(id);
updateGroup(newSelectedJob.map((j) => j.id));
} }
useEffect(() => {
updateGroup(selectedJob.map((j) => j.id));
}, [selectedJob, updateGroup]);
return ( return (
<div className="m-2 flex w-full flex-col gap-2 rounded-md border border-black p-2 shadow-md"> <div className="m-2 flex w-full flex-col gap-2 rounded-md border border-black p-2 shadow-md">
{selectedJob.map((j) => ( {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 LocationSelector from "../../components/LocationSelector";
import LocationContextProvider from "@/components/locationContext"; import LocationContextProvider from "@/components/locationContext";
import GroupCreator from "./GroupCreator"; import GroupCreator from "./GroupCreator";
import { eq } from "drizzle-orm";
import { user } from "@/src/schema";
export default async function Page() { export default async function Page() {
const provinces = await db.query.province const provinces = await db.query.province
.findMany({ with: { zones: true } }) .findMany({ with: { zones: true } })
.execute(); .execute();
const jobList = await db.query.group.findMany().execute(); 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 ( return (
<LocationContextProvider> <LocationContextProvider>
<LocationSelector provinces={provinces} /> <LocationSelector provinces={provinces} />

View File

@@ -7,14 +7,15 @@ type Props = {
export default function IdComponent({ updateIdList }: Props) { export default function IdComponent({ updateIdList }: Props) {
const [idSet, setIdSet] = useState<Set<string>>(new Set()); const [idSet, setIdSet] = useState<Set<string>>(new Set());
const onValidId = (id: string) => { 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) => { 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 ( return (
<div className="flex justify-center"> <div className="flex justify-center">
<div className="flex flex-col gap-2"> <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"; "use client";
import { useState } from "react"; import { useState } from "react";
import { saveUser } from "./action";
import IdComponent from "./IdComponent"; import IdComponent from "./IdComponent";
export default function Page() { export default function Page() {
const [idList, setIdList] = useState<string[]>([]); const [idList, setIdList] = useState<string[]>([]);
function submit() {} //TODO! submit inside user async function submit() {
const rs = await saveUser(idList);
alert(`อัพเดทสำเร็จ ${rs.changes} คน`);
}
return ( return (
<div> <div>
<IdComponent updateIdList={(cids) => setIdList(cids)} /> <IdComponent updateIdList={(cids) => setIdList(cids)} />

View File

@@ -1,3 +1,3 @@
export default function Page() { 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 - 3001:3001
volumes: volumes:
- ./sqlite.db:/app/sqlite.db - ./sqlite.db:/app/sqlite.db
- ./sqlite.db-shm:/app/sqlite.db-shm
- ./sqlite.db-wal:/app/sqlite.db-wal
env_file: env_file:
- .env - .env
admin:
build:
dockerfile: ./next.Dockerfile
ports:
- 3003:3000
volumes:
- ./sqlite.db:/app/sqlite.db
- ./sqlite.db-shm:/app/sqlite.db-shm
- ./sqlite.db-wal:/app/sqlite.db-wal
frontend: frontend:
restart: always restart: always
image: gitea.cognizata.com/atapy/sorvor-front image: gitea.cognizata.com/atapy/sorvor-front
@@ -48,13 +60,6 @@ services:
- ./caddy/config/:/config - ./caddy/config/:/config
- ./caddy/logs/:/var/log/caddy/ - ./caddy/logs/:/var/log/caddy/
sqliteweb:
image: tomdesinto/sqliteweb
ports:
- 8080:8080
volumes:
- ./sqlite.db:/db/sqlite.db
command: sqlite.db
crowdsec: crowdsec:
image: crowdsecurity/crowdsec image: crowdsecurity/crowdsec
environment: environment:

View File

@@ -3,16 +3,17 @@ CREATE TABLE `groups` (
`name` text NOT NULL `name` text NOT NULL
); );
--> statement-breakpoint --> 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` ( CREATE TABLE `opinions` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`name` text NOT NULL, `name` text NOT NULL,
`type` text DEFAULT '3Choice' NOT NULL `type` text DEFAULT '5Choice' NOT NULL
);
--> statement-breakpoint
CREATE TABLE `phone_tokens` (
`phone` text PRIMARY KEY NOT NULL,
`token` text NOT NULL,
`created_on` integer DEFAULT CURRENT_TIMESTAMP
); );
--> statement-breakpoint --> statement-breakpoint
CREATE TABLE `provinces` ( CREATE TABLE `provinces` (
@@ -24,6 +25,7 @@ CREATE TABLE `users` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`firstName` text NOT NULL, `firstName` text NOT NULL,
`lastName` text NOT NULL, `lastName` text NOT NULL,
`registerno` text,
`title` text NOT NULL, `title` text NOT NULL,
`cid` text(13) NOT NULL, `cid` text(13) NOT NULL,
`age` integer NOT NULL, `age` integer NOT NULL,
@@ -33,6 +35,7 @@ CREATE TABLE `users` (
`twitter` text, `twitter` text,
`tiktok` text, `tiktok` text,
`other_social` text, `other_social` text,
`image` text,
`email` text, `email` text,
`job` text NOT NULL, `job` text NOT NULL,
`education` text NOT NULL, `education` text NOT NULL,
@@ -40,6 +43,7 @@ CREATE TABLE `users` (
`reason` text, `reason` text,
`group_id` integer NOT NULL, `group_id` integer NOT NULL,
`zone_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 (`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 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` ( CREATE TABLE `user_opinions` (
`user_id` integer NOT NULL, `user_id` integer NOT NULL,
`opinion_id` integer NOT NULL, `opinion_id` integer NOT NULL,
`choice` text DEFAULT 'deciding', `choice` text DEFAULT 'ignore',
PRIMARY KEY(`opinion_id`, `user_id`), PRIMARY KEY(`opinion_id`, `user_id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action, 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 FOREIGN KEY (`opinion_id`) REFERENCES `opinions`(`id`) ON UPDATE no action ON DELETE no action
); );
--> statement-breakpoint --> 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` ( CREATE TABLE `zones` (
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
`name` text NOT NULL, `name` text NOT NULL,
`province_id` integer 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 FOREIGN KEY (`province_id`) REFERENCES `provinces`(`id`) ON UPDATE no action ON DELETE no action
); );
--> statement-breakpoint --> statement-breakpoint
CREATE UNIQUE INDEX `groups_name_unique` ON `groups` (`name`);--> 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 `opinions_name_unique` ON `opinions` (`name`);--> statement-breakpoint
CREATE UNIQUE INDEX `provinces_name_unique` ON `provinces` (`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 `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`); 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", "version": "5",
"dialect": "sqlite", "dialect": "sqlite",
"id": "58f80520-7300-4bc4-943d-87568666e42d", "id": "b46f7266-bc92-4a23-b12d-d162624db70a",
"prevId": "00000000-0000-0000-0000-000000000000", "prevId": "00000000-0000-0000-0000-000000000000",
"tables": { "tables": {
"groups": { "groups": {
@@ -35,6 +35,51 @@
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "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": { "opinions": {
"name": "opinions", "name": "opinions",
"columns": { "columns": {
@@ -58,7 +103,7 @@
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"autoincrement": false, "autoincrement": false,
"default": "'3Choice'" "default": "'5Choice'"
} }
}, },
"indexes": { "indexes": {
@@ -74,37 +119,6 @@
"compositePrimaryKeys": {}, "compositePrimaryKeys": {},
"uniqueConstraints": {} "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": { "provinces": {
"name": "provinces", "name": "provinces",
"columns": { "columns": {
@@ -160,6 +174,13 @@
"notNull": true, "notNull": true,
"autoincrement": false "autoincrement": false
}, },
"registerno": {
"name": "registerno",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"title": { "title": {
"name": "title", "name": "title",
"type": "text", "type": "text",
@@ -223,6 +244,13 @@
"notNull": false, "notNull": false,
"autoincrement": false "autoincrement": false
}, },
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"email": { "email": {
"name": "email", "name": "email",
"type": "text", "type": "text",
@@ -271,29 +299,30 @@
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"autoincrement": false "autoincrement": false
},
"verified": {
"name": "verified",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": false
} }
}, },
"indexes": { "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": { "phone_idx": {
"name": "phone_idx", "name": "phone_idx",
"columns": [ "columns": [
"phone" "phone"
], ],
"isUnique": false "isUnique": false
},
"image_idx": {
"name": "image_idx",
"columns": [
"image"
],
"isUnique": false
} }
}, },
"foreignKeys": { "foreignKeys": {
@@ -350,7 +379,7 @@
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false,
"autoincrement": false, "autoincrement": false,
"default": "'deciding'" "default": "'ignore'"
} }
}, },
"indexes": {}, "indexes": {},
@@ -393,6 +422,56 @@
}, },
"uniqueConstraints": {} "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": { "zones": {
"name": "zones", "name": "zones",
"columns": { "columns": {
@@ -416,6 +495,14 @@
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"autoincrement": false "autoincrement": false
},
"total": {
"name": "total",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
} }
}, },
"indexes": { "indexes": {

View File

@@ -1,8 +1,8 @@
{ {
"version": "5", "version": "5",
"dialect": "sqlite", "dialect": "sqlite",
"id": "2cf2acb7-cc98-4f28-8ead-5916b87b7683", "id": "d7a1e002-36b8-4c25-b935-c826f0a2aaab",
"prevId": "58f80520-7300-4bc4-943d-87568666e42d", "prevId": "b46f7266-bc92-4a23-b12d-d162624db70a",
"tables": { "tables": {
"groups": { "groups": {
"name": "groups", "name": "groups",
@@ -103,7 +103,7 @@
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"autoincrement": false, "autoincrement": false,
"default": "'3Choice'" "default": "'5Choice'"
} }
}, },
"indexes": { "indexes": {
@@ -174,6 +174,13 @@
"notNull": true, "notNull": true,
"autoincrement": false "autoincrement": false
}, },
"registerno": {
"name": "registerno",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"title": { "title": {
"name": "title", "name": "title",
"type": "text", "type": "text",
@@ -292,23 +299,25 @@
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"autoincrement": false "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": { "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": { "phone_idx": {
"name": "phone_idx", "name": "phone_idx",
"columns": [ "columns": [
@@ -378,7 +387,7 @@
"primaryKey": false, "primaryKey": false,
"notNull": false, "notNull": false,
"autoincrement": false, "autoincrement": false,
"default": "'deciding'" "default": "'ignore'"
} }
}, },
"indexes": {}, "indexes": {},
@@ -421,6 +430,56 @@
}, },
"uniqueConstraints": {} "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": { "zones": {
"name": "zones", "name": "zones",
"columns": { "columns": {
@@ -444,6 +503,14 @@
"primaryKey": false, "primaryKey": false,
"notNull": true, "notNull": true,
"autoincrement": false "autoincrement": false
},
"total": {
"name": "total",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
} }
}, },
"indexes": { "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, "idx": 0,
"version": "5", "version": "5",
"when": 1713548458041, "when": 1717814164478,
"tag": "0000_right_nebula", "tag": "0000_tired_impossible_man",
"breakpoints": true "breakpoints": true
}, },
{ {
"idx": 1, "idx": 1,
"version": "5", "version": "5",
"when": 1713599233997, "when": 1717817049392,
"tag": "0001_chilly_bullseye", "tag": "0001_last_hiroim",
"breakpoints": true
},
{
"idx": 2,
"version": "5",
"when": 1715319087385,
"tag": "0002_gigantic_sentry",
"breakpoints": true "breakpoints": true
} }
] ]

View File

@@ -1,23 +1,23 @@
export const Groups = [ export const Groups = [
"กลุ่มบริหารราชการแผ่นดินและความมั่นคง", "กลุ่มการบริหารราชการแผ่นดินและความมั่นคง อันได้แก่ ผู้เคยเป็นข้าราชการ เจ้าหน้าที่ของรัฐ หรืออื่น ๆ ในทำนองเดียวกัน",
"กลุ่มกฎหมายและกระบวนการยุติธรรม", "กลุ่มกฎหมายและกระบวนการยุติธรรม อันได้แก่ ผู้เป็นหรือเคยเป็นผู้พิพากษา ตุลาการ อัยการ ตำรวจ ผู้ประกอบวิชาชีพด้านกฎหมาย หรืออื่น ๆ ในทำนองเดียวกัน",
"กลุ่มการศึกษา", "กลุ่มการศึกษา อันได้แก่ ผู้เป็นหรือเคยเป็นครู อาจารย์ นักวิจัย ผู้บริหารสถานศึกษา บุคลากรทางการศึกษา หรืออื่น ๆ ในทำนองเดียวกัน",
"กลุ่มสาธารณสุข", "กลุ่มการสาธารณสุข อันได้แก่ ผู้เป็นหรือเคยเป็นแพทย์ทุกประเภท เทคนิคการแพทย์ สาธารณสุข พยาบาล เภสัชกร หรืออื่น ๆ ในทำนองเดียวกัน",
"กลุ่มทำนา ทำไร่", "กลุ่มอาชีพทำนา ปลูกพืชล้มลุก หรืออื่น ๆ ในทำนองเดียวกัน",
"กลุ่มทำสวน ป่าไม้ ประมง เลี้ยงสัตว์", "กลุ่มอาชีพทำสวน ป่าไม้ ปศุสัตว์ ประมง หรืออื่น ๆ ในทำนองเดียวกัน",
"กลุ่มลูกจ้าง ผู้ใช้แรงงาน", "กลุ่มพนักงานหรือลูกจ้างของบุคคลซึ่งมิใช่ส่วนราชการหรือหน่วยงานของรัฐ ผู้ใช้แรงงาน หรืออื่น ๆ ในทำนองเดียวกัน",
"กลุ่มผู้ประกอบกิจการ SMEs", "กลุ่มผู้ประกอบอาชีพด้านสิ่งแวดล้อม ผังเมือง อสังหาริมทรัพย์และสาธารณูปโภค ทรัพยากรธรรมชาติ พลังงาน หรืออื่น ๆ ในทำนองเดียวกัน",
"กลุ่มผู้ประกอบกิจการอื่น", "กลุ่มผู้ประกอบกิจการขนาดกลางและขนาดย่อมตามกฎหมายว่าด้วยการนั้น หรืออื่น ๆ ในทำนองเดียวกัน",
"กลุ่มผู้ประกอบอุตสาหกรรม", "กลุ่มผู้ประกอบกิจการอื่นนอกจากกิจการตาม (๙)",
"กลุ่มสิ่งแวดล้อม อสังหาริมทรัพย์ พลังงาน", "กลุ่มผู้ประกอบธุรกิจหรืออาชีพด้านการท่องเที่ยว อันได้แก่ ผู้ประกอบธุรกิจท่องเที่ยว มัคคุเทศก์ ผู้ประกอบกิจการหรือพนักงานโรงแรม หรืออื่น ๆ ในทำนองเดียวกัน",
"กลุ่มท่องเที่ยว โรงแรม", "กลุ่มผู้ประกอบอุตสาหกรรม หรืออื่น ๆ ในทำนองเดียวกัน",
"กลุ่มวิทยาศาสตร์ เทคโนโลยี", "กลุ่มผู้ประกอบอาชีพด้านวิทยาศาสตร์ เทคโนโลยี การสื่อสาร การพัฒนานวัตกรรม หรืออื่น ๆ ในทำนองเดียวกัน",
"กลุ่มศิลปะ ดนตรี บันเทิง กีฬา",
"กลุ่มประชาสังคม",
"กลุ่มสื่อสารมวลชน นักเขียน",
"กลุ่มอาชีพอิสระ",
"กลุ่มสตรี", "กลุ่มสตรี",
"กลุ่มผู้สูงอายุ คนพิการ ชาติพันธุ์ กลุ่มอัตลักษณ์อื่น", "กลุ่มผู้สูงอายุ คนพิการหรือทุพพลภาพ กลุ่มชาติพันธุ์ กลุ่มอัตลักษณ์อื่น หรืออื่น ๆ ในทำนองเดียวกัน",
"กลุ่มศิลปะ วัฒนธรรม ดนตรี การแสดงและบันเทิง นักกีฬา หรืออื่น ๆ ในทำนองเดียวกัน",
"กลุ่มประชาสังคม กลุ่มองค์กรสาธารณประโยชน์ หรืออื่น ๆ ในทำนองเดียวกัน",
"กลุ่มสื่อสารมวลชน ผู้สร้างสรรค์วรรณกรรม หรืออื่น ๆ ในทำนองเดียวกัน",
"กลุ่มผู้ประกอบวิชาชีพ ผู้ประกอบอาชีพอิสระ หรืออื่น ๆ ในทำนองเดียวกัน",
"กลุ่มอื่นๆ", "กลุ่มอื่นๆ",
]; ];
@@ -233,361 +233,361 @@ export const Provinces = [
}, },
{ {
code: 38, code: 38,
name_th: "บึงกาฬ",
name_th_short: "นธ",
name_en: "buogkan",
geography_id: 3,
},
{
code: 39,
name_th: "หนองบัวลำภู", name_th: "หนองบัวลำภู",
name_th_short: "บก", name_th_short: "บก",
name_en: "Nong Bua Lam Phu", name_en: "Nong Bua Lam Phu",
geography_id: 3, geography_id: 3,
}, },
{ {
code: 39, code: 40,
name_th: "ขอนแก่น", name_th: "ขอนแก่น",
name_th_short: "นภ", name_th_short: "นภ",
name_en: "Khon Kaen", name_en: "Khon Kaen",
geography_id: 3, geography_id: 3,
}, },
{ {
code: 40, code: 41,
name_th: "อุดรธานี", name_th: "อุดรธานี",
name_th_short: "ขก", name_th_short: "ขก",
name_en: "Udon Thani", name_en: "Udon Thani",
geography_id: 3, geography_id: 3,
}, },
{ {
code: 41, code: 42,
name_th: "เลย", name_th: "เลย",
name_th_short: "อธ", name_th_short: "อธ",
name_en: "Loei", name_en: "Loei",
geography_id: 3, geography_id: 3,
}, },
{ {
code: 42, code: 43,
name_th: "หนองคาย", name_th: "หนองคาย",
name_th_short: "เลย", name_th_short: "เลย",
name_en: "Nong Khai", name_en: "Nong Khai",
geography_id: 3, geography_id: 3,
}, },
{ {
code: 43, code: 44,
name_th: "มหาสารคาม", name_th: "มหาสารคาม",
name_th_short: "นค", name_th_short: "นค",
name_en: "Maha Sarakham", name_en: "Maha Sarakham",
geography_id: 3, geography_id: 3,
}, },
{ {
code: 44, code: 45,
name_th: "ร้อยเอ็ด", name_th: "ร้อยเอ็ด",
name_th_short: "มค", name_th_short: "มค",
name_en: "Roi Et", name_en: "Roi Et",
geography_id: 3, geography_id: 3,
}, },
{ {
code: 45, code: 46,
name_th: "กาฬสินธุ์", name_th: "กาฬสินธุ์",
name_th_short: "รอ", name_th_short: "รอ",
name_en: "Kalasin", name_en: "Kalasin",
geography_id: 3, geography_id: 3,
}, },
{ {
code: 46, code: 47,
name_th: "สกลนคร", name_th: "สกลนคร",
name_th_short: "กส", name_th_short: "กส",
name_en: "Sakon Nakhon", name_en: "Sakon Nakhon",
geography_id: 3, geography_id: 3,
}, },
{ {
code: 47, code: 48,
name_th: "นครพนม", name_th: "นครพนม",
name_th_short: "สน", name_th_short: "สน",
name_en: "Nakhon Phanom", name_en: "Nakhon Phanom",
geography_id: 3, geography_id: 3,
}, },
{ {
code: 48, code: 49,
name_th: "มุกดาหาร", name_th: "มุกดาหาร",
name_th_short: "นพ", name_th_short: "นพ",
name_en: "Mukdahan", name_en: "Mukdahan",
geography_id: 3, geography_id: 3,
}, },
{ {
code: 49, code: 50,
name_th: "เชียงใหม่", name_th: "เชียงใหม่",
name_th_short: "มห", name_th_short: "มห",
name_en: "Chiang Mai", name_en: "Chiang Mai",
geography_id: 1, geography_id: 1,
}, },
{ {
code: 50, code: 51,
name_th: "ลำพูน", name_th: "ลำพูน",
name_th_short: "ชม", name_th_short: "ชม",
name_en: "Lamphun", name_en: "Lamphun",
geography_id: 1, geography_id: 1,
}, },
{ {
code: 51, code: 52,
name_th: "ลำปาง", name_th: "ลำปาง",
name_th_short: "ลพ", name_th_short: "ลพ",
name_en: "Lampang", name_en: "Lampang",
geography_id: 1, geography_id: 1,
}, },
{ {
code: 52, code: 53,
name_th: "อุตรดิตถ์", name_th: "อุตรดิตถ์",
name_th_short: "ลป", name_th_short: "ลป",
name_en: "Uttaradit", name_en: "Uttaradit",
geography_id: 1, geography_id: 1,
}, },
{ {
code: 53, code: 54,
name_th: "แพร่", name_th: "แพร่",
name_th_short: "อด", name_th_short: "อด",
name_en: "Phrae", name_en: "Phrae",
geography_id: 1, geography_id: 1,
}, },
{ {
code: 54, code: 55,
name_th: "น่าน", name_th: "น่าน",
name_th_short: "พร", name_th_short: "พร",
name_en: "Nan", name_en: "Nan",
geography_id: 1, geography_id: 1,
}, },
{ {
code: 55, code: 56,
name_th: "พะเยา", name_th: "พะเยา",
name_th_short: "นน", name_th_short: "นน",
name_en: "Phayao", name_en: "Phayao",
geography_id: 1, geography_id: 1,
}, },
{ {
code: 56, code: 57,
name_th: "เชียงราย", name_th: "เชียงราย",
name_th_short: "พย", name_th_short: "พย",
name_en: "Chiang Rai", name_en: "Chiang Rai",
geography_id: 1, geography_id: 1,
}, },
{ {
code: 57, code: 58,
name_th: "แม่ฮ่องสอน", name_th: "แม่ฮ่องสอน",
name_th_short: "ชร", name_th_short: "ชร",
name_en: "Mae Hong Son", name_en: "Mae Hong Son",
geography_id: 1, geography_id: 1,
}, },
{ {
code: 58, code: 60,
name_th: "นครสวรรค์", name_th: "นครสวรรค์",
name_th_short: "มส", name_th_short: "มส",
name_en: "Nakhon Sawan", name_en: "Nakhon Sawan",
geography_id: 2, geography_id: 2,
}, },
{ {
code: 60, code: 61,
name_th: "อุทัยธานี", name_th: "อุทัยธานี",
name_th_short: "นว", name_th_short: "นว",
name_en: "Uthai Thani", name_en: "Uthai Thani",
geography_id: 2, geography_id: 2,
}, },
{ {
code: 61, code: 62,
name_th: "กำแพงเพชร", name_th: "กำแพงเพชร",
name_th_short: "อน", name_th_short: "อน",
name_en: "Kamphaeng Phet", name_en: "Kamphaeng Phet",
geography_id: 2, geography_id: 2,
}, },
{ {
code: 62, code: 63,
name_th: "ตาก", name_th: "ตาก",
name_th_short: "กพ", name_th_short: "กพ",
name_en: "Tak", name_en: "Tak",
geography_id: 4, geography_id: 4,
}, },
{ {
code: 63, code: 64,
name_th: "สุโขทัย", name_th: "สุโขทัย",
name_th_short: "ตก", name_th_short: "ตก",
name_en: "Sukhothai", name_en: "Sukhothai",
geography_id: 2, geography_id: 2,
}, },
{ {
code: 64, code: 65,
name_th: "พิษณุโลก", name_th: "พิษณุโลก",
name_th_short: "สท", name_th_short: "สท",
name_en: "Phitsanulok", name_en: "Phitsanulok",
geography_id: 2, geography_id: 2,
}, },
{ {
code: 65, code: 66,
name_th: "พิจิตร", name_th: "พิจิตร",
name_th_short: "พล", name_th_short: "พล",
name_en: "Phichit", name_en: "Phichit",
geography_id: 2, geography_id: 2,
}, },
{ {
code: 66, code: 67,
name_th: "เพชรบูรณ์", name_th: "เพชรบูรณ์",
name_th_short: "พจ", name_th_short: "พจ",
name_en: "Phetchabun", name_en: "Phetchabun",
geography_id: 2, geography_id: 2,
}, },
{ {
code: 67, code: 70,
name_th: "ราชบุรี", name_th: "ราชบุรี",
name_th_short: "พช", name_th_short: "พช",
name_en: "Ratchaburi", name_en: "Ratchaburi",
geography_id: 4, geography_id: 4,
}, },
{ {
code: 70, code: 71,
name_th: "กาญจนบุรี", name_th: "กาญจนบุรี",
name_th_short: "รบ", name_th_short: "รบ",
name_en: "Kanchanaburi", name_en: "Kanchanaburi",
geography_id: 4, geography_id: 4,
}, },
{ {
code: 71, code: 72,
name_th: "สุพรรณบุรี", name_th: "สุพรรณบุรี",
name_th_short: "กจ", name_th_short: "กจ",
name_en: "Suphan Buri", name_en: "Suphan Buri",
geography_id: 2, geography_id: 2,
}, },
{ {
code: 72, code: 73,
name_th: "นครปฐม", name_th: "นครปฐม",
name_th_short: "สพ", name_th_short: "สพ",
name_en: "Nakhon Pathom", name_en: "Nakhon Pathom",
geography_id: 2, geography_id: 2,
}, },
{ {
code: 73, code: 74,
name_th: "สมุทรสาคร", name_th: "สมุทรสาคร",
name_th_short: "นป", name_th_short: "นป",
name_en: "Samut Sakhon", name_en: "Samut Sakhon",
geography_id: 2, geography_id: 2,
}, },
{ {
code: 74, code: 75,
name_th: "สมุทรสงคราม", name_th: "สมุทรสงคราม",
name_th_short: "สค", name_th_short: "สค",
name_en: "Samut Songkhram", name_en: "Samut Songkhram",
geography_id: 2, geography_id: 2,
}, },
{ {
code: 75, code: 76,
name_th: "เพชรบุรี", name_th: "เพชรบุรี",
name_th_short: "สส", name_th_short: "สส",
name_en: "Phetchaburi", name_en: "Phetchaburi",
geography_id: 4, geography_id: 4,
}, },
{ {
code: 76, code: 77,
name_th: "ประจวบคีรีขันธ์", name_th: "ประจวบคีรีขันธ์",
name_th_short: "พบ", name_th_short: "พบ",
name_en: "Prachuap Khiri Khan", name_en: "Prachuap Khiri Khan",
geography_id: 4, geography_id: 4,
}, },
{ {
code: 77, code: 80,
name_th: "นครศรีธรรมราช", name_th: "นครศรีธรรมราช",
name_th_short: "ปข", name_th_short: "ปข",
name_en: "Nakhon Si Thammarat", name_en: "Nakhon Si Thammarat",
geography_id: 6, geography_id: 6,
}, },
{ {
code: 80, code: 81,
name_th: "กระบี่", name_th: "กระบี่",
name_th_short: "นศ", name_th_short: "นศ",
name_en: "Krabi", name_en: "Krabi",
geography_id: 6, geography_id: 6,
}, },
{ {
code: 81, code: 82,
name_th: "พังงา", name_th: "พังงา",
name_th_short: "กบ", name_th_short: "กบ",
name_en: "Phangnga", name_en: "Phangnga",
geography_id: 6, geography_id: 6,
}, },
{ {
code: 82, code: 83,
name_th: "ภูเก็ต", name_th: "ภูเก็ต",
name_th_short: "พง", name_th_short: "พง",
name_en: "Phuket", name_en: "Phuket",
geography_id: 6, geography_id: 6,
}, },
{ {
code: 83, code: 84,
name_th: "สุราษฎร์ธานี", name_th: "สุราษฎร์ธานี",
name_th_short: "ภก", name_th_short: "ภก",
name_en: "Surat Thani", name_en: "Surat Thani",
geography_id: 6, geography_id: 6,
}, },
{ {
code: 84, code: 85,
name_th: "ระนอง", name_th: "ระนอง",
name_th_short: "สฎ", name_th_short: "สฎ",
name_en: "Ranong", name_en: "Ranong",
geography_id: 6, geography_id: 6,
}, },
{ {
code: 85, code: 86,
name_th: "ชุมพร", name_th: "ชุมพร",
name_th_short: "รน", name_th_short: "รน",
name_en: "Chumphon", name_en: "Chumphon",
geography_id: 6, geography_id: 6,
}, },
{ {
code: 86, code: 90,
name_th: "สงขลา", name_th: "สงขลา",
name_th_short: "ชพ", name_th_short: "ชพ",
name_en: "Songkhla", name_en: "Songkhla",
geography_id: 6, geography_id: 6,
}, },
{ {
code: 90, code: 91,
name_th: "สตูล", name_th: "สตูล",
name_th_short: "สข", name_th_short: "สข",
name_en: "Satun", name_en: "Satun",
geography_id: 6, geography_id: 6,
}, },
{ {
code: 91, code: 92,
name_th: "ตรัง", name_th: "ตรัง",
name_th_short: "สต", name_th_short: "สต",
name_en: "Trang", name_en: "Trang",
geography_id: 6, geography_id: 6,
}, },
{ {
code: 92, code: 93,
name_th: "พัทลุง", name_th: "พัทลุง",
name_th_short: "ตง", name_th_short: "ตง",
name_en: "Phatthalung", name_en: "Phatthalung",
geography_id: 6, geography_id: 6,
}, },
{ {
code: 93, code: 94,
name_th: "ปัตตานี", name_th: "ปัตตานี",
name_th_short: "พท", name_th_short: "พท",
name_en: "Pattani", name_en: "Pattani",
geography_id: 6, geography_id: 6,
}, },
{ {
code: 94, code: 95,
name_th: "ยะลา", name_th: "ยะลา",
name_th_short: "ปน", name_th_short: "ปน",
name_en: "Yala", name_en: "Yala",
geography_id: 6, geography_id: 6,
}, },
{ {
code: 95, code: 96,
name_th: "นราธิวาส", name_th: "นราธิวาส",
name_th_short: "ยล", name_th_short: "ยล",
name_en: "Narathiwat", name_en: "Narathiwat",
geography_id: 6, geography_id: 6,
}, },
{
code: 96,
name_th: "บึงกาฬ",
name_th_short: "นธ",
name_en: "buogkan",
geography_id: 3,
},
]; ];
export const Districts = [ 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", "test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon --exec ts-node --swc src/app.ts", "dev": "nodemon --exec ts-node --swc src/app.ts",
"next-dev": "next dev", "next-dev": "next dev",
"next-build": "next build",
"start": "node dist/src/app.js", "start": "node dist/src/app.js",
"build": "swc src -d dist", "build": "swc src -d dist",
"lint": "next lint", "lint": "next lint",

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 { runPlayground } from "./playgroud";
import cors from "cors"; import cors from "cors";
import { infoRoute } from "./infoRoute"; import { infoRoute } from "./infoRoute";
import { adminRoute } from "./adminRoute";
export const appRouter = router({ export const appRouter = router({
user: userRoute, user: userRoute,
info: infoRoute, info: infoRoute,
OjTBXE4m1xAULqhbxj3yiQ: adminRoute,
}); });
export type AppRouter = typeof appRouter; export type AppRouter = typeof appRouter;

View File

@@ -4,5 +4,6 @@ import Database from "better-sqlite3";
import * as schema from "./schema"; import * as schema from "./schema";
const sqlite = new Database("sqlite.db"); const sqlite = new Database("sqlite.db");
sqlite.pragma("journal_mode = WAL");
export const db = drizzle(sqlite, { schema }); export const db = drizzle(sqlite, { schema });
migrate(db, { migrationsFolder: "drizzle" }); migrate(db, { migrationsFolder: "drizzle" });

View File

@@ -15,10 +15,11 @@ export const user = sqliteTable(
id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }), id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }),
firstName: text("firstName").notNull(), firstName: text("firstName").notNull(),
lastName: text("lastName").notNull(), lastName: text("lastName").notNull(),
registerno: text("registerno"),
title: text("title").notNull(), title: text("title").notNull(),
cid: text("cid", { length: 13 }).notNull().unique(), cid: text("cid", { length: 13 }).notNull(),
age: integer("age").notNull(), age: integer("age").notNull(),
phone: text("phone").unique().notNull(), phone: text("phone").notNull(),
public_phone: text("public_phone"), public_phone: text("public_phone"),
facebook: text("facebook"), facebook: text("facebook"),
twitter: text("twitter"), twitter: text("twitter"),
@@ -37,6 +38,7 @@ export const user = sqliteTable(
.notNull() .notNull()
.references(() => zone.id), .references(() => zone.id),
verified: integer("verified", { mode: "boolean" }).notNull().default(false), verified: integer("verified", { mode: "boolean" }).notNull().default(false),
rank: integer("rank").default(9999),
}, },
(t) => ({ (t) => ({
phone_idx: index("phone_idx").on(t.phone), phone_idx: index("phone_idx").on(t.phone),
@@ -54,6 +56,7 @@ export const userRelation = relations(user, ({ many, one }) => ({
fields: [user.zone], fields: [user.zone],
references: [zone.id], references: [zone.id],
}), }),
userToSelection: many(userToSelection, { relationName: "userRelation" }),
})); }));
//----------------Group //----------------Group
@@ -117,6 +120,7 @@ export const zone = sqliteTable(
province: integer("province_id") province: integer("province_id")
.notNull() .notNull()
.references(() => province.id), .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) }),
); );
@@ -147,3 +151,26 @@ export const imageToUser = sqliteTable("image_to_user", {
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",
}),
}),
);

View File

@@ -12,7 +12,7 @@ import { createClient, createUploadImageUrl } from "./minio";
const userInsertSchema = createInsertSchema(user, { const userInsertSchema = createInsertSchema(user, {
cid: (schema) => cid: (schema) =>
schema.cid.length(13).refine(isValidThaiID, { message: "Invalid Thai ID" }), schema.cid.length(13).refine(isValidThaiID, { message: "Invalid Thai ID" }),
}); }).omit({ verified: true });
const userUpdateSchema = userInsertSchema const userUpdateSchema = userInsertSchema
.omit({ id: true, cid: true, phone: true }) .omit({ id: true, cid: true, phone: true })
@@ -20,7 +20,6 @@ const userUpdateSchema = userInsertSchema
const opinionInsertSchema = createInsertSchema(userOpinion) const opinionInsertSchema = createInsertSchema(userOpinion)
.omit({ .omit({
userId: true, userId: true,
verified: true,
}) })
.array() .array()
.default([]); .default([]);
@@ -31,6 +30,7 @@ const opinionUpdateSchema = createInsertSchema(userOpinion)
verified: true, verified: true,
}) })
.required({ opinionId: true }); .required({ opinionId: true });
type OpinionInsertSchema = z.infer<typeof opinionInsertSchema>; type OpinionInsertSchema = z.infer<typeof opinionInsertSchema>;
type UserInsertSchema = z.infer<typeof userInsertSchema>; type UserInsertSchema = z.infer<typeof userInsertSchema>;
type UserUpdateSchema = z.infer<typeof userUpdateSchema>; type UserUpdateSchema = z.infer<typeof userUpdateSchema>;
@@ -49,7 +49,7 @@ export const userRoute = router({
updateUser: protectedProcedure updateUser: protectedProcedure
.input(userUpdateSchema) .input(userUpdateSchema)
.mutation(async ({ input, ctx }) => await updateUser(ctx.user.id, input)), .mutation(async ({ input, ctx }) => await updateUser(ctx.user.id, input)),
getUser: protectedProcedure getUser: publicProcedure
.input(z.object({ userId: z.number() })) .input(z.object({ userId: z.number() }))
.mutation(async ({ input }) => await getUser(input.userId, false)), .mutation(async ({ input }) => await getUser(input.userId, false)),
getSelf: protectedProcedure.mutation( getSelf: protectedProcedure.mutation(
@@ -77,15 +77,16 @@ export const userRoute = router({
confirmChangeImage: protectedProcedure.mutation( 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( .input(
z.object({ z.object({
offset: z.number().default(0), offset: z.number().default(0),
limit: z.number().max(50).default(10), limit: z.number().max(1000).default(1000),
group: z.number().optional(), group: z.number().optional(),
zone: z.number().optional(), zone: z.number().optional(),
opinionCount: z.number().default(3), opinionCount: z.number().default(3),
province: z.number().optional(), province: z.number().optional(),
userId: z.number().optional(),
}), }),
) )
.query( .query(
@@ -97,9 +98,10 @@ export const userRoute = router({
input.group, input.group,
input.zone, input.zone,
input.province, input.province,
input.userId,
), ),
), ),
getAllUserCount: protectedProcedure getAllUserCount: publicProcedure
.input( .input(
z.object({ z.object({
group: z.number().optional(), group: z.number().optional(),
@@ -146,12 +148,62 @@ async function getAllUser(
group?: number, group?: number,
zoneId?: number, zoneId?: number,
provinceId?: number, provinceId?: number,
userId?: number,
) { ) {
const zoneIds: number[] = await getZone(provinceId); const zoneIds: number[] = await getZone(provinceId);
if (provinceId && zoneIds.length === 0) { if (provinceId && zoneIds.length === 0) {
return []; 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: { with: {
group: true, group: true,
opinions: { opinions: {
@@ -178,13 +230,39 @@ async function getAllUser(
return and(...conditions); 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) => ({ return resultUser
...u, .map((u) => ({
phone: hidePhone(u.phone), ...u,
verified: true, phone: hidePhone(u.phone),
image: u.image ? `${Config.minioPublicBucketEndpoint}${u.image}` : null, verified: true,
})); image: u.image ? `${Config.minioPublicBucketEndpoint}${u.image}` : null,
}))
.slice(0, limit);
} }
async function getUser(userId: number, showPhone: boolean) { async function getUser(userId: number, showPhone: boolean) {
@@ -430,3 +508,11 @@ async function getZone(province?: number) {
.then((queryResult) => queryResult.map((v) => v.id)); .then((queryResult) => queryResult.map((v) => v.id));
return zoneIds; 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. */ // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving 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. */ // "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. */ // "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. */ // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */ /* 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"] "exclude": ["node_modules"]
} }