Skip to content

GWI – Favorite Assets Service (Engineering Challenge Submission)#55

Open
alexkazan87 wants to merge 11 commits into
GlobalWebIndex:mainfrom
alexkazan87:main
Open

GWI – Favorite Assets Service (Engineering Challenge Submission)#55
alexkazan87 wants to merge 11 commits into
GlobalWebIndex:mainfrom
alexkazan87:main

Conversation

@alexkazan87

Copy link
Copy Markdown

This Pull Request delivers the implementation for the GlobalWebIndex (GWI) engineering challenge. It provides a fully functional HTTP service that allows authenticated users to manage their personal list of favorite assets (charts, insights, audiences). The solution includes:

  • JWT-based authentication and role-based authorization
  • In-memory storage for users, tokens, and favorites
  • Preloaded demo users: alice (user) and bob (admin)
  • Complete CRUD API for managing favorites
  • Structured, layered architecture (domain, application, infrastructure, HTTP)
  • Consistent JSON error responses
  • Example cURL commands and API documentation in Solution.md
  • Dockerfile for running the service in a container

@alexandros-georgantas alexandros-georgantas 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.

Hello @alexkazan87 👋 ,

Thank you for taking the time working on this assignment!
I have left some comments on this PR and it would be great if you find the time to address them!

Thanks,
Alex

Comment thread Dockerfile
RUN CGO_ENABLED=0 GOOS=linux go build -o favorites ./cmd/main.go

FROM alpine:3
WORKDIR /root/

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 suggest any enhancements for the application image? e.g. security

Comment thread Makefile
@echo "Please use 'make <target>' where <target> is one of the following:"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

run: ## Run the application

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 please argue about your decision of including the vendor folder in general? What are you trying to achieve and what is the need in the context of this assignment?

Comment thread cmd/main.go

func main() {
infraProviders := infra.NewInfraProviders()
tp := time.NewTimeProvider()

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 please elaborate about the need of having these two providers (time and uuid)? What are the benefits of that? Are you using them consistently?

Comment thread go.mod
@@ -0,0 +1,20 @@
module github.com/akazantzidis/gwi-ass

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

:)

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 please explain the purpose for this notification service in the scope of this assignment? What is the value added by this service?

Comment thread internal/app/services.go
import (
"github.com/akazantzidis/gwi-ass/internal/app/auth/command"
"github.com/akazantzidis/gwi-ass/internal/app/favourite/commands"
queries2 "github.com/akazantzidis/gwi-ass/internal/app/user/queries"

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 is the purpose of this queries2 module?

ID uuid.UUID `json:"id,omitempty"`
Type AssetType `json:"type"`
Description string `json:"description"`
Data json.RawMessage `json:"data"`

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 suggest an alternative approach for modeling the entities required in this assignment? If you had more time how would you refactor this to make it more type safe?

)

var (
jwtKey = []byte("secret")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In a production environment how would you have handled that?

Comment thread cmd/main.go
appServices := app.NewServices(infraProviders.FavoriteRepository, infraProviders.NotificationService, infraProviders.UserRepository, infraProviders.RefreshTokenRepository, up, tp)

infraHTTPServer := infra.NewHTTPServer(appServices)
infraHTTPServer.ListenAndServe(":8080")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In a production env how would you have handled that? Please suggest an alternative solution to this implementation.

Comment thread cmd/main.go

appServices := app.NewServices(infraProviders.FavoriteRepository, infraProviders.NotificationService, infraProviders.UserRepository, infraProviders.RefreshTokenRepository, up, tp)

infraHTTPServer := infra.NewHTTPServer(appServices)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Please explain what will happen if something goes wrong during the initialization of the server? Is there any alternative more graceful handling that could be implemented?

@pamanta pamanta 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 @alexkazan87,

Thank you very much for taking the time to complete the assignment.

I've also left some comments for you to have a look when you have time :)

Comment on lines +37 to +55
favorite, err := h.repo.GetByID(command.UserID, command.ID)
if err != nil {
return fmt.Errorf("failed to fetch favorite: %w", err)
}
if favorite == nil {
return fmt.Errorf("favorite with ID %s does not exist for user %s", command.ID, command.UserID)
}

// Update fields
favorite.Type = command.Type
favorite.Description = command.Description
favorite.Data = command.Data

// Persist the update
if err := h.repo.Update(command.UserID, *favorite); err != nil {
return fmt.Errorf("failed to update favorite: %w", err)
}

return nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

can you identify any concerns with this method? Particularly about race conditions and concurrency? If yes, how would you correct them in a production environment?

}
}

type Favorite struct {

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 is the relation between user -> favorite established in the designed model? Can you elaborate a bit on how would you model this differently into a DB?

Comment on lines +106 to +110
var req CreateFavoriteRequestModel
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
helper.WriteJSONError(w, http.StatusBadRequest, fmt.Errorf("invalid request body"), nil)
return
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In the current implementation, a chart favorite could have the same data structure as an insight or audience without any differentiation or validation. What concerns does this raise in production and how would you address them if you had more time?

Comment on lines +73 to +87
fav, err := c.favoriteServices.Queries.GetFavoriteHandler.Handle(
queries.GetFavoriteRequest{
UserID: userID,
FavoriteID: favoriteID,
},
)

if err != nil {
helper.WriteJSONError(w, http.StatusInternalServerError, err, nil)
return
}
if fav == nil {
helper.WriteJSONError(w, http.StatusNotFound, fmt.Errorf("favorite not found"), nil)
return
}

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 trace what happens here when a favorite does not exist for a user? Are there any issues? If yes, how would you fix it and how would you ensure this doesn't occur again in the future?

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.

3 participants