Add asset metadata, sharing, and MinIO-backed signed links.
CI / test (pull_request) Successful in 4s
CI / test (pull_request) Successful in 4s
This introduces deduplicated per-user image/3D asset records linked into feature properties, adds visibility-controlled download routing, and wires local S3-compatible storage with automatic bucket bootstrap in Docker Compose. Made-with: Cursor
This commit is contained in:
@@ -20,6 +20,8 @@ type MemoryStore struct {
|
||||
invitations map[string]Invitation
|
||||
collections map[string]Collection
|
||||
features map[string]Feature
|
||||
assets map[string]Asset
|
||||
featureRefs map[string]map[string]FeatureAsset
|
||||
}
|
||||
|
||||
func NewMemoryStore() *MemoryStore {
|
||||
@@ -30,6 +32,8 @@ func NewMemoryStore() *MemoryStore {
|
||||
invitations: make(map[string]Invitation),
|
||||
collections: make(map[string]Collection),
|
||||
features: make(map[string]Feature),
|
||||
assets: make(map[string]Asset),
|
||||
featureRefs: make(map[string]map[string]FeatureAsset),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,6 +170,7 @@ func (s *MemoryStore) DeleteCollection(id string) error {
|
||||
for fid, f := range s.features {
|
||||
if f.CollectionID == id {
|
||||
delete(s.features, fid)
|
||||
delete(s.featureRefs, fid)
|
||||
}
|
||||
}
|
||||
delete(s.collections, id)
|
||||
@@ -207,9 +212,110 @@ func (s *MemoryStore) DeleteFeature(featureID string) error {
|
||||
return ErrNotFound
|
||||
}
|
||||
delete(s.features, featureID)
|
||||
delete(s.featureRefs, featureID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MemoryStore) SaveAsset(a Asset) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.assets[a.ID] = a
|
||||
}
|
||||
|
||||
func (s *MemoryStore) GetAsset(assetID string) (Asset, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
a, ok := s.assets[assetID]
|
||||
if !ok {
|
||||
return Asset{}, ErrNotFound
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (s *MemoryStore) GetAssetByOwnerChecksumExt(ownerKey, checksum, ext string) (Asset, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
for _, a := range s.assets {
|
||||
if a.OwnerKey == ownerKey && a.Checksum == checksum && a.Ext == ext {
|
||||
return a, nil
|
||||
}
|
||||
}
|
||||
return Asset{}, ErrNotFound
|
||||
}
|
||||
|
||||
func (s *MemoryStore) SetAssetPublic(assetID string, isPublic bool) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
a, ok := s.assets[assetID]
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
a.IsPublic = isPublic
|
||||
a.UpdatedAt = time.Now().UTC()
|
||||
s.assets[assetID] = a
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MemoryStore) LinkAssetToFeature(featureID, assetID, name, description string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if _, ok := s.features[featureID]; !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
a, ok := s.assets[assetID]
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
if _, ok := s.featureRefs[featureID]; !ok {
|
||||
s.featureRefs[featureID] = make(map[string]FeatureAsset)
|
||||
}
|
||||
if existing, exists := s.featureRefs[featureID][assetID]; exists {
|
||||
existing.Name = name
|
||||
existing.Description = description
|
||||
s.featureRefs[featureID][assetID] = existing
|
||||
return nil
|
||||
}
|
||||
s.featureRefs[featureID][assetID] = FeatureAsset{
|
||||
Asset: a,
|
||||
FeatureID: featureID,
|
||||
Name: name,
|
||||
Description: description,
|
||||
LinkedAt: time.Now().UTC(),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MemoryStore) UnlinkAssetFromFeature(featureID, assetID string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
links, ok := s.featureRefs[featureID]
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
if _, exists := links[assetID]; !exists {
|
||||
return ErrNotFound
|
||||
}
|
||||
delete(links, assetID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MemoryStore) ListAssetsByFeature(featureID string) []FeatureAsset {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
links, ok := s.featureRefs[featureID]
|
||||
if !ok {
|
||||
return []FeatureAsset{}
|
||||
}
|
||||
result := make([]FeatureAsset, 0, len(links))
|
||||
for assetID, fa := range links {
|
||||
if updated, exists := s.assets[assetID]; exists {
|
||||
fa.Asset = updated
|
||||
}
|
||||
result = append(result, fa)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *MemoryStore) PruneExpired(now time.Time) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
Reference in New Issue
Block a user