Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/nayalsaurav/Akari-Art/llms.txt

Use this file to discover all available pages before exploring further.

Akari Art relies on three external services to deliver its core functionality: Cloudflare Workers AI for text-to-image generation, Cloudinary for durable image storage and delivery, and NextAuth.js with Google OAuth for authentication. Each service is isolated into its own module under lib/ or wired directly into a Route Handler, keeping responsibilities clean and environment credentials centralised.

Cloudflare Workers AI

Akari Art uses the Cloudflare Workers AI REST API to run the @cf/black-forest-labs/flux-1-schnell model. The integration lives entirely in app/api/image-generation/route.ts and is called via axios.

Endpoint

POST https://api.cloudflare.com/client/v4/accounts/{CLOUDFLARE_ID}/ai/run/@cf/black-forest-labs/flux-1-schnell

Request

PropertyValue
MethodPOST
Content-Typeapplication/json
AuthorizationBearer {CLOUDFLARE_API_KEY}
Body{ "prompt": "<user text>" }

Response

{
  "success": true,
  "result": {
    "image": "<base64-encoded PNG string>"
  }
}
The result.image field is a raw base64-encoded PNG. It is not a URL — it must be uploaded to a hosting service (Cloudinary, in Akari Art’s case) before it can be served to a browser.

Route Handler Implementation

import cloudinary from "@/lib/cloudinary";
import axios from "axios";
import { NextRequest, NextResponse } from "next/server";

export async function POST(request: NextRequest) {
  try {
    const { prompt }: { prompt: string } = await request.json();
    const CLOUDFLARE_ID = process.env.CLOUDFLARE_ID;
    const CLOUDFLARE_API_KEY = process.env.CLOUDFLARE_API_KEY;
    const response = await axios.post(
      `https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ID}/ai/run/@cf/black-forest-labs/flux-1-schnell`,
      { prompt },
      {
        headers: {
          Authorization: `Bearer ${CLOUDFLARE_API_KEY}`,
          "Content-Type": "application/json",
        },
      }
    );

    if (!response.data.success) {
      return NextResponse.json(
        { error: "Cloudflare API returned an error" },
        { status: 500 }
      );
    }

    const base64Image = response.data.result.image;
    const photoUrl = await cloudinary.uploader.upload(
      `data:image/png;base64,${base64Image}`
    );
    return NextResponse.json(
      {
        photo: photoUrl.secure_url,
      },
      {
        status: 200,
      }
    );
  } catch (error) {
    console.error("Error generating image:", error);

    return NextResponse.json(
      { error: "Failed to generate image" },
      { status: 500 }
    );
  }
}

Required Environment Variables

VariableDescription
CLOUDFLARE_IDYour Cloudflare account ID
CLOUDFLARE_API_KEYAPI token with Workers AI read permission

Cloudinary

Cloudinary is used as the sole image storage and delivery layer. After Cloudflare Workers AI returns a base64 PNG, it is immediately uploaded to Cloudinary, and only the resulting secure_url is stored in MongoDB or returned to the client.

Configuration — lib/cloudinary.ts

import { v2 as cloudinary } from "cloudinary";

cloudinary.config({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});

export default cloudinary;
The exported cloudinary instance is the configured Cloudinary v2 SDK client. Import it wherever an upload is needed:
import cloudinary from "@/lib/cloudinary";

Upload Pattern

The base64 image is wrapped in a data URI before upload:
const photoUrl = await cloudinary.uploader.upload(
  `data:image/png;base64,${base64Image}`
);
// photoUrl.secure_url → "https://res.cloudinary.com/<cloud>/image/upload/..."
cloudinary.uploader.upload() accepts a data URI string directly. The returned object contains a secure_url property — an https://res.cloudinary.com/... URL — which is what gets stored in the photo field of a Post document and returned to the browser.

Next.js Image Domain Allow-list

next.config.ts adds res.cloudinary.com as an allowed image domain so that the Next.js <Image> component can optimise and serve Cloudinary-hosted images:
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  allowedDevOrigins: ["http://localhost:3000", "http://192.168.1.6:3000"],
  images: {
    domains: ["res.cloudinary.com"],
  },
};

export default nextConfig;

Required Environment Variables

