Skip to content

Commit

Permalink
Initial commit: Add GWI Platform project
Browse files Browse the repository at this point in the history
- Implement core server functionality
- Add asset management (Chart, Insight, Audience)
- Include user and favorites handling
- Set up basic authentication middleware
- Implement logging with daily rotation
- Add integration tests
  • Loading branch information
tkrinas committed Jul 13, 2024
1 parent 9e8b5ed commit d0a0874
Show file tree
Hide file tree
Showing 35 changed files with 4,148 additions and 18 deletions.
7 changes: 7 additions & 0 deletions .env
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Ignore the entire app/logs directory
app/logs/
185 changes: 167 additions & 18 deletions README.md
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)
16 changes: 16 additions & 0 deletions app/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM golang:1.20.5-alpine

Check failure on line 1 in app/Dockerfile

View check run for this annotation

Wiz GWI / Wiz IaC Scanner

Missing User Instruction

Rule ID: 0b5e0683-5a06-4bcd-ac73-28249add06df Severity: High Resource: FROM={{golang:1.20.5-alpine}} A user should be specified in the dockerfile, otherwise the image will run as root
Raw output
Expected: The 'Dockerfile' should contain the 'USER' instruction
Found: The 'Dockerfile' does not contain any 'USER' instruction

Check notice on line 1 in app/Dockerfile

View check run for this annotation

Wiz GWI / Wiz IaC Scanner

Healthcheck Instruction Missing

Rule ID: 704ee966-67b2-4219-871f-12a7e5126cb1 Severity: Low Resource: FROM={{golang:1.20.5-alpine}} Ensure that HEALTHCHECK is being used. The HEALTHCHECK instruction tells Docker how to test a container to check that it is still working
Raw output
Expected: Dockerfile should contain instruction 'HEALTHCHECK'
Found: Dockerfile doesn't contain instruction 'HEALTHCHECK'

WORKDIR /app

COPY go.mod ./

Check notice on line 5 in app/Dockerfile

View check run for this annotation

Wiz GWI / Wiz IaC Scanner

Multiple RUN, ADD, COPY, Instructions Listed

Rule ID: 69c79a69-14d7-4718-b485-810a7729049c Severity: Low Resource: FROM={{golang:1.20.5-alpine}}.{{COPY go.mod ./}} Multiple commands (RUN, Copy, And) should be grouped in order to reduce the number of layers.
Raw output
Expected: There isn´t any COPY instruction that could be grouped
Found: There are COPY instructions that could be grouped
COPY go.sum ./

RUN go mod download

COPY . .

RUN go build -o main .

EXPOSE 8080

CMD ["./main"]
30 changes: 30 additions & 0 deletions app/assets/asset.go
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
}
76 changes: 76 additions & 0 deletions app/assets/asset_test.go
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)
}
}
}
Loading

0 comments on commit d0a0874

Please sign in to comment.