yuh and so it beginzzzzz
This commit is contained in:
143
src/lib/server/logger.ts
Normal file
143
src/lib/server/logger.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import winston from "winston";
|
||||
import DailyRotateFile from "winston-daily-rotate-file";
|
||||
import util from "util";
|
||||
import { Err } from "./result";
|
||||
import { env } from "$env/dynamic/private";
|
||||
import path from "path";
|
||||
|
||||
process.on("warning", (warning) => {
|
||||
const msg = String(warning?.message || "");
|
||||
const name = String((warning as any)?.name || "");
|
||||
|
||||
// Ignore the noisy timer warning from Node/kafkajs interplay
|
||||
if (
|
||||
name === "TimeoutNegativeWarning" ||
|
||||
msg.includes("TimeoutNegativeWarning") ||
|
||||
msg.includes("Timeout duration was set to 1")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Keep other warnings visible
|
||||
console.warn(warning);
|
||||
});
|
||||
|
||||
const levels = {
|
||||
error: 0,
|
||||
warn: 1,
|
||||
info: 2,
|
||||
http: 3,
|
||||
debug: 4,
|
||||
};
|
||||
|
||||
const colors = {
|
||||
error: "red",
|
||||
warn: "yellow",
|
||||
info: "green",
|
||||
http: "magenta",
|
||||
debug: "white",
|
||||
};
|
||||
|
||||
const level = () => {
|
||||
const envLevel = env.LOG_LEVEL?.toLowerCase();
|
||||
if (envLevel && envLevel in levels) {
|
||||
return envLevel;
|
||||
}
|
||||
return env.NODE_ENV === "development" ? "debug" : "warn";
|
||||
};
|
||||
|
||||
// Console format with colors
|
||||
const consoleFormat = winston.format.combine(
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss:ms" }),
|
||||
winston.format.colorize({ all: true }),
|
||||
winston.format.printf((info: any) => {
|
||||
const { level, message, timestamp, ...extra } = info;
|
||||
|
||||
let formattedMessage = "";
|
||||
if (message instanceof Error) {
|
||||
formattedMessage = message.stack || message.message;
|
||||
} else if (typeof message === "object") {
|
||||
formattedMessage = util.inspect(message, {
|
||||
depth: null,
|
||||
colors: true,
|
||||
});
|
||||
} else {
|
||||
formattedMessage = message as any as string;
|
||||
}
|
||||
|
||||
// Handle extra fields (if any)
|
||||
const formattedExtra =
|
||||
Object.keys(extra).length > 0
|
||||
? `\n${util.inspect(extra, { depth: null, colors: true })}`
|
||||
: "";
|
||||
|
||||
return `[${level}] ${timestamp}: ${formattedMessage}${formattedExtra}`;
|
||||
})
|
||||
);
|
||||
|
||||
// JSON format for file logging
|
||||
const fileFormat = winston.format.combine(
|
||||
winston.format.errors({ stack: true }),
|
||||
winston.format.timestamp(),
|
||||
winston.format.json()
|
||||
);
|
||||
|
||||
// Log directory - use logs folder in project root
|
||||
const logDir = path.join(process.cwd(), "logs");
|
||||
|
||||
// Daily rotate file transport for all logs
|
||||
const dailyRotateFileTransport = new DailyRotateFile({
|
||||
filename: path.join(logDir, "app-%DATE%.log"),
|
||||
datePattern: "YYYY-MM-DD",
|
||||
zippedArchive: true,
|
||||
maxSize: "20m",
|
||||
maxFiles: "14d", // Keep logs for 14 days
|
||||
format: fileFormat,
|
||||
});
|
||||
|
||||
// Daily rotate file transport for errors only
|
||||
const dailyRotateErrorTransport = new DailyRotateFile({
|
||||
filename: path.join(logDir, "error-%DATE%.log"),
|
||||
datePattern: "YYYY-MM-DD",
|
||||
zippedArchive: true,
|
||||
maxSize: "20m",
|
||||
maxFiles: "30d", // Keep error logs for 30 days
|
||||
level: "error",
|
||||
format: fileFormat,
|
||||
});
|
||||
|
||||
// Build transports
|
||||
const transports: winston.transport[] = [
|
||||
new winston.transports.Console({ format: consoleFormat }),
|
||||
dailyRotateFileTransport,
|
||||
dailyRotateErrorTransport,
|
||||
];
|
||||
|
||||
winston.addColors(colors);
|
||||
|
||||
const logger = winston.createLogger({
|
||||
level: level(),
|
||||
levels,
|
||||
transports,
|
||||
format: fileFormat, // Default format for all transports
|
||||
exceptionHandlers: [dailyRotateFileTransport, dailyRotateErrorTransport],
|
||||
rejectionHandlers: [dailyRotateFileTransport, dailyRotateErrorTransport],
|
||||
});
|
||||
|
||||
const stream = { write: (message: string) => logger.http(message.trim()) };
|
||||
|
||||
function getError(payload: Err, error?: any) {
|
||||
logger.error(JSON.stringify({ payload, error }, null, 2));
|
||||
console.error(error);
|
||||
return {
|
||||
code: payload.code,
|
||||
message: payload.message,
|
||||
description: payload.description,
|
||||
detail: payload.detail,
|
||||
error: error instanceof Error ? error.message : error,
|
||||
actionable: payload.actionable,
|
||||
} as Err;
|
||||
}
|
||||
|
||||
export { getError, logger, stream };
|
||||
81
src/lib/server/result.ts
Normal file
81
src/lib/server/result.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
export const ERROR_CODES = {
|
||||
API_ERROR: "API_ERROR",
|
||||
EXTERNAL_API_ERROR: "EXTERNAL_API_ERROR",
|
||||
RATE_LIMIT_ERROR: "RATE_LIMIT_ERROR",
|
||||
DATABASE_ERROR: "DATABASE_ERROR",
|
||||
NETWORK_ERROR: "NETWORK_ERROR",
|
||||
BANNED: "BANNED",
|
||||
AUTH_ERROR: "AUTH_ERROR",
|
||||
PERMISSION_ERROR: "PERMISSION_ERROR",
|
||||
VALIDATION_ERROR: "VALIDATION_ERROR",
|
||||
UNKNOWN_ERROR: "UNKNOWN_ERROR",
|
||||
NOT_FOUND_ERROR: "NOT_FOUND_ERROR",
|
||||
NOT_FOUND: "NOT_FOUND",
|
||||
INPUT_ERROR: "INPUT_ERROR",
|
||||
INTERNAL_SERVER_ERROR: "INTERNAL_SERVER_ERROR",
|
||||
EXTERNAL_SERVICE_ERROR: "EXTERNAL_SERVICE_ERROR",
|
||||
FILE_SYSTEM_ERROR: "FILE_SYSTEM_ERROR",
|
||||
STORAGE_ERROR: "STORAGE_ERROR",
|
||||
NOT_ALLOWED: "NOT_ALLOWED",
|
||||
NOT_IMPLEMENTED: "NOT_IMPLEMENTED",
|
||||
PROCESSING_ERROR: "PROCESSING_ERROR",
|
||||
PARSING_ERROR: "PARSING_ERROR",
|
||||
} as const;
|
||||
|
||||
export const errorStatusMap = {
|
||||
[ERROR_CODES.VALIDATION_ERROR]: 400,
|
||||
[ERROR_CODES.AUTH_ERROR]: 403,
|
||||
[ERROR_CODES.BANNED]: 403,
|
||||
[ERROR_CODES.NOT_FOUND]: 404,
|
||||
[ERROR_CODES.NOT_ALLOWED]: 405,
|
||||
[ERROR_CODES.RATE_LIMIT_ERROR]: 429,
|
||||
[ERROR_CODES.DATABASE_ERROR]: 500,
|
||||
[ERROR_CODES.NETWORK_ERROR]: 500,
|
||||
[ERROR_CODES.EXTERNAL_API_ERROR]: 500,
|
||||
[ERROR_CODES.API_ERROR]: 500,
|
||||
[ERROR_CODES.INTERNAL_SERVER_ERROR]: 500,
|
||||
[ERROR_CODES.EXTERNAL_SERVICE_ERROR]: 500,
|
||||
[ERROR_CODES.FILE_SYSTEM_ERROR]: 500,
|
||||
[ERROR_CODES.STORAGE_ERROR]: 500,
|
||||
[ERROR_CODES.PROCESSING_ERROR]: 500,
|
||||
[ERROR_CODES.PARSING_ERROR]: 500,
|
||||
[ERROR_CODES.NOT_IMPLEMENTED]: 501,
|
||||
} as Record<string, number>;
|
||||
|
||||
export type Err = {
|
||||
flowId?: string;
|
||||
code: string;
|
||||
message: string;
|
||||
description: string;
|
||||
detail: string;
|
||||
actionable?: boolean;
|
||||
error?: any;
|
||||
};
|
||||
|
||||
type Success<T> = { data: T; error?: undefined | null };
|
||||
type Failure<E> = { data?: undefined | null; error: E };
|
||||
|
||||
// Legacy now, making use of Effect throughout the project
|
||||
export type Result<T, E = Err> = Success<T> | Failure<E>;
|
||||
|
||||
export async function tryCatch<T, E = Err>(
|
||||
promise: Promise<T>,
|
||||
err?: E
|
||||
): Promise<Result<T, E>> {
|
||||
try {
|
||||
const data = await promise;
|
||||
return { data };
|
||||
} catch (e) {
|
||||
return {
|
||||
// @ts-ignore
|
||||
error: !!err
|
||||
? err
|
||||
: {
|
||||
code: "UNKNOWN_ERROR",
|
||||
message: "An unknown error occurred",
|
||||
description: "An unknown error occurred",
|
||||
detail: "An unknown error occurred",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -11,22 +11,50 @@ import type { ServerError } from "$lib/utils/data.types";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import { createTRPCRouter, protectedProcedure } from "../t";
|
||||
import { env } from "$env/dynamic/private";
|
||||
import { logger } from "$lib/server/logger";
|
||||
import fetch from "node-fetch";
|
||||
|
||||
|
||||
export const apiAuthRouter = createTRPCRouter({
|
||||
getCaptcha: protectedProcedure.mutation(async () => {
|
||||
const scrapingbeeApiKey = env.SCRAPINGBEE_API_KEY;
|
||||
|
||||
try {
|
||||
const uuid = getUUID();
|
||||
const res = await fetch(`${constants.PROXY_API_URL}/verify/image?uuid=${uuid}`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const bloob = await res.blob();
|
||||
const imageBuffer = Buffer.from(await bloob.arrayBuffer());
|
||||
const targetUrl = `${constants.SCRAP_API_URL}/verify/image?uuid=${uuid}`;
|
||||
|
||||
logger.info(`[getCaptcha] Fetching captcha image for uuid: ${uuid}`);
|
||||
|
||||
// Build ScrapingBee API URL with params
|
||||
const scrapingbeeUrl = new URL("https://app.scrapingbee.com/api/v1");
|
||||
scrapingbeeUrl.searchParams.set("api_key", scrapingbeeApiKey);
|
||||
scrapingbeeUrl.searchParams.set("url", targetUrl);
|
||||
scrapingbeeUrl.searchParams.set("render_js", "false");
|
||||
scrapingbeeUrl.searchParams.set("block_resources", "false");
|
||||
|
||||
const res = await fetch(scrapingbeeUrl.toString());
|
||||
|
||||
if (!res.ok || res.status !== 200) {
|
||||
// Clone response before reading to avoid consuming body
|
||||
const clonedRes = res.clone();
|
||||
const errorText = await clonedRes.text().catch(() => "Unknown error");
|
||||
logger.error(`[getCaptcha] ScrapingBee error ${res.status}: ${errorText.substring(0, 200)}`);
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: `Failed to fetch captcha image: ${res.status}`,
|
||||
});
|
||||
}
|
||||
|
||||
// Read the response as arrayBuffer (recommended method)
|
||||
const arrayBuffer = await res.arrayBuffer();
|
||||
const imageBuffer = Buffer.from(arrayBuffer);
|
||||
const base64String = imageBuffer.toString("base64");
|
||||
|
||||
logger.info(`[getCaptcha] Successfully fetched captcha image for uuid: ${uuid}, size: ${imageBuffer.length} bytes`);
|
||||
return { id: uuid, image: base64String };
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
logger.error("[getCaptcha] Error getting captcha image", err);
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Error getting captcha image.",
|
||||
@@ -43,15 +71,15 @@ export const apiAuthRouter = createTRPCRouter({
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
console.log("[=] Getting new session... ", input);
|
||||
logger.info(`[getNewSession] Getting new session for userId: ${input.userId || "random"}`);
|
||||
try {
|
||||
const { captchaId, captchaAnswer } = input;
|
||||
let { userId, userType, password } = await dbApiUser.getRandomDistributor();
|
||||
|
||||
if (input.userId) {
|
||||
let _user = await dbApiUser.getUserById(input.userId);
|
||||
console.log("[=] User :: ", _user?.userId);
|
||||
if (!_user) {
|
||||
logger.warn(`[getNewSession] User not found: ${input.userId}`);
|
||||
return {
|
||||
success: false,
|
||||
errors: [{ message: "User not found." }],
|
||||
@@ -60,8 +88,10 @@ export const apiAuthRouter = createTRPCRouter({
|
||||
userId = _user.userId;
|
||||
userType = _user.userType;
|
||||
password = _user.password;
|
||||
logger.info(`[getNewSession] Using specific user: ${userId}`);
|
||||
}
|
||||
console.log(`[=] Getting session token for user ${userId}...`);
|
||||
|
||||
logger.info(`[getNewSession] Getting session token for user ${userId}`);
|
||||
const token = await getSessionToken({
|
||||
code: captchaAnswer,
|
||||
verifyToken: captchaId,
|
||||
@@ -69,17 +99,20 @@ export const apiAuthRouter = createTRPCRouter({
|
||||
userType: userType,
|
||||
password: password,
|
||||
});
|
||||
console.log("[=] Token Response :: ", JSON.stringify(token, null, 2));
|
||||
|
||||
if (!token.ok) {
|
||||
logger.warn(`[getNewSession] Failed to get session token: ${token.message}`);
|
||||
return {
|
||||
success: false,
|
||||
errors: [{ message: token.message }],
|
||||
};
|
||||
}
|
||||
|
||||
await setSessionToRedis(token.message, input.userId ?? "");
|
||||
logger.info(`[getNewSession] Successfully created session for user ${userId}`);
|
||||
return { success: true, errors: [] as ServerError };
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
logger.error("[getNewSession] Error getting new session", err);
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Error getting new session.",
|
||||
@@ -102,6 +135,7 @@ export const apiAuthRouter = createTRPCRouter({
|
||||
.input(z.object({ userId: z.string().optional() }))
|
||||
.mutation(async ({ input }) => {
|
||||
const { userId } = input;
|
||||
logger.info(`[logoutUser] Logging out user: ${userId || "all"}`);
|
||||
await removeSessionFromStore(userId);
|
||||
return { success: true, errors: [] as ServerError };
|
||||
}),
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from "$lib/utils/data.types";
|
||||
import { z } from "zod";
|
||||
import { createTRPCRouter, protectedProcedure } from "../t";
|
||||
import { logger } from "$lib/server/logger";
|
||||
|
||||
const lastFetched = {
|
||||
get: async () => {
|
||||
@@ -30,9 +31,11 @@ const lastFetched = {
|
||||
|
||||
export const apiDataRouter = createTRPCRouter({
|
||||
getDealersAndDraws: protectedProcedure.query(async () => {
|
||||
logger.debug("[getDealersAndDraws] Fetching dealers and draws");
|
||||
const draws = await dbDraw.getAllDraws(true);
|
||||
const dealers = await dbApiUser.allUsersOfType(ApiUserTypes.DEALER);
|
||||
const lf = await lastFetched.get();
|
||||
logger.info(`[getDealersAndDraws] Found ${draws.length} draws and ${dealers.length} dealers`);
|
||||
return { users: dealers, draws, lastFetched: lf };
|
||||
}),
|
||||
|
||||
@@ -47,6 +50,7 @@ export const apiDataRouter = createTRPCRouter({
|
||||
.mutation(async ({ input }) => {
|
||||
const { userIds, targetDate, drawId } = input;
|
||||
if (userIds.length < 1) {
|
||||
logger.warn("[refetchData] No users selected");
|
||||
return {
|
||||
detail: "No users selected",
|
||||
success: false,
|
||||
@@ -59,6 +63,7 @@ export const apiDataRouter = createTRPCRouter({
|
||||
(await redis.get(constants.SCRAP_API_SESSION_KEY)) ?? "",
|
||||
) as APISession;
|
||||
if (sess === null) {
|
||||
logger.warn("[refetchData] API session expired");
|
||||
return {
|
||||
detail: "API Session expired",
|
||||
success: false,
|
||||
@@ -70,9 +75,7 @@ export const apiDataRouter = createTRPCRouter({
|
||||
] as ServerError,
|
||||
};
|
||||
}
|
||||
console.log(
|
||||
`Fetching data for ${userIds.length} users for draw ${drawId}`,
|
||||
);
|
||||
logger.info(`[refetchData] Fetching data for ${userIds.length} users, draw ${drawId}, date ${targetDate}`);
|
||||
const userIdsInt = userIds.map((x) => parseInt(x.split(":")[1]));
|
||||
const out = await getData(
|
||||
sess.sessionToken,
|
||||
@@ -81,6 +84,7 @@ export const apiDataRouter = createTRPCRouter({
|
||||
targetDate,
|
||||
);
|
||||
if (!out.ok) {
|
||||
logger.error(`[refetchData] Error fetching data: ${out.message}`);
|
||||
return {
|
||||
success: false,
|
||||
detail: "Error fetching data",
|
||||
@@ -88,7 +92,9 @@ export const apiDataRouter = createTRPCRouter({
|
||||
};
|
||||
}
|
||||
const dataCount = out.data.length;
|
||||
logger.info(`[refetchData] Fetched ${dataCount} entries, upserting to database`);
|
||||
await dbApiData.upsertData(out.data, targetDate);
|
||||
logger.info(`[refetchData] Successfully scraped and saved ${dataCount} entries for ${userIds.length} users`);
|
||||
return {
|
||||
detail: `Scraped ${dataCount} entries for ${userIds.length} users`,
|
||||
success: true,
|
||||
@@ -106,12 +112,14 @@ export const apiDataRouter = createTRPCRouter({
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
const { date, drawId, userId } = input;
|
||||
logger.info(`[getDataByFilters] Fetching data for date ${date}, draw ${drawId}, user ${userId}`);
|
||||
const data = await dbApiData.getBookingEntriesForDealer(
|
||||
date,
|
||||
drawId.split(":")[1],
|
||||
userId.split(":")[1],
|
||||
true,
|
||||
);
|
||||
logger.info(`[getDataByFilters] Found ${data.length} entries`);
|
||||
return { data };
|
||||
}),
|
||||
|
||||
@@ -119,6 +127,7 @@ export const apiDataRouter = createTRPCRouter({
|
||||
.input(z.object({ date: z.string(), drawId: z.string() }))
|
||||
.mutation(async ({ input }) => {
|
||||
const { date, drawId } = input;
|
||||
logger.info(`[getReducedFinalSheet] Compiling final sheet for date ${date}, draw ${drawId}`);
|
||||
const draw = await dbDraw.getDraw(drawId);
|
||||
const fsData = {
|
||||
id: getULID(),
|
||||
@@ -128,6 +137,7 @@ export const apiDataRouter = createTRPCRouter({
|
||||
totals: getDefaultTotals(),
|
||||
} as ReducedFinalSheetData;
|
||||
if (!draw) {
|
||||
logger.warn(`[getReducedFinalSheet] Draw not found: ${drawId}`);
|
||||
return {
|
||||
ok: false,
|
||||
detail: `Draw for the passed draw ID not found`,
|
||||
@@ -137,10 +147,9 @@ export const apiDataRouter = createTRPCRouter({
|
||||
] as ServerError,
|
||||
};
|
||||
}
|
||||
console.log("Fetching data");
|
||||
const data = await getReducedFinalSheet(fsData);
|
||||
console.log(data);
|
||||
if (!data.ok) {
|
||||
logger.error(`[getReducedFinalSheet] Error compiling final sheet: ${data.errors?.map(e => e.message).join(", ")}`);
|
||||
return {
|
||||
ok: false,
|
||||
detail: `Error compiling final sheet`,
|
||||
@@ -148,6 +157,7 @@ export const apiDataRouter = createTRPCRouter({
|
||||
errors: data.errors,
|
||||
};
|
||||
}
|
||||
logger.info(`[getReducedFinalSheet] Successfully compiled final sheet for ${date}, draw ${draw.title}`);
|
||||
return {
|
||||
ok: true,
|
||||
detail: `Final sheet for ${date}, draw ${draw.title} has been compiled`,
|
||||
@@ -165,6 +175,7 @@ export const apiDataRouter = createTRPCRouter({
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ input }) => {
|
||||
logger.debug(`[getFinalSheetRow] Getting final sheet row for number ${input.number}`);
|
||||
return {
|
||||
ok: true,
|
||||
data: {},
|
||||
@@ -173,26 +184,20 @@ export const apiDataRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
delDataOlderThan2Weeks: protectedProcedure.mutation(async () => {
|
||||
logger.info("[delDataOlderThan2Weeks] Deleting data older than 2 weeks");
|
||||
await dbApiData.deleteDataOlderThan2Weeks();
|
||||
logger.info("[delDataOlderThan2Weeks] Successfully deleted old data");
|
||||
return { ok: true, detail: "Data older than 2 weeks has been deleted" };
|
||||
}),
|
||||
|
||||
postTestBooking: protectedProcedure
|
||||
.input(z.object({ drawId: z.string(), date: z.string() }))
|
||||
.mutation(async () => {
|
||||
logger.debug("[postTestBooking] Test booking endpoint called (not live)");
|
||||
return {
|
||||
ok: true,
|
||||
detail: "API not live",
|
||||
errors: [] as ServerError,
|
||||
};
|
||||
// console.log("GENERATING TEST DATA :: ", drawId, date);
|
||||
// const testData = await getTestBookingData(drawId, date);
|
||||
// // console.log(testData);
|
||||
// await dbApiData.upsertData(testData, date);
|
||||
// return {
|
||||
// ok: true,
|
||||
// detail: "Test booking committed",
|
||||
// errors: [] as ServerError,
|
||||
// };
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -2,32 +2,51 @@ import { createTRPCRouter, protectedProcedure } from "../t";
|
||||
import { ApiUserTypes, zApiPostUser } from "$lib/utils/data.types";
|
||||
import { dbApiUser } from "$lib/server/db/apiuser.db";
|
||||
import { z } from "zod";
|
||||
import { logger } from "$lib/server/logger";
|
||||
|
||||
export const apiUserRouter = createTRPCRouter({
|
||||
getAllDistributors: protectedProcedure.query(async () => {
|
||||
return await dbApiUser.allUsersOfType(ApiUserTypes.DISTRIBUTOR);
|
||||
logger.debug("[getAllDistributors] Fetching all distributors");
|
||||
const distributors = await dbApiUser.allUsersOfType(ApiUserTypes.DISTRIBUTOR);
|
||||
logger.info(`[getAllDistributors] Found ${distributors.length} distributors`);
|
||||
return distributors;
|
||||
}),
|
||||
getAllDealers: protectedProcedure.query(async () => {
|
||||
return await dbApiUser.allUsersOfType(ApiUserTypes.DEALER);
|
||||
logger.debug("[getAllDealers] Fetching all dealers");
|
||||
const dealers = await dbApiUser.allUsersOfType(ApiUserTypes.DEALER);
|
||||
logger.info(`[getAllDealers] Found ${dealers.length} dealers`);
|
||||
return dealers;
|
||||
}),
|
||||
getAllDistributorsCount: protectedProcedure.query(async () => {
|
||||
return await dbApiUser.getUserTypeCount(ApiUserTypes.DISTRIBUTOR);
|
||||
const count = await dbApiUser.getUserTypeCount(ApiUserTypes.DISTRIBUTOR);
|
||||
logger.debug(`[getAllDistributorsCount] Count: ${count}`);
|
||||
return count;
|
||||
}),
|
||||
getAllDealersCount: protectedProcedure.query(async () => {
|
||||
return await dbApiUser.getUserTypeCount(ApiUserTypes.DEALER);
|
||||
const count = await dbApiUser.getUserTypeCount(ApiUserTypes.DEALER);
|
||||
logger.debug(`[getAllDealersCount] Count: ${count}`);
|
||||
return count;
|
||||
}),
|
||||
|
||||
getAllDealersPostUserFormat: protectedProcedure.query(async () => {
|
||||
return await dbApiUser.allUsersOfTypeLimitedInfo(ApiUserTypes.DEALER);
|
||||
logger.debug("[getAllDealersPostUserFormat] Fetching dealers in post user format");
|
||||
const dealers = await dbApiUser.allUsersOfTypeLimitedInfo(ApiUserTypes.DEALER);
|
||||
logger.info(`[getAllDealersPostUserFormat] Found ${dealers.length} dealers`);
|
||||
return dealers;
|
||||
}),
|
||||
|
||||
getAllPostUsers: protectedProcedure.query(async () => {
|
||||
return await dbApiUser.getAllPostUsers();
|
||||
logger.debug("[getAllPostUsers] Fetching all post users");
|
||||
const users = await dbApiUser.getAllPostUsers();
|
||||
logger.info(`[getAllPostUsers] Found ${users.length} post users`);
|
||||
return users;
|
||||
}),
|
||||
|
||||
setPostDataFlagForUser: protectedProcedure
|
||||
.input(z.object({ users: z.array(zApiPostUser) }))
|
||||
.mutation(async ({ input }) => {
|
||||
logger.info(`[setPostDataFlagForUser] Setting post data flag for ${input.users.length} users`);
|
||||
await dbApiUser.setPostDataFlagForUsers(input.users);
|
||||
logger.info("[setPostDataFlagForUser] Successfully updated post data flags");
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from "$lib/utils/data.types";
|
||||
import { surreal } from "$lib/server/connectors/surreal.db";
|
||||
import { parseToDateString } from "$lib/utils/datetime.helper.utils";
|
||||
import { logger } from "$lib/server/logger";
|
||||
|
||||
function getTodaysTableName() {
|
||||
const today = parseToDateString(new Date());
|
||||
@@ -17,10 +18,12 @@ function getTodaysTableName() {
|
||||
|
||||
export const bookingRouter = createTRPCRouter({
|
||||
getPanelData: protectedProcedure.query(async () => {
|
||||
logger.debug("[getPanelData] Fetching panel data");
|
||||
const draws = await dbDraw.getAllDraws(true);
|
||||
const timeInDrawsTz = new Date().toLocaleString("en-US", {
|
||||
timeZone: DEFAULT_TZ,
|
||||
});
|
||||
logger.info(`[getPanelData] Found ${draws.length} draws`);
|
||||
return { draws, timeInDrawsTz: timeInDrawsTz };
|
||||
}),
|
||||
|
||||
@@ -31,17 +34,21 @@ export const bookingRouter = createTRPCRouter({
|
||||
const date = parseToDateString(new Date());
|
||||
const tn = getTodaysTableName();
|
||||
const did = parseInt(drawId.split(":")[1]);
|
||||
logger.info(`[getBookingData] Fetching booking data for draw ${did}, date ${date}`);
|
||||
const [out] = await surreal.query<[BookingEntry[]]>(
|
||||
`select * from type::table($table) where drawId = $drawId and bookDate = $bookDate order by requestId desc`,
|
||||
{ table: tn, drawId: did, bookDate: date },
|
||||
);
|
||||
return { data: out ?? [], errors: [] as ServerError };
|
||||
const data = out ?? [];
|
||||
logger.info(`[getBookingData] Found ${data.length} booking entries`);
|
||||
return { data, errors: [] as ServerError };
|
||||
}),
|
||||
|
||||
syncBooking: protectedProcedure
|
||||
.input(z.object({ data: z.array(zBookingEntry) }))
|
||||
.mutation(async ({ input }) => {
|
||||
const tableName = getTodaysTableName();
|
||||
logger.info(`[syncBooking] Syncing ${input.data.length} booking entries`);
|
||||
const syncedEntriesIds = [] as string[];
|
||||
if (input.data.length > 0) {
|
||||
await surreal.insert<BookingEntry>(
|
||||
@@ -56,17 +63,20 @@ export const bookingRouter = createTRPCRouter({
|
||||
}),
|
||||
);
|
||||
}
|
||||
logger.info(`[syncBooking] Successfully synced ${syncedEntriesIds.length} booking entries`);
|
||||
return { detail: "Add Booking api donezo", syncedEntriesIds };
|
||||
}),
|
||||
|
||||
deleteBooking: protectedProcedure
|
||||
.input(z.object({ bookingIds: z.array(z.string()) }))
|
||||
.mutation(async ({ input }) => {
|
||||
logger.info(`[deleteBooking] Deleting ${input.bookingIds.length} booking entries`);
|
||||
await Promise.all(
|
||||
input.bookingIds.map(async (id) => {
|
||||
await surreal.delete(id);
|
||||
}),
|
||||
);
|
||||
logger.info(`[deleteBooking] Successfully deleted ${input.bookingIds.length} entries`);
|
||||
return { detail: `Deleted ${input.bookingIds.length} Entries` };
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -2,10 +2,14 @@ import { createTRPCRouter, protectedProcedure } from "../t";
|
||||
import { dbDraw } from "$lib/server/db/apidraw.db";
|
||||
import { z } from "zod";
|
||||
import { zDraw } from "$lib/utils/data.types";
|
||||
import { logger } from "$lib/server/logger";
|
||||
|
||||
export const drawRouter = createTRPCRouter({
|
||||
getAllDraws: protectedProcedure.query(async () => {
|
||||
return await dbDraw.getAllDraws(true);
|
||||
logger.debug("[getAllDraws] Fetching all draws");
|
||||
const draws = await dbDraw.getAllDraws(true);
|
||||
logger.info(`[getAllDraws] Found ${draws.length} draws`);
|
||||
return draws;
|
||||
}),
|
||||
|
||||
getCurrentTime: protectedProcedure.query(async () => {
|
||||
@@ -14,18 +18,17 @@ export const drawRouter = createTRPCRouter({
|
||||
const nowKarachi = new Date(
|
||||
now.toLocaleString("en-US", { timeZone: timezone }),
|
||||
);
|
||||
// console.log(nowKarachi.toLocaleString());
|
||||
return { now: nowKarachi };
|
||||
}),
|
||||
|
||||
savePresetInfoForDraws: protectedProcedure
|
||||
.input(z.object({ draws: z.array(zDraw) }))
|
||||
.mutation(async ({ input }) => {
|
||||
console.log("Saving preset info for draws");
|
||||
logger.info(`[savePresetInfoForDraws] Saving preset info for ${input.draws.length} draws`);
|
||||
for (const draw of input.draws) {
|
||||
await dbDraw.updateDrawPresetInfo(draw);
|
||||
}
|
||||
console.log("Done saving preset info for draws");
|
||||
logger.info("[savePresetInfoForDraws] Successfully saved preset info for all draws");
|
||||
return { success: true };
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from "$lib/server/postdata/postdata.gen.controller";
|
||||
import { redis } from "$lib/server/connectors/redis";
|
||||
import { constants } from "$lib/utils/constants";
|
||||
import { logger } from "$lib/server/logger";
|
||||
|
||||
async function hasPostSession() {
|
||||
const out = await redis.get(constants.POST_SESSION_KEY);
|
||||
@@ -49,18 +50,21 @@ export const postDataApiRouter = createTRPCRouter({
|
||||
fetchPostDataHistory: protectedProcedure
|
||||
.input(zPostDataHistoryFilters)
|
||||
.mutation(async ({ input }) => {
|
||||
return await fetchPostDataHistory(input);
|
||||
logger.info(`[fetchPostDataHistory] Fetching post data history for date ${input.date}, draw ${input.draw?.id}`);
|
||||
const result = await fetchPostDataHistory(input);
|
||||
logger.info(`[fetchPostDataHistory] Found ${result.data?.length || 0} history entries`);
|
||||
return result;
|
||||
}),
|
||||
|
||||
hasPosted: protectedProcedure
|
||||
.input(zPostDataHistoryFilters)
|
||||
.query(async ({ input }) => {
|
||||
return {
|
||||
hasPosted: await dbApiPostData.doesPostHistoryDataExist(
|
||||
input.date,
|
||||
input.draw?.id ?? "",
|
||||
),
|
||||
};
|
||||
const hasPosted = await dbApiPostData.doesPostHistoryDataExist(
|
||||
input.date,
|
||||
input.draw?.id ?? "",
|
||||
);
|
||||
logger.debug(`[hasPosted] Checked for date ${input.date}, draw ${input.draw?.id}: ${hasPosted}`);
|
||||
return { hasPosted };
|
||||
}),
|
||||
|
||||
getPostDataForPreview: protectedProcedure
|
||||
@@ -69,6 +73,7 @@ export const postDataApiRouter = createTRPCRouter({
|
||||
const date = input.date;
|
||||
const cacheKey = getULID();
|
||||
if (!input.draw) {
|
||||
logger.warn("[getPostDataForPreview] Draw is required but not provided");
|
||||
return {
|
||||
ok: false,
|
||||
detail: "Draw is required",
|
||||
@@ -78,11 +83,12 @@ export const postDataApiRouter = createTRPCRouter({
|
||||
};
|
||||
}
|
||||
|
||||
console.log("[+] Fetching the users with updated balances");
|
||||
logger.info(`[getPostDataForPreview] Fetching users with updated balances for date ${date}, draw ${input.draw.id}`);
|
||||
const balOut = await updateBalanceOfPostUsers(
|
||||
await dbApiUser.getAllPostUsersWithParentUsers(),
|
||||
);
|
||||
if (!balOut.ok || !balOut.data) {
|
||||
logger.error(`[getPostDataForPreview] Failed to update balances: ${balOut.detail}`);
|
||||
return {
|
||||
ok: false,
|
||||
key: cacheKey,
|
||||
@@ -92,31 +98,37 @@ export const postDataApiRouter = createTRPCRouter({
|
||||
};
|
||||
}
|
||||
const users = balOut.data;
|
||||
console.log(`[=] ${users.length} users found`);
|
||||
console.log(users);
|
||||
logger.info(`[getPostDataForPreview] Found ${users.length} users with updated balances`);
|
||||
|
||||
const result = await fetchDataForPosting(date, input, users);
|
||||
postDataCacheStore.set(cacheKey, result.data).catch(console.error);
|
||||
console.log("result.data.length = ", result.data.length);
|
||||
postDataCacheStore.set(cacheKey, result.data).catch((err) => {
|
||||
logger.error(`[getPostDataForPreview] Error caching data: ${err}`);
|
||||
});
|
||||
logger.info(`[getPostDataForPreview] Generated ${result.data.length} entries for preview, cache key: ${cacheKey}`);
|
||||
return { ...result, key: cacheKey };
|
||||
}),
|
||||
|
||||
post: protectedProcedure
|
||||
.input(z.object({ yes: zPostDataFilters, cachedDataKey: z.string() }))
|
||||
.mutation(async ({ input }) => {
|
||||
const date = input.yes.date;
|
||||
const draw = input.yes.draw;
|
||||
|
||||
logger.info(`[post] Starting post data process for date ${date}, draw ${draw?.id}, cache key ${input.cachedDataKey}`);
|
||||
|
||||
if (await hasPostSession()) {
|
||||
const m =
|
||||
"Already posting data, please wait for the current session to finish";
|
||||
const m = "Already posting data, please wait for the current session to finish";
|
||||
logger.warn(`[post] Post session already in progress`);
|
||||
return {
|
||||
ok: false,
|
||||
detail: m,
|
||||
errors: [{ message: m }] as ServerError,
|
||||
};
|
||||
}
|
||||
const date = input.yes.date;
|
||||
const draw = input.yes.draw;
|
||||
|
||||
if (!draw) {
|
||||
await removePostSession();
|
||||
logger.warn("[post] Draw is required but not provided");
|
||||
return {
|
||||
ok: false,
|
||||
detail: "Draw is required",
|
||||
@@ -125,12 +137,12 @@ export const postDataApiRouter = createTRPCRouter({
|
||||
}
|
||||
const drawId = draw.id;
|
||||
|
||||
console.log("[+] Fetching the users");
|
||||
logger.info("[post] Fetching users and updating balances");
|
||||
const users = await dbApiUser.getAllPostUsersWithParentUsers();
|
||||
console.log(users);
|
||||
const balOut = await updateBalanceOfPostUsers(users);
|
||||
if (!balOut.ok || !balOut.data) {
|
||||
await removePostSession();
|
||||
logger.error(`[post] Failed to update balances: ${balOut.detail}`);
|
||||
return {
|
||||
ok: false,
|
||||
detail: balOut.detail,
|
||||
@@ -139,10 +151,9 @@ export const postDataApiRouter = createTRPCRouter({
|
||||
errors: [],
|
||||
};
|
||||
}
|
||||
console.log(`[=] ${users.length} users found`);
|
||||
console.log(users);
|
||||
logger.info(`[post] Found ${users.length} users with updated balances`);
|
||||
|
||||
console.log("[+] Preparing the sessions for posting");
|
||||
logger.info("[post] Preparing sessions for posting");
|
||||
const sessions = await getAllSessions();
|
||||
const userSessions = {} as Record<string, APISession>;
|
||||
for (const each of sessions) {
|
||||
@@ -155,6 +166,7 @@ export const postDataApiRouter = createTRPCRouter({
|
||||
|
||||
if (Object.keys(userSessions).length !== users.length) {
|
||||
await removePostSession();
|
||||
logger.error(`[post] Missing sessions: ${users.length} users but only ${Object.keys(userSessions).length} sessions`);
|
||||
return {
|
||||
ok: false,
|
||||
detail: `Some users don't have a session to post data with`,
|
||||
@@ -164,22 +176,25 @@ export const postDataApiRouter = createTRPCRouter({
|
||||
data: [],
|
||||
};
|
||||
}
|
||||
logger.info(`[post] Prepared ${Object.keys(userSessions).length} user sessions`);
|
||||
|
||||
let data: any[] = await postDataCacheStore.get(input.cachedDataKey);
|
||||
console.log("cached.data.length = ", data.length);
|
||||
logger.info(`[post] Retrieved ${data.length} entries from cache`);
|
||||
if (data.length < 1) {
|
||||
console.log("No data found from preview, generating a list");
|
||||
logger.info("[post] No cached data found, generating new data list");
|
||||
const _out = await fetchDataForPosting(date, input.yes, balOut.data);
|
||||
if (!_out.ok) {
|
||||
await removePostSession();
|
||||
logger.error(`[post] Failed to fetch data for posting: ${_out.detail}`);
|
||||
return _out;
|
||||
}
|
||||
data = _out.data;
|
||||
console.log("data.length = ", data.length);
|
||||
logger.info(`[post] Generated ${data.length} entries for posting`);
|
||||
}
|
||||
|
||||
if (data.length < 1) {
|
||||
await removePostSession();
|
||||
logger.warn("[post] No data found to post");
|
||||
return {
|
||||
ok: false,
|
||||
detail: "No data found to post for the selected date and draw",
|
||||
@@ -190,25 +205,24 @@ export const postDataApiRouter = createTRPCRouter({
|
||||
};
|
||||
}
|
||||
|
||||
console.log(`[+] Posting ${data.length} entries to the API`);
|
||||
|
||||
let ts = new Date().getTime();
|
||||
logger.info(`[post] Posting ${data.length} entries to the API`);
|
||||
const ts = new Date().getTime();
|
||||
const res = await postDataToApi({
|
||||
sessions: userSessions,
|
||||
data,
|
||||
users,
|
||||
draw,
|
||||
});
|
||||
let done = new Date().getTime();
|
||||
console.log(`Time taken to post data to the API: ${done - ts} ms`);
|
||||
const done = new Date().getTime();
|
||||
logger.info(`[post] API posting completed in ${done - ts}ms`);
|
||||
|
||||
if (!res.ok) {
|
||||
await removePostSession();
|
||||
logger.error(`[post] Failed to post data to API: ${res.detail}`);
|
||||
return { ok: false, detail: res.detail };
|
||||
}
|
||||
|
||||
console.log(`[+] Data posted to the API successfully`);
|
||||
|
||||
logger.info("[post] Data posted to API successfully, saving to database");
|
||||
await dbApiPostData.upsertData({
|
||||
id: getULID(),
|
||||
drawId: +drawId.split(":")[1],
|
||||
@@ -218,13 +232,12 @@ export const postDataApiRouter = createTRPCRouter({
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
// Update the balance of the users after posting to the API
|
||||
await updateBalanceOfPostUsers(users);
|
||||
|
||||
console.log("[+] Data saved to the database");
|
||||
logger.info("[post] Data saved to database and balances updated");
|
||||
|
||||
await postDataCacheStore.del(input.cachedDataKey);
|
||||
await removePostSession();
|
||||
logger.info(`[post] Successfully completed posting ${data.length} entries`);
|
||||
return {
|
||||
ok: true,
|
||||
detail: "Data successfully posted to API",
|
||||
|
||||
@@ -2,15 +2,20 @@ import { dbPresetData } from "$lib/server/db/presetdata.db";
|
||||
import { zDDFilters, zPresetDataEntry } from "$lib/utils/data.types";
|
||||
import { z } from "zod";
|
||||
import { createTRPCRouter, protectedProcedure } from "../t";
|
||||
import { logger } from "$lib/server/logger";
|
||||
|
||||
export const presetDataRouter = createTRPCRouter({
|
||||
getAll: protectedProcedure.input(zDDFilters).mutation(async ({ input }) => {
|
||||
const { draw, date } = input;
|
||||
if (!draw) {
|
||||
logger.warn("[presetData.getAll] Draw is required but not provided");
|
||||
return { ok: false, detail: "Draw is required to fetch data", data: [] };
|
||||
}
|
||||
logger.info(`[presetData.getAll] Fetching preset data for draw ${draw.id}, date ${date}`);
|
||||
const data = await dbPresetData.getDataByDraw(date, +draw.id.split(":")[1]);
|
||||
logger.info(`[presetData.getAll] Found ${data.length} preset data entries`);
|
||||
return {
|
||||
data: await dbPresetData.getDataByDraw(date, +draw.id.split(":")[1]),
|
||||
data,
|
||||
ok: true,
|
||||
detail: "Data found",
|
||||
};
|
||||
@@ -19,17 +24,22 @@ export const presetDataRouter = createTRPCRouter({
|
||||
insert: protectedProcedure
|
||||
.input(z.array(zPresetDataEntry))
|
||||
.mutation(async ({ input }) => {
|
||||
logger.info(`[presetData.insert] Inserting ${input.length} preset data entries`);
|
||||
const data = await dbPresetData.insertData(input);
|
||||
logger.info(`[presetData.insert] Successfully inserted ${data.length} entries`);
|
||||
return {
|
||||
ok: true,
|
||||
detail: "Data inserted",
|
||||
data: await dbPresetData.insertData(input),
|
||||
data,
|
||||
};
|
||||
}),
|
||||
|
||||
delete: protectedProcedure
|
||||
.input(z.object({ date: z.string(), ids: z.array(z.string()) }))
|
||||
.mutation(async ({ input }) => {
|
||||
logger.info(`[presetData.delete] Deleting ${input.ids.length} preset data entries for date ${input.date}`);
|
||||
await dbPresetData.deleteDataByIds(input.date, input.ids);
|
||||
logger.info("[presetData.delete] Successfully deleted preset data entries");
|
||||
return { ok: true, detail: "Data deleted" };
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { SessionData } from "$lib/utils/data.types";
|
||||
import { createTRPCRouter, protectedProcedure } from "../t";
|
||||
import { logger } from "$lib/server/logger";
|
||||
|
||||
export const sessionRouter = createTRPCRouter({
|
||||
getSession: protectedProcedure.query(async ({ ctx }) => {
|
||||
logger.debug(`[getSession] Getting session for user: ${ctx.session.username}`);
|
||||
return {
|
||||
user: {
|
||||
username: ctx.session.username,
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
|
||||
let captchaQ = api.apiAuth.getCaptcha.createMutation({
|
||||
onSuccess: (d) => {
|
||||
console.log("[=] Captcha Response :: ", JSON.stringify(d, null, 2));
|
||||
captchaId = d.id;
|
||||
captchaImage = d.image;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user