Repository navigation
Expand file tree
/
Copy pathresource.go
More file actions
84 lines (77 loc) · 2.88 KB
/
Copy pathresource.go
File metadata and controls
84 lines (77 loc) · 2.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package common
import (
"time"
"shopnexus/internal/shared/id"
"shopnexus/internal/shared/validation"
)
// Resource is an uploaded file held by a storage provider. Owning rows point at it from their
// own "attachments" array; there is no join table.
//
// One row per module schema: the module that took the upload owns it. A resource id therefore
// only resolves inside the module that stored it, which is why every DTO carrying one also
// carries the resolved Resource rather than expecting the client to fetch it.
type Resource struct {
ID int64
UploadedByID *int64 // nil for a system-generated resource
Provider string `validate:"required"`
ObjectKey string `validate:"required,max=2048"`
Mime string `validate:"required,max=100"`
Size int64 `validate:"gte=0"`
Metadata []byte // JSON; defaults to {}
Checksum *string
CreatedAt time.Time
// URL and URLExpiresAt are not columns: they are the short-lived link a storage provider
// presigns, filled in by whoever resolved the row. Empty until a presigner exists, and a
// consumer that needs the bytes — the KYC check reading a scan — has to treat an empty URL
// as "not available yet" rather than as an empty object.
URL string
URLExpiresAt *time.Time
}
func NewResource(uploadedByID *int64, provider, objectKey, mime string, size int64, metadata []byte, checksum *string) (Resource, error) {
if len(metadata) == 0 {
metadata = []byte("{}")
}
r := Resource{
UploadedByID: uploadedByID,
Provider: provider,
ObjectKey: objectKey,
Mime: mime,
Size: size,
Metadata: metadata,
Checksum: checksum,
}
if err := validation.Default().Struct(r); err != nil {
return Resource{}, validation.AsError(err)
}
return r, nil
}
// ResourceDTO is the wire form every module's api package hands out for an attachment: the
// shape is shared because a listing image, an avatar and a review photo are the same fact.
type ResourceDTO struct {
ID id.ID[id.Resource] `json:"id"`
Provider string `json:"provider"`
ObjectKey string `json:"object_key"`
Mime string `json:"mime"`
Size int64 `json:"size"`
Checksum string `json:"checksum"`
// URL is the presigned link, as on Resource above: short-lived, not an address to store.
URL string `json:"url"`
// URLExpiresAt is when that link stops working, so a caller can tell a stale URL from a
// wrong one.
URLExpiresAt *time.Time `json:"url_expires_at"`
}
// ToDTO is the one conversion, so a module does not write its own.
func (r Resource) ToDTO() ResourceDTO {
dto := ResourceDTO{
ID: id.Of[id.Resource](r.ID),
Provider: r.Provider,
ObjectKey: r.ObjectKey,
Mime: r.Mime,
Size: r.Size,
}
if r.Checksum != nil {
dto.Checksum = *r.Checksum
}
dto.URL, dto.URLExpiresAt = r.URL, r.URLExpiresAt
return dto
}