oz
oz
Explore posts from servers
TTCTheo's Typesafe Cult
Created by oz on 9/29/2024 in #questions
Best NextJS Logging Stack?
Hey guys so I am working on a NextJS eCommerce application and I also have other apps I have in prod and I am not happy with how I am handling logging. A big feature I want is one that easily allows local logs as well that way I can filter by environment and see whats going on. Looking for a solution that can handle large environments with multiple environments / branches As of my research I found these stacks: BetterStack + Winston/Pino Axiom - I dont see a way to do local environments though Sentry - not ideal as it seems more of a not focussed feature
14 replies
TTCTheo's Typesafe Cult
Created by oz on 8/1/2024 in #questions
Embedable Scripts
Hey guys, I’m trying to make an embedable script that any website can use HTML script tag to embed. I want to make my script obviously looking very nice and even somehow inherit the ShadCN and styles. How would I do this? I’m unsure how to customize the style beyond standard HTML styling. Also, is this bad, with this slow down the website to have such complex components? This is for my next JS 14 application.
5 replies
TTCTheo's Typesafe Cult
Created by oz on 7/12/2024 in #questions
Best Frontend Testing System?
For my NextJS app what is the BEST software out there for a small freelance team to create tests for our clients projects so as we develop we ensure each input, form, page, drawer, etc have the info and function as they need. Bonus if it connects to github to pass / fail pull requests based on those tests. Also we will be using Vercel for hosting on most if not all platforms if that info is helpful. What do you guys recommend?
24 replies
TTCTheo's Typesafe Cult
Created by oz on 7/12/2024 in #questions
Drizzle where query problems
I am using drizzle. In my below aPI I cant get my where request to correctly get me my restricted products. When i call the API, it says no restrictions found for product id 63905 which is wrong as I have the below row that DOES have that ID???? db obj example sent in next msg
import {
integer,
pgTable,
serial,
text,
timestamp,
jsonb,
boolean,
} from "drizzle-orm/pg-core";

export const restrictionGroupTable = pgTable("restriction_group", {
id: serial("id").primaryKey(),
name: text("name").notNull().unique(),
brands: jsonb("brands").notNull().default("[]"),
products: jsonb("products").notNull().default("[]"),
categories: jsonb("categories").notNull().default("[]"),
zipcodes: jsonb("zipcodes").notNull().default("[]"),
states: jsonb("states").notNull().default("[]"),
enabled: boolean("enabled").notNull().default(false),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at")
.notNull()
.$onUpdate(() => new Date()),
});

export type InsertRestrictionGroup = typeof restrictionGroupTable.$inferInsert;
export type SelectRestrictionGroup = typeof restrictionGroupTable.$inferSelect;
import {
integer,
pgTable,
serial,
text,
timestamp,
jsonb,
boolean,
} from "drizzle-orm/pg-core";

export const restrictionGroupTable = pgTable("restriction_group", {
id: serial("id").primaryKey(),
name: text("name").notNull().unique(),
brands: jsonb("brands").notNull().default("[]"),
products: jsonb("products").notNull().default("[]"),
categories: jsonb("categories").notNull().default("[]"),
zipcodes: jsonb("zipcodes").notNull().default("[]"),
states: jsonb("states").notNull().default("[]"),
enabled: boolean("enabled").notNull().default(false),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at")
.notNull()
.$onUpdate(() => new Date()),
});

export type InsertRestrictionGroup = typeof restrictionGroupTable.$inferInsert;
export type SelectRestrictionGroup = typeof restrictionGroupTable.$inferSelect;
// api/restrictions/[productId]/[brandId]/[categoryId]/route.ts

import { NextRequest, NextResponse } from "next/server";
import { db } from "@/db";
import { restrictionGroupTable } from "@/db/schema";
import { eq, sql, or, inArray } from "drizzle-orm";

export const revalidate = 3600;

