···149149150150 return nil
151151}
152152+153153+// UpdateCrewMemberTier updates a crew member's tier
154154+// Since ATProto records are immutable, this finds the member's record by DID,
155155+// deletes it, and recreates it with the new tier value.
156156+func (p *HoldPDS) UpdateCrewMemberTier(ctx context.Context, memberDID, tier string) error {
157157+ // Find the crew member's record by iterating over crew records
158158+ members, err := p.ListCrewMembers(ctx)
159159+ if err != nil {
160160+ return fmt.Errorf("failed to list crew members: %w", err)
161161+ }
162162+163163+ // Find the member with matching DID
164164+ var targetMember *CrewMemberWithKey
165165+ for _, m := range members {
166166+ if m.Record.Member == memberDID {
167167+ targetMember = m
168168+ break
169169+ }
170170+ }
171171+172172+ if targetMember == nil {
173173+ return fmt.Errorf("crew member not found: %s", memberDID)
174174+ }
175175+176176+ // If tier is already the same, no update needed
177177+ if targetMember.Record.Tier == tier {
178178+ return nil
179179+ }
180180+181181+ // Delete the old record
182182+ if err := p.RemoveCrewMember(ctx, targetMember.Rkey); err != nil {
183183+ return fmt.Errorf("failed to remove old crew record: %w", err)
184184+ }
185185+186186+ // Create new record with updated tier
187187+ newRecord := &atproto.CrewRecord{
188188+ Type: atproto.CrewCollection,
189189+ Member: targetMember.Record.Member,
190190+ Role: targetMember.Record.Role,
191191+ Permissions: targetMember.Record.Permissions,
192192+ Tier: tier,
193193+ AddedAt: targetMember.Record.AddedAt, // Preserve original add time
194194+ }
195195+196196+ _, _, err = p.repomgr.CreateRecord(ctx, p.uid, atproto.CrewCollection, newRecord)
197197+ if err != nil {
198198+ return fmt.Errorf("failed to create updated crew record: %w", err)
199199+ }
200200+201201+ return nil
202202+}
+23
pkg/hold/quota/config.go
···141141 return len(m.tiers)
142142}
143143144144+// TierInfo represents tier information for listing
145145+type TierInfo struct {
146146+ Key string
147147+ Limit *int64
148148+}
149149+150150+// ListTiers returns all configured tiers with their limits
151151+func (m *Manager) ListTiers() []TierInfo {
152152+ if !m.IsEnabled() {
153153+ return nil
154154+ }
155155+156156+ tiers := make([]TierInfo, 0, len(m.tiers))
157157+ for key, limit := range m.tiers {
158158+ limitCopy := limit // Create copy to take address of
159159+ tiers = append(tiers, TierInfo{
160160+ Key: key,
161161+ Limit: &limitCopy,
162162+ })
163163+ }
164164+ return tiers
165165+}
166166+144167// ParseHumanBytes parses human-readable byte sizes like "5GB", "100MB", "1.5TB"
145168func ParseHumanBytes(s string) (int64, error) {
146169 s = strings.TrimSpace(strings.ToUpper(s))