Compare commits
6 commits
50d735ae5d
...
7b9977db6b
Author | SHA1 | Date | |
---|---|---|---|
7b9977db6b | |||
ff0f252389 | |||
c03d01d9c8 | |||
b90590f9a5 | |||
4366678935 | |||
ecc4e4da1c |
6 changed files with 449 additions and 0 deletions
44
src/app/api/urls/[encodedUrl]/items/[itemId]/route.ts
Normal file
44
src/app/api/urls/[encodedUrl]/items/[itemId]/route.ts
Normal 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 }
|
||||
);
|
||||
}
|
||||
}
|
95
src/app/api/urls/[encodedUrl]/items/route.ts
Normal file
95
src/app/api/urls/[encodedUrl]/items/route.ts
Normal 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 }
|
||||
);
|
||||
}
|
||||
}
|
44
src/app/api/urls/[encodedUrl]/properties/[property]/route.ts
Normal file
44
src/app/api/urls/[encodedUrl]/properties/[property]/route.ts
Normal 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 }
|
||||
);
|
||||
}
|
||||
}
|
86
src/app/api/urls/[encodedUrl]/properties/route.ts
Normal file
86
src/app/api/urls/[encodedUrl]/properties/route.ts
Normal 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
115
src/lib/api-client.ts
Normal 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
65
src/types/api.ts
Normal 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;
|
Loading…
Add table
Reference in a new issue