export async function GET(
request: NextRequest,
{
params,
}: { params: { productId: string; brandId: string; categoryId: string } },
) {
const { productId, brandId, categoryId } = params;

try {
console.log("Fetching restrictions for:", {
productId,
brandId,
categoryId,
});

const restrictions = await db
.select({
name: restrictionGroupTable.name,
brands: restrictionGroupTable.brands,
categories: restrictionGroupTable.categories,
products: restrictionGroupTable.products,
zipcodes: restrictionGroupTable.zipcodes,
states: restrictionGroupTable.states,
})
.from(restrictionGroupTable)
.where(
or(
inArray(restrictionGroupTable.products, [sql${productId}::jsonb]),
inArray(restrictionGroupTable.brands, [sql${brandId}::jsonb]),
inArray(restrictionGroupTable.categories, [
sql${categoryId}::jsonb,
]),
),
)
.execute();

console.log("Restrictions fetched:", restrictions);

if (restrictions.length === 0) {
console.log(
"No restrictions found for this product, brand, or category.",
);
}

return NextResponse.json(restrictions);
} catch (error) {
console.error("Error fetching restrictions:", error);
return NextResponse.json(
{ error: "Failed to fetch restrictions" },
{ status: 500 },
);
}
}
// api/restrictions/[productId]/[brandId]/[categoryId]/route.ts

import { NextRequest, NextResponse } from "next/server";
import { db } from "@/db";
import { restrictionGroupTable } from "@/db/schema";
import { eq, sql, or, inArray } from "drizzle-orm";

export const revalidate = 3600;

export async function GET(
request: NextRequest,
{
params,
}: { params: { productId: string; brandId: string; categoryId: string } },
) {
const { productId, brandId, categoryId } = params;

try {
console.log("Fetching restrictions for:", {
productId,
brandId,
categoryId,
});

const restrictions = await db
.select({
name: restrictionGroupTable.name,
brands: restrictionGroupTable.brands,
categories: restrictionGroupTable.categories,
products: restrictionGroupTable.products,
zipcodes: restrictionGroupTable.zipcodes,
states: restrictionGroupTable.states,
})
.from(restrictionGroupTable)
.where(
or(
inArray(restrictionGroupTable.products, [sql${productId}::jsonb]),
inArray(restrictionGroupTable.brands, [sql${brandId}::jsonb]),
inArray(restrictionGroupTable.categories, [
sql${categoryId}::jsonb,
]),
),
)
.execute();

console.log("Restrictions fetched:", restrictions);

if (restrictions.length === 0) {
console.log(
"No restrictions found for this product, brand, or category.",
);
}

return NextResponse.json(restrictions);
} catch (error) {
console.error("Error fetching restrictions:", error);
return NextResponse.json(
{ error: "Failed to fetch restrictions" },
{ status: 500 },
);
}
}
18 replies
TTCTheo's Typesafe Cult
Created by oz on 7/9/2024 in #questions
Cost Efficient XL Generation for Large Products
Hey guys, title could have been better, but I am basically trying to setup an XML product feed for my store, which has 20k+ products including variants, and it constantly grows and shrinks throughout each day. I want the feed to rerun every 15 minutes, and I want to host this on vercel. I am worried about serverles costs since this is a function, below is my code with my approach I am doing caching, is this the most efficient way to handle this to reduce costs in Vercel? Is serverless (vercel) hosting bad for this use case?
// app/api/product-feed/route.ts

import { NextResponse } from 'next/server';
import { kv } from '@vercel/kv';
import { BigCommerceClient } from './bigcommerce-client';
import { generateXMLFeed } from './xml-generator';

const CACHE_KEY = 'product-feed-cache';
const CACHE_TTL = 15 * 60; // 15 minutes in seconds

