### Performing Basic Database Queries Source: https://nexty.dev/docs/guide/auth-database/database Demonstrates various basic querying techniques including fetching a single record by ID, retrieving lists with pagination, sorting, and filtering with a keyword search. It also shows how to get the total count for pagination purposes. ```typescript import { eq, ilike, or, desc, count } from 'drizzle-orm'; // Single record const one = await db.select().from(user).where(eq(user.id, userId)).limit(1); // List + pagination + sorting + search const pageIndex = 0, pageSize = 20, keyword = 'john'; const cond = keyword ? or(ilike(user.email, `%${keyword}%`), ilike(user.name, `%${keyword}%`)) : undefined; const users = await db .select() .from(user) .where(cond) .orderBy(desc(user.createdAt)) .offset(pageIndex * pageSize) .limit(pageSize); // Total count (for pagination) const totalCount = (await db.select({ value: count() }).from(user).where(cond))[0].value; ``` -------------------------------- ### Create Frontend Display Pages for Guide Module (TypeScript/JSX) Source: https://nexty.dev/docs/guide/cms This snippet demonstrates how to set up frontend pages for a new 'guide' content module. It includes initializing a CMS module instance, fetching posts and tags, and rendering a PostList component. It also shows how to handle individual guide page display, including view count statistics and related posts. ```typescript // Create CMS module instance (in lib/cms/index.ts) export const guideCms = createCmsModule('guide'); // Use in page const { posts: localPosts } = await guideCms.getLocalList(locale); const initialServerPostsResult = await listPublishedPostsAction({ pageIndex: 0, pageSize: SERVER_POST_PAGE_SIZE, postType: "guide", // Modify here locale: locale, }); // Tags const tagsResult = await listTagsAction({ postType: "guide" }); // PostList component // guide/[slug]/page.tsx // Use guideCms to get content const { post, errorCode } = await guideCms.getBySlug(slug, locale); // View count statistics const viewCountConfig = POST_CONFIGS.guide.viewCount; if (viewCountConfig.enabled) { if (viewCountConfig.mode === "unique") { await incrementUniqueViewCountAction({ slug, postType: "guide", locale }); } else { await incrementViewCountAction({ slug, postType: "guide", locale }); } } // Related articles ``` -------------------------------- ### Executing Relational Queries with Joins Source: https://nexty.dev/docs/guide/auth-database/database Illustrates how to perform relational queries using a left join to combine data from multiple tables. This example selects specific columns from 'orders' and 'pricingPlans' tables, joining them on their respective IDs. ```typescript import { eq } from 'drizzle-orm'; const rows = await db .select({ orderId: orders.id, planInterval: pricingPlans.recurringInterval, amount: orders.amountTotal, }) .from(orders) .leftJoin(pricingPlans, eq(orders.planId, pricingPlans.id)); ``` -------------------------------- ### Start Application with Node.js Source: https://nexty.dev/docs/start-project/dokploy This command initiates the application server using Node.js. It's a fundamental step for running the application. ```dockerfile CMD ["node", "server.js"] ``` -------------------------------- ### Install Dokploy using cURL Source: https://nexty.dev/docs/start-project/dokploy This command installs Dokploy on your server by downloading and executing an installation script from dokploy.com. Ensure you have SSH access to your server before running this command. ```bash curl -sSL https://dokploy.com/install.sh | sh ``` -------------------------------- ### Install and Update Supabase CLI Source: https://nexty.dev/docs/integration/supabase Commands to install and update the Supabase Command Line Interface (CLI) on macOS, Linux, and Windows systems. ```shell # mac OS or Linux brew install supabase/tap/supabase # Windows scoop bucket add supabase https://github.com/supabase/scoop-bucket.git scoop install supabase ``` ```shell # mac OS or Linux brew upgrade supabase # Windows scoop update supabase ``` -------------------------------- ### Start Development Server Source: https://nexty.dev/docs/guide/upgrade Launches the local development server to allow for testing of the upgraded features. This command is used for local verification before deploying the changes. ```bash npm run dev ``` -------------------------------- ### Update Sitemap for Guide Module (TypeScript) Source: https://nexty.dev/docs/guide/cms This code demonstrates how to update the `sitemap.ts` file to include routes for the new 'guide' module. It adds entries for the main guide listing page and dynamically generates routes for individual guide detail pages based on published posts. ```typescript // Add guide list page { url: `${siteUrl}/${locale}/guide`, lastModified: new Date(), changeFrequency: 'daily', priority: 0.8, } // Add guide detail pages const guideResult = await listPublishedPostsAction({ locale: locale, pageSize: 1000, visibility: "public", postType: "guide", }); if (guideResult.success && guideResult.data?.posts) { guideResult.data.posts.forEach((post) => { sitemap.push({ url: `${siteUrl}/${locale}/guide/${post.slug}`, lastModified: post.updatedAt ? new Date(post.updatedAt) : new Date(), changeFrequency: 'weekly', priority: 0.7, }); }); } ``` -------------------------------- ### Initialize and Seed Database with npm Source: https://nexty.dev/docs/integration/database Commands to initialize the database schema and import example pricing plan seed data using npm scripts. These commands are essential for setting up your database with initial data. ```bash # Initialize database npm run db:migrate # Import example pricing plan seed data npm run db:seed ``` -------------------------------- ### Monthly Subscription Benefits Configuration Source: https://nexty.dev/docs/guide/payment/prices-card Example JSON structure for configuring benefits for a monthly subscription plan, granting credits upon subscription and renewal. ```json { "monthlyCredits": 50 } ``` -------------------------------- ### Extending CMS Configuration for New Modules (TypeScript) Source: https://nexty.dev/docs/guide/cms Shows how to add configuration for a new content module, like 'guide', in `components/cms/post-config.ts`. This includes defining schemas, image paths, tag enablement, and routing for the new module. ```typescript export const POST_CONFIGS: Record = { blog: { // ... existing configuration }, glossary: { // ... existing configuration }, // New guide configuration guide: { postType: "guide", schema: basePostSchema, actionSchema: postActionSchema, imagePath: GUIDE_IMAGE_PATH, // Needs to be defined in config/common.ts enableTags: true, // localDirectory: 'guides', // If you need to support local MDX files viewCount: { enabled: true, // Enable view count statistics mode: 'unique', // Use unique IP statistics showInUI: true, }, showCoverInList: true, routes: { list: "/dashboard/guides", create: "/dashboard/guides/new", edit: (id: string) => `/dashboard/guides/${id}`, }, }, }; ``` -------------------------------- ### Importing Database Client and Schema Source: https://nexty.dev/docs/guide/auth-database/database Imports the database client 'db' and schema definitions from '@/lib/db' and '@/lib/db/schema' respectively. This is the foundational step for any database interaction. ```typescript import { db } from '@/lib/db'; import { user, orders, pricingPlans, usage, creditLogs } from '@/lib/db/schema'; ``` -------------------------------- ### Email Magic Link and Signup Email Templates Source: https://nexty.dev/docs/integration/supabase Example HTML templates for Supabase Auth's email Magic Link and signup confirmation emails. These templates can be customized, but the `href` attribute in the confirmation link must match the provided structure for cross-browser compatibility. ```html

