Skip to content

Done #201

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

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open

Done #201

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
PORT=4010
RATE_LIMIT=60
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
main
.vscode
__debug_bin*
.env
.DS_Store
41 changes: 41 additions & 0 deletions Assignment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Real Image Challenge 2016

In the cinema business, a feature film is usually provided to a regional distributor based on a contract for exhibition in a particular geographical territory.

Each authorization is specified by a combination of included and excluded regions. For example, a distributor might be authorzied in the following manner:
```
Permissions for DISTRIBUTOR1
INCLUDE: INDIA
INCLUDE: UNITEDSTATES
EXCLUDE: KARNATAKA-INDIA
EXCLUDE: CHENNAI-TAMILNADU-INDIA
```
This allows `DISTRIBUTOR1` to distribute in any city inside the United States and India, *except* cities in the state of Karnataka (in India) and the city of Chennai (in Tamil Nadu, India).

At this point, asking your program if `DISTRIBUTOR1` has permission to distribute in `CHICAGO-ILLINOIS-UNITEDSTATES` should get `YES` as the answer, and asking if distribution can happen in `CHENNAI-TAMILNADU-INDIA` should of course be `NO`. Asking if distribution is possible in `BANGALORE-KARNATAKA-INDIA` should also be `NO`, because the whole state of Karnataka has been excluded.

Sometimes, a distributor might split the work of distribution amount smaller sub-distiributors inside their authorized geographies. For instance, `DISTRIBUTOR1` might assign the following permissions to `DISTRIBUTOR2`:

```
Permissions for DISTRIBUTOR2 < DISTRIBUTOR1
INCLUDE: INDIA
EXCLUDE: TAMILNADU-INDIA
```
Now, `DISTRIBUTOR2` can distribute the movie anywhere in `INDIA`, except inside `TAMILNADU-INDIA` and `KARNATAKA-INDIA` - `DISTRIBUTOR2`'s permissions are always a subset of `DISTRIBUTOR1`'s permissions. It's impossible/invalid for `DISTRIBUTOR2` to have `INCLUDE: CHINA`, for example, because `DISTRIBUTOR1` isn't authorized to do that in the first place.

If `DISTRIBUTOR2` authorizes `DISTRIBUTOR3` to handle just the city of Hubli, Karnataka, India, for example:
```
Permissions for DISTRIBUTOR3 < DISTRIBUTOR2 < DISTRIBUTOR1
INCLUDE: HUBLI-KARNATAKA-INDIA
```
Again, `DISTRIBUTOR2` cannot authorize `DISTRIBUTOR3` with a region that they themselves do not have access to.

We've provided a CSV with the list of all countries, states and cities in the world that we know of - please use the data mentioned there for this program. *The codes you see there may be different from what you see here, so please always use the codes in the CSV*. This Readme is only an example.

