Skip to content

Commit

Permalink
Add an API endpoint to get the summary
Browse files Browse the repository at this point in the history
Break out the image page summary to a separate function and expose the
reused functionality under /api/v1/summary. The data is exactly the same
as the summary retrieved through /api/v1/images.

Solves: #64
  • Loading branch information
AlexGustafsson committed Jan 14, 2025
1 parent 88dc5ac commit 9a8a64e
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 90 deletions.
52 changes: 33 additions & 19 deletions api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,17 @@ paths:
content:
application/rss+xml:

/summary:
get:
summary: Get a summary.
responses:
"200":
description: Summary
content:
application/json:
schema:
$ref: "#/components/schemas/Summary"

components:
schemas:
ImagePage:
Expand All @@ -186,25 +197,7 @@ components:
items:
$ref: "#/components/schemas/Image"
summary:
type: object
properties:
images:
description: Total number of images
type: number
outdated:
description: Total number of outdated images
type: number
vulnerable:
description: Total number of vulnerable images
type: number
processing:
description: Total number of unprocessed images
type: number
required:
- images
- outdated
- vulnerable
- processing
$ref: "#/components/schemas/Summary"
pagination:
$ref: "#/components/schemas/PaginationMetadata"
required:
Expand Down Expand Up @@ -384,3 +377,24 @@ components:
type:
type: string
enum: ["imageUpdated"]

Summary:
type: object
properties:
images:
description: Total number of images
type: number
outdated:
description: Total number of outdated images
type: number
vulnerable:
description: Total number of vulnerable images
type: number
processing:
description: Total number of unprocessed images
type: number
required:
- images
- outdated
- vulnerable
- processing
8 changes: 8 additions & 0 deletions internal/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,14 @@ func NewServer(api *store.Store, hub *events.Hub[store.Event], processQueue chan
}
})

s.mux.HandleFunc("GET /api/v1/summary", func(w http.ResponseWriter, r *http.Request) {
ctx, span := httputil.SpanFromRequest(r)
span.SetAttributes(semconv.HTTPRoute("/api/v1/summary"))

response, err := api.Summary(ctx)
s.handleJSONResponse(w, r, response, err)
})

return s
}

Expand Down
153 changes: 82 additions & 71 deletions internal/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -828,80 +828,14 @@ func (s *Store) ListImages(ctx context.Context, options *ListImageOptions) (*mod

offset := page * limit

// Total images
res, err := s.db.QueryContext(ctx, `SELECT COUNT(1) FROM images;`)
if err != nil {
return nil, err
}

if !res.Next() {
return nil, res.Err()
}

var totalImages int
if err := res.Scan(&totalImages); err != nil {
res.Close()
return nil, err
}
res.Close()

// Total outdated images
res, err = s.db.QueryContext(ctx, `SELECT COUNT(1) FROM images WHERE latestReference IS NOT NULL AND reference != latestReference;`)
if err != nil {
return nil, err
}

if !res.Next() {
return nil, res.Err()
}

var totalOutdatedImages int
if err := res.Scan(&totalOutdatedImages); err != nil {
res.Close()
return nil, err
}
res.Close()

// Total vulnerable images
res, err = s.db.QueryContext(ctx, `SELECT COUNT(DISTINCT reference) FROM images_vulnerabilities;`)
if err != nil {
return nil, err
}

if !res.Next() {
return nil, res.Err()
}

var totalVulnerableImages int
if err := res.Scan(&totalVulnerableImages); err != nil {
res.Close()
return nil, err
}
res.Close()
var result models.ImagePage
result.Images = make([]models.Image, 0)

// Total raw images
res, err = s.db.QueryContext(ctx, `SELECT COUNT(1) FROM raw_images;`)
summary, err := s.Summary(ctx)
if err != nil {
return nil, err
}

if !res.Next() {
return nil, res.Err()
}

var totalRawImages int
if err := res.Scan(&totalRawImages); err != nil {
res.Close()
return nil, err
}
res.Close()

var result models.ImagePage
result.Images = make([]models.Image, 0)
result.Summary.Images = totalImages
result.Summary.Outdated = totalOutdatedImages
result.Summary.Vulnerable = totalVulnerableImages
result.Summary.Processing = totalRawImages - totalImages
result.Summary = *summary

orderClause := ""
switch sort {
Expand Down Expand Up @@ -950,7 +884,7 @@ func (s *Store) ListImages(ctx context.Context, options *ListImageOptions) (*mod
} else if options.Query != "" {
args = append(args, ftsEscape(options.Query))
}
res, err = statement.QueryContext(ctx, args...)
res, err := statement.QueryContext(ctx, args...)
statement.Close()
if err != nil {
return nil, err
Expand Down Expand Up @@ -1086,6 +1020,83 @@ func (s *Store) DeleteNonPresent(ctx context.Context, references []string) (int6
return rowsAffected, nil
}

func (s *Store) Summary(ctx context.Context) (*models.ImagePageSummary, error) {
// Total images
res, err := s.db.QueryContext(ctx, `SELECT COUNT(1) FROM images;`)
if err != nil {
return nil, err
}

if !res.Next() {
return nil, res.Err()
}

var totalImages int
if err := res.Scan(&totalImages); err != nil {
res.Close()
return nil, err
}
res.Close()

// Total outdated images
res, err = s.db.QueryContext(ctx, `SELECT COUNT(1) FROM images WHERE latestReference IS NOT NULL AND reference != latestReference;`)
if err != nil {
return nil, err
}

if !res.Next() {
return nil, res.Err()
}

var totalOutdatedImages int
if err := res.Scan(&totalOutdatedImages); err != nil {
res.Close()
return nil, err
}
res.Close()

// Total vulnerable images
res, err = s.db.QueryContext(ctx, `SELECT COUNT(DISTINCT reference) FROM images_vulnerabilities;`)
if err != nil {
return nil, err
}

if !res.Next() {
return nil, res.Err()
}

var totalVulnerableImages int
if err := res.Scan(&totalVulnerableImages); err != nil {
res.Close()
return nil, err
}
res.Close()

// Total raw images
res, err = s.db.QueryContext(ctx, `SELECT COUNT(1) FROM raw_images;`)
if err != nil {
return nil, err
}

if !res.Next() {
return nil, res.Err()
}

var totalRawImages int
if err := res.Scan(&totalRawImages); err != nil {
res.Close()
return nil, err
}
res.Close()

return &models.ImagePageSummary{
Images: totalImages,
Outdated: totalOutdatedImages,
Vulnerable: totalVulnerableImages,
Processing: totalRawImages - totalImages,
}, nil
}

func (s *Store) Close() error {
return s.db.Close()
}
Expand Down

0 comments on commit 9a8a64e

Please sign in to comment.