Confirm your signup

Follow this link to confirm your user:

Confirm your mail

``` ```html

Hi there!

Click the link below to sign in to your Nexty.dev account:

Sign In to Nexty.dev

If you didn't request this, you can safely ignore this email.

The Nexty.dev Team

``` -------------------------------- ### Run Better Auth Database Migration (CLI) Source: https://nexty.dev/docs/guide/auth-database/rate-limiting This command uses the Better Auth CLI to run database migrations. This is a necessary step after configuring database storage to set up the required database tables. ```bash pnpm dlx @better-auth/cli migrate ``` -------------------------------- ### One-time Payment Benefits Configuration Source: https://nexty.dev/docs/guide/payment/prices-card Example JSON structure for configuring benefits for a one-time payment plan, specifically granting a fixed number of credits upon purchase. ```json { "oneTimeCredits": 100 } ``` -------------------------------- ### Initialize and Link Supabase Database Source: https://nexty.dev/docs/integration/supabase Commands to log in to Supabase, link your project to the database, and update the database schema with initial data. This process requires Supabase API keys and the database password. ```shell pnpm db:login pnpm db:link pnpm db:update ``` -------------------------------- ### Install Project Dependencies with PNPM Source: https://nexty.dev/docs/guide/upgrade Installs all necessary project dependencies using the PNPM package manager. This step is essential after merging new code to ensure all required libraries and packages are present. ```bash pnpm install ``` -------------------------------- ### Stricter Daily Rate Limits for Free Tier Abuse Prevention (TypeScript) Source: https://nexty.dev/docs/guide/auth-database/rate-limiting Implements stricter daily rate limits for authentication endpoints to prevent free tier abuse. This example extends the default configuration by setting longer windows (24 hours) and lower request caps for magic link, OTP send, and OTP verification. It also includes optional Upstash Redis integration for persistent storage. ```typescript rateLimit: { enabled: true, window: 60, max: 100, customRules: { // Strict email sending limits "/sign-in/magic-link": { window: 60 * 60 * 24, // 24 hours max: 5, // Only 5 magic links per IP per day }, "/email-otp/send-verification-otp": { window: 60 * 60 * 24, // 24 hours max: 10, // Only 10 OTP codes per IP per day }, // Keep verification attempts reasonable "/sign-in/email-otp": { window: 60 * 60 * 24, // 24 hours max: 20, // 20 attempts per 24 hours } }, ...(redis && { customStorage: { get: async (key: string) => { const data = await redis!.get<{ key: string; count: number; lastRequest: number }>(key); return data || undefined; }, set: async (key: string, value: { key: string; count: number; lastRequest: number }) => { await redis!.set(key, value, { ex: 60 * 60 * 24 }); // Auto-expire after 24 hours }, }, }), } ``` -------------------------------- ### Relaxed Rate Limits for Internal Tools (TypeScript) Source: https://nexty.dev/docs/guide/auth-database/rate-limiting Configures relaxed rate limits suitable for internal tools or low-risk applications. This example increases the global default request limit and raises the maximum requests for specific endpoints, allowing for more lenient usage patterns. ```typescript rateLimit: { enabled: true, window: 60, max: 200, // Increase global default customRules: { "/sign-in/magic-link": { window: 60, max: 10 }, "/email-otp/send-verification-otp": { window: 60, max: 10 }, "/sign-in/email-otp": { window: 60, max: 20 }, }, } ``` -------------------------------- ### Updating Records Source: https://nexty.dev/docs/guide/auth-database/database Shows how to update existing records in the 'user' table. This example sets the 'banned' status to true, provides a reason, and nullifies the ban expiration date for a specific user identified by their ID. ```typescript await db.update(user) .set({ banned: true, banReason: 'By admin', banExpires: null }) .where(eq(user.id, userId)); ``` -------------------------------- ### HTML Metadata Example Source: https://nexty.dev/docs/guide/metadata-config This snippet shows a typical HTML `` section with various metadata tags, including title, description, Open Graph, canonical, and hreflang. These tags are essential for search engine optimization and social media sharing. ```html NEXTY.DEV - Build and ship your SaaS faster ``` -------------------------------- ### Numeric Type Conversion for Stripe Amounts (TypeScript) Source: https://nexty.dev/docs/guide/auth-database/database Provides a concise example of handling currency amounts, specifically for Stripe integration. It shows the necessary conversion from cents (as typically provided by Stripe) to a displayable or storable format by dividing by 100 and converting to a string. ```typescript const amountStr = (invoice.amount_paid / 100).toString(); ``` -------------------------------- ### Updating Post Editor Client for New Content Module (TypeScript) Source: https://nexty.dev/docs/guide/cms Demonstrates the necessary changes in `guides/new/page.tsx` and `guides/[postId]/edit/page.tsx` to configure the `PostEditorClient` for the 'guide' post type. This involves setting the `postType` prop and potentially the `mode` and `postId`. ```typescript ``` -------------------------------- ### Performing Upsert Operations Source: https://nexty.dev/docs/guide/auth-database/database Provides examples of Upsert operations, which are useful for synchronizing data or handling idempotent writes. It covers updating an existing subscription if a 'stripeSubscriptionId' exists, or inserting it if it's new, and accumulating usage credits based on 'userId'. ```typescript // Subscription upsert const { stripeSubscriptionId, ...updateData } = subscriptionData; await db.insert(subscriptions) .values(subscriptionData) .onConflictDoUpdate({ target: subscriptions.stripeSubscriptionId, set: updateData, }); // usage table upsert by userId (accumulate/overwrite) await db.insert(usage) .values({ userId, oneTimeCreditsBalance: add }) .onConflictDoUpdate({ target: usage.userId, set: { oneTimeCreditsBalance: sql`${usage.oneTimeCreditsBalance} + ${add}` }, }); ``` -------------------------------- ### Configure GitHub OAuth for Development Source: https://nexty.dev/docs/integration/auth Guides the configuration of GitHub OAuth for a development environment. It involves setting the Homepage URL and Authorization callback URL in GitHub's OAuth Apps settings. ```bash # Homepage URL: http://localhost:3000 # Authorization callback URL: http://localhost:3000/api/auth/callback/github ``` -------------------------------- ### Get Session and Protect APIs (Server-side) Source: https://nexty.dev/docs/guide/auth-database/auth Demonstrates how to retrieve the current user session on the server-side using `getSession()`. This is essential for accessing user information and protecting API routes or server actions from unauthorized access. It expects a session object containing user details. ```typescript import { getSession } from "@/lib/auth/server"; export async function SomeServerActions(req: Request) { const session = await getSession() const user = session?.user; if (!user) return actionResponse.unauthorized(); // session.user: { id, email, role, ... } return actionResponse.success({}); } ``` -------------------------------- ### Get Session and Login Status (Client-side) Source: https://nexty.dev/docs/guide/auth-database/auth Demonstrates how to fetch the user's session data and check their login status from a React client component using the `authClient.useSession()` hook. This hook provides reactive state for session data and loading status. ```typescript import { authClient } from "@/lib/auth/auth-client"; // React client component const { data: session, isPending } = authClient.useSession(); ``` -------------------------------- ### Update Supabase Database with Migrations Source: https://nexty.dev/docs/integration/supabase Command to apply all pending SQL migration files in the `supabase/migrations` directory to the database. This command should be run after creating or modifying migration files. ```shell pnpm db:update ``` -------------------------------- ### Export New CMS Module in lib/cms/index.ts (TypeScript) Source: https://nexty.dev/docs/guide/cms This snippet shows how to export a newly created CMS module, in this case, 'guide', from the central `lib/cms/index.ts` file. This makes the module's functionalities available throughout the application. ```typescript // Add at the end of the file export const guideCms = createCmsModule('guide'); ``` -------------------------------- ### Get User Benefits (TypeScript) Source: https://nexty.dev/docs/guide/payment/credit-system Retrieves a user's complete benefits information, including credit balances (subscription and one-time), and subscription details. It queries the database for usage data, processes yearly subscription allocations, fetches subscription information, and constructs a consolidated benefits object. Requires a `userId` as input. ```typescript export async function getUserBenefits(userId: string): Promise { // 1. Query credit balance const usage = await db .select({ subscriptionCreditsBalance: usageSchema.subscriptionCreditsBalance, oneTimeCreditsBalance: usageSchema.oneTimeCreditsBalance, balanceJsonb: usageSchema.balanceJsonb, }) .from(usageSchema) .where(eq(usageSchema.userId, userId)); // 2. Process yearly subscription monthly allocation if (usage[0]) { await processYearlySubscriptionCatchUp(userId); } // 3. Query subscription information const subscription = await fetchSubscriptionData(userId); // 4. Build benefits object return { activePlanId: subscription?.status === 'active' ? subscription.planId : null, subscriptionStatus: subscription?.status, currentPeriodEnd: subscription?.currentPeriodEnd, nextCreditDate: usage[0]?.balanceJsonb?.yearlyAllocationDetails?.nextCreditDate, totalAvailableCredits: (usage[0]?.subscriptionCreditsBalance || 0) + (usage[0]?.oneTimeCreditsBalance || 0), subscriptionCreditsBalance: usage[0]?.subscriptionCreditsBalance || 0, oneTimeCreditsBalance: usage[0]?.oneTimeCreditsBalance || 0, }; } ``` -------------------------------- ### Create New Supabase Database Migration Source: https://nexty.dev/docs/integration/supabase Command to generate a new migration file in the `supabase/migrations` directory for designing or modifying database tables. The `` should describe the changes being made. ```shell pnpm db:new-migration ## eg: pnpm db:new-migration add_image_jobs_table ``` -------------------------------- ### Extending Database Schema for New Post Types (TypeScript) Source: https://nexty.dev/docs/guide/cms Demonstrates how to extend the PostgreSQL enumeration for post types in `lib/db/schema.ts`. This involves adding a new type, such as 'guide', to the existing `post_type` enum. ```typescript export const postTypeEnum = pgEnum('post_type', [ 'blog', 'glossary', 'guide', // New addition ]) ``` -------------------------------- ### Defining Page Path Source: https://nexty.dev/docs/guide/metadata-config Shows how to set the `path` property, which must start with '/'. This path is used in conjunction with the locale to generate canonical URLs and hreflang links, helping to avoid duplicate content issues and improve international SEO. ```javascript path: "/about" ``` -------------------------------- ### Implement SSG for Locale-Only Dynamic Pages Source: https://nexty.dev/docs/guide/i18n Provides an example of implementing Static Site Generation (SSG) for pages where the only dynamic parameter is the locale. It shows how to extract the locale from `params` and use `generateStaticParams` to pre-render pages for all supported locales. This approach is suitable for pages like `/privacy-policy` or `/zh/privacy-policy`. ```typescript export default async function Page({ params }: { params: Params }) { // ✅ Extract locale from params for SSG compatibility // ❌ Don't use getLocale() as it prevents static generation const { locale } = await params; // ... other code ... return ( // ... page content ... ); } export async function generateStaticParams() { return LOCALES.map((locale) => ({ locale, })); } ``` -------------------------------- ### Page-level Guard for Login (Server Component) Source: https://nexty.dev/docs/guide/auth-database/auth Illustrates how to implement a page-level access control using the `AuthGuard` component. This ensures that the content within the `AuthGuard` is only visible to logged-in users, providing a seamless way to protect routes. ```typescript // Component: components/auth/AuthGuard.tsx already encapsulated import { AuthGuard } from "@/components/auth/AuthGuard"; export default async function LoginRequiredLayout() { return ( {/* Content visible only to logged-in users */} ); } ``` -------------------------------- ### Performing Aggregate Statistics Source: https://nexty.dev/docs/guide/auth-database/database Shows how to use aggregate functions like COUNT and SUM with filtering, and how to perform daily aggregation using date truncation. This is useful for generating reports and analyzing data trends. ```typescript import { sql, and, gte, lt, eq, count } from 'drizzle-orm'; const start = new Date('2025-01-01'); const end = new Date('2025-02-01'); // Aggregation with FILTER const stats = await db .select({ oneTimeCount: sql`COUNT(*) FILTER (WHERE ${orders.orderType} = 'one_time_purchase')`.mapWith(Number), monthlyRevenue: sql`COALESCE(SUM(${orders.amountTotal}) FILTER (WHERE ${pricingPlans.recurringInterval} = 'month'), 0)`.mapWith(Number), }) .from(orders) .leftJoin(pricingPlans, eq(orders.planId, pricingPlans.id)) .where(and(gte(orders.createdAt, start), lt(orders.createdAt, end))); // Daily aggregation (date_trunc) const dt = sql`date_trunc('day', ${orders.createdAt})`; const daily = await db .select({ date: dt, count: count(orders.id) }) .from(orders) .where(and(gte(orders.createdAt, start), lt(orders.createdAt, end))) .groupBy(dt); ``` -------------------------------- ### Sign Out (Client-side) Source: https://nexty.dev/docs/guide/auth-database/auth Shows how to implement the sign-out functionality on the client-side using `authClient.signOut()`. It includes an option to refresh the router after a successful sign-out, ensuring the UI is updated accordingly. ```typescript await authClient.signOut({ fetchOptions: { onSuccess: () => router.refresh(), }, }); ``` -------------------------------- ### Page-level Guard for Admin Role (Server Component) Source: https://nexty.dev/docs/guide/auth-database/auth Shows how to use the `AuthGuard` component to restrict access to specific pages or sections for users with the 'admin' role. This is a declarative way to enforce role-based access control at the component level. ```typescript // Component: components/auth/AuthGuard.tsx already encapsulated import { AuthGuard } from "@/components/auth/AuthGuard"; export default async function AdminLayout() { return ( {/* Content visible only to admins */} ); } ``` -------------------------------- ### Inserting Data and Returning Primary Key Source: https://nexty.dev/docs/guide/auth-database/database Demonstrates how to insert a new record into the 'orders' table and retrieve the newly generated primary key. It uses 'InferInsertModel' for type safety and returns the 'id' of the inserted order. ```typescript import { InferInsertModel, eq, count } from 'drizzle-orm'; type NewOrder = InferInsertModel; const orderData: NewOrder = { userId, provider: 'stripe', providerOrderId: 'pi_123', status: 'succeeded', orderType: 'one_time_purchase', amountTotal: '9.90', // Numeric type recommended to use string storage currency: 'usd', }; const inserted = await db.insert(orders).values(orderData).returning({ id: orders.id }); const orderId = inserted[0]?.id; ``` -------------------------------- ### Get Last Used Login Method (Client-side) Source: https://nexty.dev/docs/guide/auth-database/auth Provides a utility function `authClient.getLastUsedLoginMethod()` to retrieve the user's most recent login method. This can be used to optimize the login flow by pre-selecting or suggesting the last used method. ```typescript const lastLogin = authClient.getLastUsedLoginMethod(); // "google" | "github" | "email" | undefined ``` -------------------------------- ### Updating Dashboard List Page for New Content Module (TypeScript) Source: https://nexty.dev/docs/guide/cms Shows modifications needed in `guides/page.tsx` to adapt the post listing functionality for the 'guide' post type. This includes updating the `postType` parameter in `listPostsAction` and the `PostDataTable` configuration. ```typescript // Change postType to "guide" const result = await listPostsAction({ pageIndex: 0, pageSize: PAGE_SIZE, postType: "guide", // Modify here }); // Update configuration ``` -------------------------------- ### Social Login (Google/GitHub) (Client-side) Source: https://nexty.dev/docs/guide/auth-database/auth Provides a client-side function `authClient.signIn.social()` to initiate login via social providers like Google or GitHub. It allows specifying callback URLs for success and error scenarios, and includes optional callbacks for request, success, and error events. ```typescript await authClient.signIn.social( { provider: "google", // or "github" callbackURL: window.location.origin, // where to redirect after login errorCallbackURL: "/redirect-error", }, { onRequest: () => {/* loading UI */}, onSuccess: (ctx) => {/* logged in */}, onError: (ctx) => {/* error handling */}, } ); ``` -------------------------------- ### Configure Plausible Analytics Environment Variables in Dockerfile Source: https://nexty.dev/docs/start-project/dokploy This section sets environment variables for Plausible Analytics, an open-source, privacy-focused analytics tool. `PLAUSIBLE_API_KEY` and `PLAUSIBLE_URL` are used to configure the connection and data reporting to the Plausible instance. The variables are sourced from the build environment. Dependencies include a Plausible Analytics setup. ```dockerfile # PLAUSIBLE ENV PLAUSIBLE_API_KEY=${PLAUSIBLE_API_KEY} ENV PLAUSIBLE_URL=${PLAUSIBLE_URL} ``` -------------------------------- ### Stripe CLI Webhook Testing (Shell) Source: https://nexty.dev/docs/guide/payment/webhook-handling Configures and tests Stripe webhooks locally using the Stripe Command Line Interface. It involves installing the CLI, logging in, forwarding webhooks to a local server, and triggering test events. ```shell # Install Stripe CLI brew install stripe/stripe-cli/stripe # Login stripe login # Forward webhooks to local stripe listen --forward-to localhost:3000/api/stripe/webhook # Trigger test event stripe trigger checkout.session.completed ``` -------------------------------- ### Set up Node.js Runtime Environment in Dockerfile Source: https://nexty.dev/docs/start-project/dokploy This section defines the Docker image for the runtime stage, using `node:20-alpine`. It sets the working directory to `/app` and configures the Node.js environment for production. It also creates a system user and group (`nodejs` and `nextjs`) for secure execution and copies the necessary built artifacts from the builder stage. Dependencies include the `node:20-alpine` Docker image. ```dockerfile # ============================================ # Runtime Stage # ============================================ FROM node:20-alpine AS runner WORKDIR /app # ============================================ # No build arguments or environment variables needed here # All runtime secrets will be injected by the container runtime (dokploy) # Next.js standalone mode reads environment variables at runtime # ============================================ # Set production environment ENV NODE_ENV=production # Disable Next.js telemetry ENV NEXT_TELEMETRY_DISABLED=1 # Create system user group and user (for secure execution) RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs # Copy necessary files from build stage COPY --from=builder /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static ``` -------------------------------- ### Local Webhook Testing with ngrok (Shell) Source: https://nexty.dev/docs/guide/payment/webhook-handling Exposes a local development server to the public internet using ngrok, enabling testing of webhooks from external services like Creem. After starting ngrok, the provided URL is used to configure the webhook in the Creem dashboard. ```shell ngrok http 3000 # Use the URL provided by ngrok to configure the webhook ``` -------------------------------- ### Configure Database Storage for Better Auth (TypeScript) Source: https://nexty.dev/docs/guide/auth-database/rate-limiting This snippet demonstrates how to configure database storage for Better Auth's rate limiting. It specifies that database storage should be enabled and provides a model name for the database table. Note that database storage requires manual implementation and scheduled cleanup of expired records. ```typescript rateLimit: { enabled: true, storage: "database", modelName: "rateLimit", // Table name // ... } ``` -------------------------------- ### Create Pricing Plan via API Source: https://nexty.dev/docs/guide/payment/prices-card Uses the `createPricingPlanAction` function to create a new pricing plan. Requires plan details including pricing, localization, and features. Returns the result of the creation operation. ```javascript import { createPricingPlanAction } from '@/actions/prices/admin'; const result = await createPricingPlanAction({ planData: { environment: 'live', cardTitle: 'Pro Plan', provider: 'stripe', stripePriceId: 'price_xxx', paymentType: 'recurring', recurringInterval: 'month', price: '29.00', currency: 'USD', displayPrice: '$29', priceSuffix: 'month', isActive: true, isHighlighted: false, displayOrder: 1, langJsonb: { en: { cardTitle: 'Pro Plan', cardDescription: 'Best for professionals', displayPrice: '$29', priceSuffix: 'month', buttonText: 'Get Started', features: [ { description: 'Feature 1', included: true } ] } }, benefitsJsonb: { monthlyCredits: 100 }, features: [ { description: 'Unlimited projects', included: true } ] }, locale: 'en' }); ``` -------------------------------- ### Get Client User Benefits (TypeScript) Source: https://nexty.dev/docs/guide/payment/credit-system Provides a client-side function to fetch user benefits. It first obtains the current user's session and ID, then calls `getUserBenefits` to retrieve the data. Returns an `ActionResult` containing the benefits or an error message if the user is unauthorized or an error occurs during fetching. ```typescript export async function getClientUserBenefits(): Promise> { const session = await getSession(); const user = session?.user; if (!user) return actionResponse.unauthorized(); try { const benefits = await getUserBenefits(user.id); return actionResponse.success(benefits); } catch (error: any) { return actionResponse.error(error.message || 'Failed to fetch user benefits.'); } } ``` -------------------------------- ### Adding Image Path Configuration for New Modules (TypeScript) Source: https://nexty.dev/docs/guide/cms Illustrates adding a new image path constant, `GUIDE_IMAGE_PATH`, in `config/common.ts` and integrating it into the `R2_CATEGORIES` array for resource selection. ```typescript export const GUIDE_IMAGE_PATH = "guide-images"; export const R2_CATEGORIES: R2Category[] = [ { name: "All", prefix: "" }, { name: "Admin Uploads", prefix: `${ADMIN_UPLOAD_IMAGE_PATH}/` }, { name: "Blogs Images", prefix: `${BLOGS_IMAGE_PATH}/` }, { name: "Glossary Images", prefix: `${GLOSSARY_IMAGE_PATH}/` }, { name: "Guide Images", prefix: `${GUIDE_IMAGE_PATH}/` }, // New addition ]; ``` -------------------------------- ### Google One Tap Login (Client-side) Source: https://nexty.dev/docs/guide/auth-database/auth Details the client-side implementation of Google One Tap, a passwordless sign-in experience. It uses `authClient.oneTap()` and allows for configuration of success and error handlers, as well as a notification handler for prompt events. ```typescript import { authClient } from "@/lib/auth/auth-client"; await authClient.oneTap({ fetchOptions: { onSuccess: () => window.location.reload(), onError: (ctx) => console.error(ctx.error), }, onPromptNotification: (note) => console.log(note), }); ``` -------------------------------- ### Environment Variables for Supabase and Google Auth Source: https://nexty.dev/docs/integration/supabase This snippet shows the necessary environment variables to configure Supabase authentication and Google OAuth, including the Supabase URL, anonymous and service role keys, and the Google Client ID. These are essential for connecting your application to Supabase and enabling Google One Tap sign-in. ```dotenv # .env.local or .env NEXT_PUBLIC_SUPABASE_URL="" NEXT_PUBLIC_SUPABASE_ANON_KEY="" SUPABASE_SERVICE_ROLE_KEY="" NEXT_PUBLIC_GOOGLE_CLIENT_ID="" ``` -------------------------------- ### Resolve Supabase DB Update Error on Windows Source: https://nexty.dev/docs/integration/supabase A workaround for the 'Invalid project ref format' error encountered on Windows when running `pnpm db:update`. This involves modifying the `db:update` script in `package.json` to use a semicolon instead of `&&` for command chaining. ```json { "scripts": { "db:update": "npm run db:push ; npm run db:gen-types" } } ``` -------------------------------- ### Configure Upstash Redis for Better Auth Storage (TypeScript) Source: https://nexty.dev/docs/guide/auth-database/rate-limiting This snippet shows how to configure Upstash Redis as a custom storage solution for Better Auth. It leverages the Upstash Redis client to get and set data with automatic expiration (TTL). This is recommended for production and serverless environments. ```typescript ...(redis && { customStorage: { get: async (key: string) => { const data = await redis!.get<{ key: string; count: number; lastRequest: number }>(key); return data || undefined; }, set: async (key: string, value: { key: string; count: number; lastRequest: number }) => { await redis!.set(key, value, { ex: 120 }); // Auto-expire after 120 seconds }, }, }), ``` -------------------------------- ### Allocate Yearly Subscription Credits Source: https://nexty.dev/docs/guide/payment/credit-system Initializes and allocates credits for yearly subscriptions using a monthly allocation approach. It calculates the next credit date and sets up the initial balance and details for yearly allocation, logging the initial grant. ```typescript if (isYearlyInterval(plan.recurringInterval) && benefits?.totalMonths && benefits?.monthlyCredits) { await db.transaction(async (tx) => { const startDate = new Date(currentPeriodStart); const nextCreditDate = new Date( startDate.getFullYear(), startDate.getMonth() + 1, startDate.getDate() ); const yearlyDetails = { yearlyAllocationDetails: { remainingMonths: benefits.totalMonths - 1, nextCreditDate: nextCreditDate.toISOString(), monthlyCredits: benefits.monthlyCredits, lastAllocatedMonth: `${startDate.getFullYear()}-${(startDate.getMonth() + 1).toString().padStart(2, '0')}`, relatedOrderId: orderId, } }; // Initialize yearly subscription, immediately allocate first month's credits const updatedUsage = await tx .insert(usageSchema) .values({ userId, subscriptionCreditsBalance: benefits.monthlyCredits, balanceJsonb: yearlyDetails, }) .onConflictDoUpdate({ target: usageSchema.userId, set: { subscriptionCreditsBalance: benefits.monthlyCredits, balanceJsonb: sql`coalesce(${usageSchema.balanceJsonb}, '{}'::jsonb) - 'yearlyAllocationDetails' || ${JSON.stringify(yearlyDetails)}::jsonb`, } }) .returning({ oneTimeBalanceAfter: usageSchema.oneTimeCreditsBalance, subscriptionBalanceAfter: usageSchema.subscriptionCreditsBalance, }); // Record credit log await tx.insert(creditLogsSchema).values({ userId, amount: benefits.monthlyCredits, oneTimeBalanceAfter: updatedUsage[0].oneTimeBalanceAfter, subscriptionBalanceAfter: updatedUsage[0].subscriptionBalanceAfter, type: 'subscription_grant', notes: 'Yearly plan initial credits granted', relatedOrderId: orderId, }); }); } ``` -------------------------------- ### JSONB Field Operations: Merge, Overwrite, and Delete Keys (TypeScript) Source: https://nexty.dev/docs/guide/auth-database/database Illustrates how to manipulate JSONB fields within a database, covering scenarios like updating monthly and yearly quota details. The examples show how to overwrite specific keys, merge new data while removing old keys to prevent data remnants, and delete individual keys from a JSONB object. ```typescript // Overwrite monthlyAllocationDetails field const details = { monthlyAllocationDetails: { monthlyCredits: 1000 } }; await db.insert(usage) .values({ userId, subscriptionCreditsBalance: 1000, balanceJsonb: details }) .onConflictDoUpdate({ target: usage.userId, set: { subscriptionCreditsBalance: 1000, balanceJsonb: sql`coalesce(${usage.balanceJsonb}, '{}'::jsonb) - 'monthlyAllocationDetails' || ${JSON.stringify(details)}::jsonb`, }, }); // Delete a specific key (e.g., cancel yearly allocation info) await db.update(usage).set({ balanceJsonb: sql`coalesce(${usage.balanceJsonb}, '{}'::jsonb) - 'yearlyAllocationDetails'`, }).where(eq(usage.userId, userId)); ``` -------------------------------- ### GitHub Actions Workflow for Docker Image Build and Push Source: https://nexty.dev/docs/start-project/dokploy This workflow automates the process of building a Docker image for the Nexty.dev project and pushing it to a container registry. It checks out the repository, logs into the registry, extracts metadata for tagging, sets up Docker Buildx, and then builds and pushes the image. It utilizes environment variables and secrets for registry authentication and build arguments. ```yaml name: Create and publish a Docker image on: push: branches: ["main"] workflow_dispatch: env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} jobs: build-and-push-image: runs-on: ubuntu-latest timeout-minutes: 30 permissions: contents: read packages: write attestations: write id-token: write steps: - name: Checkout repository uses: actions/checkout@v4 - name: Log in to the Container registry uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build and push Docker image id: push timeout-minutes: 25 uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max # ⚠️ # It's recommended to only include environment variables needed for the build # Environment variables starting with NEXT_PUBLIC_ use vars # Other environment variables use secrets build-args: | NEXT_PUBLIC_SITE_URL=${{ vars.NEXT_PUBLIC_SITE_URL }} NEXT_PUBLIC_PRICING_PATH=${{ vars.NEXT_PUBLIC_PRICING_PATH }} NEXT_PUBLIC_OPTIMIZED_IMAGES=${{ vars.NEXT_PUBLIC_OPTIMIZED_IMAGES }} NEXT_PUBLIC_LOGIN_MODE=${{ vars.NEXT_PUBLIC_LOGIN_MODE }} NEXT_PUBLIC_GITHUB_CLIENT_ID=${{ vars.NEXT_PUBLIC_GITHUB_CLIENT_ID }} NEXT_PUBLIC_GOOGLE_CLIENT_ID=${{ vars.NEXT_PUBLIC_GOOGLE_CLIENT_ID }} NEXT_PUBLIC_TURNSTILE_SITE_KEY=${{ vars.NEXT_PUBLIC_TURNSTILE_SITE_KEY }} NEXT_PUBLIC_ENABLE_STRIPE=${{ vars.NEXT_PUBLIC_ENABLE_STRIPE }} NEXT_PUBLIC_DEFAULT_CURRENCY=${{ vars.NEXT_PUBLIC_DEFAULT_CURRENCY }} NEXT_PUBLIC_GOOGLE_ID=${{ vars.NEXT_PUBLIC_GOOGLE_ID }} NEXT_PUBLIC_PLAUSIBLE_DOMAIN=${{ vars.NEXT_PUBLIC_PLAUSIBLE_DOMAIN }} NEXT_PUBLIC_PLAUSIBLE_SRC=${{ vars.NEXT_PUBLIC_PLAUSIBLE_SRC }} NEXT_PUBLIC_DISCORD_INVITE_URL=${{ vars.NEXT_PUBLIC_DISCORD_INVITE_URL }} NEXT_PUBLIC_AUTO_FILL_AI_PROVIDER=${{ vars.NEXT_PUBLIC_AUTO_FILL_AI_PROVIDER }} NEXT_PUBLIC_AUTO_FILL_AI_MODEL_ID=${{ vars.NEXT_PUBLIC_AUTO_FILL_AI_MODEL_ID }} NEXT_PUBLIC_DAILY_AI_AUTO_FILL_LIMIT=${{ vars.NEXT_PUBLIC_DAILY_AI_AUTO_FILL_LIMIT }} NEXT_PUBLIC_DAILY_SUBMIT_LIMIT=${{ vars.NEXT_PUBLIC_DAILY_SUBMIT_LIMIT }} NEXT_PUBLIC_DAILY_IMAGE_UPLOAD_LIMIT=${{ vars.NEXT_PUBLIC_DAILY_IMAGE_UPLOAD_LIMIT }} R2_PUBLIC_URL=${{ secrets.R2_PUBLIC_URL }} R2_ACCOUNT_ID=${{ secrets.R2_ACCOUNT_ID }} R2_ACCESS_KEY_ID=${{ secrets.R2_ACCESS_KEY_ID }} R2_SECRET_ACCESS_KEY=${{ secrets.R2_SECRET_ACCESS_KEY }} R2_BUCKET_NAME=${{ secrets.R2_BUCKET_NAME }} DATABASE_URL=${{ secrets.DATABASE_URL }} BETTER_AUTH_SECRET=${{ secrets.BETTER_AUTH_SECRET }} BETTER_AUTH_GITHUB_CLIENT_SECRET=${{ secrets.BETTER_AUTH_GITHUB_CLIENT_SECRET }} GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }} STRIPE_SECRET_KEY=${{ secrets.STRIPE_SECRET_KEY }} STRIPE_PUBLISHABLE_KEY=${{ secrets.STRIPE_PUBLISHABLE_KEY }} STRIPE_WEBHOOK_SECRET=${{ secrets.STRIPE_WEBHOOK_SECRET }} STRIPE_CUSTOMER_PORTAL_URL=${{ secrets.STRIPE_CUSTOMER_PORTAL_URL }} ``` -------------------------------- ### Blog Article Multilingual Metadata (TypeScript) Source: https://nexty.dev/docs/guide/metadata-config Generates metadata for a blog article, including title, description, featured image, and multilingual support. It checks for available localized versions of the article and passes them via `availableLocales`. Handles 404 for non-existent posts. ```typescript export async function generateMetadata({ params, }: MetadataProps): Promise { const { locale, slug } = await params const { post } = await getPostBySlug(slug, locale) if (!post) { return constructMetadata({ title: "404", description: "Page not found", noIndex: true, // Don't index 404 pages locale: locale as Locale, path: `/blogs/${slug}`, }) } // Detect which languages have this article const availableLocales: string[] = [] for (const checkLocale of LOCALES) { const { post: localePost } = await getPostBySlug(slug, checkLocale) if (localePost) { availableLocales.push(checkLocale) } } return constructMetadata({ title: post.title, description: post.description, images: post.featuredImageUrl ? [post.featuredImageUrl] : [], locale: locale as Locale, path: `/blogs/${post.slug.replace(/^\/, '')}`, availableLocales: availableLocales.length > 0 ? availableLocales : undefined, }) } ``` -------------------------------- ### Create Creem Customer Portal Session Source: https://nexty.dev/docs/guide/payment/orders-subscriptions Creates a customer portal link for a Creem subscription. It first retrieves the user's Creem customer ID from the local database based on the logged-in user's ID. Then, it calls a helper function to generate the portal link and redirects the user to it. Requires database access and a function to create Creem portal links. ```typescript export async function createCreemPortalSession(): Promise { const session = await getSession(); const user = session?.user; if (!user) { redirect('/login'); } // 1. Get user's Creem subscription const subscriptionResults = await db .select({ customerId: subscriptionsSchema.customerId, }) .from(subscriptionsSchema) .where( and( eq(subscriptionsSchema.userId, user.id), eq(subscriptionsSchema.provider, 'creem') ) ) .orderBy(desc(subscriptionsSchema.createdAt)) .limit(1); const subscription = subscriptionResults[0]; if (!subscription) { throw new Error('Creem subscription not found'); } // 2. Create portal link const portalUrl = await createCreemCustomerPortalLink(subscription.customerId); if (!portalUrl) { throw new Error('Failed to create Creem portal link'); } // 3. Redirect to portal redirect(portalUrl); } ```