Write a program in any language you want (If you're here from Gophercon, use Go :D) that does this. Feel free to make your own input and output format / command line tool / GUI / Webservice / whatever you want. Feel free to hold the dataset in whatever structure you want, but try not to use external databases - as far as possible stick to your langauage without bringing in MySQL/Postgres/MongoDB/Redis/Etc.

To submit a solution, fork this repo and send a Pull Request on Github.

For any questions or clarifications, raise an issue on this repo and we'll answer your questions as fast as we can.


8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
running:
CompileDaemon -build="go build -o ./cmd/main ./cmd" -command=./cmd/main

run:
go run ./cmd/main.go

test:
go test -v ./tests/...
252 changes: 227 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,243 @@
# Real Image Challenge 2016
# 🏢 Distribution Management System

In the cinema business, a feature film is usually provided to a regional distributor based on a contract for exhibition in a particular geographical territory.
This project was developed as part of a machine task for a company interview process. It implements a distribution management system with features for managing distributors and their permissions across different regions.

Each authorization is specified by a combination of included and excluded regions. For example, a distributor might be authorzied in the following manner:
```
Permissions for DISTRIBUTOR1
INCLUDE: INDIA
INCLUDE: UNITEDSTATES
EXCLUDE: KARNATAKA-INDIA
EXCLUDE: CHENNAI-TAMILNADU-INDIA
```
This allows `DISTRIBUTOR1` to distribute in any city inside the United States and India, *except* cities in the state of Karnataka (in India) and the city of Chennai (in Tamil Nadu, India).
## 🎯 Core Features

📦 **Distributor Management**
- Add new distributors
- Remove existing distributors
- List all distributors

🔑 **Permission Management**
- Allow distribution rights over a region
- Disallow distribution rights over a region
- Check permission status over specific regions (Responses: FULLY_ALLOWED/PARTIALLY_ALLOWED/FULLY_DENIED)
- View distributor-specific permissions (As text(contract) or JSON)
- Contract-based permission management

🌍 **Region Management**
- Hierarchical region structure (Country → Province → City)
- Region validation against cities.csv database
- Region code format: "CITYCODE-PROVINCECODE-COUNTRYCODE"

### Region Format
- Countries: 2-letter code (e.g., "IN", "US")
- Provinces: 2-letter code + country (e.g., "TN-IN")
- Cities: City code + province + country (e.g., "CENAI-TN-IN")

### 🌍 Region Management

#### 1. Get Countries
- **Endpoint**: `GET /regions/countries`
- **Description**: Get list of available countries
- **Success Response**: 200 OK with countries list

#### 2. Get Provinces
- **Endpoint**: `GET /regions/provinces/:countryCode`
- **Description**: Get provinces in a country
- **Path Parameter**: `countryCode`
- **Success Response**: 200 OK with provinces list

#### 3. Get Cities
- **Endpoint**: `GET /regions/cities/:countryCode/:provinceCode`
- **Description**: Get cities in a province
- **Path Parameters**:
- `countryCode`
- `provinceCode`
- **Success Response**: 200 OK with cities list

## 🏗️ Technical Implementation

### 🎨 Architecture
- **Clean Architecture Pattern**
- Separation of concerns with handlers and business logic
- RESTful API design
- Modular component structure

### 🛡️ Security Enhancements
- **Rate Limiting**: Prevents excessive API requests to safeguard system resources
- **Data Validation & Sanitization**: Ensures proper input handling to avoid malicious data

### 🔧 Key Components
1. **Route Handlers** (`internal/handler`)
- HTTP request handling
- Input validation
- Response formatting
- Error handling

2. **Data Management**
- In-memory data storage
- Thread-safe operations using `sync.RWMutex`
- CSV-based region validation
- Contract validation and processing

### ⚙️ Technical Features
- Region validation against cities.csv
- Concurrent access handling with sync.RWMutex
- Hierarchical permission system
- Contract-based permission management
- Region-based distribution control

### 🧪 Integration Testing
- Integration tests implemented to verify system functionality
- Tests cover contract validation, permissions management and their inheritance.

At this point, asking your program if `DISTRIBUTOR1` has permission to distribute in `CHICAGO-ILLINOIS-UNITEDSTATES` should get `YES` as the answer, and asking if distribution can happen in `CHENNAI-TAMILNADU-INDIA` should of course be `NO`. Asking if distribution is possible in `BANGALORE-KARNATAKA-INDIA` should also be `NO`, because the whole state of Karnataka has been excluded.
## 📝 Technical Notes
- Thread-safe operations using read-write mutex locks
- CSV-based region validation
- Hierarchical region structure validation
- Contract template validation

Sometimes, a distributor might split the work of distribution amount smaller sub-distiributors inside their authorized geographies. For instance, `DISTRIBUTOR1` might assign the following permissions to `DISTRIBUTOR2`:

## 🚀 How to use

### Prerequisites
- Go 1.23 or higher
- Git

### Installation
1. Clone the repository
```bash
git clone https://github.com/AbdulRahimOM/machine_task-challenge2016.git
cd machine_task-challenge2016
```
Permissions for DISTRIBUTOR2 < DISTRIBUTOR1
INCLUDE: INDIA
EXCLUDE: TAMILNADU-INDIA

2. Set up environment variables
```bash
touch .env
echo PORT="4010" >> .env # Or any other port number
echo RATE_LIMIT="60" >> .env # Requests per minute limit
```
Now, `DISTRIBUTOR2` can distribute the movie anywhere in `INDIA`, except inside `TAMILNADU-INDIA` and `KARNATAKA-INDIA` - `DISTRIBUTOR2`'s permissions are always a subset of `DISTRIBUTOR1`'s permissions. It's impossible/invalid for `DISTRIBUTOR2` to have `INCLUDE: CHINA`, for example, because `DISTRIBUTOR1` isn't authorized to do that in the first place.

If `DISTRIBUTOR2` authorizes `DISTRIBUTOR3` to handle just the city of Hubli, Karnataka, India, for example:
3. Build the project
```bash
make build
```
Permissions for DISTRIBUTOR3 < DISTRIBUTOR2 < DISTRIBUTOR1
INCLUDE: HUBLI-KARNATAKA-INDIA

4. Run the server
```bash
./bin/app
```
Again, `DISTRIBUTOR2` cannot authorize `DISTRIBUTOR3` with a region that they themselves do not have access to.

We've provided a CSV with the list of all countries, states and cities in the world that we know of - please use the data mentioned there for this program. *The codes you see there may be different from what you see here, so please always use the codes in the CSV*. This Readme is only an example.
The server will start on `localhost:4010` (or the port specified in the .env file).


## 🛠️ API Endpoints

### 📦 Distributor Management

#### 1. Add Distributor
- **Endpoint**: `POST /distributor`
- **Description**: Register a new distributor in the system
- **Request Body**:
```json
{
"distributor": "distributor_name"
}
```
- **Success Response**: 201 Created

#### 2. Remove Distributor
- **Endpoint**: `DELETE /distributor/:distributor`
- **Description**: Remove an existing distributor from the system
- **Path Parameter**: `distributor` - Name of the distributor
- **Success Response**: 200 OK

#### 3. Get Distributors
- **Endpoint**: `GET /distributor`
- **Description**: Retrieve list of all distributors
- **Success Response**: 200 OK with distributors list

### 🔑 Permission Management

#### 1. Check Distribution Permission
- **Endpoint**: `GET /permission/check`
- **Description**: Verify distribution permission status for a region
- **Query Parameters**:
- `distributor`: Distributor name
- `region`: Region to check
- **Success Response**: 200 OK with permission status

#### 2. Allow Distribution
- **Endpoint**: `POST /permission/allow`
- **Description**: Grant distribution rights for a region
- **Request Body**:
```json
{
"distributor": "distributor_name",
"region": "region_name" // Example: "KLRAI-TN-IN"
}
```
- **Success Response**: 200 OK

#### 3. Apply Contract
- **Endpoint**: `POST /permission/contract`
- **Description**: Apply distribution contract with permissions
- **Success Response**: 200 OK

#### 4. Disallow Distribution
- **Endpoint**: `POST /permission/disallow`
- **Description**: Revoke distribution rights
- **Request Body**:
```json
{
"distributor": "distributor_name",
"region": "region_name"
}
```
- **Success Response**: 200 OK

Write a program in any language you want (If you're here from Gophercon, use Go :D) that does this. Feel free to make your own input and output format / command line tool / GUI / Webservice / whatever you want. Feel free to hold the dataset in whatever structure you want, but try not to use external databases - as far as possible stick to your langauage without bringing in MySQL/Postgres/MongoDB/Redis/Etc.
#### 5. Get Distributor Permissions
- **Endpoint**: `GET /permission/:distributor`
- **Description**: Retrieve all permissions for a distributor in either JSON or contract text format
- **Path Parameter**: `distributor` - Name of the distributor
- **Query Parameter**: `type` - Response format type ("json" or "text")
- `json`: Returns structured JSON format with permissions
- `text`: Returns formatted contract-like text representation
- **Success Response**: 200 OK with permissions in requested format
- **Response Examples**:
- Text format (`type=text`):
```text
Permissions for DISTRIBUTOR1
INCLUDE: IN
INCLUDE: US
INCLUDE: ONATI-SS-ES
EXCLUDE: KA-IN
EXCLUDE: CENAI-TN-IN
```
- JSON format (`type=json`):
```json
{
"status": true,
"resp_code": "SUCCESS",
"data": {
"Distributor": "DISTRIBUTOR1",
"Included": [
"IN",
"US",
"ONATI-SS-ES"
],
"Excluded": [
"KA-IN",
"CENAI-TN-IN"
]
}
}
```

To submit a solution, fork this repo and send a Pull Request on Github.

For any questions or clarifications, raise an issue on this repo and we'll answer your questions as fast as we can.
## 🚀 Potential Improvements (if assignment is flexible)

⏳ **Contract-expiry**
- When contract expires, cascade expiration to all dependent sub-contracts
- Inheritance on contract, and not on permission
↳ This would be more matching to the real-world scenario, where permissions are time-based and amendable contracts

🔐 **Distributor Self-Service Portal**
- Implement secure authentication system
- Enable distributors to manage their own sub-contracts
↳ Create and modify sub-contracts within their permitted scope
↳ Monitor contract status and expiration dates
↳ View inheritance chain and dependencies
↳ Notify them when contract expires
28 changes: 28 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package main

import (
"challenge16/internal/config"
"challenge16/internal/regions"
"challenge16/internal/server"
"fmt"
)

const (
csvFile = "cities.csv"
envPath = ".env"
)

func main() {
//initialize the region data
regions.LoadDataIntoMap(csvFile)

//initialize the environment configuration
config.LoadEnv(envPath)

app := server.NewServer(config.RateLimit)

err := app.Listen(fmt.Sprintf(":%s", config.Port))
if err != nil {
panic("Couldn't start the server. Error: " + err.Error())
}
}
36 changes: 36 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
module challenge16

go 1.23.2

require (
github.com/go-playground/validator/v10 v10.25.0
github.com/gofiber/fiber/v2 v2.52.6
github.com/joho/godotenv v1.5.1
github.com/stretchr/testify v1.8.4
)

require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/tinylib/msgp v1.2.5 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading