-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
GWI Platform project #20
Open
tkrinas
wants to merge
1
commit into
GlobalWebIndex:main
Choose a base branch
from
tkrinas:feature/develop
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
DB_ROOT_PASSWORD=password | ||
DB_NAME=gwi | ||
DB_USER=nero | ||
DB_PASSWORD=password | ||
|
||
APP_HOST=localhost | ||
APP_PORT=8080 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# Ignore the entire app/logs directory | ||
app/logs/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,180 @@ | ||
# GlobalWebIndex Engineering Challenge | ||
# GWI Assets Platform | ||
|
||
## Introduction | ||
## Overview | ||
|
||
This challenge is designed to give you the opportunity to demonstrate your abilities as a software engineer and specifically your knowledge of the Go language. | ||
GWI Platform is a robust web application that manages user data, assets, and favorites. | ||
It provides RESTful API endpoints for various operations including user management, asset handling, and user favorites. | ||
|
||
On the surface the challenge is trivial to solve, however you should choose to add features or capabilities which you feel demonstrate your skills and knowledge the best. For example, you could choose to optimise for performance and concurrency, you could choose to add a robust security layer or ensure your application is highly available. Or all of these. | ||
## Features | ||
|
||
Of course, usually we would choose to solve any given requirement with the simplest possible solution, however that is not the spirit of this challenge. | ||
- User management (add) | ||
- Asset management (add, delete, modify, get) | ||
- User favorites handling | ||
- Database integration with MySQL | ||
- Middleware for logging and user authentication | ||
- Graceful server shutdown | ||
|
||
## Challenge | ||
## Project Structure | ||
|
||
Let's say that in GWI platform all of our users have access to a huge list of assets. We want our users to have a peronal list of favourites, meaning assets that favourite or “star” so that they have them in their frontpage dashboard for quick access. An asset can be one the following | ||
* Chart (that has a small title, axes titles and data) | ||
* Insight (a small piece of text that provides some insight into a topic, e.g. "40% of millenials spend more than 3hours on social media daily") | ||
* Audience (which is a series of characteristics, for that exercise lets focus on gender (Male, Female), birth country, age groups, hours spent daily on social media, number of purchases last month) | ||
e.g. Males from 24-35 that spent more than 3 hours on social media daily. | ||
gwi-platform/ | ||
├── README.md | ||
├── app | ||
│ ├── Dockerfile | ||
│ ├── assets | ||
│ │ ├── asset.go | ||
│ │ ├── asset_test.go | ||
│ │ ├── audience.go | ||
│ │ ├── audience_test.go | ||
│ │ ├── chart.go | ||
│ │ ├── chart_test.go | ||
│ │ ├── insight.go | ||
│ │ ├── insight_test.go | ||
│ │ └── queries.go | ||
│ ├── auth | ||
│ │ └── auth.go | ||
│ ├── database | ||
│ │ └── database.go | ||
│ ├── go.mod | ||
│ ├── go.sum | ||
│ ├── handlers | ||
│ │ ├── asset_handlers.go | ||
│ │ ├── favorite_handlers.go | ||
│ │ ├── handlers.go | ||
│ │ ├── queries.go | ||
│ │ └── users_handlers.go | ||
│ ├── html | ||
│ │ ├── api_docs.html | ||
│ │ └── index.html | ||
│ ├── logs | ||
│ │ └── app.log | ||
│ ├── main.go | ||
│ ├── models | ||
│ │ └── models.go | ||
│ ├── server | ||
│ │ ├── app.go | ||
│ │ └── app_test.go | ||
│ └── utils | ||
│ └── utils.go | ||
├── db | ||
│ └── init.sql | ||
├── docker-compose.yml | ||
└── test | ||
├── Dockerfile | ||
├── go.mod | ||
└── main.go | ||
|
||
Build a web server which has some endpoint to receive a user id and return a list of all the user’s favourites. Also we want endpoints that would add an asset to favourites, remove it, or edit its description. Assets obviously can share some common attributes (like their description) but they also have completely different structure and data. It’s up to you to decide the structure and we are not looking for something overly complex here (especially for the cases of audiences). There is no need to have/deploy/create an actual database although we would like to discuss about storage options and data representations. | ||
|
||
Note that users have no limit on how many assets they want on their favourites so your service will need to provide a reasonable response time. | ||
## Prerequisites | ||
|
||
A working server application with functional API is required, along with a clear readme.md. Useful and passing tests would be also be viewed favourably | ||
- Go 1.20 or higher | ||
- MySQL 8.0 or higher | ||
|
||
It is appreciated, though not required, if a Dockerfile is included. | ||
## Setup | ||
|
||
## Submission | ||
1. Clone the repository: | ||
- git clone https://github.com/your-username/gwi-platform.git | ||
- cd gwi-platform | ||
|
||
Just create a fork from the current repo and send it to us! | ||
2. Set up the database: | ||
- Create a MySQL database | ||
- Update the database connection details in `database/database.go` | ||
|
||
Good luck, potential colleague! | ||
3. Install dependencies: | ||
- cd app | ||
- go mod tidy | ||
|
||
4. Build the application: | ||
- go build -o gwi-platform main.go | ||
|
||
5. Run the application: | ||
- ./gwi-platform | ||
|
||
## API Endpoints | ||
|
||
### User Management | ||
|
||
- `POST /user/add`: Add a new user | ||
|
||
### Asset Management | ||
|
||
- `POST /assets/add`: Add a new asset | ||
- `DELETE /assets/delete`: Delete an asset | ||
- `PUT /assets/modify`: Modify an existing asset | ||
- `GET /assets/get`: Retrieve an asset | ||
|
||
### User Favorites | ||
|
||
- `POST /user/favorite/add`: Add a favorite for a user | ||
- `GET /user/favorites/`: Get detailed favorites for a user | ||
|
||
### Other Endpoints | ||
|
||
- `GET /`: Home endpoint | ||
- `GET /docs`: Documentation endpoint | ||
- `GET /ping`: Health check endpoint | ||
|
||
## Authentication | ||
|
||
The application implements a basic authentication system through the `UserStatusAuth` middleware, | ||
located in the `auth` package. This middleware serves as a secondary authentication layer, | ||
focusing on user status verification rather than primary authentication, | ||
which should be another application eg a CAS. | ||
|
||
## Logging | ||
|
||
Request and response logging is implemented using the `LogHandler` middleware in the `utils` package. | ||
|
||
## Database | ||
|
||
The application uses MySQL for data persistence. Database operations are handled in the `database` package. | ||
|
||
### Docker Compose | ||
|
||
The `docker-compose.yml` file defines the services needed to run the application: | ||
|
||
- `app`: The main application service | ||
- `db`: MySQL database service | ||
- `test`: Service for running integration tests | ||
|
||
To start the services: | ||
- docker-compose up -d | ||
|
||
This will run the integration tests defined in `test/main.go` against the running application. | ||
|
||
The integration tests cover: | ||
- User creation | ||
- Asset management (creation, retrieval, modification, deletion) | ||
- User favorites operations | ||
|
||
These tests help ensure that all parts of the system are working together as expected in a new environment. | ||
|
||
|
||
## Future Work | ||
|
||
### Enhance Test Coverage | ||
1. **Unit Tests** | ||
- Implement comprehensive unit tests for all packages | ||
|
||
2. **Integration Tests** | ||
- Expand current integration test suite to cover more complex scenarios | ||
- Introduce performance tests to ensure scalability | ||
- Rewrite the current integration test suite using a BDD (Behavior-Driven Development). | ||
eg | ||
```gherkin | ||
Feature: User Favorites Management | ||
|
||
Scenario: User adds an asset to favorites | ||
Given a user {testUser} exists in the system | ||
And an asset {testAsset} of type "CHART" exists | ||
When the user adds the asset to their favorites | ||
Then the asset should appear in the user's list of favorites | ||
And the total count of user's favorites should increase by 1 | ||
|
||
|
||
### Performance Optimization | ||
1. **Database Optimization** | ||
- Introduce caching mechanisms for frequently accessed data | ||
|
||
### Documentation | ||
3. **API Documentation** | ||
- Provide interactive API exploration tools (e.g., Swagger) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
FROM golang:1.20.5-alpine | ||
|
||
WORKDIR /app | ||
|
||
COPY go.mod ./ | ||
COPY go.sum ./ | ||
|
||
RUN go mod download | ||
|
||
COPY . . | ||
|
||
RUN go build -o main . | ||
|
||
EXPOSE 8080 | ||
|
||
CMD ["./main"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package assets | ||
|
||
import ( | ||
"context" | ||
"gwi-platform/database" | ||
"gwi-platform/models" | ||
) | ||
|
||
type Asset interface { | ||
Add(ctx context.Context, db database.DB) error | ||
Delete(ctx context.Context, db database.DB, id models.ID) error | ||
Modify(ctx context.Context, db database.DB) error | ||
Get(ctx context.Context, db database.DB, id models.ID) (interface{}, error) | ||
} | ||
|
||
type AssetFactory func() Asset | ||
|
||
var assetFactories = map[string]AssetFactory{ | ||
"INSIGHT": func() Asset { return &Insight{} }, | ||
"CHART": func() Asset { return &Chart{} }, | ||
"AUDIENCE": func() Asset { return &Audience{} }, | ||
} | ||
|
||
func GetAsset(assetType string) (Asset, bool) { | ||
factory, exists := assetFactories[assetType] | ||
if !exists { | ||
return nil, false | ||
} | ||
return factory(), true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package assets | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func TestGetAsset(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
assetType string | ||
wantType reflect.Type | ||
wantOk bool | ||
}{ | ||
{ | ||
name: "Get Insight Asset", | ||
assetType: "INSIGHT", | ||
wantType: reflect.TypeOf(&Insight{}), | ||
wantOk: true, | ||
}, | ||
{ | ||
name: "Get Chart Asset", | ||
assetType: "CHART", | ||
wantType: reflect.TypeOf(&Chart{}), | ||
wantOk: true, | ||
}, | ||
{ | ||
name: "Get Audience Asset", | ||
assetType: "AUDIENCE", | ||
wantType: reflect.TypeOf(&Audience{}), | ||
wantOk: true, | ||
}, | ||
{ | ||
name: "Get Invalid Asset", | ||
assetType: "INVALID", | ||
wantType: nil, | ||
wantOk: false, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
got, ok := GetAsset(tt.assetType) | ||
|
||
if ok != tt.wantOk { | ||
t.Errorf("GetAsset() ok = %v, want %v", ok, tt.wantOk) | ||
return | ||
} | ||
|
||
if !tt.wantOk { | ||
if got != nil { | ||
t.Errorf("GetAsset() got = %v, want nil", got) | ||
} | ||
return | ||
} | ||
|
||
if reflect.TypeOf(got) != tt.wantType { | ||
t.Errorf("GetAsset() got type = %v, want %v", reflect.TypeOf(got), tt.wantType) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestAssetInterface(t *testing.T) { | ||
assets := []Asset{ | ||
&Insight{}, | ||
&Chart{}, | ||
&Audience{}, | ||
} | ||
|
||
for _, asset := range assets { | ||
if _, ok := asset.(Asset); !ok { | ||
t.Errorf("%T does not implement Asset interface", asset) | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for providing a containerized version here. To reduce the image size, could you consider using a multi-stage build? Are there any other techniques you might consider for minimizing the Docker image size?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could either have the app to build before we build the image and copy only the binary, eg if we had this set up in a pipeline build, or we can have a builder image to generate the binary and the copy to the runtime image.