Compare commits
10 Commits
ada1af805a
...
7ab206943e
| Author | SHA1 | Date | |
|---|---|---|---|
|
7ab206943e
|
|||
|
406f433984
|
|||
|
8ac2815618
|
|||
|
4ea2a178f3
|
|||
|
8e630edfed
|
|||
|
f7392598c5
|
|||
|
6b80ddd5e6
|
|||
|
28f053e91b
|
|||
|
6e58e17be1
|
|||
|
5eda99b443
|
@@ -16,3 +16,8 @@ tasks:
|
|||||||
studio:
|
studio:
|
||||||
cmds:
|
cmds:
|
||||||
- pnpm drizzle-kit studio
|
- pnpm drizzle-kit studio
|
||||||
|
start:
|
||||||
|
cmds:
|
||||||
|
- node -r @swc-node/register src/app.ts
|
||||||
|
env:
|
||||||
|
NODE_ENV: production
|
||||||
|
|||||||
@@ -3,6 +3,13 @@ import { group, opinion, zone, province } from "./src/schema.ts";
|
|||||||
import { Groups, Opinions, Provinces, Districts } from "./initialData.ts";
|
import { Groups, Opinions, Provinces, Districts } from "./initialData.ts";
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
|
const isInitialized = await db.query.group
|
||||||
|
.findMany()
|
||||||
|
.then((groups) => groups.length > 0);
|
||||||
|
if (isInitialized) {
|
||||||
|
console.log("Already initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
let groupValues = Groups.map((group) => ({ name: group }));
|
let groupValues = Groups.map((group) => ({ name: group }));
|
||||||
await db.insert(group).values(groupValues);
|
await db.insert(group).values(groupValues);
|
||||||
let opinionValues = Opinions.map((opinion) => ({
|
let opinionValues = Opinions.map((opinion) => ({
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"@swc-node/register": "^1.9.0",
|
"@swc-node/register": "^1.9.0",
|
||||||
"@swc/core": "^1.4.16",
|
"@swc/core": "^1.4.16",
|
||||||
"@types/better-sqlite3": "^7.6.9",
|
"@types/better-sqlite3": "^7.6.9",
|
||||||
|
"@types/cors": "^2.8.17",
|
||||||
"@types/jsonwebtoken": "^9.0.6",
|
"@types/jsonwebtoken": "^9.0.6",
|
||||||
"drizzle-kit": "^0.20.14",
|
"drizzle-kit": "^0.20.14",
|
||||||
"nodemon": "^3.1.0",
|
"nodemon": "^3.1.0",
|
||||||
|
|||||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -46,6 +46,9 @@ devDependencies:
|
|||||||
'@types/better-sqlite3':
|
'@types/better-sqlite3':
|
||||||
specifier: ^7.6.9
|
specifier: ^7.6.9
|
||||||
version: 7.6.9
|
version: 7.6.9
|
||||||
|
'@types/cors':
|
||||||
|
specifier: ^2.8.17
|
||||||
|
version: 2.8.17
|
||||||
'@types/jsonwebtoken':
|
'@types/jsonwebtoken':
|
||||||
specifier: ^9.0.6
|
specifier: ^9.0.6
|
||||||
version: 9.0.6
|
version: 9.0.6
|
||||||
@@ -711,6 +714,12 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.12.7
|
'@types/node': 20.12.7
|
||||||
|
|
||||||
|
/@types/cors@2.8.17:
|
||||||
|
resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==}
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 20.12.7
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/jsonwebtoken@9.0.6:
|
/@types/jsonwebtoken@9.0.6:
|
||||||
resolution: {integrity: sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==}
|
resolution: {integrity: sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import { createHTTPServer } from "@trpc/server/adapters/standalone";
|
|||||||
import { userRoute } from "./userRoute";
|
import { userRoute } from "./userRoute";
|
||||||
import { runPlayground } from "./playgroud";
|
import { runPlayground } from "./playgroud";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
|
import { infoRoute } from "./infoRoute";
|
||||||
|
|
||||||
export const appRouter = router({
|
export const appRouter = router({
|
||||||
user: userRoute,
|
user: userRoute,
|
||||||
|
info: infoRoute,
|
||||||
});
|
});
|
||||||
export type AppRouter = typeof appRouter;
|
export type AppRouter = typeof appRouter;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
export const Config = {
|
export const Config = {
|
||||||
sms_api_key: "1796570121771765",
|
jwt_secret:
|
||||||
sms_api_secret: "0957b611d575febff1ae0fc51070c8b7",
|
"T4kE6/tIqCVEZYg9lwsqeJjYfOoXTXSXDEMyParsJjj57CjSdkrfPOLWP74/9lJpcBA=",
|
||||||
sms_api_request_endpoint: "https://otp.thaibulksms.com/v2/otp/request",
|
token_duration: "365d",
|
||||||
sms_api_verify_endpoint: "https://otp.thaibulksms.com/v2/otp/verify",
|
|
||||||
};
|
};
|
||||||
|
|||||||
30
src/infoRoute.ts
Normal file
30
src/infoRoute.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { router, publicProcedure } from "./trpc";
|
||||||
|
import { db } from "./db";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const infoRoute = router({
|
||||||
|
getAllProvinces: publicProcedure.query(getProvinces),
|
||||||
|
getAllGroups: publicProcedure.query(getGroups),
|
||||||
|
getAllZones: publicProcedure
|
||||||
|
.input(z.object({ provice_id: z.number().optional() }))
|
||||||
|
.query(async ({ input }) => await getZone(input.provice_id)),
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getProvinces() {
|
||||||
|
return await db.query.province.findMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getZone(province?: number) {
|
||||||
|
return await db.query.zone.findMany({
|
||||||
|
where: (zone, { and, eq }) => {
|
||||||
|
if (province === undefined) {
|
||||||
|
return and();
|
||||||
|
}
|
||||||
|
return eq(zone.province, province);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getGroups() {
|
||||||
|
return await db.query.group.findMany();
|
||||||
|
}
|
||||||
@@ -3,28 +3,43 @@ import {
|
|||||||
text,
|
text,
|
||||||
integer,
|
integer,
|
||||||
primaryKey,
|
primaryKey,
|
||||||
|
unique,
|
||||||
|
index,
|
||||||
} from "drizzle-orm/sqlite-core";
|
} from "drizzle-orm/sqlite-core";
|
||||||
import { relations, sql } from "drizzle-orm";
|
import { relations, sql } from "drizzle-orm";
|
||||||
|
|
||||||
//----------------User
|
//----------------User
|
||||||
export const user = sqliteTable("users", {
|
export const user = sqliteTable(
|
||||||
id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }),
|
"users",
|
||||||
firstName: text("firstName").notNull(),
|
{
|
||||||
lastName: text("lastName").notNull(),
|
id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }),
|
||||||
title: text("title").notNull(),
|
firstName: text("firstName").notNull(),
|
||||||
phone: text("phone").unique().notNull(),
|
lastName: text("lastName").notNull(),
|
||||||
email: text("email"),
|
title: text("title").notNull(),
|
||||||
job: text("job").notNull(),
|
cid: text("cid", { length: 13 }).notNull().unique(),
|
||||||
education: text("education").notNull(),
|
age: integer("age").notNull(),
|
||||||
vision: text("vision"),
|
phone: text("phone").unique().notNull(),
|
||||||
reason: text("reason"),
|
public_phone: text("public_phone"),
|
||||||
group: integer("group_id")
|
facebook: text("facebook"),
|
||||||
.references(() => group.id)
|
twitter: text("twitter"),
|
||||||
.notNull(),
|
tiktok: text("tiktok"),
|
||||||
zone: integer("zone_id")
|
otherSocial: text("other_social"),
|
||||||
.notNull()
|
email: text("email"),
|
||||||
.references(() => zone.id),
|
job: text("job").notNull(),
|
||||||
});
|
education: text("education").notNull(),
|
||||||
|
vision: text("vision"),
|
||||||
|
reason: text("reason"),
|
||||||
|
group: integer("group_id")
|
||||||
|
.references(() => group.id)
|
||||||
|
.notNull(),
|
||||||
|
zone: integer("zone_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => zone.id),
|
||||||
|
},
|
||||||
|
(t) => ({
|
||||||
|
phone_idx: index("phone_idx").on(t.phone),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
export const userRelation = relations(user, ({ many, one }) => ({
|
export const userRelation = relations(user, ({ many, one }) => ({
|
||||||
opinions: many(userOpinion),
|
opinions: many(userOpinion),
|
||||||
@@ -41,7 +56,7 @@ export const userRelation = relations(user, ({ many, one }) => ({
|
|||||||
//----------------Group
|
//----------------Group
|
||||||
export const group = sqliteTable("groups", {
|
export const group = sqliteTable("groups", {
|
||||||
id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }),
|
id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }),
|
||||||
name: text("name").notNull(),
|
name: text("name").unique().notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const groupRelation = relations(group, ({ many }) => ({
|
export const groupRelation = relations(group, ({ many }) => ({
|
||||||
@@ -51,7 +66,7 @@ export const groupRelation = relations(group, ({ many }) => ({
|
|||||||
//----------------Opinion
|
//----------------Opinion
|
||||||
export const opinion = sqliteTable("opinions", {
|
export const opinion = sqliteTable("opinions", {
|
||||||
id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }),
|
id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }),
|
||||||
name: text("name").notNull(),
|
name: text("name").unique().notNull(),
|
||||||
type: text("type", { enum: ["3Choice", "4Choice"] })
|
type: text("type", { enum: ["3Choice", "4Choice"] })
|
||||||
.default("3Choice")
|
.default("3Choice")
|
||||||
.notNull(),
|
.notNull(),
|
||||||
@@ -84,13 +99,17 @@ export const userOpinionRelation = relations(userOpinion, ({ one }) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
//----------------Zone
|
//----------------Zone
|
||||||
export const zone = sqliteTable("zones", {
|
export const zone = sqliteTable(
|
||||||
id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }),
|
"zones",
|
||||||
name: text("name").notNull(),
|
{
|
||||||
province: integer("province_id")
|
id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }),
|
||||||
.notNull()
|
name: text("name").notNull(),
|
||||||
.references(() => province.id),
|
province: integer("province_id")
|
||||||
});
|
.notNull()
|
||||||
|
.references(() => province.id),
|
||||||
|
},
|
||||||
|
(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, {
|
||||||
fields: [zone.province],
|
fields: [zone.province],
|
||||||
@@ -101,7 +120,7 @@ export const zoneRelation = relations(zone, ({ one }) => ({
|
|||||||
//----------------Province
|
//----------------Province
|
||||||
export const province = sqliteTable("provinces", {
|
export const province = sqliteTable("provinces", {
|
||||||
id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }),
|
id: integer("id", { mode: "number" }).primaryKey({ autoIncrement: true }),
|
||||||
name: text("name").notNull(),
|
name: text("name").unique().notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const provinceRelation = relations(province, ({ many }) => ({
|
export const provinceRelation = relations(province, ({ many }) => ({
|
||||||
|
|||||||
34
src/trpc.ts
34
src/trpc.ts
@@ -1,6 +1,9 @@
|
|||||||
import { initTRPC } from "@trpc/server";
|
import { initTRPC } from "@trpc/server";
|
||||||
import type { CreateHTTPContextOptions } from "@trpc/server/adapters/standalone";
|
import type { CreateHTTPContextOptions } from "@trpc/server/adapters/standalone";
|
||||||
import { db } from "./db";
|
import { db } from "./db";
|
||||||
|
import * as jwt from "jsonwebtoken";
|
||||||
|
import { Config } from "./config";
|
||||||
|
import { z } from "zod";
|
||||||
const t = initTRPC.context<Context>().create();
|
const t = initTRPC.context<Context>().create();
|
||||||
|
|
||||||
export const router = t.router;
|
export const router = t.router;
|
||||||
@@ -35,8 +38,10 @@ type Context = Awaited<ReturnType<typeof createContext>>;
|
|||||||
|
|
||||||
export const createContext = async (opts: CreateHTTPContextOptions) => {
|
export const createContext = async (opts: CreateHTTPContextOptions) => {
|
||||||
const authorizationHeader = opts.req.headers.authorization || "";
|
const authorizationHeader = opts.req.headers.authorization || "";
|
||||||
|
|
||||||
const bearerToken = authorizationHeader.split(" ")[1];
|
const bearerToken = authorizationHeader.split(" ")[1];
|
||||||
const phone = verifyToken(bearerToken);
|
console.log(authorizationHeader, bearerToken);
|
||||||
|
const phone = await verifyToken(bearerToken);
|
||||||
if (phone !== null) {
|
if (phone !== null) {
|
||||||
let user = await db.query.user.findFirst({
|
let user = await db.query.user.findFirst({
|
||||||
where: (user, { eq }) => eq(user.phone, phone),
|
where: (user, { eq }) => eq(user.phone, phone),
|
||||||
@@ -53,7 +58,28 @@ export const createContext = async (opts: CreateHTTPContextOptions) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function verifyToken(token: string): string | null {
|
async function verifyToken(token: string): Promise<string | null> {
|
||||||
//TODO: Implement token verification
|
try {
|
||||||
return "08999";
|
let rs = await new Promise((resolve, reject) => {
|
||||||
|
jwt.verify(token, Config.jwt_secret, (err, decoded) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(decoded);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
let data = z
|
||||||
|
.object({
|
||||||
|
phone: z.string(),
|
||||||
|
})
|
||||||
|
.safeParse(rs);
|
||||||
|
if (data.success) {
|
||||||
|
return data.data.phone;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
200
src/userRoute.ts
200
src/userRoute.ts
@@ -1,18 +1,21 @@
|
|||||||
import {
|
import { router, publicProcedure, protectedProcedure } from "./trpc";
|
||||||
router,
|
|
||||||
verifiedPhone,
|
|
||||||
publicProcedure,
|
|
||||||
protectedProcedure,
|
|
||||||
} from "./trpc";
|
|
||||||
import { db } from "./db";
|
import { db } from "./db";
|
||||||
import { phoneToken, user, userOpinion } from "./schema";
|
import { opinion, user, userOpinion } from "./schema";
|
||||||
import { createInsertSchema } from "drizzle-zod";
|
import { createInsertSchema } from "drizzle-zod";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { SQL, eq } from "drizzle-orm";
|
import { SQL, eq } from "drizzle-orm";
|
||||||
import { Config } from "./config";
|
import { Config } from "./config";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
|
import * as jwt from "jsonwebtoken";
|
||||||
|
|
||||||
const userInsertSchema = createInsertSchema(user);
|
const userInsertSchema = createInsertSchema(user, {
|
||||||
|
cid: (schema) =>
|
||||||
|
schema.cid.length(13).refine(isValidThaiID, { message: "Invalid Thai ID" }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const userUpdateSchema = userInsertSchema
|
||||||
|
.omit({ id: true, cid: true, phone: true })
|
||||||
|
.partial();
|
||||||
const opinionInsertSchema = createInsertSchema(userOpinion)
|
const opinionInsertSchema = createInsertSchema(userOpinion)
|
||||||
.omit({
|
.omit({
|
||||||
userId: true,
|
userId: true,
|
||||||
@@ -27,6 +30,7 @@ const opinionUpdateSchema = createInsertSchema(userOpinion)
|
|||||||
.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>;
|
||||||
|
|
||||||
export const userRoute = router({
|
export const userRoute = router({
|
||||||
getAllUser: publicProcedure
|
getAllUser: publicProcedure
|
||||||
@@ -36,33 +40,34 @@ export const userRoute = router({
|
|||||||
limit: z.number().max(50).default(10),
|
limit: z.number().max(50).default(10),
|
||||||
group: z.number().optional(),
|
group: z.number().optional(),
|
||||||
zone: z.number().optional(),
|
zone: z.number().optional(),
|
||||||
|
opinionCount: z.number().default(3),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.query(
|
.query(
|
||||||
async ({ input }) =>
|
async ({ input }) =>
|
||||||
await getAllUser(input.offset, input.limit, input.group, input.zone)
|
await getAllUser(
|
||||||
|
input.offset,
|
||||||
|
input.limit,
|
||||||
|
input.opinionCount,
|
||||||
|
input.group,
|
||||||
|
input.zone
|
||||||
|
)
|
||||||
),
|
),
|
||||||
createUser: verifiedPhone
|
createUser: publicProcedure
|
||||||
.input(
|
.input(
|
||||||
userInsertSchema.omit({ id: true, phone: true }).extend({
|
userInsertSchema.omit({ id: true }).extend({
|
||||||
opinions: opinionInsertSchema,
|
opinions: opinionInsertSchema,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.mutation(
|
.mutation(
|
||||||
async ({ input, ctx }) =>
|
async ({ input }) => await createUser({ ...input }, input.opinions)
|
||||||
await createUser({ ...input, phone: ctx.phone }, input.opinions)
|
|
||||||
),
|
),
|
||||||
requestOtp: publicProcedure
|
updateUser: protectedProcedure
|
||||||
.input(z.object({ phone: z.string().trim().min(5) }))
|
.input(userUpdateSchema)
|
||||||
.mutation(async ({ input }) => await requestOtp(input.phone)),
|
.mutation(async ({ input, ctx }) => await updateUser(ctx.user.id, input)),
|
||||||
verifyOtp: publicProcedure
|
login: publicProcedure
|
||||||
.input(
|
.input(z.object({ cid: z.string(), phone: z.string() }))
|
||||||
z.object({
|
.mutation(async ({ input }) => await login(input.cid, input.phone)),
|
||||||
pin: z.string().trim(),
|
|
||||||
token: z.string(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.mutation(async ({ input }) => await verifyOtp(input.token, input.pin)),
|
|
||||||
changeOpinion: protectedProcedure
|
changeOpinion: protectedProcedure
|
||||||
.input(opinionUpdateSchema)
|
.input(opinionUpdateSchema)
|
||||||
.mutation(
|
.mutation(
|
||||||
@@ -74,13 +79,16 @@ export const userRoute = router({
|
|||||||
async function getAllUser(
|
async function getAllUser(
|
||||||
offset: number,
|
offset: number,
|
||||||
limit: number,
|
limit: number,
|
||||||
|
opinionLimit: number,
|
||||||
group?: number,
|
group?: number,
|
||||||
zone?: number
|
zone?: number
|
||||||
) {
|
) {
|
||||||
let users = await db.query.user.findMany({
|
let users = await db.query.user.findMany({
|
||||||
with: {
|
with: {
|
||||||
group: true,
|
group: true,
|
||||||
opinions: true,
|
opinions: {
|
||||||
|
limit: opinionLimit,
|
||||||
|
},
|
||||||
zone: {
|
zone: {
|
||||||
with: { province: true },
|
with: { province: true },
|
||||||
},
|
},
|
||||||
@@ -98,7 +106,11 @@ async function getAllUser(
|
|||||||
return and(...conditions);
|
return and(...conditions);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return users;
|
|
||||||
|
return users.map((u) => ({
|
||||||
|
...u,
|
||||||
|
phone: hidePhone(u.phone),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
async function createUser(
|
async function createUser(
|
||||||
newUser: UserInsertSchema,
|
newUser: UserInsertSchema,
|
||||||
@@ -111,112 +123,78 @@ async function createUser(
|
|||||||
for (let op of opinions) {
|
for (let op of opinions) {
|
||||||
await db.insert(userOpinion).values({ ...op, userId: result.id });
|
await db.insert(userOpinion).values({ ...op, userId: result.id });
|
||||||
}
|
}
|
||||||
return { status: "OK" };
|
return { token: createJWT(newUser.phone) };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
throw new Error(`Unable to create new user:\n${e}`);
|
throw new Error(`Unable to create new user`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function requestOtp(phone: string) {
|
async function updateUser(userId: number, update: UserUpdateSchema) {
|
||||||
const _phone = phone.trim();
|
|
||||||
try {
|
try {
|
||||||
let rs = await fetch(Config.sms_api_request_endpoint, {
|
await db.update(user).set(update).where(eq(user.id, userId));
|
||||||
method: "POST",
|
return { status: "success" };
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
key: Config.sms_api_key,
|
|
||||||
secret: Config.sms_api_secret,
|
|
||||||
msisdn: _phone,
|
|
||||||
}),
|
|
||||||
}).then((res) => res.json());
|
|
||||||
|
|
||||||
if (rs.errors) {
|
|
||||||
console.error(rs);
|
|
||||||
throw new TRPCError({
|
|
||||||
message: `Unable to request OTP`,
|
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await db.insert(phoneToken).values({ phone: _phone, token: rs.token });
|
|
||||||
return {
|
|
||||||
status: rs.status as string,
|
|
||||||
token: rs.token as string,
|
|
||||||
refno: rs.refno as string,
|
|
||||||
};
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
throw new TRPCError({
|
throw new Error(`Unable to update user`);
|
||||||
message: `Unable to request OTP:\n${e}`,
|
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function verifyOtp(token: string, pin: string) {
|
async function login(cid: string, phone: string) {
|
||||||
try {
|
let user = await db.query.user.findFirst({
|
||||||
console.log(token, pin);
|
where: (user, { and, eq }) => and(eq(user.cid, cid), eq(user.phone, phone)),
|
||||||
let pt = await db.query.phoneToken.findFirst({
|
});
|
||||||
where: (pt, { eq }) => eq(pt.token, token),
|
if (user === undefined) {
|
||||||
orderBy: (pt, { desc }) => desc(pt.createdOn),
|
|
||||||
});
|
|
||||||
if (pt === undefined) {
|
|
||||||
throw new TRPCError({
|
|
||||||
message: `Invalid token`,
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
pt;
|
|
||||||
let rs = await fetch(Config.sms_api_verify_endpoint, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
key: Config.sms_api_key,
|
|
||||||
secret: Config.sms_api_secret,
|
|
||||||
token,
|
|
||||||
pin,
|
|
||||||
}),
|
|
||||||
}).then((res) => res.json());
|
|
||||||
if (rs.errors) {
|
|
||||||
console.error(rs);
|
|
||||||
throw new TRPCError({
|
|
||||||
message: `Unable to verify OTP`,
|
|
||||||
code: "BAD_REQUEST",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await db.delete(phoneToken).where(eq(phoneToken.phone, pt.phone));
|
|
||||||
console.log(rs, pt.phone);
|
|
||||||
return rs;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
message: `Unable to verify OTP:\n${e}`,
|
message: "Invalid Credentials",
|
||||||
code: "BAD_REQUEST",
|
code: "BAD_REQUEST",
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
return { token: createJWT(user.phone) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createJWT(phone: string) {
|
||||||
|
return jwt.sign({ phone: phone }, Config.jwt_secret, {
|
||||||
|
expiresIn: "365d",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function changeOpinion(
|
async function changeOpinion(
|
||||||
opinionId: number,
|
opinionId: number,
|
||||||
userId: number,
|
userId: number,
|
||||||
opinion: OpinionInsertSchema[0]["choice"]
|
opinionChoice: OpinionInsertSchema[0]["choice"]
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
db.insert(userOpinion)
|
let thisOpinion = await db
|
||||||
|
.select()
|
||||||
|
.from(opinion)
|
||||||
|
.where(eq(opinion.id, opinionId))
|
||||||
|
.then((opinions) => opinions.at(0));
|
||||||
|
|
||||||
|
if (thisOpinion === undefined) {
|
||||||
|
throw new TRPCError({
|
||||||
|
message: "Invalid Opinion ID",
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
});
|
||||||
|
} else if (thisOpinion.type === "3Choice" && opinionChoice === "ignore") {
|
||||||
|
throw new TRPCError({
|
||||||
|
message: "Invalid Opinion Choice",
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await db
|
||||||
|
.insert(userOpinion)
|
||||||
.values({
|
.values({
|
||||||
opinionId,
|
opinionId,
|
||||||
userId,
|
userId,
|
||||||
choice: opinion,
|
choice: opinionChoice,
|
||||||
})
|
})
|
||||||
.onConflictDoUpdate({
|
.onConflictDoUpdate({
|
||||||
target: [userOpinion.userId, userOpinion.opinionId],
|
target: [userOpinion.userId, userOpinion.opinionId],
|
||||||
set: { choice: opinion },
|
set: { choice: opinionChoice },
|
||||||
});
|
});
|
||||||
|
return { status: "success" };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -225,3 +203,19 @@ async function changeOpinion(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function isValidThaiID(id: string) {
|
||||||
|
if (!/^\d{13}$/.test(id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let sum = 0;
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
sum += Number(id[i]) * (13 - i);
|
||||||
|
}
|
||||||
|
const checkSum = (11 - (sum % 11)) % 10;
|
||||||
|
return checkSum === Number(id[12]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hidePhone(phone: string | null) {
|
||||||
|
if (phone === null) return phone;
|
||||||
|
return phone.slice(0, 2).concat("******").concat(phone.slice(-3));
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user