s3 support

This commit is contained in:
onysd 2026-08-04 23:09:11 +03:00
parent fa5cfaf14d
commit 03f10b66ee
53 changed files with 2796 additions and 102 deletions

View file

@ -18,6 +18,8 @@ type MediaBackend string
const (
// MediaBackendLocalFS 表示 blob 字节存在本地磁盘(object_key 为相对路径)。
MediaBackendLocalFS MediaBackend = "localfs"
// MediaBackendS3 表示 blob 字节存在 S3 兼容对象存储(MinIO 或 AWS S3)。
MediaBackendS3 MediaBackend = "s3"
)
// FileBlob 是一个可下载的二进制对象的索引项:location_key → 后端/对象键/大小/mime。
@ -233,6 +235,10 @@ type Document struct {
DCID int `json:"dc_id,omitempty"`
Attributes []DocumentAttribute `json:"attributes,omitempty"`
Thumbs []PhotoSize `json:"thumbs,omitempty"`
// OwnerUserID is who uploaded this document; 0 for rows predating storage
// accounting or for system-originated documents. Used for per-account
// storage usage reporting, not part of the tg wire protocol.
OwnerUserID int64 `json:"owner_user_id,omitempty"`
}
// StickerSetRef 返回该文档归属的贴纸集引用(若有 sticker/custom_emoji 属性)。
@ -384,6 +390,9 @@ type Photo struct {
DCID int `json:"dc_id,omitempty"`
HasStickers bool `json:"has_stickers,omitempty"`
Sizes []PhotoSize `json:"sizes,omitempty"`
// OwnerUserID is who uploaded this photo; 0 for rows predating storage
// accounting or for system-originated photos (e.g. webpage previews).
OwnerUserID int64 `json:"owner_user_id,omitempty"`
}
func ClonePhotoPtr(photo *Photo) *Photo {

View file

@ -11,4 +11,8 @@ var (
ErrUploadQuotaExceeded = errors.New("upload quota exceeded")
ErrPhotoInvalid = errors.New("photo invalid")
ErrDocumentInvalid = errors.New("document invalid")
// ErrStorageFull is returned when the configured low-space guard rejects a
// write: local disk free bytes (or, on the s3 backend, the configured
// total-bytes budget) has fallen below the configured threshold.
ErrStorageFull = errors.New("storage full")
)

View file

@ -0,0 +1,90 @@
package domain
// MediaKind identifies which table a media_references row points into.
type MediaKind string
const (
MediaKindDocument MediaKind = "document"
MediaKindPhoto MediaKind = "photo"
)
// MediaRefKind identifies why a document/photo is still considered "live" --
// each distinct kind of place that can hold a durable pointer to media gets
// its own ref_kind, so removing one kind of reference (e.g. a deleted
// message) doesn't accidentally drop a still-live reference of another kind
// (e.g. the same document also set as someone's profile photo).
type MediaRefKind string
const (
MediaRefKindMessageBox MediaRefKind = "message_box"
MediaRefKindChannelMessage MediaRefKind = "channel_message"
MediaRefKindProfilePhoto MediaRefKind = "profile_photo"
MediaRefKindStickerSet MediaRefKind = "sticker_set"
MediaRefKindGift MediaRefKind = "gift"
)
// MediaReference is one live pointer to a document/photo. GC (the storage
// retention sweep) only considers a document/photo eligible for deletion
// once every reference to it has been removed.
type MediaReference struct {
Kind MediaKind
MediaID int64
RefKind MediaRefKind
RefKey string
}
// OrphanCandidate is a document/photo whose last reference has been removed
// (orphaned_at is set) and is old enough to be considered for the storage
// retention sweep.
type OrphanCandidate struct {
Kind MediaKind
MediaID int64
Backend MediaBackend
ObjectKey string
Size int64
OrphanedAt int64 // unix seconds
}
// MediaRefTarget identifies one document/photo embedded in a message's media
// snapshot.
type MediaRefTarget struct {
Kind MediaKind
ID int64
}
// ExtractMediaRefTargets returns every document/photo id embedded in a
// message's media snapshot (including nested ones -- a live photo's video
// document, a webpage preview's photo), deduplicated. Used to register/drop
// media_references rows when a message carrying this media is
// created/edited/deleted.
func ExtractMediaRefTargets(media *MessageMedia) []MediaRefTarget {
if media == nil {
return nil
}
seen := make(map[MediaRefTarget]struct{}, 2)
var out []MediaRefTarget
add := func(kind MediaKind, id int64) {
if id == 0 {
return
}
t := MediaRefTarget{Kind: kind, ID: id}
if _, ok := seen[t]; ok {
return
}
seen[t] = struct{}{}
out = append(out, t)
}
if media.Photo != nil {
add(MediaKindPhoto, media.Photo.ID)
}
if media.Document != nil {
add(MediaKindDocument, media.Document.ID)
}
if media.LivePhotoVideo != nil {
add(MediaKindDocument, media.LivePhotoVideo.ID)
}
if media.WebPage != nil && media.WebPage.Photo != nil {
add(MediaKindPhoto, media.WebPage.Photo.ID)
}
return out
}