Skip to content

Commit 7ec92f5

Browse files
SPIRIT-0: add preprocessor for db actions and add credentials logging for logged in use cases
1 parent df8c75b commit 7ec92f5

20 files changed

Lines changed: 178 additions & 88 deletions

File tree

libs/db/generics/actions/types.ts

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,49 +37,65 @@ export type BaseMapperFunction<QueryFunction extends BaseQueryFunction> = (
3737
result: Awaited<ReturnType<QueryFunction>>,
3838
) => unknown;
3939

40-
export type QueryAction<Handler extends BaseQueryFunction> = (
41-
...params: Parameters<Handler>
42-
) => {
43-
query: () => ReturnType<Handler>;
44-
run: () => ReturnType<Handler>;
45-
};
46-
47-
export type MappedAction<
48-
Handler extends BaseQueryFunction,
49-
Mapper extends BaseMapperFunction<Handler>,
50-
> = (...params: Parameters<Handler>) => {
51-
run: () => Promise<ReturnType<Mapper>>;
52-
query: () => ReturnType<Handler>;
53-
map: Mapper;
54-
};
40+
export type BasePreProcessorFunction<QueryFunction extends BaseQueryFunction> =
41+
(
42+
...args: any[]
43+
) => Parameters<QueryFunction> | Promise<Parameters<QueryFunction>>;
5544