export async function GET() {
try {
// Check cache first
const cachedFeed = await kv.get(CACHE_KEY);
if (cachedFeed) {
return new NextResponse(cachedFeed, {
headers: { 'Content-Type': 'application/xml' },
});
}

// If not in cache, generate new feed
const client = new BigCommerceClient();
const products = await client.getAllProducts();
const xmlFeed = generateXMLFeed(products);

// Cache the new feed
await kv.set(CACHE_KEY, xmlFeed, { ex: CACHE_TTL });

return new NextResponse(xmlFeed, {
headers: { 'Content-Type': 'application/xml' },
});
} catch (error) {
console.error('Error generating product feed:', error);
return new NextResponse('Error generating product feed', { status: 500 });
}
}

// Scheduled job to update cache (run every 15 minutes)
export async function POST() {
try {
const client = new BigCommerceClient();
const products = await client.getAllProducts();
const xmlFeed = generateXMLFeed(products);
await kv.set(CACHE_KEY, xmlFeed, { ex: CACHE_TTL });
return new NextResponse('Cache updated successfully', { status: 200 });
} catch (error) {
console.error('Error updating cache:', error);
return new NextResponse('Error updating cache', { status: 500 });
}
}
// app/api/product-feed/route.ts

import { NextResponse } from 'next/server';
import { kv } from '@vercel/kv';
import { BigCommerceClient } from './bigcommerce-client';
import { generateXMLFeed } from './xml-generator';

const CACHE_KEY = 'product-feed-cache';
const CACHE_TTL = 15 * 60; // 15 minutes in seconds

export async function GET() {
try {
// Check cache first
const cachedFeed = await kv.get(CACHE_KEY);
if (cachedFeed) {
return new NextResponse(cachedFeed, {
headers: { 'Content-Type': 'application/xml' },
});
}

// If not in cache, generate new feed
const client = new BigCommerceClient();
const products = await client.getAllProducts();
const xmlFeed = generateXMLFeed(products);

// Cache the new feed
await kv.set(CACHE_KEY, xmlFeed, { ex: CACHE_TTL });

return new NextResponse(xmlFeed, {
headers: { 'Content-Type': 'application/xml' },
});
} catch (error) {
console.error('Error generating product feed:', error);
return new NextResponse('Error generating product feed', { status: 500 });
}
}

// Scheduled job to update cache (run every 15 minutes)
export async function POST() {
try {
const client = new BigCommerceClient();
const products = await client.getAllProducts();
const xmlFeed = generateXMLFeed(products);
await kv.set(CACHE_KEY, xmlFeed, { ex: CACHE_TTL });
return new NextResponse('Cache updated successfully', { status: 200 });
} catch (error) {
console.error('Error updating cache:', error);
return new NextResponse('Error updating cache', { status: 500 });
}
}
Then I would setup a cron job in vercel
9 replies
TTCTheo's Typesafe Cult
Created by oz on 7/7/2024 in #questions
Route workin in Dev and not Prod?
Hey guys I have a route defined (code below) that in dev works fine, I call it with PUT and it does just that fine. I am using latest NextJS 14 and am hosgting prod env in Vercel. In prod I get:
Request URL:
https://xxxxxx/api/defendants?id=13
Request Method:
PUT
Status Code:
405 Method Not Allowed
Remote Address:
xxxxxxx
Referrer Policy:
strict-origin-when-cross-origin
Request URL:
https://xxxxxx/api/defendants?id=13
Request Method:
PUT
Status Code:
405 Method Not Allowed
Remote Address:
xxxxxxx
Referrer Policy:
strict-origin-when-cross-origin
But the same call works perfectly fine in dev
Request URL:
http://localhost:3000/api/defendants?id=210
Request Method:
PUT
Status Code:
200 OK
Remote Address:
[::1]:3000
Referrer Policy:
strict-origin-when-cross-origin
Request URL:
http://localhost:3000/api/defendants?id=210
Request Method:
PUT
Status Code:
200 OK
Remote Address:
[::1]:3000
Referrer Policy:
strict-origin-when-cross-origin
// /api/defendants.js
import { NextRequest, NextResponse } from "next/server";
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

