Compare commits

...

6 commits

6 changed files with 449 additions and 0 deletions

View file

@ -0,0 +1,44 @@
//Handles DELETE requests for specific items
import { NextRequest, NextResponse } from 'next/server';
import { deleteItemByUrl } from '@/lib/database';
// DELETE /api/urls/[encodedUrl]/items/[itemId] - Delete a specific item
export async function DELETE(
request: NextRequest,
{ params }: { params: { encodedUrl: string; itemId: string } }
) {
const url = decodeURIComponent(params.encodedUrl);
const itemId = params.itemId;
console.log('[API] Deleting item', itemId, 'from URL', url);
try {
if (!itemId) {
return NextResponse.json(
{ error: 'Item ID is required' },
{ status: 400 }
);
}
await deleteItemByUrl(url, itemId);
console.log('[API] Successfully deleted item:', itemId);
return NextResponse.json(
{ message: 'Item deleted', itemId: itemId },
{ status: 200 }
);
} catch (error) {
console.log('[API] Delete error:', error);
return NextResponse.json(
{
error: 'Failed to delete item',
details: error instanceof Error ? error.message : 'Unknown error',
itemId: itemId,
url: url
},
{ status: 500 }
);
}
}

View file

@ -0,0 +1,95 @@
//Handles GET (fetch items) and POST (create/update item) for a specific URL
import { NextRequest, NextResponse } from 'next/server';
import { getItemsByUrl, insertItemByUrl, insertUrl } from '@/lib/database';
import { Item } from '@/types/database';
// GET /api/urls/[encodedUrl]/items - Fetch items for a URL
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ encodedUrl: string }> }
) {
// Await params before accessing properties
const resolvedParams = await params;
const url = decodeURIComponent(resolvedParams.encodedUrl);
console.log('[SERVER] Received request for URL:', url);
try {
const items = await getItemsByUrl(url);
console.log('[SERVER] Returning', items.length, 'items for URL:', url);
return NextResponse.json(items, {
status: 200,
headers: {
'Content-Type': 'application/json',
},
});
} catch (error) {
console.error('[SERVER ERROR] Failed to fetch items for', url, ':', error);
return NextResponse.json(
{
error: 'Failed to fetch items',
details: error instanceof Error ? error.message : 'Unknown error',
url: url
},
{ status: 500 }
);
}
}
// POST /api/urls/[encodedUrl]/items - Create/update an item
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ encodedUrl: string }> }
) {
// Await params before accessing properties
const resolvedParams = await params;
const url = decodeURIComponent(resolvedParams.encodedUrl);
try {
const item: Item = await request.json();
const itemId = item.id;
// Request logging
console.log('[API] Received item request - URL:', url, 'Item ID:', itemId);
// Raw JSON logging
const rawJson = JSON.stringify(item);
console.log('[API] Raw request JSON:', rawJson);
// Validate item data
if (!item.id || typeof item.name !== 'string' || typeof item.description !== 'string') {
console.log('[API] Validation error: Invalid item data');
return NextResponse.json(
{ error: 'Invalid item data', item: item },
{ status: 400 }
);
}
// Ensure URL exists in database
await insertUrl(url);
// Insert/update the item
await insertItemByUrl(url, item);
console.log('[API] Successfully saved item ID:', itemId);
return NextResponse.json(item, {
status: 200,
headers: {
'Content-Type': 'application/json',
},
});
} catch (error) {
console.log('[API] Database error:', error);
return NextResponse.json(
{
error: 'Database error',
details: error instanceof Error ? error.message : 'Unknown error',
url: url
},
{ status: 400 }
);
}
}

View file

@ -0,0 +1,44 @@
//Handles DELETE requests for specific properties
import { NextRequest, NextResponse } from 'next/server';
import { deletePropertyByUrl } from '@/lib/database';
// DELETE /api/urls/[encodedUrl]/properties/[property] - Delete a property from a URL
export async function DELETE(
request: NextRequest,
{ params }: { params: { encodedUrl: string; property: string } }
) {
const url = decodeURIComponent(params.encodedUrl);
const property = decodeURIComponent(params.property);
console.log('[API] Deleting property', property, 'from URL', url);
try {
if (!property) {
return NextResponse.json(
{ error: 'Property name is required' },
{ status: 400 }
);
}
await deletePropertyByUrl(url, property);
console.log('[API] Successfully deleted property:', property);
return NextResponse.json(
{ message: 'Property deleted', property: property },
{ status: 200 }
);
} catch (error) {
console.log('[API] Delete error:', error);
return NextResponse.json(
{
error: 'Failed to delete property',
details: error instanceof Error ? error.message : 'Unknown error',
property: property,
url: url
},
{ status: 500 }
);
}
}

View file

@ -0,0 +1,86 @@
//property management API Route Handles GET (fetch properties) and POST (add property) for a URL
import { NextRequest, NextResponse } from 'next/server';
import { getSelectedProperties, addSelectedProperty, insertUrl } from '@/lib/database';
// GET /api/urls/[encodedUrl]/properties - Get selected properties for a URL
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ encodedUrl: string }> }
) {
// Await params before accessing properties
const resolvedParams = await params;
const url = decodeURIComponent(resolvedParams.encodedUrl);
console.log('[API] Getting selected properties for URL:', url);
try {
const properties = await getSelectedProperties(url);
console.log('[API] Returning', properties.length, 'properties for URL:', url);
return NextResponse.json(properties, {
status: 200,
headers: {
'Content-Type': 'application/json',
},
});
} catch (error) {
console.error('[API] Failed to fetch properties for URL:', url, error);
return NextResponse.json(
{
error: 'Failed to fetch properties',
details: error instanceof Error ? error.message : 'Unknown error',
url: url
},
{ status: 500 }
);
}
}
// POST /api/urls/[encodedUrl]/properties - Add a selected property to a URL
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ encodedUrl: string }> }
) {
// Await params before accessing properties
const resolvedParams = await params;
const url = decodeURIComponent(resolvedParams.encodedUrl);
try {
const property: string = await request.json();
console.log('[API] Adding property', property, 'to URL', url);
// Validate property data
if (!property || typeof property !== 'string') {
console.log('[API] Validation error: Invalid property data');
return NextResponse.json(
{ error: 'Invalid property data', property: property },
{ status: 400 }
);
}
// Ensure URL exists in database
await insertUrl(url);
// Add the selected property
await addSelectedProperty(url, property);
console.log('[API] Successfully added property:', property);
return NextResponse.json(
{ message: 'Property added', property: property },
{ status: 200 }
);
} catch (error) {
console.log('[API] Error adding property:', error);
return NextResponse.json(
{
error: 'Failed to add property',
details: error instanceof Error ? error.message : 'Unknown error',
url: url
},
{ status: 500 }
);
}
}

115
src/lib/api-client.ts Normal file
View file

@ -0,0 +1,115 @@
// Client-side API functions
import { Item } from '@/types/database';
import { API_ENDPOINTS, ErrorResponse } from '@/types/api';
// API Client class
export class ApiClient {
private baseUrl: string;
constructor(baseUrl: string = '') {
this.baseUrl = baseUrl;
}
// Helper method for making requests with error handling
private async makeRequest<T>(
url: string,
options: RequestInit = {}
): Promise<T> {
const fullUrl = `${this.baseUrl}${url}`;
console.log('[API CLIENT] Making request to:', fullUrl);
try {
const response = await fetch(fullUrl, {
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
});
if (!response.ok) {
const errorData: ErrorResponse = await response.json().catch(() => ({
error: `HTTP ${response.status}: ${response.statusText}`,
}));
console.error('[API CLIENT] Request failed:', errorData);
throw new Error(errorData.error || `Request failed with status ${response.status}`);
}
const data = await response.json();
console.log('[API CLIENT] Request successful');
return data;
} catch (error) {
console.error('[API CLIENT] Network error:', error);
throw error;
}
}
// Get items by URL
async getItemsByUrl(url: string): Promise<Item[]> {
console.log('[API CLIENT] Fetching items for URL:', url);
const encodedUrl = encodeURIComponent(url);
return this.makeRequest<Item[]>(API_ENDPOINTS.ITEMS(encodedUrl));
}
// Create/update item
async saveItem(url: string, item: Item): Promise<Item> {
console.log('[API CLIENT] Saving item - ID:', item.id, 'Name:', item.name);
const encodedUrl = encodeURIComponent(url);
return this.makeRequest<Item>(API_ENDPOINTS.ITEMS(encodedUrl), {
method: 'POST',
body: JSON.stringify(item),
});
}
// Delete item
async deleteItem(url: string, itemId: string): Promise<void> {
console.log('[API CLIENT] Deleting item:', itemId, 'from URL:', url);
const encodedUrl = encodeURIComponent(url);
await this.makeRequest(API_ENDPOINTS.ITEM(encodedUrl, itemId), {
method: 'DELETE',
});
}
// Get selected properties
async getSelectedProperties(url: string): Promise<string[]> {
console.log('[API CLIENT] Fetching properties for URL:', url);
const encodedUrl = encodeURIComponent(url);
return this.makeRequest<string[]>(API_ENDPOINTS.PROPERTIES(encodedUrl));
}
// Add selected property
async addProperty(url: string, property: string): Promise<void> {
console.log('[API CLIENT] Adding property:', property, 'to URL:', url);
const encodedUrl = encodeURIComponent(url);
await this.makeRequest(API_ENDPOINTS.PROPERTIES(encodedUrl), {
method: 'POST',
body: JSON.stringify(property),
});
}
// Delete property
async deleteProperty(url: string, property: string): Promise<void> {
console.log('[API CLIENT] Deleting property:', property, 'from URL:', url);
const encodedUrl = encodeURIComponent(url);
const encodedProperty = encodeURIComponent(property);
await this.makeRequest(API_ENDPOINTS.PROPERTY(encodedUrl, encodedProperty), {
method: 'DELETE',
});
}
}
// Default API client instance
export const apiClient = new ApiClient();
// Individual functions (for backward compatibility)
export const loadItemsFromDb = (url: string) => apiClient.getItemsByUrl(url);
export const saveItemToDb = (url: string, item: Item) => apiClient.saveItem(url, item);
export const deleteItemFromDb = (url: string, itemId: string) => apiClient.deleteItem(url, itemId);
export const getSelectedPropertiesFromDb = (url: string) => apiClient.getSelectedProperties(url);
export const addPropertyToDb = (url: string, property: string) => apiClient.addProperty(url, property);
export const deletePropertyFromDb = (url: string, property: string) => apiClient.deleteProperty(url, property);

65
src/types/api.ts Normal file
View file

@ -0,0 +1,65 @@
//TypeScript types for API requests/responses
import { Item } from './database';
// API Request Types (matching your Rust structs)
export interface ItemRequest {
url: string;
item: Item;
}
// API Response Types
export interface ApiResponse<T> {
data?: T;
error?: string;
details?: string;
message?: string;
}
export interface ItemsResponse {
items: Item[];
selectedProperties: string[];
}
export interface ErrorResponse {
error: string;
details?: string;
url?: string;
itemId?: string;
property?: string;
}
// API Success Responses
export interface ItemCreatedResponse {
message: string;
item: Item;
}
export interface ItemDeletedResponse {
message: string;
itemId: string;
}
export interface PropertyAddedResponse {
message: string;
property: string;
}
export interface PropertyDeletedResponse {
message: string;
property: string;
}
// HTTP Status Codes (for consistency)
export const HTTP_STATUS = {
OK: 200,
BAD_REQUEST: 400,
INTERNAL_SERVER_ERROR: 500,
} as const;
// API Endpoints (for consistency)
export const API_ENDPOINTS = {
ITEMS: (encodedUrl: string) => `/api/urls/${encodedUrl}/items`,
ITEM: (encodedUrl: string, itemId: string) => `/api/urls/${encodedUrl}/items/${itemId}`,
PROPERTIES: (encodedUrl: string) => `/api/urls/${encodedUrl}/properties`,
PROPERTY: (encodedUrl: string, property: string) => `/api/urls/${encodedUrl}/properties/${property}`,
} as const;