diff --git a/AGENTS.md b/AGENTS.md index a3f964b..bd389f5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,6 +25,8 @@ Regenerate: - `cd frontend && pnpm run generate:api` Notes: +- `format: date-time` diverges by generator: **oapi-codegen (Go)** emits `time.Time`, while **orval (TS)** keeps `string`. Adding it to an existing string field will break Go handlers until they supply a `time.Time` (see `parseTime` / `parseTimeAny` in `api/v1/utils.go`). +- Marking a schema field `required` flips the generated Go type from a pointer to a value; update every handler build site to drop the `&`. Only require a field when **all** endpoints sharing that schema populate it — `Progress` is populated with different subsets by its list vs single handlers. - If you add response headers in `api/v1/openapi.yaml` (for example `Set-Cookie`), `oapi-codegen` will generate typed response header structs in `api/v1/api.gen.go`; update the handler response values to populate those headers explicitly. Examples of generated files: diff --git a/api/v1/activity.go b/api/v1/activity.go index 3466a04..279533a 100644 --- a/api/v1/activity.go +++ b/api/v1/activity.go @@ -68,18 +68,10 @@ func (s *Server) GetActivity(ctx context.Context, request GetActivityRequestObje apiActivities := make([]Activity, len(activities)) for i, a := range activities { - // Convert StartTime from interface{} to string - startTimeStr := "" - if a.StartTime != nil { - if str, ok := a.StartTime.(string); ok { - startTimeStr = str - } - } - apiActivities[i] = Activity{ DocumentId: a.DocumentID, DeviceId: a.DeviceID, - StartTime: startTimeStr, + StartTime: parseTimeAny(a.StartTime), Title: a.Title, Author: a.Author, Duration: a.Duration, diff --git a/api/v1/api.gen.go b/api/v1/api.gen.go index cba8e34..10dc312 100644 --- a/api/v1/api.gen.go +++ b/api/v1/api.gen.go @@ -145,15 +145,15 @@ func (e GetSearchParamsSource) Valid() bool { // Activity defines model for Activity. type Activity struct { - Author *string `json:"author,omitempty"` - DeviceId string `json:"device_id"` - DocumentId string `json:"document_id"` - Duration int64 `json:"duration"` - EndPercentage float32 `json:"end_percentage"` - ReadPercentage float32 `json:"read_percentage"` - StartPercentage float32 `json:"start_percentage"` - StartTime string `json:"start_time"` - Title *string `json:"title,omitempty"` + Author *string `json:"author,omitempty"` + DeviceId string `json:"device_id"` + DocumentId string `json:"document_id"` + Duration int64 `json:"duration"` + EndPercentage float32 `json:"end_percentage"` + ReadPercentage float32 `json:"read_percentage"` + StartPercentage float32 `json:"start_percentage"` + StartTime time.Time `json:"start_time"` + Title *string `json:"title,omitempty"` } // ActivityResponse defines model for ActivityResponse. @@ -200,10 +200,10 @@ type DatabaseInfo struct { // Device defines model for Device. type Device struct { - CreatedAt *time.Time `json:"created_at,omitempty"` - DeviceName *string `json:"device_name,omitempty"` - Id *string `json:"id,omitempty"` - LastSynced *time.Time `json:"last_synced,omitempty"` + CreatedAt time.Time `json:"created_at"` + DeviceName string `json:"device_name"` + Id string `json:"id"` + LastSynced time.Time `json:"last_synced"` } // DirectoryItem defines model for DirectoryItem. @@ -356,15 +356,15 @@ type OperationType string // Progress defines model for Progress. type Progress struct { - Author *string `json:"author,omitempty"` - CreatedAt *time.Time `json:"created_at,omitempty"` - DeviceId *string `json:"device_id,omitempty"` - DeviceName *string `json:"device_name,omitempty"` - DocumentId *string `json:"document_id,omitempty"` - Percentage *float64 `json:"percentage,omitempty"` - Progress *string `json:"progress,omitempty"` - Title *string `json:"title,omitempty"` - UserId *string `json:"user_id,omitempty"` + Author *string `json:"author,omitempty"` + CreatedAt time.Time `json:"created_at"` + DeviceId *string `json:"device_id,omitempty"` + DeviceName string `json:"device_name"` + DocumentId string `json:"document_id"` + Percentage float64 `json:"percentage"` + Progress *string `json:"progress,omitempty"` + Title *string `json:"title,omitempty"` + UserId string `json:"user_id"` } // ProgressListResponse defines model for ProgressListResponse. @@ -384,14 +384,13 @@ type ProgressResponse struct { // SearchItem defines model for SearchItem. type SearchItem struct { - Author *string `json:"author,omitempty"` - FileSize *string `json:"file_size,omitempty"` - FileType *string `json:"file_type,omitempty"` - Id *string `json:"id,omitempty"` - Language *string `json:"language,omitempty"` - Series *string `json:"series,omitempty"` - Title *string `json:"title,omitempty"` - UploadDate *string `json:"upload_date,omitempty"` + Author *string `json:"author,omitempty"` + FileSize *string `json:"file_size,omitempty"` + FileType *string `json:"file_type,omitempty"` + Id *string `json:"id,omitempty"` + Language *string `json:"language,omitempty"` + Series *string `json:"series,omitempty"` + Title *string `json:"title,omitempty"` } // SearchResponse defines model for SearchResponse. diff --git a/api/v1/openapi.yaml b/api/v1/openapi.yaml index d864d0f..7195dbe 100644 --- a/api/v1/openapi.yaml +++ b/api/v1/openapi.yaml @@ -106,6 +106,12 @@ components: created_at: type: string format: date-time + required: + - device_name + - percentage + - document_id + - user_id + - created_at UpdateProgressRequest: type: object @@ -198,6 +204,7 @@ components: type: string start_time: type: string + format: date-time title: type: string author: @@ -240,8 +247,6 @@ components: type: string file_size: type: string - upload_date: - type: string SearchResponse: type: object @@ -384,6 +389,11 @@ components: last_synced: type: string format: date-time + required: + - id + - device_name + - created_at + - last_synced SettingsResponse: type: object diff --git a/api/v1/progress.go b/api/v1/progress.go index bc725af..362a078 100644 --- a/api/v1/progress.go +++ b/api/v1/progress.go @@ -68,11 +68,11 @@ func (s *Server) GetProgressList(ctx context.Context, request GetProgressListReq apiProgress[i] = Progress{ Title: row.Title, Author: row.Author, - DeviceName: &row.DeviceName, - Percentage: &row.Percentage, - DocumentId: &row.DocumentID, - UserId: &row.UserID, - CreatedAt: parseTimePtr(row.CreatedAt), + DeviceName: row.DeviceName, + Percentage: row.Percentage, + DocumentId: row.DocumentID, + UserId: row.UserID, + CreatedAt: parseTimeAny(row.CreatedAt), } } @@ -105,13 +105,13 @@ func (s *Server) GetProgress(ctx context.Context, request GetProgressRequestObje } apiProgress := Progress{ - DeviceName: &row.DeviceName, + DeviceName: row.DeviceName, DeviceId: &row.DeviceID, - Percentage: &row.Percentage, + Percentage: row.Percentage, Progress: &row.Progress, - DocumentId: &row.DocumentID, - UserId: &row.UserID, - CreatedAt: parseTimePtr(row.CreatedAt), + DocumentId: row.DocumentID, + UserId: row.UserID, + CreatedAt: parseTime(row.CreatedAt), } response := ProgressResponse{ diff --git a/api/v1/search.go b/api/v1/search.go index 29bf5b9..11de1ee 100644 --- a/api/v1/search.go +++ b/api/v1/search.go @@ -38,7 +38,6 @@ func (s *Server) GetSearch(ctx context.Context, request GetSearchRequestObject) Series: ptrOf(item.Series), FileType: ptrOf(item.FileType), FileSize: ptrOf(item.FileSize), - UploadDate: ptrOf(item.UploadDate), } } diff --git a/api/v1/settings.go b/api/v1/settings.go index a0bd532..64c91aa 100644 --- a/api/v1/settings.go +++ b/api/v1/settings.go @@ -29,10 +29,10 @@ func (s *Server) GetSettings(ctx context.Context, request GetSettingsRequestObje apiDevices := make([]Device, len(devices)) for i, device := range devices { apiDevices[i] = Device{ - Id: &device.ID, - DeviceName: &device.DeviceName, - CreatedAt: parseTimePtr(device.CreatedAt), - LastSynced: parseTimePtr(device.LastSynced), + Id: device.ID, + DeviceName: device.DeviceName, + CreatedAt: parseTimeAny(device.CreatedAt), + LastSynced: parseTimeAny(device.LastSynced), } } @@ -140,10 +140,10 @@ func (s *Server) UpdateSettings(ctx context.Context, request UpdateSettingsReque apiDevices := make([]Device, len(devices)) for i, device := range devices { apiDevices[i] = Device{ - Id: &device.ID, - DeviceName: &device.DeviceName, - CreatedAt: parseTimePtr(device.CreatedAt), - LastSynced: parseTimePtr(device.LastSynced), + Id: device.ID, + DeviceName: device.DeviceName, + CreatedAt: parseTimeAny(device.CreatedAt), + LastSynced: parseTimeAny(device.LastSynced), } } diff --git a/api/v1/utils.go b/api/v1/utils.go index dd9b627..8717602 100644 --- a/api/v1/utils.go +++ b/api/v1/utils.go @@ -68,6 +68,14 @@ func parseTime(s string) time.Time { return t } +// parseTimeAny parses an interface{} (from SQL) to time.Time, zero on failure. +func parseTimeAny(v interface{}) time.Time { + if s, ok := v.(string); ok { + return parseTime(s) + } + return time.Time{} +} + // parseTimePtr parses an interface{} (from SQL) to *time.Time func parseTimePtr(v interface{}) *time.Time { if v == nil { diff --git a/frontend/src/generated/model/device.ts b/frontend/src/generated/model/device.ts index 8c340d7..12f3ed7 100644 --- a/frontend/src/generated/model/device.ts +++ b/frontend/src/generated/model/device.ts @@ -7,8 +7,8 @@ */ export interface Device { - id?: string; - device_name?: string; - created_at?: string; - last_synced?: string; + id: string; + device_name: string; + created_at: string; + last_synced: string; } diff --git a/frontend/src/generated/model/progress.ts b/frontend/src/generated/model/progress.ts index 3a8403e..f213396 100644 --- a/frontend/src/generated/model/progress.ts +++ b/frontend/src/generated/model/progress.ts @@ -9,11 +9,11 @@ export interface Progress { title?: string; author?: string; - device_name?: string; + device_name: string; device_id?: string; - percentage?: number; + percentage: number; progress?: string; - document_id?: string; - user_id?: string; - created_at?: string; + document_id: string; + user_id: string; + created_at: string; } diff --git a/frontend/src/generated/model/searchItem.ts b/frontend/src/generated/model/searchItem.ts index e354f36..e146893 100644 --- a/frontend/src/generated/model/searchItem.ts +++ b/frontend/src/generated/model/searchItem.ts @@ -14,5 +14,4 @@ export interface SearchItem { series?: string; file_type?: string; file_size?: string; - upload_date?: string; } diff --git a/frontend/src/pages/ProgressPage.tsx b/frontend/src/pages/ProgressPage.tsx index ca6c8bc..541b045 100644 --- a/frontend/src/pages/ProgressPage.tsx +++ b/frontend/src/pages/ProgressPage.tsx @@ -26,7 +26,7 @@ export default function ProgressPage() { { id: 'percentage', header: 'Percentage', - render: row => (typeof row.percentage === 'number' ? `${Math.round(row.percentage)}%` : '0%'), + render: row => `${Math.round(row.percentage)}%`, }, { id: 'created_at', diff --git a/frontend/src/pages/SearchPage.test.tsx b/frontend/src/pages/SearchPage.test.tsx index 2b5ae61..601fbcb 100644 --- a/frontend/src/pages/SearchPage.test.tsx +++ b/frontend/src/pages/SearchPage.test.tsx @@ -77,7 +77,6 @@ describe('SearchPage', () => { series: 'Earthsea', file_type: 'epub', file_size: '1 MB', - upload_date: '2025-01-01', }, ], }, diff --git a/frontend/src/pages/SearchPage.tsx b/frontend/src/pages/SearchPage.tsx index fab57d7..a180aa9 100644 --- a/frontend/src/pages/SearchPage.tsx +++ b/frontend/src/pages/SearchPage.tsx @@ -6,7 +6,6 @@ import { Button, Table, type Column, TextInput, IconInput } from '../components' import { inputClassName } from '../components/TextInput'; import { useDebouncedState } from '../hooks/useDebouncedState'; import { Search2Icon, BookIcon } from '../icons'; -import { formatDate } from '../utils/formatters'; import { dataForStatus } from '../utils/apiResponses'; const searchColumns: Column[] = [ @@ -18,12 +17,6 @@ const searchColumns: Column[] = [ { id: 'series', header: 'Series', render: item => item.series || 'N/A' }, { id: 'type', header: 'Type', render: item => item.file_type || 'N/A' }, { id: 'size', header: 'Size', render: item => item.file_size || 'N/A' }, - { - id: 'date', - header: 'Date', - className: 'hidden md:table-cell', - render: item => formatDate(item.upload_date), - }, ]; interface SearchPageViewProps { diff --git a/search/search.go b/search/search.go index 0ac0de5..9c00fa4 100644 --- a/search/search.go +++ b/search/search.go @@ -37,7 +37,6 @@ type SearchItem struct { Series string FileType string FileSize string - UploadDate string } type searchFunc func(query string) (searchResults []SearchItem, err error)