Add attachments to service, maintenance, fuel and parts records
Documents already carried a single optional file: upload, download, detach,
access-checked on every request and proxied through this server, so PocketBase's
files are never public URLs. Service records, workshop visits, refills and
catalog parts all want the same thing — a receipt, an invoice, a photo of the
box — so extend it to them.
Rather than copy the document handlers four more times, lift them into one
shared layer. Every attachable collection has a car relation and a file field,
which is what lets a single set of handlers authorize and serve all of them. An
upload finishes by delegating to the collection's own GET handler, so the
response carries the full record — derived fields and all — exactly as a re-read
would. The web side gets the same treatment: one picker component and one
upload-after-save helper behind all five forms. Net effect is five features for
about the cost of the one that was already there.
Alongside:
- parts gain a notes field
- Reminders moves behind Parts catalog in the car detail tabs
- the changed-part labels spell out in full ("Oil & Oil filter" rather than
"Oil & filter"), and the form and table now agree
The PocketBase schema must be migrated before the new attachments work:
scripts/setup-pocketbase.mjs adds the file fields and parts.notes. It is
additive and safe to re-run.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e64c89a564
commit
07192f1238
@@ -10,6 +10,18 @@ package models
|
||||
|
||||
import "time"
|
||||
|
||||
// Attachment is the single optional file a record carries — a scan, a receipt, a
|
||||
// workshop invoice, a photo of a part's box. Embedded by every type that can
|
||||
// hold one.
|
||||
//
|
||||
// FileName is the name PocketBase stored it under. The bytes are not in here:
|
||||
// they are served from GET /api/{records}/{id}/file, which re-checks access on
|
||||
// every request, so an attachment is never a public URL.
|
||||
type Attachment struct {
|
||||
FileName string `json:"fileName,omitempty"`
|
||||
HasFile bool `json:"hasFile"`
|
||||
}
|
||||
|
||||
// Car corresponds to one worksheet in the original spreadsheet.
|
||||
type Car struct {
|
||||
ID string `json:"id"`
|
||||
@@ -64,6 +76,9 @@ type ServiceRecord struct {
|
||||
|
||||
Notes string `json:"notes,omitempty"`
|
||||
|
||||
// The workshop receipt or stamped service-book page for this visit.
|
||||
Attachment
|
||||
|
||||
// Derived (not stored): filled in by the API on read.
|
||||
NextServiceDate *time.Time `json:"nextServiceDate,omitempty"` // Excel col C
|
||||
NextServiceKm *int `json:"nextServiceKm,omitempty"` // Excel col D
|
||||
@@ -79,6 +94,10 @@ type Part struct {
|
||||
Name string `json:"name"` // e.g. "Oil Filter"
|
||||
PartNumber string `json:"partNumber"` // e.g. "04152-YZZA7"
|
||||
Category string `json:"category"` // optional: oil|filter|wiper|other
|
||||
Notes string `json:"notes,omitempty"`
|
||||
|
||||
// A photo of the box, or the spec sheet for the part.
|
||||
Attachment
|
||||
|
||||
Created string `json:"created,omitempty"`
|
||||
Updated string `json:"updated,omitempty"`
|
||||
@@ -112,6 +131,9 @@ type FuelEntry struct {
|
||||
Station string `json:"station,omitempty"`
|
||||
Notes string `json:"notes,omitempty"`
|
||||
|
||||
// The pump receipt.
|
||||
Attachment
|
||||
|
||||
// Derived (not stored): filled in by the API on read.
|
||||
PricePerLiter *float64 `json:"pricePerLiter,omitempty"`
|
||||
DistanceKm *int `json:"distanceKm,omitempty"` // since the previous full tank
|
||||
@@ -169,6 +191,9 @@ type MaintenanceEntry struct {
|
||||
WarrantyUntil *time.Time `json:"warrantyUntil,omitempty"`
|
||||
Notes string `json:"notes,omitempty"`
|
||||
|
||||
// The workshop's invoice.
|
||||
Attachment
|
||||
|
||||
// Derived (not stored): filled in by the API on read.
|
||||
TotalCost float64 `json:"totalCost"`
|
||||
WarrantyActive *bool `json:"warrantyActive,omitempty"`
|
||||
@@ -196,10 +221,8 @@ type CarDocument struct {
|
||||
Cost float64 `json:"cost"`
|
||||
Notes string `json:"notes,omitempty"`
|
||||
|
||||
// FileName is the stored attachment (scan/PDF), served via
|
||||
// GET /api/car-documents/{id}/file. Empty when nothing is attached.
|
||||
FileName string `json:"fileName,omitempty"`
|
||||
HasFile bool `json:"hasFile"`
|
||||
// The scan or photo of the paperwork itself.
|
||||
Attachment
|
||||
|
||||
// Derived (not stored): filled in by the API on read.
|
||||
Expiry ExpiryAssessment `json:"expiry"`
|
||||
|
||||
Reference in New Issue
Block a user