Compare commits
2 Commits
d125687536
...
c1a019a461
| Author | SHA1 | Date | |
|---|---|---|---|
|
c1a019a461
|
|||
|
5c4abf24bb
|
10
.eslintrc.json
Normal file
10
.eslintrc.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"next/core-web-vitals",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:prettier/recommended"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"prefer-const": "error"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { LocationContext } from "@/components/locationContenxt";
|
import { LocationContext } from "@/components/locationContext";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
import Grouping from "./Grouping";
|
import Grouping from "./Grouping";
|
||||||
import { unique } from "drizzle-orm/mysql-core";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
allJobs: JobCategory[];
|
allJobs: JobCategory[];
|
||||||
@@ -19,10 +18,10 @@ type Group = {
|
|||||||
jobs: number[];
|
jobs: number[];
|
||||||
};
|
};
|
||||||
export default function GroupCreator({ allJobs }: Props) {
|
export default function GroupCreator({ allJobs }: Props) {
|
||||||
let locationContext = useContext(LocationContext);
|
const locationContext = useContext(LocationContext);
|
||||||
let [usedJobs, setUsedJobs] = useState<number[]>([]);
|
const [usedJobs, setUsedJobs] = useState<number[]>([]);
|
||||||
let [groups, setGroup] = useState<Group[]>(
|
const [groups, setGroup] = useState<Group[]>(
|
||||||
[...Array(4).keys()].map((i) => ({ id: i + 1, jobs: [] }))
|
[...Array(4).keys()].map((i) => ({ id: i + 1, jobs: [] })),
|
||||||
);
|
);
|
||||||
function useJob(id: number) {
|
function useJob(id: number) {
|
||||||
setUsedJobs((u) => [...u, id]);
|
setUsedJobs((u) => [...u, id]);
|
||||||
@@ -37,13 +36,11 @@ export default function GroupCreator({ allJobs }: Props) {
|
|||||||
groups.map((g) => {
|
groups.map((g) => {
|
||||||
if (g.id != id) return g;
|
if (g.id != id) return g;
|
||||||
else return { id, jobs };
|
else return { id, jobs };
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
function submit() {} //TODO! submit group
|
||||||
console.log(groups);
|
|
||||||
}, [groups]);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
locationContext?.zone[0] == undefined ||
|
locationContext?.zone[0] == undefined ||
|
||||||
@@ -66,7 +63,9 @@ export default function GroupCreator({ allJobs }: Props) {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<button className="bg-green-300 rounded-md p-2">ยืนยัน</button>
|
<button className="rounded-md bg-green-300 p-2" onClick={submit}>
|
||||||
|
ยืนยัน
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ export default function Grouping({
|
|||||||
removeJob,
|
removeJob,
|
||||||
updateGroup,
|
updateGroup,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
let [selectedJob, setSelectedJob] = useState<JobCategory[]>([]);
|
const [selectedJob, setSelectedJob] = useState<JobCategory[]>([]);
|
||||||
function addJob(id: string) {
|
function addJob(id: string) {
|
||||||
let _id = parseInt(id);
|
const _id = parseInt(id);
|
||||||
let 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]);
|
setSelectedJob((old) => [...old, job]);
|
||||||
selectJob(_id);
|
selectJob(_id);
|
||||||
@@ -28,9 +28,9 @@ export default function Grouping({
|
|||||||
}
|
}
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateGroup(selectedJob.map((j) => j.id));
|
updateGroup(selectedJob.map((j) => j.id));
|
||||||
}, [selectedJob]);
|
}, [selectedJob, updateGroup]);
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2 m-2 p-2 border-black rounded-md shadow-md border w-full">
|
<div className="m-2 flex w-full flex-col gap-2 rounded-md border border-black p-2 shadow-md">
|
||||||
{selectedJob.map((j) => (
|
{selectedJob.map((j) => (
|
||||||
<div className="flex justify-between gap-2 p-2" key={j.id}>
|
<div className="flex justify-between gap-2 p-2" key={j.id}>
|
||||||
<p>{j.name}</p>
|
<p>{j.name}</p>
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { db } from "@/src/db";
|
import { db } from "@/src/db";
|
||||||
import LocationSelector from "../../components/LocationSelector";
|
import LocationSelector from "../../components/LocationSelector";
|
||||||
import LocationContextProvider from "@/components/locationContenxt";
|
import LocationContextProvider from "@/components/locationContext";
|
||||||
import GroupCreator from "./GroupCreator";
|
import GroupCreator from "./GroupCreator";
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
let provinces = await db.query.province
|
const provinces = await db.query.province
|
||||||
.findMany({ with: { zones: true } })
|
.findMany({ with: { zones: true } })
|
||||||
.execute();
|
.execute();
|
||||||
let jobList = await db.query.group.findMany().execute();
|
const jobList = await db.query.group.findMany().execute();
|
||||||
return (
|
return (
|
||||||
<LocationContextProvider>
|
<LocationContextProvider>
|
||||||
<LocationSelector provinces={provinces} />
|
<LocationSelector provinces={provinces} />
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function IdComponent({ updateIdList }: Props) {
|
export default function IdComponent({ updateIdList }: Props) {
|
||||||
let [idSet, setIdSet] = useState<Set<string>>(new Set());
|
const [idSet, setIdSet] = useState<Set<string>>(new Set());
|
||||||
let onValidId = (id: string) => {
|
const onValidId = (id: string) => {
|
||||||
setIdSet((prev) => new Set(prev.add(id)));
|
setIdSet((prev) => new Set(prev.add(id)));
|
||||||
};
|
};
|
||||||
let removeCid = (id: string) => {
|
const removeCid = (id: string) => {
|
||||||
setIdSet((prev) => new Set([...prev].filter((x) => x !== id)));
|
setIdSet((prev) => new Set([...prev].filter((x) => x !== id)));
|
||||||
};
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateIdList([...idSet]);
|
updateIdList([...idSet]);
|
||||||
}, [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">
|
||||||
@@ -37,7 +37,7 @@ function FixedId({ cid, removeCid }: FixedIdProps) {
|
|||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input type="text" className="border-2" disabled value={cid} />
|
<input type="text" className="border-2" disabled value={cid} />
|
||||||
<button
|
<button
|
||||||
className="bg-red-300 p-2 rounded-md"
|
className="rounded-md bg-red-300 p-2"
|
||||||
onClick={() => removeCid(cid)}
|
onClick={() => removeCid(cid)}
|
||||||
>
|
>
|
||||||
ลบ
|
ลบ
|
||||||
@@ -51,17 +51,17 @@ type SingleIdProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function SingleIdComponent({ onValidId }: SingleIdProps) {
|
function SingleIdComponent({ onValidId }: SingleIdProps) {
|
||||||
let [isValid, setIsValid] = useState(false);
|
const [isValid, setIsValid] = useState(false);
|
||||||
let [cid, setCid] = useState("");
|
const [cid, setCid] = useState("");
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isValidId = isValidThaiID(cid);
|
const isValidId = isValidThaiID(cid);
|
||||||
setIsValid(isValidId);
|
setIsValid(isValidId);
|
||||||
if (isValidId) {
|
if (isValidId) {
|
||||||
onValidId(cid);
|
onValidId(cid);
|
||||||
|
|
||||||
setCid("");
|
setCid("");
|
||||||
}
|
}
|
||||||
}, [cid]);
|
}, [cid, onValidId]);
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -3,16 +3,14 @@ import { useState } from "react";
|
|||||||
import IdComponent from "./IdComponent";
|
import IdComponent from "./IdComponent";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
let [idList, setIdList] = useState<string[]>([]);
|
const [idList, setIdList] = useState<string[]>([]);
|
||||||
function submit() {
|
function submit() {} //TODO! submit inside user
|
||||||
console.log(idList);
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<IdComponent updateIdList={(cids) => setIdList(cids)} />
|
<IdComponent updateIdList={(cids) => setIdList(cids)} />
|
||||||
<p className="flex justify-center gap-4 mt-2 items-center">
|
<p className="mt-2 flex items-center justify-center gap-4">
|
||||||
Total: {idList.length}{" "}
|
Total: {idList.length}{" "}
|
||||||
<button className="bg-green-200 p-2 rounded-md" onClick={submit}>
|
<button className="rounded-md bg-green-200 p-2" onClick={submit}>
|
||||||
Submit
|
Submit
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { LocationContext } from "../locationContenxt";
|
import { LocationContext } from "../locationContext";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
provinces: Province[];
|
provinces: Province[];
|
||||||
@@ -17,20 +17,20 @@ type Zone = {
|
|||||||
province: number;
|
province: number;
|
||||||
};
|
};
|
||||||
export default function LocationSelector({ provinces }: Props) {
|
export default function LocationSelector({ provinces }: Props) {
|
||||||
let [provinceId, setProvinceId] = useState<number | undefined>(undefined);
|
const [provinceId, setProvinceId] = useState<number | undefined>(undefined);
|
||||||
let [amphurList, setAmphurList] = useState<Zone[] | undefined>(undefined);
|
const [amphurList, setAmphurList] = useState<Zone[] | undefined>(undefined);
|
||||||
let [amphurId, setAmphurId] = useState<number | undefined>(undefined);
|
const [amphurId, setAmphurId] = useState<number | undefined>(undefined);
|
||||||
const locationContext = useContext(LocationContext);
|
const locationContext = useContext(LocationContext);
|
||||||
function setProvince(_id: string) {
|
function setProvince(_id: string) {
|
||||||
let id = parseInt(_id);
|
const id = parseInt(_id);
|
||||||
setProvinceId(id);
|
setProvinceId(id);
|
||||||
let province = provinces.find((p) => p.id == id);
|
const province = provinces.find((p) => p.id == id);
|
||||||
if (province == undefined) return;
|
if (province == undefined) return;
|
||||||
setAmphurList(province.zones);
|
setAmphurList(province.zones);
|
||||||
setAmphurId(undefined);
|
setAmphurId(undefined);
|
||||||
}
|
}
|
||||||
function setAmphur(_id: string) {
|
function setAmphur(_id: string) {
|
||||||
let id = parseInt(_id);
|
const id = parseInt(_id);
|
||||||
setAmphurId(id);
|
setAmphurId(id);
|
||||||
}
|
}
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -43,7 +43,7 @@ export default function LocationSelector({ provinces }: Props) {
|
|||||||
|
|
||||||
locationContext.zone[1](amphurId);
|
locationContext.zone[1](amphurId);
|
||||||
locationContext.province[1](provinceId);
|
locationContext.province[1](provinceId);
|
||||||
}, [amphurId]);
|
}, [amphurId, locationContext, provinceId]);
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type LocationContextType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const LocationContext = createContext<LocationContextType | undefined>(
|
export const LocationContext = createContext<LocationContextType | undefined>(
|
||||||
undefined
|
undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
export default function LocationContextProvider({
|
export default function LocationContextProvider({
|
||||||
@@ -21,8 +21,8 @@ export default function LocationContextProvider({
|
|||||||
}: {
|
}: {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
let zone = useState<number | undefined>(undefined);
|
const zone = useState<number | undefined>(undefined);
|
||||||
let province = useState<number | undefined>(undefined);
|
const province = useState<number | undefined>(undefined);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LocationContext.Provider value={{ zone, province }}>
|
<LocationContext.Provider value={{ zone, province }}>
|
||||||
11
package.json
11
package.json
@@ -9,6 +9,7 @@
|
|||||||
"next-dev": "next dev",
|
"next-dev": "next dev",
|
||||||
"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",
|
||||||
"initialize_data": "node -r @swc-node/register addMetadata.ts"
|
"initialize_data": "node -r @swc-node/register addMetadata.ts"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.2.0",
|
||||||
"@swc-node/register": "^1.9.0",
|
"@swc-node/register": "^1.9.0",
|
||||||
"@swc/cli": "^0.3.12",
|
"@swc/cli": "^0.3.12",
|
||||||
"@swc/core": "^1.4.16",
|
"@swc/core": "^1.4.16",
|
||||||
@@ -41,10 +43,17 @@
|
|||||||
"@types/jsonwebtoken": "^9.0.6",
|
"@types/jsonwebtoken": "^9.0.6",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"drizzle-kit": "^0.20.14",
|
"drizzle-kit": "^0.20.14",
|
||||||
|
"eslint": "^8",
|
||||||
|
"eslint-config-next": "14.2.3",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"nodemon": "^3.1.0",
|
"nodemon": "^3.1.0",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
|
"prettier": "^3.2.5",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.5.14",
|
||||||
"tailwindcss": "^3.4.3",
|
"tailwindcss": "^3.4.3",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.4.5",
|
||||||
|
"typescript-eslint": "^7.9.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1906
pnpm-lock.yaml
generated
1906
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
6
prettier.config.mjs
Normal file
6
prettier.config.mjs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/** @type {import("prettier").Config} */
|
||||||
|
const config = {
|
||||||
|
plugins: ["prettier-plugin-tailwindcss"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
@@ -16,18 +16,18 @@ export function createClient() {
|
|||||||
export async function createUploadImageUrl(
|
export async function createUploadImageUrl(
|
||||||
mc: minio.Client,
|
mc: minio.Client,
|
||||||
objectName: string,
|
objectName: string,
|
||||||
contentType: string
|
contentType: string,
|
||||||
) {
|
) {
|
||||||
let policy = mc.newPostPolicy();
|
const policy = mc.newPostPolicy();
|
||||||
policy.setKey(objectName);
|
policy.setKey(objectName);
|
||||||
policy.setBucket(Config.bucketName);
|
policy.setBucket(Config.bucketName);
|
||||||
let expires = new Date();
|
const expires = new Date();
|
||||||
expires.setSeconds(30 * 60);
|
expires.setSeconds(30 * 60);
|
||||||
policy.setExpires(expires);
|
policy.setExpires(expires);
|
||||||
policy.setContentType(contentType);
|
policy.setContentType(contentType);
|
||||||
policy.setContentDisposition(`attachment; filename="${objectName}"`);
|
policy.setContentDisposition(`attachment; filename="${objectName}"`);
|
||||||
policy.setContentLengthRange(1, 3 * 1024 * 1024);
|
policy.setContentLengthRange(1, 3 * 1024 * 1024);
|
||||||
let rs = await mc.presignedPostPolicy(policy);
|
const rs = await mc.presignedPostPolicy(policy);
|
||||||
return rs;
|
return rs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const runPlayground = async (appRouter: AppRouter) => {
|
|||||||
trpcApiEndpoint,
|
trpcApiEndpoint,
|
||||||
playgroundEndpoint,
|
playgroundEndpoint,
|
||||||
router: appRouter,
|
router: appRouter,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
app.listen(3001, () => {
|
app.listen(3001, () => {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const user = sqliteTable(
|
|||||||
(t) => ({
|
(t) => ({
|
||||||
phone_idx: index("phone_idx").on(t.phone),
|
phone_idx: index("phone_idx").on(t.phone),
|
||||||
image_idx: index("image_idx").on(t.image),
|
image_idx: index("image_idx").on(t.image),
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const userRelation = relations(user, ({ many, one }) => ({
|
export const userRelation = relations(user, ({ many, one }) => ({
|
||||||
@@ -98,7 +98,7 @@ export const userOpinion = sqliteTable(
|
|||||||
},
|
},
|
||||||
(t) => ({
|
(t) => ({
|
||||||
pk: primaryKey({ columns: [t.userId, t.opinionId] }),
|
pk: primaryKey({ columns: [t.userId, t.opinionId] }),
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const userOpinionRelation = relations(userOpinion, ({ one }) => ({
|
export const userOpinionRelation = relations(userOpinion, ({ one }) => ({
|
||||||
@@ -118,7 +118,7 @@ export const zone = sqliteTable(
|
|||||||
.notNull()
|
.notNull()
|
||||||
.references(() => province.id),
|
.references(() => province.id),
|
||||||
},
|
},
|
||||||
(t) => ({ unique_name_province: unique().on(t.name, t.province) })
|
(t) => ({ unique_name_province: unique().on(t.name, t.province) }),
|
||||||
);
|
);
|
||||||
export const zoneRelation = relations(zone, ({ one }) => ({
|
export const zoneRelation = relations(zone, ({ one }) => ({
|
||||||
province: one(province, {
|
province: one(province, {
|
||||||
@@ -144,6 +144,6 @@ export const imageToUser = sqliteTable("image_to_user", {
|
|||||||
.references(() => user.id),
|
.references(() => user.id),
|
||||||
imageName: text("image_name").notNull(),
|
imageName: text("image_name").notNull(),
|
||||||
createdOn: integer("created_on", { mode: "timestamp" }).default(
|
createdOn: integer("created_on", { mode: "timestamp" }).default(
|
||||||
sql`CURRENT_TIMESTAMP`
|
sql`CURRENT_TIMESTAMP`,
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const createContext = async (opts: CreateHTTPContextOptions) => {
|
|||||||
const bearerToken = authorizationHeader.split(" ")[1];
|
const bearerToken = authorizationHeader.split(" ")[1];
|
||||||
const phone = await verifyToken(bearerToken);
|
const phone = await verifyToken(bearerToken);
|
||||||
if (phone !== null) {
|
if (phone !== null) {
|
||||||
let user = await db.query.user.findFirst({
|
const user = await db.query.user.findFirst({
|
||||||
where: (user, { eq }) => eq(user.phone, phone),
|
where: (user, { eq }) => eq(user.phone, phone),
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
@@ -59,7 +59,7 @@ export const createContext = async (opts: CreateHTTPContextOptions) => {
|
|||||||
|
|
||||||
async function verifyToken(token: string): Promise<string | null> {
|
async function verifyToken(token: string): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
let rs = await new Promise((resolve, reject) => {
|
const rs = await new Promise((resolve, reject) => {
|
||||||
jwt.verify(token, Config.jwt_secret, (err, decoded) => {
|
jwt.verify(token, Config.jwt_secret, (err, decoded) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
@@ -68,7 +68,7 @@ async function verifyToken(token: string): Promise<string | null> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
let data = z
|
const data = z
|
||||||
.object({
|
.object({
|
||||||
phone: z.string(),
|
phone: z.string(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
import { router, publicProcedure, protectedProcedure } from "./trpc";
|
import { router, publicProcedure, protectedProcedure } from "./trpc";
|
||||||
import { db } from "./db";
|
import { db } from "./db";
|
||||||
import {
|
import { imageToUser, opinion, user, userOpinion, zone } from "./schema";
|
||||||
imageToUser,
|
|
||||||
opinion,
|
|
||||||
province,
|
|
||||||
user,
|
|
||||||
userOpinion,
|
|
||||||
zone,
|
|
||||||
} from "./schema";
|
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { SQL, and, count, eq, inArray, sql } from "drizzle-orm";
|
import { SQL, and, count, eq, inArray, sql } from "drizzle-orm";
|
||||||
@@ -47,10 +40,10 @@ export const userRoute = router({
|
|||||||
.input(
|
.input(
|
||||||
userInsertSchema.omit({ id: true }).extend({
|
userInsertSchema.omit({ id: true }).extend({
|
||||||
opinions: opinionInsertSchema,
|
opinions: opinionInsertSchema,
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
.mutation(
|
.mutation(
|
||||||
async ({ input }) => await createUser({ ...input }, input.opinions)
|
async ({ input }) => await createUser({ ...input }, input.opinions),
|
||||||
),
|
),
|
||||||
// changeImage: protectedProcedure
|
// changeImage: protectedProcedure
|
||||||
updateUser: protectedProcedure
|
updateUser: protectedProcedure
|
||||||
@@ -60,7 +53,7 @@ export const userRoute = router({
|
|||||||
.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(
|
||||||
async ({ ctx }) => await getUser(ctx.user.id, true)
|
async ({ ctx }) => await getUser(ctx.user.id, true),
|
||||||
),
|
),
|
||||||
login: publicProcedure
|
login: publicProcedure
|
||||||
.input(z.object({ cid: z.string(), phone: z.string() }))
|
.input(z.object({ cid: z.string(), phone: z.string() }))
|
||||||
@@ -69,7 +62,7 @@ export const userRoute = router({
|
|||||||
.input(opinionUpdateSchema)
|
.input(opinionUpdateSchema)
|
||||||
.mutation(
|
.mutation(
|
||||||
async ({ input, ctx }) =>
|
async ({ input, ctx }) =>
|
||||||
await changeOpinion(input.opinionId, ctx.user.id, input.choice)
|
await changeOpinion(input.opinionId, ctx.user.id, input.choice),
|
||||||
),
|
),
|
||||||
requestChangeImage: protectedProcedure
|
requestChangeImage: protectedProcedure
|
||||||
.input(z.object({ imageName: z.string(), contentType: z.string() }))
|
.input(z.object({ imageName: z.string(), contentType: z.string() }))
|
||||||
@@ -78,11 +71,11 @@ export const userRoute = router({
|
|||||||
await requestChangeImage(
|
await requestChangeImage(
|
||||||
ctx.user.id,
|
ctx.user.id,
|
||||||
input.imageName,
|
input.imageName,
|
||||||
input.contentType
|
input.contentType,
|
||||||
)
|
),
|
||||||
),
|
),
|
||||||
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: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
@@ -93,7 +86,7 @@ export const userRoute = router({
|
|||||||
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(),
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
.query(
|
.query(
|
||||||
async ({ input }) =>
|
async ({ input }) =>
|
||||||
@@ -103,8 +96,8 @@ export const userRoute = router({
|
|||||||
input.opinionCount,
|
input.opinionCount,
|
||||||
input.group,
|
input.group,
|
||||||
input.zone,
|
input.zone,
|
||||||
input.province
|
input.province,
|
||||||
)
|
),
|
||||||
),
|
),
|
||||||
getAllUserCount: protectedProcedure
|
getAllUserCount: protectedProcedure
|
||||||
.input(
|
.input(
|
||||||
@@ -112,24 +105,24 @@ export const userRoute = router({
|
|||||||
group: z.number().optional(),
|
group: z.number().optional(),
|
||||||
zone: z.number().optional(),
|
zone: z.number().optional(),
|
||||||
province: z.number().optional(),
|
province: z.number().optional(),
|
||||||
})
|
}),
|
||||||
)
|
)
|
||||||
.query(
|
.query(
|
||||||
async ({ input }) =>
|
async ({ input }) =>
|
||||||
await getAllUserCount(input.group, input.zone, input.province)
|
await getAllUserCount(input.group, input.zone, input.province),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
async function getAllUserCount(
|
async function getAllUserCount(
|
||||||
group?: number,
|
group?: number,
|
||||||
zoneId?: number,
|
zoneId?: number,
|
||||||
provinceId?: number
|
provinceId?: number,
|
||||||
) {
|
) {
|
||||||
let zoneIds: number[] = await getZone(provinceId);
|
const zoneIds: number[] = await getZone(provinceId);
|
||||||
if (provinceId && zoneIds.length === 0) {
|
if (provinceId && zoneIds.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
let conditions: SQL[] = [];
|
const conditions: SQL[] = [];
|
||||||
if (group !== undefined) {
|
if (group !== undefined) {
|
||||||
conditions.push(eq(user.group, group));
|
conditions.push(eq(user.group, group));
|
||||||
}
|
}
|
||||||
@@ -152,13 +145,13 @@ async function getAllUser(
|
|||||||
opinionLimit: number,
|
opinionLimit: number,
|
||||||
group?: number,
|
group?: number,
|
||||||
zoneId?: number,
|
zoneId?: number,
|
||||||
provinceId?: number
|
provinceId?: number,
|
||||||
) {
|
) {
|
||||||
let zoneIds: number[] = await getZone(provinceId);
|
const zoneIds: number[] = await getZone(provinceId);
|
||||||
if (provinceId && zoneIds.length === 0) {
|
if (provinceId && zoneIds.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
let users = await db.query.user.findMany({
|
const users = await db.query.user.findMany({
|
||||||
with: {
|
with: {
|
||||||
group: true,
|
group: true,
|
||||||
opinions: {
|
opinions: {
|
||||||
@@ -172,7 +165,7 @@ async function getAllUser(
|
|||||||
offset,
|
offset,
|
||||||
orderBy: sql`random()`,
|
orderBy: sql`random()`,
|
||||||
where: (user, { eq, and }) => {
|
where: (user, { eq, and }) => {
|
||||||
let conditions: SQL[] = [];
|
const conditions: SQL[] = [];
|
||||||
if (group !== undefined) {
|
if (group !== undefined) {
|
||||||
conditions.push(eq(user.group, group));
|
conditions.push(eq(user.group, group));
|
||||||
}
|
}
|
||||||
@@ -195,7 +188,7 @@ async function getAllUser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getUser(userId: number, showPhone: boolean) {
|
async function getUser(userId: number, showPhone: boolean) {
|
||||||
let user = await db.query.user.findFirst({
|
const user = await db.query.user.findFirst({
|
||||||
where: (user, { eq }) => eq(user.id, userId),
|
where: (user, { eq }) => eq(user.id, userId),
|
||||||
with: {
|
with: {
|
||||||
group: true,
|
group: true,
|
||||||
@@ -221,13 +214,13 @@ async function getUser(userId: number, showPhone: boolean) {
|
|||||||
|
|
||||||
async function createUser(
|
async function createUser(
|
||||||
newUser: UserInsertSchema,
|
newUser: UserInsertSchema,
|
||||||
opinions: OpinionInsertSchema
|
opinions: OpinionInsertSchema,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
let result = (
|
const result = (
|
||||||
await db.insert(user).values(newUser).returning({ id: user.id })
|
await db.insert(user).values(newUser).returning({ id: user.id })
|
||||||
)[0];
|
)[0];
|
||||||
for (let op of opinions) {
|
for (const op of opinions) {
|
||||||
await db.insert(userOpinion).values({ ...op, userId: result.id });
|
await db.insert(userOpinion).values({ ...op, userId: result.id });
|
||||||
}
|
}
|
||||||
return { token: createJWT(newUser.phone) };
|
return { token: createJWT(newUser.phone) };
|
||||||
@@ -248,7 +241,7 @@ async function updateUser(userId: number, update: UserUpdateSchema) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function login(cid: string, phone: string) {
|
async function login(cid: string, phone: string) {
|
||||||
let user = await db.query.user.findFirst({
|
const user = await db.query.user.findFirst({
|
||||||
where: (user, { and, eq }) => and(eq(user.cid, cid), eq(user.phone, phone)),
|
where: (user, { and, eq }) => and(eq(user.cid, cid), eq(user.phone, phone)),
|
||||||
});
|
});
|
||||||
if (user === undefined) {
|
if (user === undefined) {
|
||||||
@@ -264,10 +257,10 @@ async function login(cid: string, phone: string) {
|
|||||||
async function changeOpinion(
|
async function changeOpinion(
|
||||||
opinionId: number,
|
opinionId: number,
|
||||||
userId: number,
|
userId: number,
|
||||||
opinionChoice: OpinionInsertSchema[0]["choice"]
|
opinionChoice: OpinionInsertSchema[0]["choice"],
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
let thisOpinion = await db
|
const thisOpinion = await db
|
||||||
.select()
|
.select()
|
||||||
.from(opinion)
|
.from(opinion)
|
||||||
.where(eq(opinion.id, opinionId))
|
.where(eq(opinion.id, opinionId))
|
||||||
@@ -308,7 +301,7 @@ async function changeOpinion(
|
|||||||
async function requestChangeImage(
|
async function requestChangeImage(
|
||||||
userId: number,
|
userId: number,
|
||||||
imageName: string,
|
imageName: string,
|
||||||
contentType: string
|
contentType: string,
|
||||||
) {
|
) {
|
||||||
const mc = createClient();
|
const mc = createClient();
|
||||||
// Check if the image is valid
|
// Check if the image is valid
|
||||||
@@ -328,11 +321,11 @@ async function requestChangeImage(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Create a unique image name
|
// Create a unique image name
|
||||||
let tryCount = 0;
|
const tryCount = 0;
|
||||||
let objectName: string | null = null;
|
let objectName: string | null = null;
|
||||||
while (tryCount < 3) {
|
while (tryCount < 3) {
|
||||||
let imageName = `${generateRandomString()}.${extension}`;
|
const imageName = `${generateRandomString()}.${extension}`;
|
||||||
let ok = await db
|
const ok = await db
|
||||||
.select({ value: count(user.image) })
|
.select({ value: count(user.image) })
|
||||||
.from(user)
|
.from(user)
|
||||||
.where(eq(user.image, imageName))
|
.where(eq(user.image, imageName))
|
||||||
@@ -356,14 +349,14 @@ async function requestChangeImage(
|
|||||||
target: [imageToUser.userId],
|
target: [imageToUser.userId],
|
||||||
set: { imageName: objectName },
|
set: { imageName: objectName },
|
||||||
});
|
});
|
||||||
let request = await createUploadImageUrl(mc, objectName, contentType);
|
const request = await createUploadImageUrl(mc, objectName, contentType);
|
||||||
request.postURL = Config.minioPublicBucketEndpoint;
|
request.postURL = Config.minioPublicBucketEndpoint;
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function confirmChangeImage(userId: number, oldImage: string | null) {
|
async function confirmChangeImage(userId: number, oldImage: string | null) {
|
||||||
const mc = createClient();
|
const mc = createClient();
|
||||||
let rs = await db
|
const rs = await db
|
||||||
.select({ imageName: imageToUser.imageName })
|
.select({ imageName: imageToUser.imageName })
|
||||||
.from(imageToUser)
|
.from(imageToUser)
|
||||||
.where(eq(imageToUser.userId, userId));
|
.where(eq(imageToUser.userId, userId));
|
||||||
@@ -373,7 +366,7 @@ async function confirmChangeImage(userId: number, oldImage: string | null) {
|
|||||||
code: "BAD_REQUEST",
|
code: "BAD_REQUEST",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let imageName = rs[0].imageName;
|
const imageName = rs[0].imageName;
|
||||||
const isImageExist = await mc
|
const isImageExist = await mc
|
||||||
.statObject(Config.bucketName, imageName)
|
.statObject(Config.bucketName, imageName)
|
||||||
.then(() => true)
|
.then(() => true)
|
||||||
@@ -384,7 +377,7 @@ async function confirmChangeImage(userId: number, oldImage: string | null) {
|
|||||||
code: "BAD_REQUEST",
|
code: "BAD_REQUEST",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const promises: Promise<any>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
if (oldImage) {
|
if (oldImage) {
|
||||||
promises.push(mc.removeObject(Config.bucketName, oldImage).catch(() => {}));
|
promises.push(mc.removeObject(Config.bucketName, oldImage).catch(() => {}));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user