Skip to content

Commit a7c8bfb

Browse files
committed
Refactor: Service layer now determines is_latest flag
- Service layer determines if a version should be marked as latest - Database interface updated to accept isLatest parameter - UpdateLatestFlag method added for updating existing records - Memory and PostgreSQL implementations updated accordingly - Removed version comparison logic from database layer - All tests passing :house: Remote-Dev: homespace
1 parent 2d549aa commit a7c8bfb

File tree

5 files changed

+78
-129
lines changed

5 files changed

+78
-129
lines changed

internal/database/database.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ type Database interface {
2323
// GetByID retrieves a single ServerRecord by its ID
2424
GetByID(ctx context.Context, id string) (*model.ServerRecord, error)
2525
// Publish adds a new server to the database with separated server.json and extensions
26-
Publish(ctx context.Context, serverDetail model.ServerDetail, publisherExtensions map[string]interface{}) (*model.ServerRecord, error)
26+
// The isLatest flag indicates if this version should be marked as the latest
27+
Publish(ctx context.Context, serverDetail model.ServerDetail, publisherExtensions map[string]interface{}, isLatest bool) (*model.ServerRecord, error)
28+
// UpdateLatestFlag updates the is_latest flag for a specific server record
29+
UpdateLatestFlag(ctx context.Context, id string, isLatest bool) error
2730
// ImportSeed imports initial data from a seed file
2831
ImportSeed(ctx context.Context, seedFilePath string) error
2932
// Close closes the database connection

internal/database/memory.go

Lines changed: 20 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import (
44
"context"
55
"fmt"
66
"sort"
7-
"strconv"
8-
"strings"
97
"sync"
108
"time"
119

@@ -44,53 +42,6 @@ func NewMemoryDB(e map[string]*model.ServerDetail) *MemoryDB {
4442
}
4543
}
4644

47-
// compareSemanticVersions compares two semantic version strings
48-
// Returns:
49-
//
50-
// -1 if version1 < version2
51-
// 0 if version1 == version2
52-
// +1 if version1 > version2
53-
func compareSemanticVersions(version1, version2 string) int {
54-
// Simple semantic version comparison
55-
// Assumes format: major.minor.patch
56-
57-
parts1 := strings.Split(version1, ".")
58-
parts2 := strings.Split(version2, ".")
59-
60-
// Pad with zeros if needed
61-
maxLen := max(len(parts2), len(parts1))
62-
63-
for len(parts1) < maxLen {
64-
parts1 = append(parts1, "0")
65-
}
66-
for len(parts2) < maxLen {
67-
parts2 = append(parts2, "0")
68-
}
69-
70-
// Compare each part
71-
for i := 0; i < maxLen; i++ {
72-
num1, err1 := strconv.Atoi(parts1[i])
73-
num2, err2 := strconv.Atoi(parts2[i])
74-
75-
// If parsing fails, fall back to string comparison
76-
if err1 != nil || err2 != nil {
77-
if parts1[i] < parts2[i] {
78-
return -1
79-
} else if parts1[i] > parts2[i] {
80-
return 1
81-
}
82-
continue
83-
}
84-
85-
if num1 < num2 {
86-
return -1
87-
} else if num1 > num2 {
88-
return 1
89-
}
90-
}
91-
92-
return 0
93-
}
9445

9546
// List retrieves ServerRecord entries with optional filtering and pagination
9647
func (db *MemoryDB) List(
@@ -202,7 +153,7 @@ func (db *MemoryDB) GetByID(ctx context.Context, id string) (*model.ServerRecord
202153
}
203154

204155
// Publish adds a new server to the database with separated server.json and extensions
205-
func (db *MemoryDB) Publish(ctx context.Context, serverDetail model.ServerDetail, publisherExtensions map[string]interface{}) (*model.ServerRecord, error) {
156+
func (db *MemoryDB) Publish(ctx context.Context, serverDetail model.ServerDetail, publisherExtensions map[string]interface{}, isLatest bool) (*model.ServerRecord, error) {
206157
if ctx.Err() != nil {
207158
return nil, ctx.Err()
208159
}
@@ -221,23 +172,6 @@ func (db *MemoryDB) Publish(ctx context.Context, serverDetail model.ServerDetail
221172
db.mu.Lock()
222173
defer db.mu.Unlock()
223174

224-
// Check for existing entry with same name and compare versions
225-
var existingRecord *model.ServerRecord
226-
for _, entry := range db.entries {
227-
if entry.RegistryMetadata.IsLatest && entry.ServerJSON.Name == name {
228-
existingRecord = entry
229-
break
230-
}
231-
}
232-
233-
// Version comparison
234-
if existingRecord != nil {
235-
existingVersion := existingRecord.ServerJSON.VersionDetail.Version
236-
if compareSemanticVersions(version, existingVersion) <= 0 {
237-
return nil, fmt.Errorf("version must be greater than existing version %s", existingVersion)
238-
}
239-
}
240-
241175
// Validate repository URL
242176
if serverDetail.Repository.URL == "" {
243177
return nil, ErrInvalidInput
@@ -249,7 +183,7 @@ func (db *MemoryDB) Publish(ctx context.Context, serverDetail model.ServerDetail
249183
ID: uuid.New().String(),
250184
PublishedAt: now,
251185
UpdatedAt: now,
252-
IsLatest: true,
186+
IsLatest: isLatest,
253187
ReleaseDate: now.Format(time.RFC3339),
254188
}
255189

@@ -260,11 +194,6 @@ func (db *MemoryDB) Publish(ctx context.Context, serverDetail model.ServerDetail
260194
PublisherExtensions: publisherExtensions,
261195
}
262196

263-
// Mark existing record as not latest
264-
if existingRecord != nil {
265-
existingRecord.RegistryMetadata.IsLatest = false
266-
}
267-
268197
// Store the record using registry metadata ID
269198
db.entries[registryMetadata.ID] = record
270199

@@ -298,6 +227,24 @@ func (db *MemoryDB) ImportSeed(ctx context.Context, seedFilePath string) error {
298227
return nil
299228
}
300229

230+
// UpdateLatestFlag updates the is_latest flag for a specific server record
231+
func (db *MemoryDB) UpdateLatestFlag(ctx context.Context, id string, isLatest bool) error {
232+
if ctx.Err() != nil {
233+
return ctx.Err()
234+
}
235+
236+
db.mu.Lock()
237+
defer db.mu.Unlock()
238+
239+
if entry, exists := db.entries[id]; exists {
240+
entry.RegistryMetadata.IsLatest = isLatest
241+
entry.RegistryMetadata.UpdatedAt = time.Now()
242+
return nil
243+
}
244+
245+
return ErrNotFound
246+
}
247+
301248
// Close closes the database connection
302249
// For an in-memory database, this is a no-op
303250
func (db *MemoryDB) Close() error {

internal/database/postgres.go

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ func (db *PostgreSQL) GetByID(ctx context.Context, id string) (*model.ServerReco
273273
}
274274

275275
// Publish adds a new server to the database with separated server.json and extensions
276-
func (db *PostgreSQL) Publish(ctx context.Context, serverDetail model.ServerDetail, publisherExtensions map[string]interface{}) (*model.ServerRecord, error) {
276+
func (db *PostgreSQL) Publish(ctx context.Context, serverDetail model.ServerDetail, publisherExtensions map[string]interface{}, isLatest bool) (*model.ServerRecord, error) {
277277
if ctx.Err() != nil {
278278
return nil, ctx.Err()
279279
}
@@ -288,23 +288,6 @@ func (db *PostgreSQL) Publish(ctx context.Context, serverDetail model.ServerDeta
288288
}
289289
}()
290290

291-
// Check if there's an existing latest version for this server
292-
var existingVersion string
293-
checkQuery := `
294-
SELECT s.version
295-
FROM servers s
296-
JOIN server_extensions se ON s.id = se.server_id
297-
WHERE s.name = $1 AND se.is_latest = true
298-
`
299-
err = tx.QueryRow(ctx, checkQuery, serverDetail.Name).Scan(&existingVersion)
300-
if err != nil && !errors.Is(err, pgx.ErrNoRows) {
301-
return nil, fmt.Errorf("failed to check existing version: %w", err)
302-
}
303-
304-
// Validate version ordering
305-
if existingVersion != "" && serverDetail.VersionDetail.Version <= existingVersion {
306-
return nil, ErrInvalidVersion
307-
}
308291

309292
// Prepare JSON data for server table
310293
repositoryJSON, err := json.Marshal(serverDetail.Repository)
@@ -332,21 +315,6 @@ func (db *PostgreSQL) Publish(ctx context.Context, serverDetail model.ServerDeta
332315
registryID := uuid.New().String()
333316
now := time.Now()
334317

335-
// Update existing latest version to not be latest
336-
if existingVersion != "" {
337-
updateQuery := `
338-
UPDATE server_extensions
339-
SET is_latest = false
340-
WHERE server_id IN (
341-
SELECT s.id FROM servers s WHERE s.name = $1 AND server_extensions.server_id = s.id
342-
)
343-
AND is_latest = true
344-
`
345-
_, err = tx.Exec(ctx, updateQuery, serverDetail.Name)
346-
if err != nil {
347-
return nil, fmt.Errorf("failed to update existing latest version: %w", err)
348-
}
349-
}
350318

351319
// Insert new server record
352320
insertServerQuery := `
@@ -377,7 +345,7 @@ func (db *PostgreSQL) Publish(ctx context.Context, serverDetail model.ServerDeta
377345
serverID,
378346
now,
379347
now,
380-
true, // is_latest
348+
isLatest,
381349
now, // release_date
382350
publisherExtensionsJSON,
383351
)
@@ -545,6 +513,30 @@ func (db *PostgreSQL) publishWithTransaction(ctx context.Context, tx pgx.Tx, ser
545513
return nil
546514
}
547515

516+
// UpdateLatestFlag updates the is_latest flag for a specific server record
517+
func (db *PostgreSQL) UpdateLatestFlag(ctx context.Context, id string, isLatest bool) error {
518+
if ctx.Err() != nil {
519+
return ctx.Err()
520+
}
521+
522+
query := `
523+
UPDATE server_extensions
524+
SET is_latest = $1, updated_at = $2
525+
WHERE id = $3
526+
`
527+
528+
result, err := db.conn.Exec(ctx, query, isLatest, time.Now(), id)
529+
if err != nil {
530+
return fmt.Errorf("failed to update latest flag: %w", err)
531+
}
532+
533+
if result.RowsAffected() == 0 {
534+
return ErrNotFound
535+
}
536+
537+
return nil
538+
}
539+
548540
// Close closes the database connection
549541
func (db *PostgreSQL) Close() error {
550542
return db.conn.Close(context.Background())

internal/service/fake_service.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@ func (s *fakeRegistryService) Publish(req model.PublishRequest) (*model.ServerRe
127127
// Extract publisher extensions from request
128128
publisherExtensions := model.ExtractPublisherExtensions(req)
129129

130-
// Publish to database
131-
serverRecord, err := s.db.Publish(ctx, req.Server, publisherExtensions)
130+
// Publish to database (fake service always marks as latest)
131+
serverRecord, err := s.db.Publish(ctx, req.Server, publisherExtensions, true)
132132
if err != nil {
133133
return nil, err
134134
}

internal/service/registry_service.go

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -85,40 +85,47 @@ func (s *registryServiceImpl) Publish(req model.PublishRequest) (*model.ServerRe
8585
// Get the new version's details
8686
newVersion := req.Server.VersionDetail.Version
8787
newName := req.Server.Name
88+
currentTime := time.Now()
8889

8990
// Check for existing versions of this server
9091
existingServers, _, err := s.db.List(ctx, map[string]any{"name": newName}, "", 1000)
9192
if err != nil && !errors.Is(err, database.ErrNotFound) {
9293
return nil, err
9394
}
9495

95-
// Find the current latest version if it exists
96-
var latestExisting *model.ServerRecord
96+
// Determine if this version should be marked as latest
97+
isLatest := true
98+
var existingLatestID string
99+
100+
// Check all existing versions to determine if new version should be latest
97101
for _, server := range existingServers {
98102
if server.RegistryMetadata.IsLatest {
99-
latestExisting = server
100-
break
101-
}
102-
}
103-
104-
// Validate version ordering using the versioning strategy
105-
if latestExisting != nil {
106-
existingVersion := latestExisting.ServerJSON.VersionDetail.Version
107-
existingTime, _ := time.Parse(time.RFC3339, latestExisting.RegistryMetadata.ReleaseDate)
108-
currentTime := time.Now()
109-
110-
// Compare versions using the proper versioning strategy
111-
comparison := CompareVersions(newVersion, existingVersion, currentTime, existingTime)
112-
if comparison <= 0 {
113-
return nil, database.ErrInvalidVersion
103+
existingLatestID = server.RegistryMetadata.ID
104+
existingVersion := server.ServerJSON.VersionDetail.Version
105+
existingTime, _ := time.Parse(time.RFC3339, server.RegistryMetadata.ReleaseDate)
106+
107+
// Compare versions using the proper versioning strategy
108+
comparison := CompareVersions(newVersion, existingVersion, currentTime, existingTime)
109+
if comparison <= 0 {
110+
// New version is not greater than existing latest
111+
isLatest = false
112+
}
114113
}
115114
}
116115

117116
// Extract publisher extensions from request
118117
publisherExtensions := model.ExtractPublisherExtensions(req)
119118

120-
// Publish to database
121-
serverRecord, err := s.db.Publish(ctx, req.Server, publisherExtensions)
119+
// If this will be the latest version, we need to update the existing latest
120+
if isLatest && existingLatestID != "" {
121+
// Update the existing latest to no longer be latest
122+
if err := s.db.UpdateLatestFlag(ctx, existingLatestID, false); err != nil {
123+
return nil, err
124+
}
125+
}
126+
127+
// Publish to database with the determined is_latest flag
128+
serverRecord, err := s.db.Publish(ctx, req.Server, publisherExtensions, isLatest)
122129
if err != nil {
123130
return nil, err
124131
}

0 commit comments

Comments
 (0)