Skip to content

Instantly share code, notes, and snippets.

@feliche93
Created June 26, 2023 09:08
Show Gist options
  • Save feliche93/0f43f94e46435760ef6775005da96f32 to your computer and use it in GitHub Desktop.
Save feliche93/0f43f94e46435760ef6775005da96f32 to your computer and use it in GitHub Desktop.
Route handlers for synching products, product variants and stores
import { NextResponse } from 'next/server';
import { z } from 'zod';
import camelcaseKeys from 'camelcase-keys'
import { db } from '@lib/db';
import { products } from '@schema';
export const runtime = 'edge' // 'nodejs' is the default
const camelize = <T extends readonly unknown[] | Record<string, unknown>>(
val: T,
) => camelcaseKeys(val, { deep: true });
const singleProductSchema = z.object({
type: z.string(),
id: z.string(),
attributes: z.object({
store_id: z.number().positive().int(),
name: z.string(),
slug: z.string(),
description: z.string(),
status: z.enum(['draft', 'published']),
status_formatted: z.string(),
thumb_url: z.string().url().nullable(),
large_thumb_url: z.string().url().nullable(),
price: z.number().positive().int(),
pay_what_you_want: z.boolean(),
from_price: z.union([z.number().positive().int(), z.null()]),
to_price: z.union([z.number().positive().int(), z.null()]),
buy_now_url: z.string().url(),
price_formatted: z.string(),
created_at: z.coerce.date(),
updated_at: z.coerce.date(),
test_mode: z.boolean(),
}).transform(camelize),
relationships: z.object({
store: z.object({
links: z.object({
related: z.string().url(),
self: z.string().url(),
}).transform(camelize),
}).transform(camelize),
variants: z.object({
links: z.object({
related: z.string().url(),
self: z.string().url(),
}).transform(camelize),
}).transform(camelize),
}).transform(camelize),
links: z.object({
self: z.string().url(),
}).transform(camelize),
}).transform(camelize);
const productSchema = z.object({
meta: z.object({
page: z.object({
currentPage: z.number().int(),
from: z.number().int(),
lastPage: z.number().int(),
perPage: z.number().int(),
to: z.number().int(),
total: z.number().int()
}).transform(camelize),
}).transform(camelize),
jsonapi: z.object({
version: z.string(),
}).transform(camelize),
links: z.object({
first: z.string().url(),
last: z.string().url(),
self: z.string().url().optional(),
}).transform(camelize),
data: z.array(singleProductSchema),
}).transform(camelize);
export async function GET(request: Request) {
const lemonSqueezyBaseUrl = 'https://api.lemonsqueezy.com/v1';
const lemonSqueezyApiKey = process.env.LEMON_SQUEEZY_API_KEY;
if (!lemonSqueezyApiKey) throw new Error("No LEMON_SQUEEZY_API_KEY environment variable set");
function createHeaders() {
const headers = new Headers();
headers.append('Accept', 'application/vnd.api+json');
headers.append('Content-Type', 'application/vnd.api+json');
headers.append('Authorization', `Bearer ${lemonSqueezyApiKey}`);
return headers;
}
function createRequestOptions(method: string, headers: Headers): RequestInit {
return {
method,
headers,
redirect: 'follow',
cache: "no-store"
};
}
// try {
const headers = createHeaders();
const requestOptions = createRequestOptions('GET', headers);
const response = await fetch(`${lemonSqueezyBaseUrl}/products`, requestOptions);
const data = await response.json();
// console.log(JSON.stringify(data, null, 2));
// parse and validate the data against the schema
const parsedData = productSchema.parse(data);
parsedData.data.map(async (product) => {
await db.insert(products).values({
...product.attributes,
id: product.id,
}).onConflictDoUpdate({
target: [products.id],
set: {
...product.attributes,
}
})
})
return NextResponse.json(parsedData);
}
import { db } from "@lib/db";
import { stores } from "@schema";
import camelcaseKeys from 'camelcase-keys';
import { NextResponse } from "next/server";
import { z } from "zod";
export const runtime = 'edge' // 'nodejs' is the default
const camelize = <T extends readonly unknown[] | Record<string, unknown>>(
val: T,
) => camelcaseKeys(val, { deep: true });
const pageSchema = z.object({
currentPage: z.number(),
from: z.number(),
lastPage: z.number(),
perPage: z.number(),
to: z.number(),
total: z.number()
}).transform(camelize);
const linksSchema = z.object({
first: z.string().url(),
last: z.string().url()
}).transform(camelize);
const relationshipLinksSchema = z.object({
related: z.string().url(),
self: z.string().url()
}).transform(camelize);
const relationshipsSchema = z.object({
products: z.object({
links: relationshipLinksSchema
}),
orders: z.object({
links: relationshipLinksSchema
}),
subscriptions: z.object({
links: relationshipLinksSchema
}),
discounts: z.object({
links: relationshipLinksSchema
}),
"license-keys": z.object({
links: relationshipLinksSchema
}),
webhooks: z.object({
links: relationshipLinksSchema
}),
}).transform(camelize);
const storeSchema = z.object({
type: z.string(),
id: z.string(),
attributes: z.object({
name: z.string(),
slug: z.string(),
domain: z.string(),
url: z.string().url(),
avatar_url: z.string().url(),
plan: z.union([z.literal('fresh'), z.literal('sweet'), z.literal('free')]),
country: z.string(),
country_nicename: z.string(),
currency: z.string(),
total_sales: z.number().min(0),
total_revenue: z.number().min(0),
thirty_day_sales: z.number().min(0),
thirty_day_revenue: z.number().min(0),
created_at: z.string(),
updated_at: z.string(),
}).transform(camelize),
relationships: relationshipsSchema,
links: z.object({
self: z.string().url()
}).transform(camelize)
}).transform(camelize);
const storesSchema = z.object({
meta: z.object({
page: pageSchema,
}).transform(camelize),
jsonapi: z.object({
version: z.string()
}).transform(camelize),
links: linksSchema,
data: z.array(storeSchema),
}).transform(camelize);
export async function GET(request: Request) {
const lemonSqueezyBaseUrl = 'https://api.lemonsqueezy.com/v1';
const lemonSqueezyApiKey = process.env.LEMON_SQUEEZY_API_KEY;
if (!lemonSqueezyApiKey) throw new Error("No LEMON_SQUEEZY_API_KEY environment variable set");
function createHeaders() {
const headers = new Headers();
headers.append('Accept', 'application/vnd.api+json');
headers.append('Content-Type', 'application/vnd.api+json');
headers.append('Authorization', `Bearer ${lemonSqueezyApiKey}`);
return headers;
}
function createRequestOptions(method: string, headers: Headers): RequestInit {
return {
method,
headers,
redirect: 'follow',
cache: "no-store"
};
}
const headers = createHeaders();
const requestOptions = createRequestOptions('GET', headers);
const response = await fetch(`${lemonSqueezyBaseUrl}/stores`, requestOptions);
const data = await response.json();
// console.log(JSON.stringify(data, null, 2));
// return
const parsedData = storesSchema.parse(data);
parsedData.data.map(async (store) => {
await db.insert(stores).values({
...store.attributes,
id: store.id,
}).onConflictDoUpdate({
target: [stores.id],
set: {
...store.attributes,
}
})
})
return NextResponse.json(parsedData);
}
import { NextResponse } from 'next/server';
import camelcaseKeys from 'camelcase-keys'
import { z } from 'zod';
import { productVariants } from '@schema';
import { db } from '@lib/db';
export const runtime = 'edge' // 'nodejs' is the default
const camelize = <T extends readonly unknown[] | Record<string, unknown>>(
val: T,
) => camelcaseKeys(val, { deep: true });
const singleVariantSchema = z.object({
type: z.string(),
id: z.string(),
attributes: z.object({
product_id: z.number().positive().int(),
name: z.string(),
slug: z.string(),
description: z.string(),
price: z.number().positive().int(),
is_subscription: z.boolean(),
interval: z.union([z.enum(['day', 'week', 'month', 'year']), z.null()]),
interval_count: z.number().int().nullable(),
has_free_trial: z.boolean(),
trial_interval: z.enum(['day', 'week', 'month', 'year']),
trial_interval_count: z.number().int(),
pay_what_you_want: z.boolean(),
min_price: z.number().int(),
suggested_price: z.number().int(),
has_license_keys: z.boolean(),
license_activation_limit: z.number().int(),
is_license_limit_unlimited: z.boolean(),
license_length_value: z.number().int(),
license_length_unit: z.enum(['days', 'months', 'years']),
is_license_length_unlimited: z.boolean(),
sort: z.number().int(),
status: z.enum(['pending', 'draft', 'published']),
status_formatted: z.string(),
created_at: z.coerce.date(),
updated_at: z.coerce.date()
}).transform(camelize),
relationships: z.object({
product: z.object({
links: z.object({
related: z.string().url(),
self: z.string().url(),
}).transform(camelize),
}).transform(camelize),
}).transform(camelize),
links: z.object({
self: z.string().url(),
}).transform(camelize),
}).transform(camelize);
const variantSchema = z.object({
meta: z.object({
page: z.object({
currentPage: z.number().int(),
from: z.number().int(),
lastPage: z.number().int(),
perPage: z.number().int(),
to: z.number().int(),
total: z.number().int()
}).transform(camelize),
}).transform(camelize),
jsonapi: z.object({
version: z.string(),
}).transform(camelize),
links: z.object({
first: z.string().url(),
last: z.string().url()
}).transform(camelize),
data: z.array(singleVariantSchema),
}).transform(camelize);
export async function GET(request: Request) {
const lemonSqueezyBaseUrl = 'https://api.lemonsqueezy.com/v1';
const lemonSqueezyApiKey = process.env.LEMON_SQUEEZY_API_KEY;
if (!lemonSqueezyApiKey) throw new Error("No LEMON_SQUEEZY_API_KEY environment variable set");
function createHeaders() {
const headers = new Headers();
headers.append('Accept', 'application/vnd.api+json');
headers.append('Content-Type', 'application/vnd.api+json');
headers.append('Authorization', `Bearer ${lemonSqueezyApiKey}`);
return headers;
}
function createRequestOptions(method: string, headers: Headers): RequestInit {
return {
method,
headers,
redirect: 'follow',
cache: "no-store"
};
}
// try {
const headers = createHeaders();
const requestOptions = createRequestOptions('GET', headers);
const response = await fetch(`${lemonSqueezyBaseUrl}/variants`, requestOptions);
const data = await response.json();
// console.log(JSON.stringify(data, null, 2));
// parse and validate the data against the schema
const parsedData = variantSchema.parse(data);
parsedData.data.map(async (productVariant) => {
await db.insert(productVariants).values({
...productVariant.attributes,
id: productVariant.id,
}).onConflictDoUpdate({
target: [productVariants.id],
set: {
...productVariant.attributes,
}
})
})
return NextResponse.json(parsedData);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment