diff --git a/src/shared/api/client.ts b/src/shared/api/client.ts new file mode 100644 index 0000000..1efafbb --- /dev/null +++ b/src/shared/api/client.ts @@ -0,0 +1,72 @@ +import type { ListResponse } from './types' + +/** + * Native fetch wrapper for PocketBase API requests. + */ +const PB_URL = process.env.NEXT_PUBLIC_PB_URL || 'http://127.0.0.1:8090' + +/** + * Options for PocketBase collection fetching. + */ +export type PBFetchOptions = { + /** + * Sorting criteria (e.g., "-created,order") + */ + sort?: string + /** + * Filter query string + */ + filter?: string + /** + * Fields to expand (e.g., "stack") + */ + expand?: string + /** + * Cache revalidation time in seconds + * @default 3600 + */ + revalidate?: number +} + +/** + * Fetch a list of records from a PocketBase collection. + */ +export async function getCollection( + collection: string, + options: PBFetchOptions = {} +): Promise> { + const { sort, filter, expand, revalidate = 3600 } = options + + const params = new URLSearchParams() + if (sort) params.set('sort', sort) + if (filter) params.set('filter', filter) + if (expand) params.set('expand', expand) + + const url = `${PB_URL}/api/collections/${collection}/records?${params.toString()}` + + const res = await fetch(url, { + next: { revalidate }, + }) + + if (!res.ok) { + throw new Error(`Failed to fetch collection: ${collection}`) + } + + return res.json() +} + +/** + * Fetch a single record from a PocketBase collection by ID or filter. + */ +export async function getFirstRecord( + collection: string, + options: PBFetchOptions = {} +): Promise { + const data = await getCollection(collection, { + ...options, + // PocketBase convention for "first" or "singleton" patterns + filter: options.filter, + }) + + return data.items[0] || null +} diff --git a/src/shared/api/index.ts b/src/shared/api/index.ts new file mode 100644 index 0000000..886d07b --- /dev/null +++ b/src/shared/api/index.ts @@ -0,0 +1,2 @@ +export * from './types' +export * from './client' diff --git a/src/shared/api/types.ts b/src/shared/api/types.ts new file mode 100644 index 0000000..204fb2d --- /dev/null +++ b/src/shared/api/types.ts @@ -0,0 +1,173 @@ +/** + * Common properties for all PocketBase records. + */ +export type BaseRecord = { + /** + * Unique record ID + */ + id: string + /** + * ID of the collection this record belongs to + */ + collectionId: string + /** + * Name of the collection this record belongs to + */ + collectionName: string + /** + * Record creation timestamp (ISO 8601) + */ + created: string + /** + * Record last update timestamp (ISO 8601) + */ + updated: string +} + +/** + * PocketBase collection for site sections and routing. + */ +export type SectionRecord = BaseRecord & { + /** + * URL-friendly identifier used for routing + */ + slug: string + /** + * Display name of the section + */ + title: string + /** + * Visual numbering prefix (e.g., "01") + */ + number: string + /** + * Sorting weight for section order + */ + order: number +} + +/** + * PocketBase collection for simple text blocks (Intro, Bio). + */ +export type PageContentRecord = BaseRecord & { + /** + * Slug corresponding to the parent section + */ + slug: string + /** + * HTML or Markdown content string + */ + content: string +} + +/** + * PocketBase collection for technology skills. + */ +export type SkillRecord = BaseRecord & { + /** + * Name of the technology or tool + */ + name: string + /** + * Grouping category (e.g., 'Frontend', 'Backend') + */ + category: 'Frontend' | 'Backend' | 'Tools' | 'Design' | string + /** + * Sorting weight within the category + */ + order: number +} + +/** + * PocketBase collection for work experience history. + */ +export type ExperienceRecord = BaseRecord & { + /** + * Name of the organization + */ + company: string + /** + * Professional title held + */ + role: string + /** + * Start date of the tenure + */ + start_date: string + /** + * End date of the tenure, or null if currently employed + */ + end_date: string | null + /** + * Rich text description of responsibilities and achievements + */ + description: string + /** + * Sorting weight for chronological display + */ + order: number +} + +/** + * PocketBase collection for portfolio projects. + */ +export type ProjectRecord = BaseRecord & { + /** + * Full title of the project + */ + title: string + /** + * Completion or duration year (e.g., "2024") + */ + year: string + /** + * Role performed on the project + */ + role: string + /** + * Short summary of the project + */ + description: string + /** + * List of specific feature or achievement points + */ + details: string[] + /** + * List of SkillRecord IDs used in the project + */ + stack: string[] + /** + * Primary thumbnail or hero image filename + */ + image: string + /** + * Sorting weight for the project list + */ + order: number +} + +/** + * Generic response for a list of PocketBase records. + */ +export type ListResponse = { + /** + * Current page index + */ + page: number + /** + * Number of items per page + */ + perPage: number + /** + * Total number of items across all pages + */ + totalItems: number + /** + * Total number of pages available + */ + totalPages: number + /** + * Array of records for the current page + */ + items: T[] +} diff --git a/src/shared/index.ts b/src/shared/index.ts index 1485652..f715fb8 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -1,2 +1,3 @@ export * from './ui' export * from './lib' +export * from './api'