Skip to content

Commit 813331b

Browse files
(SP:1) [Backend] Add env docs and unify structured logging (remove runtime console.*)
1 parent 8216aa7 commit 813331b

15 files changed

Lines changed: 244 additions & 115 deletions

File tree

frontend/app/[locale]/shop/admin/products/_components/product-form.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation';
55

66
import { CATEGORIES, COLORS, PRODUCT_TYPES, SIZES } from '@/lib/config/catalog';
77
import type { ProductAdminInput } from '@/lib/validation/shop';
8+
import { logError } from '@/lib/logging';
89

910
const localSlugify = (input: string): string => {
1011
return input
@@ -344,10 +345,11 @@ export function ProductForm({
344345
const destinationSlug = data.product?.slug ?? slugValue;
345346
router.push(`/shop/products/${destinationSlug}`);
346347
} catch (err) {
347-
console.error(
348-
`Failed to ${mode === 'create' ? 'create' : 'update'} product`,
349-
err
350-
);
348+
logError('admin_product_form_failed', err, {
349+
mode,
350+
productId: productId ?? null,
351+
slug: slugValue,
352+
});
351353
setError(
352354
`Unexpected error while ${
353355
mode === 'create' ? 'creating' : 'updating'

frontend/app/[locale]/shop/admin/products/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { db } from '@/db';
66
import { products, productPrices } from '@/db/schema';
77
import { formatMoney, resolveCurrencyFromLocale } from '@/lib/shop/currency';
88
import { fromDbMoney } from '@/lib/shop/money';
9+
import { logWarn } from '@/lib/logging';
910

1011
function formatDate(value: Date | null, locale: string) {
1112
if (!value) return '-';
@@ -22,7 +23,7 @@ function safeFromDbMoney(
2223
try {
2324
return fromDbMoney(value);
2425
} catch (err) {
25-
console.warn('[admin products] fromDbMoney failed', {
26+
logWarn('admin_products_from_db_money_failed', {
2627
...ctx,
2728
valueType: typeof value,
2829
value,

frontend/app/[locale]/shop/checkout/payment/StripePaymentClient.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
resolveCurrencyFromLocale,
2323
type CurrencyCode,
2424
} from '@/lib/shop/currency';
25+
import { logError } from '@/lib/logging';
2526

2627
type PaymentFormProps = {
2728
orderId: string;
@@ -111,7 +112,7 @@ function StripePaymentForm({ orderId, locale }: PaymentFormProps) {
111112
});
112113
router.push(next);
113114
} catch (error) {
114-
console.error('Payment confirmation failed', error);
115+
logError('stripe_payment_confirm_failed', error, { orderId });
115116
setErrorMessage('We couldn’t confirm your payment. Please try again.');
116117
router.push(`/shop/checkout/error?orderId=${orderId}`);
117118
} finally {

frontend/app/[locale]/shop/checkout/payment/[orderId]/page.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { orderIdParamSchema } from '@/lib/validation/shop';
88
import { getStripeEnv } from '@/lib/env/stripe';
99
import { createPaymentIntent, retrievePaymentIntent } from '@/lib/psp/stripe';
1010
import { setOrderPaymentIntent } from '@/lib/services/orders';
11+
import { logError } from '@/lib/logging';
1112

1213
function getOrderId(params: { orderId?: string }) {
1314
const parsed = orderIdParamSchema.safeParse({ id: params.orderId ?? '' });
@@ -169,15 +170,11 @@ export default async function PaymentPage(props: PaymentPageProps) {
169170
clientSecret = created.clientSecret;
170171
}
171172
} catch (error) {
172-
console.error(
173-
'Failed to initialize Stripe payment intent',
174-
{
175-
orderId: order.id,
176-
existingPi,
177-
phase,
178-
},
179-
error
180-
);
173+
logError('payment_page_failed', error, {
174+
orderId: order.id,
175+
existingPi,
176+
phase,
177+
});
181178

182179
// Leave clientSecret empty -> UI shows "Payment cannot be initialized"
183180
}

frontend/app/api/shop/cart/rehydrate/route.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
1-
import { NextRequest, NextResponse } from "next/server";
1+
import { NextRequest, NextResponse } from 'next/server';
22

3-
import { MoneyValueError } from "@/db/queries/shop/orders";
4-
import { resolveLocaleAndCurrency } from "@/lib/shop/request-locale";
3+
import { MoneyValueError } from '@/db/queries/shop/orders';
4+
import { resolveLocaleAndCurrency } from '@/lib/shop/request-locale';
55

6-
import { rehydrateCartItems } from "@/lib/services/products";
7-
import { cartRehydratePayloadSchema } from "@/lib/validation/shop";
8-
import { InvalidPayloadError, PriceConfigError } from "@/lib/services/errors";
6+
import { rehydrateCartItems } from '@/lib/services/products';
7+
import { cartRehydratePayloadSchema } from '@/lib/validation/shop';
8+
import { InvalidPayloadError, PriceConfigError } from '@/lib/services/errors';
9+
import { logError } from '@/lib/logging';
910

1011
function normalizeCartPayload(body: unknown) {
11-
if (!body || typeof body !== "object") return body;
12+
if (!body || typeof body !== 'object') return body;
1213
const { items, ...rest } = body as { items?: unknown };
1314

1415
if (!Array.isArray(items)) return body;
1516

1617
return {
1718
...rest,
18-
items: items.map((item) => {
19-
if (!item || typeof item !== "object") return item;
19+
items: items.map(item => {
20+
if (!item || typeof item !== 'object') return item;
2021
const { quantity, ...itemRest } = item as { quantity?: unknown };
2122
const normalizedQuantity =
22-
typeof quantity === "string" && quantity.trim().length > 0
23+
typeof quantity === 'string' && quantity.trim().length > 0
2324
? Number(quantity)
2425
: quantity;
2526

@@ -35,7 +36,7 @@ export async function POST(request: NextRequest) {
3536
body = await request.json();
3637
} catch {
3738
return NextResponse.json(
38-
{ error: "Unable to process cart data." },
39+
{ error: 'Unable to process cart data.' },
3940
{ status: 400 }
4041
);
4142
}
@@ -45,20 +46,19 @@ export async function POST(request: NextRequest) {
4546

4647
if (!parsedPayload.success) {
4748
return NextResponse.json(
48-
{ error: "Invalid cart payload", details: parsedPayload.error.format() },
49+
{ error: 'Invalid cart payload', details: parsedPayload.error.format() },
4950
{ status: 400 }
5051
);
5152
}
5253

5354
const { currency } = resolveLocaleAndCurrency(request);
5455

55-
5656
try {
5757
const { items } = parsedPayload.data;
5858
const parsedResult = await rehydrateCartItems(items, currency);
5959
return NextResponse.json(parsedResult);
6060
} catch (error) {
61-
console.error("Cart rehydrate failed", error);
61+
logError('cart_rehydrate_failed', error);
6262

6363
if (error instanceof PriceConfigError) {
6464
return NextResponse.json(
@@ -73,8 +73,8 @@ export async function POST(request: NextRequest) {
7373
if (error instanceof MoneyValueError) {
7474
return NextResponse.json(
7575
{
76-
code: "PRICE_CONFIG_ERROR",
77-
message: "Invalid price configuration for one or more products.",
76+
code: 'PRICE_CONFIG_ERROR',
77+
message: 'Invalid price configuration for one or more products.',
7878
details: {
7979
productId: error.productId,
8080
field: error.field,
@@ -93,7 +93,7 @@ export async function POST(request: NextRequest) {
9393
}
9494

9595
return NextResponse.json(
96-
{ error: "Unable to rehydrate cart." },
96+
{ error: 'Unable to rehydrate cart.' },
9797
{ status: 500 }
9898
);
9999
}

frontend/app/api/shop/internal/orders/restock-stale/route.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// C:\Users\milka\devlovers.net\frontend\app\api\shop\internal\orders\restock-stale\route.ts
1+
//app\api\shop\internal\orders\restock-stale\route.ts
22
import { NextRequest, NextResponse } from 'next/server';
33
import crypto from 'crypto';
44
import { sql } from 'drizzle-orm';
@@ -10,6 +10,7 @@ import {
1010
} from '@/lib/services/orders';
1111

1212
import { requireInternalJanitorAuth } from '@/lib/auth/internal-janitor';
13+
import { logError } from '@/lib/logging';
1314

1415
export const runtime = 'nodejs';
1516

@@ -422,7 +423,7 @@ export async function POST(request: NextRequest) {
422423
minIntervalSeconds,
423424
});
424425
} catch (e) {
425-
console.error('restock-stale failed', { runId, error: e });
426+
logError('restock_stale_failed', e, { runId });
426427
return NextResponse.json(
427428
{ success: false, code: 'INTERNAL_ERROR' },
428429
{ status: 500 }

frontend/app/api/shop/webhooks/stripe/route.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import { db } from '@/db';
66
import { verifyWebhookSignature, retrieveCharge } from '@/lib/psp/stripe';
77
import { orders, stripeEvents } from '@/db/schema';
88
import { restockOrder } from '@/lib/services/orders';
9-
import { logError } from '@/lib/logging';
109
import { guardedPaymentStatusUpdate } from '@/lib/services/orders/payment-state';
10+
import { logError, logInfo, logWarn } from '@/lib/logging';
1111

1212
function warnRefundFullnessUndetermined(payload: {
1313
eventId: string;
@@ -24,7 +24,7 @@ function warnRefundFullnessUndetermined(payload: {
2424
refundId?: string | null;
2525
refundAmount?: number | null;
2626
}) {
27-
console.warn('stripe_webhook_refund_fullness_undetermined', payload);
27+
logWarn('stripe_webhook_refund_fullness_undetermined', payload);
2828
}
2929

3030
function logWebhookEvent(payload: {
@@ -35,7 +35,7 @@ function logWebhookEvent(payload: {
3535
}) {
3636
const { orderId, paymentIntentId, paymentStatus, eventType } = payload;
3737

38-
console.log('stripe_webhook', {
38+
logInfo('stripe_webhook', {
3939
provider: 'stripe',
4040
orderId,
4141
paymentIntentId,
@@ -286,7 +286,7 @@ export async function POST(request: NextRequest) {
286286
.limit(1);
287287

288288
if (existing?.processedAt) {
289-
console.log('stripe_webhook_duplicate_event', {
289+
logInfo('stripe_webhook_duplicate_event', {
290290
eventId: event.id,
291291
eventType,
292292
});
@@ -310,7 +310,7 @@ export async function POST(request: NextRequest) {
310310
if (candidates.length === 1) {
311311
resolvedOrderId = candidates[0].id;
312312
} else {
313-
console.log('stripe_webhook_missing_order_id', {
313+
logWarn('stripe_webhook_missing_order_id', {
314314
paymentIntentId,
315315
eventType,
316316
reason:
@@ -353,7 +353,7 @@ export async function POST(request: NextRequest) {
353353
}
354354

355355
if (order.paymentIntentId && order.paymentIntentId !== paymentIntentId) {
356-
console.log('stripe_webhook_payment_intent_mismatch', {
356+
logInfo('stripe_webhook_payment_intent_mismatch', {
357357
orderId: order.id,
358358
paymentIntentId,
359359
orderPaymentIntentId: order.paymentIntentId,
@@ -417,7 +417,7 @@ export async function POST(request: NextRequest) {
417417
})
418418
.where(eq(orders.id, order.id));
419419

420-
console.log('stripe_webhook_mismatch', {
420+
logWarn('stripe_webhook_mismatch', {
421421
orderId: order.id,
422422
paymentIntentId,
423423
eventType,

frontend/db/index.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { neon } from '@neondatabase/serverless';
22
import { drizzle } from 'drizzle-orm/neon-http';
33
import * as dotenv from 'dotenv';
4-
4+
import { logWarn } from '@/lib/logging';
55
import * as schema from './schema';
66

77
dotenv.config();
@@ -15,9 +15,7 @@ function resolveDatabaseUrl(): string {
1515
return process.env.DATABASE_URL_PREVIEW;
1616
}
1717
if (process.env.DATABASE_URL) {
18-
console.warn(
19-
'[db] DATABASE_URL_PREVIEW missing; falling back to DATABASE_URL for preview build.'
20-
);
18+
logWarn('db_preview_url_fallback', { context });
2119
return process.env.DATABASE_URL;
2220
}
2321
throw new Error('DATABASE_URL_PREVIEW is missing for preview deploys');

frontend/lib/cart.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from '@/lib/validation/shop';
1212
import { fromCents } from '@/lib/shop/money';
1313
import { createCartItemKey } from '@/lib/shop/cart-item-key';
14+
import { logWarn } from '@/lib/logging';
1415

1516
const CART_KEY = 'devlovers-cart';
1617

@@ -86,7 +87,9 @@ export function getStoredCartItems(): CartClientItem[] {
8687
.map(item => normalizeStoredItem(item))
8788
.filter((item): item is CartClientItem => item !== null);
8889
} catch (error) {
89-
console.warn('Failed to read cart from localStorage', error);
90+
logWarn('cart_read_failed', {
91+
message: error instanceof Error ? error.message : String(error),
92+
});
9093
return [];
9194
}
9295
}
@@ -97,7 +100,9 @@ export function persistCartItems(items: CartClientItem[]): void {
97100
try {
98101
window.localStorage.setItem(CART_KEY, JSON.stringify(items));
99102
} catch (error) {
100-
console.warn('Failed to save cart', error);
103+
logWarn('cart_save_failed', {
104+
message: error instanceof Error ? error.message : String(error),
105+
});
101106
}
102107
}
103108

frontend/lib/cloudinary.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { v2 as cloudinary } from "cloudinary";
1+
import { v2 as cloudinary } from 'cloudinary';
22

3-
import { getCloudinaryEnvRequired } from "@/lib/env/cloudinary";
3+
import { getCloudinaryEnvRequired } from '@/lib/env/cloudinary';
4+
import { logError } from '@/lib/logging';
45

56
let isConfigured = false;
67

@@ -18,7 +19,7 @@ function ensureConfigured() {
1819
}
1920

2021
async function toBuffer(fileOrBuffer: File | Buffer): Promise<Buffer> {
21-
if (typeof Buffer !== "undefined" && Buffer.isBuffer(fileOrBuffer)) {
22+
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(fileOrBuffer)) {
2223
return fileOrBuffer;
2324
}
2425

@@ -41,15 +42,18 @@ export async function uploadImage(
4142
const uploadStream = cloudinary.uploader.upload_stream(
4243
{
4344
folder: options?.folder ?? env.uploadFolder,
44-
resource_type: "image",
45+
resource_type: 'image',
4546
},
4647
(
4748
error: unknown,
4849
result: { secure_url: string; public_id: string } | undefined
4950
) => {
5051
if (error || !result) {
51-
console.error("Cloudinary upload failed", error);
52-
reject(new Error("Failed to upload image to Cloudinary"));
52+
logError('cloudinary_upload_failed', error, {
53+
folder: options?.folder ?? env.uploadFolder,
54+
hasResult: Boolean(result),
55+
});
56+
reject(new Error('Failed to upload image to Cloudinary'));
5357
return;
5458
}
5559

0 commit comments

Comments
 (0)