Skip to content

Commit 2c460cc

Browse files
committed
feat: Add generated TypeScript contracts and DTOs for Commercify API
- Introduced `contracts.ts` containing interfaces for various API requests and responses. - Added `dtos.ts` with data transfer objects for categories, checkouts, orders, products, and users. - Updated API index to export new DTOs and contracts. - Refactored attribute handling in product variants to use key-value pairs instead of arrays. - Enhanced utility functions for SKU generation and attribute transformation. - Implemented tests for attribute transformation and product creation request formatting. - Cleaned up health check and environment configuration code for better readability.
1 parent 425903a commit 2c460cc

18 files changed

Lines changed: 695 additions & 547 deletions

docker-compose.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
services:
22
# Commercify API Backend
33
commercify-api:
4-
image: ghcr.io/zenfulcode/commercifygo:dev
4+
image: ghcr.io/zenfulcode/commercifygo:v2-dev
55
ports:
66
- '6091:6091'
77
environment:
88
# Server Configuration
99
- CORS_ALLOWED_ORIGINS=${CORS_ALLOWED_ORIGINS:-http://localhost:3000}
1010

1111
# Database Configuration
12+
- DB_DRIVER=postgres
1213
- DB_HOST=postgres
1314
- DB_PORT=5432
1415
- DB_NAME=commercify
@@ -44,10 +45,10 @@ services:
4445
- MOBILEPAY_CLIENT_SECRET=${MOBILEPAY_CLIENT_SECRET:-}
4546
- MOBILEPAY_WEBHOOK_URL=${MOBILEPAY_WEBHOOK_URL:-}
4647
- MOBILEPAY_PAYMENT_DESCRIPTION=${MOBILEPAY_PAYMENT_DESCRIPTION:-Commercify Store Purchase}
47-
- MOBILEPAY_MARKET=${MOBILEPAY_MARKET:-NOK}
4848

4949
# Return URL
5050
- RETURN_URL=${RETURN_URL:-http://localhost:3000/payment/complete}
51+
- DEFAULT_CURRENCY=${DEFAULT_CURRENCY:-USD}
5152
depends_on:
5253
- postgres
5354
restart: unless-stopped
@@ -92,7 +93,7 @@ services:
9293
- NODE_ENV=production
9394
- PORT=3000
9495
- HOST=0.0.0.0
95-
- ORIGIN=https://ffc1-2a05-f6c6-5509-0-f946-cd78-df73-103c.ngrok-free.app
96+
- ORIGIN=https://e070-2a05-f6c6-5509-0-9848-7eb2-e94f-832f.ngrok-free.app
9697
- API_BASE_URL_DEV=http://commercify-api:6091/api
9798
- API_BASE_URL_PROD=http://commercify-api:6091/api
9899
depends_on:

src/lib/schemas/product.schema.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,7 @@ export const productVariantSchema = z.object({
66
price: z.number().min(1, 'Price must be 0 or greater'),
77
stock: z.number().int().min(0, 'Stock must be 0 or greater'),
88
weight: z.number().min(0, 'Weight must be 0 or greater').optional(),
9-
attributes: z
10-
.array(
11-
z.object({
12-
name: z.string().min(1, 'Attribute name is required'),
13-
value: z.string().min(1, 'Attribute value is required')
14-
})
15-
)
16-
.default([]),
9+
attributes: z.record(z.string(), z.string()).default({}),
1710
images: z.array(z.string().url('Invalid image URL')).default([]),
1811
isDefault: z.boolean().default(false)
1912
});

src/lib/server/api/commercify.ts

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ import {
5252
type CompleteCheckoutRequest,
5353
type CheckoutCompleteResponse,
5454
type OrderSummaryDTO
55-
} from './types';
55+
} from './index';
5656
import type {
5757
Address,
5858
Checkout,
@@ -128,11 +128,11 @@ export class CommercifyClient {
128128
headers['Cookie'] = this.cookies;
129129
}
130130

131-
// console.log('Making request to:', url, {
132-
// headers: Object.keys(headers),
133-
// hasAuthHeader: !!headers['Authorization'],
134-
// origin: headers['Origin']
135-
// });
131+
console.log('Making request to:', url, {
132+
headers: Object.keys(headers),
133+
hasAuthHeader: !!headers['Authorization'],
134+
origin: headers['Origin']
135+
});
136136

137137
const response = await fetch(url, {
138138
headers,
@@ -591,8 +591,11 @@ export class CommercifyClient {
591591
sku: variant.sku,
592592
price: variant.price,
593593
stock: variant.stock,
594-
attributes: variant.attributes || [],
595-
images: variant.images,
594+
weight: variant.weight || 0,
595+
attributes: variant.attributes
596+
? Object.entries(variant.attributes).map(([name, value]) => ({ name, value }))
597+
: [],
598+
images: variant.images || [],
596599
is_default: variant.isDefault
597600
})) || []
598601
};
@@ -776,12 +779,21 @@ export class CommercifyClient {
776779
error: 'No product ID provided'
777780
};
778781
}
782+
783+
// Transform attributes to match API expectations
784+
const apiVariantData = {
785+
...variantData,
786+
attributes: variantData.attributes
787+
? Object.entries(variantData.attributes).map(([name, value]) => ({ name, value }))
788+
: []
789+
};
790+
779791
try {
780792
const response = await this.request<ResponseDTO<VariantDTO>>(
781793
`/admin/products/${productId}/variants`,
782794
{
783795
method: 'POST',
784-
body: JSON.stringify(variantData)
796+
body: JSON.stringify(apiVariantData)
785797
}
786798
);
787799
if (response.success && response.data) {
@@ -856,7 +868,9 @@ export class CommercifyClient {
856868
sku: variantData.sku,
857869
price: variantData.price,
858870
stock: variantData.stock,
859-
attributes: variantData.attributes,
871+
attributes: variantData.attributes
872+
? Object.entries(variantData.attributes).map(([name, value]) => ({ name, value }))
873+
: undefined,
860874
images: variantData.images,
861875
is_default: variantData.isDefault
862876
};
@@ -982,8 +996,8 @@ export class CommercifyClient {
982996
amount: apiProduct.price,
983997
currency: apiProduct.currency
984998
},
985-
stock: apiProduct.stock,
986-
categoryId: apiProduct.category_id.toString(),
999+
stock: apiProduct.total_stock,
1000+
categoryId: apiProduct.category_id?.toString() || null,
9871001
images: apiProduct.images,
9881002
hasVariants: apiProduct.has_variants,
9891003
variants: apiProduct.variants?.map(this.mapApiVariantToVariant) || [],
@@ -997,6 +1011,16 @@ export class CommercifyClient {
9971011
* Map API variant response to our ProductVariant interface
9981012
*/
9991013
private mapApiVariantToVariant(apiVariant: VariantDTO): ProductVariant {
1014+
// Convert attributes array back to key-value object
1015+
const attributes: { [key: string]: string } = {};
1016+
if (apiVariant.attributes && Array.isArray(apiVariant.attributes)) {
1017+
apiVariant.attributes.forEach((attr: any) => {
1018+
if (attr.name && attr.value) {
1019+
attributes[attr.name] = attr.value;
1020+
}
1021+
});
1022+
}
1023+
10001024
return {
10011025
id: apiVariant.id,
10021026
sku: apiVariant.sku,
@@ -1005,7 +1029,7 @@ export class CommercifyClient {
10051029
currency: apiVariant.currency
10061030
},
10071031
stock: apiVariant.stock ?? apiVariant.stock ?? 0,
1008-
attributes: apiVariant.attributes,
1032+
attributes,
10091033
images: apiVariant.images || [],
10101034
isDefault: apiVariant.is_default
10111035
};
@@ -1085,9 +1109,7 @@ export class CommercifyClient {
10851109
: undefined,
10861110
discountDetails: this.mapDiscountDetails(dto.applied_discount),
10871111
paymentProvider: dto.payment_provider,
1088-
status: dto.status,
1089-
createdAt: dto.created_at,
1090-
updatedAt: dto.updated_at
1112+
status: dto.status
10911113
};
10921114
}
10931115

@@ -1630,9 +1652,7 @@ export class CommercifyClient {
16301652
? this.mapShippingDetails(dto.shipping_details)
16311653
: undefined,
16321654
discountDetails: this.mapDiscountDetails(dto.discount_details),
1633-
paymentProvider: dto.payment_details.provider,
1634-
createdAt: dto.created_at,
1635-
updatedAt: dto.updated_at
1655+
paymentProvider: dto.payment_details?.provider
16361656
};
16371657
}
16381658

0 commit comments

Comments
 (0)