VariableDescription
CLOUDINARY_CLOUD_NAMEYour Cloudinary cloud name
CLOUDINARY_API_KEYCloudinary API key
CLOUDINARY_API_SECRETCloudinary API secret

NextAuth.js — Google OAuth

Authentication is configured in lib/auth.ts using NextAuth.js v4 with a single Google OAuth 2.0 provider. Sessions are stored as signed JWTs in a cookie — no Session collection is ever written to MongoDB.

Auth Configuration — lib/auth.ts

import GoogleProvider from "next-auth/providers/google";
import { NextAuthOptions } from "next-auth";
import { dbConnect } from "./database";
import User from "@/model/user";

export const authOptions: NextAuthOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
  ],

  callbacks: {
    async signIn({ user, account }) {
      await dbConnect();
      const existingUser = await User.findOne({ email: user.email });

      if (!existingUser) {
        await User.create({
          name: user.name,
          email: user.email,
          image: user.image,
          provider: account?.provider,
        });
      }

      return true;
    },

    async jwt({ token, user }) {
      await dbConnect();
      if (user) {
        const existingUser = await User.findOne({ email: user.email });
        if (existingUser) {
          token.id = existingUser._id.toString();
          token.name = existingUser.name;
        }
      }
      return token;
    },

    async session({ session, token }) {
      if (token && session.user) {
        session.user.id = token.id as string;
        session.user.name = token.name as string;
      }

      return session;
    },

    async redirect({ baseUrl }) {
      return baseUrl;
    },
  },
  pages: {
    signIn: "/signin",
    error: "/signin",
  },
  session: {
    strategy: "jwt",
  },
  secret: process.env.NEXTAUTH_SECRET,
};

Callback Responsibilities

CallbackWhat It Does
signInCalls dbConnect(), checks for an existing user by email, and creates one if absent — effectively an upsert on first login
jwtLooks up the MongoDB _id for the authenticated user and attaches it to the JWT token as token.id
sessionCopies token.id and token.name onto session.user, making the MongoDB _id accessible as session.user.id in client components and API routes
redirectReturns baseUrl to prevent open-redirect vulnerabilities and ensure post-login navigation always lands on the app root
The session.user.id field is made type-safe via next-auth.d.ts:
import NextAuth, { DefaultSession } from "next-auth";

declare module "next-auth" {
  interface Session {
    user: {
      id: string;
    } & DefaultSession["user"];
  }
}

Custom Pages

PagePathUsed For
Sign-in/signinRenders the Google sign-in button
Error/signinAuth errors (e.g. OAuth callback failure) are sent to the same page

Route Protection — middleware.ts

withAuth from next-auth/middleware wraps the middleware function. The authorized callback allows unauthenticated access only to /, /signin, and /api/auth/*. Every other route requires a valid JWT token:
import { withAuth } from "next-auth/middleware";
import { NextResponse } from "next/server";

export default withAuth(
  function middleware() {
    return NextResponse.next();
  },
  {
    callbacks: {
      authorized({ req, token }) {
        const { pathname } = req.nextUrl;
        console.log("The token is ", token);
        // Allow unauthenticated access to these paths
        if (
          pathname.startsWith("/api/auth/*") ||
          pathname === "/signin" ||
          pathname === "/"
        ) {
          return true;
        }

        const isAuthorized = !!token;
        if (!isAuthorized) console.warn(`Unauthorized access to ${pathname}`);
        console.log("is Authorized : ", isAuthorized);
        return isAuthorized;
      },
    },
  }
);

export const config = {
  matcher: [
    // Protect all routes except static files and public assets
    "/((?!_next/static|_next/image|favicon.ico).*)",
  ],
};

Required Environment Variables

VariableDescription
GOOGLE_CLIENT_IDOAuth 2.0 client ID from Google Cloud Console
GOOGLE_CLIENT_SECRETOAuth 2.0 client secret from Google Cloud Console
NEXTAUTH_SECRETRandom secret used to sign and encrypt JWT cookies
NEXTAUTH_URLCanonical URL of the deployment (e.g. https://akari-art.vercel.app)
Generate a strong NEXTAUTH_SECRET with openssl rand -base64 32 and store it in .env.local for local development. Never commit this value to source control.

Build docs developers (and LLMs) love