Skip to content

Initial implementation of Assets Favourites API service#60

Open
nikoumba wants to merge 22 commits into
GlobalWebIndex:mainfrom
nikoumba:feat/favourites-api
Open

Initial implementation of Assets Favourites API service#60
nikoumba wants to merge 22 commits into
GlobalWebIndex:mainfrom
nikoumba:feat/favourites-api

Conversation

@nikoumba

Copy link
Copy Markdown

Summary

This PR introduces the initial version of the Assets Favourites API service. It exposes endpoints for managing per-user favourites (list, add, remove, edit description) across different asset types (chart, insight, audience).

What’s included

  • HTTP API for:

    • GET /v1/users/{userId}/favourites (with type filter + pagination)
    • POST /v1/users/{userId}/favourites
    • DELETE /v1/users/{userId}/favourites/{assetId}
    • PATCH /v1/users/{userId}/favourites/{assetId}
  • Domain layer for favourites and assets, with validation and service logic

  • In-memory repository with concurrency safety (RWMutex) and deterministic pagination

  • Simple auth abstraction (X-User-ID header) wired as middleware

  • Observability:

    • /health endpoint
    • /metrics (Prometheus)
    • Docker + Prometheus + Grafana stack with a basic dashboard
  • OpenAPI spec file

  • Tests:

    • Unit tests for domain, repository, service, handlers, and infrastructure
    • Basic integration tests through the HTTP router
  • Makefile for a consistent local workflow (make fmt, make test, make run, etc.)

  • Design documentation

How to run

# Using Makefile
make run

# Or directly with Go
go run ./cmd/api

# Or run full stack (API + Prometheus + Grafana)
docker compose up --build

Submitted by Nikos Koumbakis as part of the GWI Engineering take-home challenge.

@dzacharakis dzacharakis left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @SpiralOutDotEu 👋

Thanks for your work on this assignment!

I added some comments on your PR with a few questions. I’d love to hear your feedback when you get a chance.

Thanks,
Dimitris

Comment on lines +64 to +66
Chart *ChartPayload `json:"chart,omitempty"`
Insight *InsightPayload `json:"insight,omitempty"`
Audience *AudiencePayload `json:"audience,omitempty"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could there be a case where more than one is set?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Right now the mapper uses the field that matches the type on the model and silently ignores any extras.
Even that the domain model enforces a single variant, it would be better to validate this at the DTO/mapper level and early reject payloads when more than one is set.

Comment thread .env.example

# HTTP listen address for the API (matches internal/config)
# If unset, the service defaults to :8080
HTTP_ADDR=:8080 No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you use secret keys as environment variables in a production-grade application?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depending on the deployment environment, I’d use a secret manager (AWS/Azure/GCP) or HashiCorp Vault with clear rotation policies and access control, and have the platform inject them as environment variables or files.

Comment on lines +24 to +26
Chart *Chart
Insight *Insight
Audience *Audience

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain why you selected this option? Can you identify any other possible alternatives?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at a couple of options here. Go-style polymorphism (an Asset interface with ChartAsset / InsightAsset / AudienceAsset) would definitely work, but for this small service it felt like unnecessary extra complexity and type switches for what is basically a “shape of data” problem.

I ended up with a tagged union instead, as it stays type-safe and keeps the JSON side simple. If this were a bigger system and assets had more behaviours, I’d probably use an Asset interface with concrete types for each asset kind.


// userFavourites stores favourites per user:
// userID -> assetID -> Favourite
userFavourites map[string]map[string]favourites.Favourite

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would occur if two users happen to mark a specific asset as a favourite?
How could you implement functionality to retrieve all users who have favourited an asset?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a simplification assumption, in order to keep it small for this take-home task.
As it is now, two users favoriting the same asset by content, will get two separate assets.
In a real system, asset should be a separate entity with their own lifecycle, and favourites would point to their assetId, instead of the full content.
In order to be able to support users who have favourited an asset, I'd add a reverse index, like assetId->[]userId and a ListUsersByAssetId(ctx, assetId) method.

return
}

entry, err := h.svc.AddFavourite(ctx, userID, domainAsset)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, AddFavourite doesn’t just assign an existing asset to a user—it both creates the Asset and marks it as a favourite. Shouldn’t the Asset already exist beforehand? Typically, you could only add something as a favourite once it already exists.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes correct. Same as the previous comment, this was a simplification assumption in order to focus on the favourites handling.
On a real system I would expect that assets are generated from other processes, so that our favourites entity would be a join of userId, assetId and the custom description.
So the schema would be (userId, assetId, customDescription)

Comment thread Makefile

.PHONY: all tidy fmt vet build test run

all: tidy fmt vet build test

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice — providing a Makefile with common commands is helpful. Is there any Go development tool that might be missing from this list and could add value?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’d add a stronger linter like golangci-lint or staticcheck, and definitely a test coverage target.

@nikoumba

Copy link
Copy Markdown
Author

Hi @dzacharakis ,

Thanks a lot for taking the time to review the assignment and for your comments.
I’ve gone through them and replied inline.

Happy to clarify anything further.

Nikos

@dzacharakis

Copy link
Copy Markdown

Thank you too, Nikos, for your responses to my comments.
All good on my end. Good luck with the next steps, and wishing you a Merry Christmas and happy holidays!

@nikoumba

Copy link
Copy Markdown
Author

Merry Christmas and happy holidays!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants