Initial implementation of Assets Favourites API service#60
Conversation
…metrics endpoints
…health check endpoint
dzacharakis
left a comment
There was a problem hiding this comment.
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
| Chart *ChartPayload `json:"chart,omitempty"` | ||
| Insight *InsightPayload `json:"insight,omitempty"` | ||
| Audience *AudiencePayload `json:"audience,omitempty"` |
There was a problem hiding this comment.
Could there be a case where more than one is set?
There was a problem hiding this comment.
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.
|
|
||
| # 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 |
There was a problem hiding this comment.
How would you use secret keys as environment variables in a production-grade application?
There was a problem hiding this comment.
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.
| Chart *Chart | ||
| Insight *Insight | ||
| Audience *Audience |
There was a problem hiding this comment.
Could you explain why you selected this option? Can you identify any other possible alternatives?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
|
|
||
| .PHONY: all tidy fmt vet build test run | ||
|
|
||
| all: tidy fmt vet build test |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
I’d add a stronger linter like golangci-lint or staticcheck, and definitely a test coverage target.
|
Hi @dzacharakis , Thanks a lot for taking the time to review the assignment and for your comments. Happy to clarify anything further. Nikos |
|
Thank you too, Nikos, for your responses to my comments. |
|
Merry Christmas and happy holidays! |
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}/favouritesDELETE /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-IDheader) wired as middlewareObservability:
/healthendpoint/metrics(Prometheus)OpenAPI spec file
Tests:
Makefile for a consistent local workflow (
make fmt,make test,make run, etc.)Design documentation
How to run
Submitted by Nikos Koumbakis as part of the GWI Engineering take-home challenge.