export async function GET() {
try {
const defendants = await prisma.defendant.findMany({
select: {
id: true,
companyName: true,
companyAddress: true,
ownerName: true,
ownerAddress: true,
ownerPhone: true,
ownerEmail: true,
},
});

return NextResponse.json({ defendants });
} catch (error) {
console.error("Error fetching defendants:", error);
return NextResponse.json(
{ error: "Internal Server Error" },
{ status: 500 },
);
} finally {
await prisma.$disconnect();
}
}

export async function PUT(request: NextRequest) {
try {
const data = await request.json();
const updatedDefendant = await prisma.defendant.update({
where: { id: data.id },
data: {
companyName: data.companyName,
companyAddress: data.companyAddress,
ownerName: data.ownerName,
ownerAddress: data.ownerAddress,
ownerPhone: data.ownerPhone,
ownerEmail: data.ownerEmail,
},
});

return NextResponse.json({ defendant: updatedDefendant });
} catch (error) {
console.error("Error updating defendant:", error);
return NextResponse.json(
{ error: "Internal Server Error" },
{ status: 500 },
);
} finally {
await prisma.$disconnect();
}
}
// /api/defendants.js
import { NextRequest, NextResponse } from "next/server";
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

export async function GET() {
try {
const defendants = await prisma.defendant.findMany({
select: {
id: true,
companyName: true,
companyAddress: true,
ownerName: true,
ownerAddress: true,
ownerPhone: true,
ownerEmail: true,
},
});

return NextResponse.json({ defendants });
} catch (error) {
console.error("Error fetching defendants:", error);
return NextResponse.json(
{ error: "Internal Server Error" },
{ status: 500 },
);
} finally {
await prisma.$disconnect();
}
}

export async function PUT(request: NextRequest) {
try {
const data = await request.json();
const updatedDefendant = await prisma.defendant.update({
where: { id: data.id },
data: {
companyName: data.companyName,
companyAddress: data.companyAddress,
ownerName: data.ownerName,
ownerAddress: data.ownerAddress,
ownerPhone: data.ownerPhone,
ownerEmail: data.ownerEmail,
},
});

return NextResponse.json({ defendant: updatedDefendant });
} catch (error) {
console.error("Error updating defendant:", error);
return NextResponse.json(
{ error: "Internal Server Error" },
{ status: 500 },
);
} finally {
await prisma.$disconnect();
}
}
6 replies
TTCTheo's Typesafe Cult
Created by oz on 7/3/2024 in #questions
Advice for LARGE Product Embedding for AI?
Hey guys asking for advice. I want to make an AI chat bot. I want it to be able to chat about ANY product we have, but the store I am working with has 10k+ products and more get added / edited everyday. I am using openai, what would be the best way to feed the AI about these products? Would it be crazy expensive?
2 replies
TTCTheo's Typesafe Cult
Created by oz on 6/19/2024 in #questions
Best PDF Parsing Practices?
I have a system that gets a bunch of legal documents. The documents are all for the same legal case type but they are not the same documents, as each lawyer has their own wording, they look and are formatted diff but have the same important info of course. I want to extract certain data. What is the best approach for this? As of now I am leaning towards: Anyscale JSON Mode that returns json object of vals I ask for, thoughts? https://www.anyscale.com/blog/anyscale-endpoints-json-mode-and-function-calling-features ^ Credit to Rauch 🙂 https://github.com/rauchg/next-ai-news As of now I dont see how I can escape AI for this, but want to ensure I maximize the best model/service for this and not waste money on things I dont need (like throwing chatgpt-4o for example which is much more expensive for a model I dont need) Any ideas from anyone who did things similarly?
2 replies
TTCTheo's Typesafe Cult
Created by oz on 6/18/2024 in #questions
UploadThing - Cant pass userId
Hey guys, my lack of knowledge is showing here 😓 I am trying to pass the input prop to pass my user ID but I cant use that prop since I am not really using the base UploadComponent as I followed the nextJS docs and it uses a generator. So it says input is not a valid prop, how do I fix this? /api/uploadthing/core.ts
import { createUploadthing, type FileRouter } from "uploadthing/next";
import { UploadThingError } from "uploadthing/server";

const f = createUploadthing();

// FileRouter for your app, can contain multiple FileRoutes
export const ourFileRouter = {
// Define as many FileRoutes as you like, each with a unique routeSlug
imageUploader: f({ image: { maxFileSize: "4MB" } })
// Set permissions and file types for this FileRoute
.middleware(async ({ req, input }) => {
try {
// This code runs on your server before upload
const userId = input.userId as string | null;

// If you throw, the user will not be able to upload
if (!userId) throw new UploadThingError("Unauthorized");

// Whatever is returned here is accessible in onUploadComplete as `metadata`
return { userId };
} catch (error) {
console.error("Middleware error:", error);
throw new UploadThingError("Internal Server Error");
}
})
.onUploadComplete(async ({ metadata, file }) => {
// This code RUNS ON YOUR SERVER after upload
console.log("Upload complete for userId:", metadata.userId);

console.log("file url", file.url);

// !!! Whatever is returned here is sent to the clientside `onClientUploadComplete` callback
return { uploadedBy: metadata.userId };
}),
} satisfies FileRouter;

export type OurFileRouter = typeof ourFileRouter;
import { createUploadthing, type FileRouter } from "uploadthing/next";
import { UploadThingError } from "uploadthing/server";

const f = createUploadthing();

// FileRouter for your app, can contain multiple FileRoutes
export const ourFileRouter = {
// Define as many FileRoutes as you like, each with a unique routeSlug
imageUploader: f({ image: { maxFileSize: "4MB" } })
// Set permissions and file types for this FileRoute
.middleware(async ({ req, input }) => {
try {
// This code runs on your server before upload
const userId = input.userId as string | null;

// If you throw, the user will not be able to upload
if (!userId) throw new UploadThingError("Unauthorized");

// Whatever is returned here is accessible in onUploadComplete as `metadata`
return { userId };
} catch (error) {
console.error("Middleware error:", error);
throw new UploadThingError("Internal Server Error");
}
})
.onUploadComplete(async ({ metadata, file }) => {
// This code RUNS ON YOUR SERVER after upload
console.log("Upload complete for userId:", metadata.userId);

console.log("file url", file.url);

// !!! Whatever is returned here is sent to the clientside `onClientUploadComplete` callback
return { uploadedBy: metadata.userId };
}),
} satisfies FileRouter;

export type OurFileRouter = typeof ourFileRouter;
page.tsx
"use client";

import { UploadButton } from "../../../util/uploadthing";
import { useEffect, useState } from "react";

export default function Home() {
const [userId, setUserId] = useState<string | null>(null);

useEffect(() => {
const fetchUserId = async () => {
const response = await fetch("/api/auth/me/id");
const data = await response.json();
setUserId(data.userId);
};

fetchUserId();
}, []);

return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<UploadButton
endpoint="imageUploader"
input={{ userId }}
onClientUploadComplete={(res) => {
// Do something with the response
console.log("Files: ", res);
alert("Upload Completed");
}}
onUploadError={(error: Error) => {
// Do something with the error.
alert(`ERROR! ${error.message}`);
}}
/>
</main>
);
}
"use client";

import { UploadButton } from "../../../util/uploadthing";
import { useEffect, useState } from "react";

export default function Home() {
const [userId, setUserId] = useState<string | null>(null);

useEffect(() => {
const fetchUserId = async () => {
const response = await fetch("/api/auth/me/id");
const data = await response.json();
setUserId(data.userId);
};

fetchUserId();
}, []);

return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<UploadButton
endpoint="imageUploader"
input={{ userId }}
onClientUploadComplete={(res) => {
// Do something with the response
console.log("Files: ", res);
alert("Upload Completed");
}}
onUploadError={(error: Error) => {
// Do something with the error.
alert(`ERROR! ${error.message}`);
}}
/>
</main>
);
}
22 replies