yuh and so it beginzzzzz

This commit is contained in:
bootunloader
2026-01-03 13:21:39 +02:00
parent c7ff53e8ac
commit a39f137b03
15 changed files with 690 additions and 132 deletions

143
src/lib/server/logger.ts Normal file
View 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
View 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",
},
};
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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