5645
export const actionBuilder = <
5746
Handler extends BaseQueryFunction,
47+
PreProcessor extends
48+
| BasePreProcessorFunction<Handler>
49+
| undefined = undefined,
5850
Mapper extends BaseMapperFunction<Handler> | undefined = undefined,
5951
>(
6052
handler: Handler,
6153
mapper?: Mapper,
62-
): ((...params: Parameters<Handler>) => Action<Handler, Mapper>) => {
63-
return (...params: Parameters<Handler>) => {
54+
preProcessor?: PreProcessor,
55+
): ((
56+
...params: PreProcessor extends BasePreProcessorFunction<Handler>
57+
? Parameters<PreProcessor>
58+
: Parameters<Handler>
59+
) => Action<Handler, Mapper, PreProcessor>) => {
60+
return (...params) => {
6461
return {
65-
query: (() => handler(...params)) as Handler,
66-
run: (mapper
67-
? async () => mapper(await handler(...params))
68-
: () => handler(...params)) as Action<Handler, Mapper>["run"],
62+
params,
63+
query: handler,
64+
run: (async () => {
65+
let handlerParams = params as
66+
| (PreProcessor extends BasePreProcessorFunction<Handler>
67+
? Awaited<ReturnType<PreProcessor>>
68+
: Parameters<Handler>)
69+
| Parameters<Handler>;
70+
71+
if (preProcessor) {
72+
handlerParams = await preProcessor(...params);
73+
}
74+
const queryResult = await handler(...handlerParams);
75+
return mapper?.(queryResult) ?? queryResult;
76+
}) as Action<Handler, Mapper, PreProcessor>["run"],
6977
map: mapper,
78+
preProcess: preProcessor,
7079
};
7180
};
7281
};
7382

7483
export type Action<
7584
Handler extends BaseQueryFunction,
7685
Mapper extends BaseMapperFunction<Handler> | undefined = undefined,
86+
PreProcessor extends
87+
| BasePreProcessorFunction<Handler>
88+
| undefined = undefined,
7789
> = {
7890
query: Handler;
7991
run: Mapper extends BaseMapperFunction<Handler>
8092
? () => Promise<ReturnType<Mapper>>
81-
: () => ReturnType<Handler>;
93+
: () => Promise<ReturnType<Handler>>;
8294
map?: Mapper;
95+
preprocess?: PreProcessor;
96+
params: PreProcessor extends BasePreProcessorFunction<Handler>
97+
? Parameters<PreProcessor>
98+
: Parameters<Handler>;
8399
};
84100

85101
export type Result<T extends (...args: any) => any> = Awaited<ReturnType<T>>;

libs/db/generics/operations/batch.ts

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,64 +7,72 @@ import {
77
BaseMapperFunction,
88
BaseQueryFunction,
99
actionBuilder,
10+
BasePreProcessorFunction,
1011
} from "../actions";
12+
import { BatchItem } from "drizzle-orm/batch";
1113

1214
const NO_COUNT = 0;
1315
const FIRST_INDEX = 0;
1416

1517
type BaseAction = Action<
1618
BaseQueryFunction,
17-
BaseMapperFunction<BaseQueryFunction> | undefined
19+
BaseMapperFunction<BaseQueryFunction> | undefined,
20+
BasePreProcessorFunction<BaseQueryFunction> | undefined
1821
>;
1922

20-
type ActionsType = NonEmptyArray<BaseAction>;
23+
type ActionsType = NonEmptyArray<BaseAction | undefined>;
2124

22-
type BatchOperationResult<T extends ActionsType> = {
23-
[K in keyof T]: T[K] extends { map?: infer M }
25+
type ActionFinalReturnType<Action extends BaseAction | undefined> =
26+
Action extends {
27+
map?: infer M;
28+
}
2429
? M extends BaseMapperFunction<BaseQueryFunction>
2530
? Awaited<ReturnType<M>>
26-
: Awaited<ReturnType<T[K]["query"]>>
27-
: never;
31+
: Awaited<ReturnType<Action["query"]>>
32+
: undefined;
33+
34+
type BatchOperationResult<Actions extends ActionsType> = {
35+
[K in keyof Actions]: ActionFinalReturnType<Actions[K]>;
2836
};
2937

30-
export const runBatchOperation = async <const Actions extends ActionsType>(
38+
export const runBatchOperation = async <Actions extends ActionsType>(
3139
...actions: Actions
3240
): Promise<BatchOperationResult<Actions>> => {
33-
const queries = actions.map((action) => action.query()) as {
34-
[K in keyof Actions]: ReturnType<Actions[K]["query"]>;
35-
};
41+
const params = await Promise.all(
42+
actions.map(
43+
async (action) =>
44+
(await action?.preprocess?.(action.params)) ?? action?.params,
45+
),
46+
);
47+
48+
const queries = actions
49+
.map((action, index) => action?.query(params[index]))
50+
.filter((action) => action !== undefined) as NonEmptyArray<
51+
BatchItem<"sqlite">
52+
>;
53+
3654
if (!queries.length) {
3755
throw Error("No queries provided in batch operation");
3856
}
3957
const results = await db().batch(queries);
4058

4159
const mappedResults = [];
60+
let resultsIndex = 0;
4261

4362
for (let i = 0; i < actions.length; i++) {
44-
mappedResults[i] = (await actions[i].map?.(results[i])) ?? results[i];
63+
if (actions[i] !== undefined) {
64+
mappedResults[i] =
65+
(await actions[i]?.map?.(results[resultsIndex])) ??
66+
results[resultsIndex];
67+
resultsIndex++;
68+
} else {
69+
mappedResults[i] = undefined;
70+
}
4571
}
4672

4773
return mappedResults as BatchOperationResult<Actions>;
4874
};
4975

50-
export const createOperationBatch = <Actions extends ActionsType>(
51-
...args: Actions
52-
) => {
53-
const operationBatch: Actions = [...args];
54-
const addAction = <Action extends BaseAction>(action: Action) => {
55-
operationBatch.push(action);
56-
};
57-
const run = async () => {
58-
const results = await runBatchOperation(...operationBatch);
59-
operationBatch.length = 0;
60-
return results;
61-
};
62-
return {
63-
addAction,
64-
run,
65-
};
66-
};
67-
6876
const countQuery = <T extends TableConfig>(table: SQLiteTableWithColumns<T>) =>
6977
db().select({ count: count() }).from(table);
7078

libs/db/user/actions/insert.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { actionBuilder, Result } from "@generics-db";
22
import { BaseUser, baseUserSchema, InsertUser } from "@user-entity";
33
import { db } from "@db";
44
import { usersTable } from "@schema";
5+
import { hashPassword } from "@crypto-utils";
56

67
const insertUserQuery = (user: InsertUser) => {
78
return db().insert(usersTable).values(user).returning();
@@ -17,7 +18,17 @@ const mapUserFromInsertResult = (
1718
return baseUserSchema.parse(firstResult);
1819
};
1920

21+
const preProcessUserInsert = async (
22+
user: InsertUser,
23+
): Promise<[user: InsertUser]> => {
24+
if (user.password) {
25+
user.password = await hashPassword(user.password, user.id);
26+
}
27+
return [user];
28+
};
29+
2030
export const getInsertUserAction = actionBuilder(
2131
insertUserQuery,
2232
mapUserFromInsertResult,
33+
preProcessUserInsert,
2334
);

libs/db/user/actions/update.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { db } from "@db";
33
import { usersTable } from "@schema";
44
import { eq, sql } from "drizzle-orm";
55
import { actionBuilder, Result } from "@generics-db";
6+
import { hashPassword } from "@crypto-utils";
67

78
const ONE_VERSION = 1;
89

@@ -23,6 +24,16 @@ const mapUserFromUpdateQueryResult = (
2324
};
2425
};
2526

27+
const preProcessUserUpdate = async (
28+
userId: string,
29+
changes: Partial<InsertUser>,
30+
): Promise<[userId: string, changes: Partial<InsertUser>]> => {
31+
if (changes.password) {
32+
changes.password = await hashPassword(changes.password, userId);
33+
}
34+
return [userId, changes];
35+
};
36+
2637
const incrementUserVersionQuery = (userId: string) =>
2738
db()
2839
.update(usersTable)
@@ -42,6 +53,7 @@ const mapUserFromncrementVersionQueryResult = async (
4253
export const getUpdateUserAction = actionBuilder(
4354
updateUserQuery,
4455
mapUserFromUpdateQueryResult,
56+
preProcessUserUpdate,
4557
);
4658

4759
export const getIncrementUserVersionAction = actionBuilder(
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Currency } from "@currency-entity";
22
import { getSelectAllCurrenciesAction } from "@currency-db";
33

4-
export const getAllCurrencies = (): Promise<Currency[]> => {
5-
return getSelectAllCurrenciesAction().run();
4+
export const getAllCurrencies = async (): Promise<Currency[]> => {
5+
return await getSelectAllCurrenciesAction().run();
66
};

libs/modules/order/use-cases/get-order-from-session-result.ts

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import { sendEmail } from "@sendgrid-adapter";
3333
import { LoggerUseCaseEnum } from "@logger-entity";
3434
import { stringifyObject } from "@string-utils";
3535
import { getRandomUUID } from "@crypto-utils";
36-
import { createOperationBatch } from "@generics-db";
36+
import { runBatchOperation } from "@generics-db";
3737
import { withRetry } from "./payment/utils/retry-payment";
3838

3939
export const getOrderFromSessionResult = async (
@@ -110,24 +110,15 @@ export const getOrderFromSessionResult = async (
110110
await getIncrementUserVersionAction(userId).run();
111111
}
112112

113-
const operationBatch = createOperationBatch(
113+
const [, , [order]] = await runBatchOperation(
114114
insertShippingTransactionAction,
115115
insertOrderAction,
116116
selectOrdersByIdAction,
117+
deleteCartAction,
118+
insertCartAction,
119+
updateUserAction,
117120
);
118121

119-
if (deleteCartAction) {
120-
operationBatch.addAction(deleteCartAction);
121-
}
122-
if (insertCartAction) {
123-
operationBatch.addAction(insertCartAction);
124-
}
125-
if (updateUserAction) {
126-
operationBatch.addAction(updateUserAction);
127-
}
128-
129-
const [, , [order]] = await operationBatch.run();
130-
131122
if (!order) {
132123
throw new Error("Unable to create order");
133124
}

libs/modules/order/use-cases/payment/utils/retry-payment.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
const MAX_RETRIES = 3;
22
const ONE_INCREMENT = 1;
33
const MIN_RETRIES = 0;
4-
export const withRetry = async <T, K>(
4+
export const withRetry = <T, K>(
55
args: T,
66
callBack: (args: T) => Promise<K>,
77
retries: number = MIN_RETRIES,
88
): Promise<K> => {
99
try {
10-
return await callBack(args);
10+
return callBack(args);
1111
} catch (error) {
1212
if (retries < MAX_RETRIES) {
1313
return withRetry(args, callBack, retries + ONE_INCREMENT);

libs/modules/user/use-cases/details/get-user-details.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ import { UserDetails } from "@user-entity";
22
import { contextStore } from "@context-utils";
33
import { decryptObjectString } from "@crypto-utils";
44
import { getSelectUserDetailsByIdAction } from "@user-db";
5+
import { logCredentials } from "@logger-utils";
56

67
export const getUserDetails = async (): Promise<UserDetails | undefined> => {
7-
const { userDetails, userId } = contextStore.context;
8+
const { userDetails, userId, cartId } = contextStore.context;
9+
10+
logCredentials(cartId, userId);
11+
812
if (userDetails) {
913
return decryptObjectString<UserDetails>(userDetails);
1014
}

libs/modules/user/use-cases/details/handle-details-post.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { UserDetails, userDetailsSchema } from "@user-entity";
22
import { encryptObject } from "@crypto-utils";
33
import { requiredObjectSchema } from "@global-entity";
4+
import { contextStore } from "@context-utils";
5+
import { logCredentials } from "@logger-utils";
46

57
const userDetailsPostPayloadSchema = requiredObjectSchema("Personal Details", {
68
personalDetails: userDetailsSchema,
@@ -9,6 +11,10 @@ const userDetailsPostPayloadSchema = requiredObjectSchema("Personal Details", {
911
export const handleDetailsPost = async (
1012
body: unknown,
1113
): Promise<{ userDetails: UserDetails; encryptedUserDetails: string }> => {
14+
const { userId, cartId } = contextStore.context;
15+
16+
logCredentials(cartId, userId);
17+
1218
const { personalDetails } = userDetailsPostPayloadSchema.parse(body);
1319
const encryptedUserDetails = await encryptObject(personalDetails);
1420
return { userDetails: personalDetails, encryptedUserDetails };

libs/modules/user/use-cases/forgot-password/reset.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { validateBaseSecret } from "@user-module";
33
import { getSelectUserByEmailAction, getUpdateUserAction } from "@user-db";
44
import { errors } from "@error-handling-utils";
55
import { getLoggedInRefreshToken } from "@jwt-utils";
6-
import { hashPassword } from "@crypto-utils";
6+
import { logCredentials } from "@logger-utils";
77

88
export const resetForgottenPassword = async (
99
password: string,
@@ -12,9 +12,12 @@ export const resetForgottenPassword = async (
1212
): Promise<void> => {
1313
const user = await getSelectUserByEmailAction(email).run();
1414
if (!user) throw errors.USER_NOT_FOUND();
15+
16+
logCredentials(user.cart?.id, user.id);
17+
1518
await validateBaseSecret(email, code);
1619
await getUpdateUserAction(user.id, {
17-
password: await hashPassword(password, user.id),
20+
password,
1821
}).run();
1922
const userRefreshToken = await getLoggedInRefreshToken(user.id);
2023
await setAuthSecret(user.id, userRefreshToken);

0 commit comments

Comments
 (0)