refactor: switch landing from DB to api requests

This commit is contained in:
Oleg Proskurin 2025-10-25 18:58:01 +07:00
parent d1806bfd7e
commit b9a8ca8368
7 changed files with 86 additions and 149 deletions

View File

@ -28,13 +28,16 @@ export default function ApiKeysPage() {
router.push('/admin/master'); router.push('/admin/master');
} else { } else {
setMasterKey(saved); setMasterKey(saved);
loadApiKeys(); // Load API keys with the saved master key
listApiKeys(saved).then(setApiKeys);
} }
}, [router]); }, [router]);
const loadApiKeys = async () => { const loadApiKeys = async () => {
const keys = await listApiKeys(); if (masterKey) {
setApiKeys(keys); const keys = await listApiKeys(masterKey);
setApiKeys(keys);
}
}; };
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {

View File

@ -1,7 +1,5 @@
'use server'; 'use server';
import { listApiKeys as listApiKeysQuery } from '../db/queries/apiKeys';
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000'; const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
interface BootstrapResponse { interface BootstrapResponse {
@ -26,6 +24,48 @@ interface CreateKeyResponse {
message: string; message: string;
} }
interface ListKeysApiResponse {
keys: Array<{
id: string;
type: string;
name: string | null;
scopes: string[];
isActive: boolean;
createdAt: string;
expiresAt: string | null;
lastUsedAt: string | null;
createdBy: string | null;
organization: {
id: string;
slug: string;
name: string;
email: string;
} | null;
project: {
id: string;
slug: string;
name: string;
} | null;
}>;
total: number;
}
interface ApiKeyListItem {
id: string;
keyType: string;
name: string | null;
scopes: string[];
isActive: boolean;
createdAt: Date;
expiresAt: Date | null;
lastUsedAt: Date | null;
organizationId: string | null;
organizationName: string | null;
organizationEmail: string | null;
projectId: string | null;
projectName: string | null;
}
export async function bootstrapMasterKey(): Promise<{ export async function bootstrapMasterKey(): Promise<{
success: boolean; success: boolean;
apiKey?: string; apiKey?: string;
@ -86,9 +126,45 @@ export async function createProjectApiKey(
} }
} }
export async function listApiKeys() { export async function listApiKeys(masterKey: string): Promise<ApiKeyListItem[]> {
try { try {
return await listApiKeysQuery(); if (!masterKey) {
console.error('Master key not provided');
return [];
}
const response = await fetch(`${API_BASE_URL}/api/admin/keys`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-API-Key': masterKey,
},
cache: 'no-store',
});
if (!response.ok) {
console.error('Failed to fetch API keys:', response.statusText);
return [];
}
const data: ListKeysApiResponse = await response.json();
// Transform nested API response to flat structure for backwards compatibility
return data.keys.map((key) => ({
id: key.id,
keyType: key.type,
name: key.name,
scopes: key.scopes,
isActive: key.isActive,
createdAt: new Date(key.createdAt),
expiresAt: key.expiresAt ? new Date(key.expiresAt) : null,
lastUsedAt: key.lastUsedAt ? new Date(key.lastUsedAt) : null,
organizationId: key.organization?.id || null,
organizationName: key.organization?.name || null,
organizationEmail: key.organization?.email || null,
projectId: key.project?.id || null,
projectName: key.project?.name || null,
}));
} catch (error) { } catch (error) {
console.error('List keys error:', error); console.error('List keys error:', error);
return []; return [];

View File

@ -1,41 +0,0 @@
'use server';
import { getOrganizationByEmail, createOrganization } from '../db/queries/organizations';
import { getProjectByName, createProject } from '../db/queries/projects';
import type { Organization, Project } from '@banatie/database';
export async function getOrCreateOrganization(email: string, name: string): Promise<Organization> {
// Try to find existing organization
const existing = await getOrganizationByEmail(email);
if (existing) {
return existing;
}
// Create new organization
return createOrganization({ email, name });
}
export async function getOrCreateProject(organizationId: string, name: string): Promise<Project> {
// Try to find existing project
const existing = await getProjectByName(organizationId, name);
if (existing) {
return existing;
}
// Create new project
return createProject({ organizationId, name });
}
export async function getOrCreateOrgAndProject(
email: string,
orgName: string,
projectName: string,
): Promise<{ organization: Organization; project: Project }> {
// Get or create organization
const organization = await getOrCreateOrganization(email, orgName);
// Get or create project
const project = await getOrCreateProject(organization.id, projectName);
return { organization, project };
}

View File

@ -1,13 +0,0 @@
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from '@banatie/database';
const connectionString =
process.env.DATABASE_URL ||
'postgresql://banatie_user:banatie_secure_password@localhost:5460/banatie_db';
// Create postgres client
const client = postgres(connectionString);
// Create drizzle instance with schema
export const db = drizzle(client, { schema });

View File

@ -1,35 +0,0 @@
import { db } from '../client';
import { apiKeys, organizations, projects, type ApiKey } from '@banatie/database';
import { eq, desc } from 'drizzle-orm';
export async function listApiKeys() {
return db
.select({
id: apiKeys.id,
keyType: apiKeys.keyType,
name: apiKeys.name,
scopes: apiKeys.scopes,
isActive: apiKeys.isActive,
createdAt: apiKeys.createdAt,
expiresAt: apiKeys.expiresAt,
lastUsedAt: apiKeys.lastUsedAt,
organizationId: apiKeys.organizationId,
organizationName: organizations.name,
organizationEmail: organizations.email,
projectId: apiKeys.projectId,
projectName: projects.name,
})
.from(apiKeys)
.leftJoin(organizations, eq(apiKeys.organizationId, organizations.id))
.leftJoin(projects, eq(apiKeys.projectId, projects.id))
.orderBy(desc(apiKeys.createdAt))
.limit(50);
}
export async function getApiKeysByProject(projectId: string) {
return db
.select()
.from(apiKeys)
.where(eq(apiKeys.projectId, projectId))
.orderBy(desc(apiKeys.createdAt));
}

View File

@ -1,23 +0,0 @@
import { db } from '../client';
import { organizations, type Organization, type NewOrganization } from '@banatie/database';
import { eq } from 'drizzle-orm';
export async function getOrganizationByEmail(email: string): Promise<Organization | null> {
const [org] = await db
.select()
.from(organizations)
.where(eq(organizations.email, email))
.limit(1);
return org || null;
}
export async function createOrganization(data: NewOrganization): Promise<Organization> {
const [org] = await db.insert(organizations).values(data).returning();
return org!;
}
export async function listOrganizations(): Promise<Organization[]> {
return db.select().from(organizations).orderBy(organizations.createdAt);
}

View File

@ -1,30 +0,0 @@
import { db } from '../client';
import { projects, type Project, type NewProject } from '@banatie/database';
import { eq, and } from 'drizzle-orm';
export async function getProjectByName(
organizationId: string,
name: string,
): Promise<Project | null> {
const [project] = await db
.select()
.from(projects)
.where(and(eq(projects.organizationId, organizationId), eq(projects.name, name)))
.limit(1);
return project || null;
}
export async function createProject(data: NewProject): Promise<Project> {
const [project] = await db.insert(projects).values(data).returning();
return project!;
}
export async function listProjectsByOrganization(organizationId: string): Promise<Project[]> {
return db
.select()
.from(projects)
.where(eq(projects.organizationId, organizationId))
.orderBy(projects.createdAt);
}