Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 6 additions & 2 deletions app/actions/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import { organizationsService } from "@/lib/services/organizations";
export async function getCreditBalance(): Promise<number> {
const user = await requireAuthWithOrg();

const organization = await organizationsService.getById(user.organization_id!);
const organization = await organizationsService.getById(
user.organization_id!,
);
// Convert numeric type (string) to number for UI display
return organization?.credit_balance ? Number.parseFloat(String(organization.credit_balance)) : 0;
return organization?.credit_balance
? Number.parseFloat(String(organization.credit_balance))
: 0;
}
35 changes: 28 additions & 7 deletions app/actions/characters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ export async function createCharacter(elizaCharacter: ElizaCharacter) {
const user = await requireAuthWithOrg();

// Normalize isPublic to ensure consistency between is_public column and character_data
const isPublic = typeof elizaCharacter.isPublic === "boolean" ? elizaCharacter.isPublic : false;
const isPublic =
typeof elizaCharacter.isPublic === "boolean"
? elizaCharacter.isPublic
: false;

const newCharacter: NewUserCharacter = {
organization_id: user.organization_id!,
Expand All @@ -63,7 +66,10 @@ export async function createCharacter(elizaCharacter: ElizaCharacter) {
username: elizaCharacter.username ?? null,
system: elizaCharacter.system ?? null,
bio: elizaCharacter.bio,
message_examples: (elizaCharacter.messageExamples ?? []) as Record<string, unknown>[][],
message_examples: (elizaCharacter.messageExamples ?? []) as Record<
string,
unknown
>[][],
post_examples: elizaCharacter.postExamples ?? [],
topics: elizaCharacter.topics ?? [],
adjectives: elizaCharacter.adjectives ?? [],
Expand Down Expand Up @@ -98,7 +104,9 @@ export async function createCharacter(elizaCharacter: ElizaCharacter) {
userName: user.name || user.email || null,
userId: user.id,
organizationName: user.organization.name,
bio: Array.isArray(elizaCharacter.bio) ? elizaCharacter.bio.join(" ") : elizaCharacter.bio,
bio: Array.isArray(elizaCharacter.bio)
? elizaCharacter.bio.join(" ")
: elizaCharacter.bio,
plugins: elizaCharacter.plugins,
})
.catch((error) => {
Expand All @@ -117,15 +125,21 @@ export async function createCharacter(elizaCharacter: ElizaCharacter) {
* @returns The updated character in Eliza format.
* @throws If the character is not found or access is denied.
*/
export async function updateCharacter(characterId: string, elizaCharacter: ElizaCharacter) {
export async function updateCharacter(
characterId: string,
elizaCharacter: ElizaCharacter,
) {
const user = await requireAuthWithOrg();

const updates: Partial<NewUserCharacter> = {
name: elizaCharacter.name,
username: elizaCharacter.username ?? null,
system: elizaCharacter.system ?? null,
bio: elizaCharacter.bio,
message_examples: (elizaCharacter.messageExamples ?? []) as Record<string, unknown>[][],
message_examples: (elizaCharacter.messageExamples ?? []) as Record<
string,
unknown
>[][],
post_examples: elizaCharacter.postExamples ?? [],
topics: elizaCharacter.topics ?? [],
adjectives: elizaCharacter.adjectives ?? [],
Expand All @@ -145,7 +159,11 @@ export async function updateCharacter(characterId: string, elizaCharacter: Eliza
avatar_url: elizaCharacter.avatarUrl ?? null,
};

const character = await charactersService.updateForUser(characterId, user.id, updates);
const character = await charactersService.updateForUser(
characterId,
user.id,
updates,
);

if (!character) {
throw new Error("Character not found or access denied");
Expand Down Expand Up @@ -200,7 +218,10 @@ export async function listCharacters() {
export async function getCharacter(characterId: string) {
const user = await requireAuthWithOrg();

const character = await charactersService.getByIdForUser(characterId, user.id);
const character = await charactersService.getByIdForUser(
characterId,
user.id,
);

if (!character) {
throw new Error("Character not found");
Expand Down
22 changes: 17 additions & 5 deletions app/actions/gallery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ export async function deleteMedia(generationId: string): Promise<boolean> {
}

// Delete from Vercel Blob if it's a blob URL
if (generation.storage_url && generation.storage_url.includes("blob.vercel-storage.com")) {
if (
generation.storage_url &&
generation.storage_url.includes("blob.vercel-storage.com")
) {
try {
await deleteBlob(generation.storage_url);
} catch (error) {
Expand Down Expand Up @@ -115,7 +118,9 @@ export async function deleteMedia(generationId: string): Promise<boolean> {
* @param limit - Maximum number of images to return (default 20).
* @returns Array of gallery items from random users.
*/
export async function listExploreImages(limit: number = 20): Promise<GalleryItem[]> {
export async function listExploreImages(
limit: number = 20,
): Promise<GalleryItem[]> {
const generations = await generationsService.listRandomPublicImages(limit);

return generations.map((gen) => ({
Expand Down Expand Up @@ -152,9 +157,16 @@ export async function getUserMediaStats(): Promise<{

const userGenerations = generations.filter((gen) => gen.storage_url);

const totalImages = userGenerations.filter((gen) => gen.type === "image").length;
const totalVideos = userGenerations.filter((gen) => gen.type === "video").length;
const totalSize = userGenerations.reduce((acc, gen) => acc + Number(gen.file_size || 0), 0);
const totalImages = userGenerations.filter(
(gen) => gen.type === "image",
).length;
const totalVideos = userGenerations.filter(
(gen) => gen.type === "video",
).length;
const totalSize = userGenerations.reduce(
(acc, gen) => acc + Number(gen.file_size || 0),
0,
);

return {
totalImages,
Expand Down
18 changes: 14 additions & 4 deletions app/actions/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ export async function updateProfile(formData: FormData) {

return {
success: false,
error: error instanceof Error ? error.message : "Failed to update profile. Please try again.",
error:
error instanceof Error
? error.message
: "Failed to update profile. Please try again.",
};
}
}
Expand All @@ -79,7 +82,8 @@ export async function updateEmail(formData: FormData) {
if (user.email) {
return {
success: false,
error: "Email already set. Please contact support to change your email.",
error:
"Email already set. Please contact support to change your email.",
};
}

Expand Down Expand Up @@ -124,7 +128,10 @@ export async function updateEmail(formData: FormData) {

return {
success: false,
error: error instanceof Error ? error.message : "Failed to update email. Please try again.",
error:
error instanceof Error
? error.message
: "Failed to update email. Please try again.",
};
}
}
Expand Down Expand Up @@ -199,7 +206,10 @@ export async function uploadAvatar(formData: FormData) {

return {
success: false,
error: error instanceof Error ? error.message : "Failed to upload avatar. Please try again.",
error:
error instanceof Error
? error.message
: "Failed to upload avatar. Please try again.",
};
}
}
52 changes: 34 additions & 18 deletions app/api/a2a/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,29 @@ function a2aSuccess<T>(result: T, id: string | number | null): NextResponse {
}

// Method registry
type MethodHandler = (params: Record<string, unknown>, ctx: A2AContext) => Promise<unknown>;

const METHODS: Record<string, { handler: MethodHandler; description: string }> = {
"message/send": {
handler: (params, ctx) => handleMessageSend(params as unknown as MessageSendParams, ctx),
description: "Send a message to create/continue a task (A2A standard)",
},
"tasks/get": {
handler: (params, ctx) => handleTasksGet(params as unknown as TaskGetParams, ctx),
description: "Get task status and history (A2A standard)",
},
"tasks/cancel": {
handler: (params, ctx) => handleTasksCancel(params as unknown as TaskCancelParams, ctx),
description: "Cancel a running task (A2A standard)",
},
};
type MethodHandler = (
params: Record<string, unknown>,
ctx: A2AContext,
) => Promise<unknown>;

const METHODS: Record<string, { handler: MethodHandler; description: string }> =
{
"message/send": {
handler: (params, ctx) =>
handleMessageSend(params as unknown as MessageSendParams, ctx),
description: "Send a message to create/continue a task (A2A standard)",
},
"tasks/get": {
handler: (params, ctx) =>
handleTasksGet(params as unknown as TaskGetParams, ctx),
description: "Get task status and history (A2A standard)",
},
"tasks/cancel": {
handler: (params, ctx) =>
handleTasksCancel(params as unknown as TaskCancelParams, ctx),
description: "Cancel a running task (A2A standard)",
},
};

// Request schema
const JsonRpcRequestSchema = z.object({
Expand All @@ -81,7 +88,11 @@ export async function POST(request: NextRequest) {
try {
body = JSON.parse(bodyText);
} catch {
return a2aError(A2AErrorCodes.PARSE_ERROR, "Parse error: Invalid JSON", null);
return a2aError(
A2AErrorCodes.PARSE_ERROR,
"Parse error: Invalid JSON",
null,
);
}

const parsed = JsonRpcRequestSchema.safeParse(body);
Expand Down Expand Up @@ -121,7 +132,12 @@ export async function POST(request: NextRequest) {
// Find handler
const methodDef = METHODS[method];
if (!methodDef) {
return a2aError(A2AErrorCodes.METHOD_NOT_FOUND, `Method not found: ${method}`, id, 404);
return a2aError(
A2AErrorCodes.METHOD_NOT_FOUND,
`Method not found: ${method}`,
id,
404,
);
}

// Execute
Expand Down
27 changes: 21 additions & 6 deletions app/api/admin/redemptions/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { dbRead } from "@/db/client";
import { apps } from "@/db/schemas/apps";
import { type TokenRedemption, tokenRedemptions } from "@/db/schemas/token-redemptions";
import {
type TokenRedemption,
tokenRedemptions,
} from "@/db/schemas/token-redemptions";
import { users } from "@/db/schemas/users";
import { requireAdmin } from "@/lib/auth";
import { RateLimitPresets, withRateLimit } from "@/lib/middleware/rate-limit";
Expand All @@ -33,7 +36,9 @@ const AdminActionSchema = z.object({
* GET /api/admin/redemptions
* List redemptions pending admin review.
*/
async function listPendingRedemptionsHandler(request: NextRequest): Promise<Response> {
async function listPendingRedemptionsHandler(
request: NextRequest,
): Promise<Response> {
const { user: adminUser } = await requireAdmin(request);

const statusFilter = request.nextUrl.searchParams.get("status") || "pending";
Expand Down Expand Up @@ -187,7 +192,10 @@ async function adminActionHandler(request: NextRequest): Promise<Response> {
);

if (!result.success) {
return NextResponse.json({ success: false, error: result.error }, { status: 400 });
return NextResponse.json(
{ success: false, error: result.error },
{ status: 400 },
);
}

logger.info("[Admin Redemptions] Approved redemption", {
Expand All @@ -208,7 +216,10 @@ async function adminActionHandler(request: NextRequest): Promise<Response> {
);

if (!result.success) {
return NextResponse.json({ success: false, error: result.error }, { status: 400 });
return NextResponse.json(
{ success: false, error: result.error },
{ status: 400 },
);
}

logger.info("[Admin Redemptions] Rejected redemption", {
Expand All @@ -224,7 +235,10 @@ async function adminActionHandler(request: NextRequest): Promise<Response> {
}
}

export const GET = withRateLimit(listPendingRedemptionsHandler, RateLimitPresets.STANDARD);
export const GET = withRateLimit(
listPendingRedemptionsHandler,
RateLimitPresets.STANDARD,
);
export const POST = withRateLimit(adminActionHandler, RateLimitPresets.STRICT);

/**
Expand All @@ -236,7 +250,8 @@ export async function OPTIONS() {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-API-Key, X-App-Id",
"Access-Control-Allow-Headers":
"Content-Type, Authorization, X-API-Key, X-App-Id",
},
});
}
Loading
Loading