# OWS Project Manager
OWS Project Manager is a microservice that performs CRU(D) operations on client projects. It is intended to be called through [Grass](https://github.com/theorchard/grass).

## High-Level Flow
* [Lucid Chart](https://www.lucidchart.com/documents/edit/46a98207-9170-4e81-8500-a0fa021ee68c)

## Endpoints
 1. **POST /project**: insert a new project
 2. **PUT /project/(project_id)**: update an existing project. Project_id is the unique identifier for a project and is automatically generated by the database.
 3. **GET /project/(project_id)**: get a single project
 4. **GET /project/(project_id)/product/(product_id)**: get a single product.
 5. **GET /project/(project_id)/products**: get a list of digital and physical products for a project, or empty list if no products.
 6. **GET /projects**: get a paginated list of projects for a vendor_id and/or subaccount_id

### POST /project
#### Input
[JSON object](https://github.com/theorchard/ows-project-manager/blob/master/project_manager/validation_schema/posted_project.py) containing the following fields:
 1. *project_code* (string, required): user-generated identifier for a project. This identifier is unique within a vendor_id/subaccount_id combination
 2. *project_name* (string, required): user-generated free-text name of the project
 3. *subaccount_id* (int, optional): included only if user is a distributor acting on behalf of a subaccount

Example:
```
{
	"project_code": "test_project_code",
	"project_name": "test_project_name"
}
```

#### Output
JSON object representing the newly-created project. Example:
```
{
	"project_id": 136,
	"project_code": "test_project_code",
	"project_name": "test_project_name",
	"vendor_id": 12353,
	"subaccount_id": 0,
	"created_date_utc": "2016-03-10 18:14:36.418956Z",
	"updated_date_utc": "2016-03-10 18:14:36.418956Z",
	"correlation_id": "73c98f1e-e6ec-11e5-a897-a0999b161eef"
}
```

### PUT /project/(project__id)
#### Input
[JSON object](https://github.com/theorchard/ows-project-manager/blob/master/project_manager/validation_schema/put_project.py) containing the following fields:
1. project_name (string, required): user-generated free-text name of the project

Example:
```
{
	"project_name": "test_project_name_updated"
}
```

#### Output
JSON object representing the updated project plus the number of products in the project (product_count). Example:
```
{
	"project_id": 136,
	"project_code": "test_project_code",
	"project_name": "test_project_name_updated",
	"vendor_id": 12353,
	"subaccount_id": 0,
	"created_date_utc": "2016-03-10 18:14:36.418956Z",
	"updated_date_utc": "2016-03-10 18:27:29.171495Z",
	"product_count": 0
}
```

### GET /project/(project_id)
#### Output
JSON object representing the updated project plus the number of products in the project (product_count). Example:
```
{
	"project_id": 136,
	"project_code": "test_project_code",
	"project_name": "test_project_name_updated",
	"vendor_id": 12353,
	"subaccount_id": 0,
	"created_date_utc": "2016-03-10 18:14:36.418956Z",
	"updated_date_utc": "2016-03-10 18:27:29.171495Z",
	"product_count": 0
}
```

### GET /project/(project_id)/product/(product_id)
Retrieve basic data for a single digital or physical product for a project.

#### Output
If the project and product ids are valid:
JSON object representing the product
```
{
    'artist_names': ['Mike Jones'],
    'product_type': 'Music',
    'product_type_id': 1,
    'product_id': 1001,
    'release_name': 'Something Strange Here',
    'release_status': 'error_correction',
    'display_upc': '888831283041',
    'version': 'Deluxe',
    'distribution_format': {
        'name': 'DIGITAL',
        'context': 'digital'
    }
}
```

If the project or product ids are invalid:
404 status code and error json object:
```
{
    "code": "not_found_error",
    "message": "Unable to find product_id 0"
}
```

### GET /project/(project_id)/products
Get a list of digital and physical products for a project, or empty list if no product.

#### Output
JSON List of product objects associated with the project_id.
```
{
    'items': [{
        'artist_names': ['Mike Jones'],
        'product_type': 'Music',
        'product_type_id': 1,
        'product_id': 1001,
        'release_name': 'Something Strange Here',
        'release_status': 'error_correction',
        'display_upc': '888831283041',
        'version': 'Deluxe',
        'distribution_format': {
            'name': 'DIGITAL',
            'context': 'digital'
        }
    }, {
        'artist_names': ['Mike Jones'],
        'product_type': 'Music',
        'product_type_id': 1,
        'product_id': 1002,
        'release_name': 'Something Strange Here',
        'release_status': 'action_required',
        'display_upc': '889845667810',
        'version': 'Unremarkable',
        'distribution_format': {
            'name': 'CD',
            'context': 'physical'
        }
    }, {
        'artist_names': ['Mike Jones'],
        'product_type': 'Music',
        'product_type_id': 1,
        'product_id': 1003,
        'release_name': 'Something Strange Here',
        'release_status': 'transfer_to_content',
        'display_upc': '889845667872',
        'version': 'Target Special Edition',
        'distribution_format': {
            'name': 'DIGITAL',
            'context': 'digital'
        }
    }]
}
```

### GET /projects
Retrieve a paginated list of projects for the logged-in user. Pagination info will be used to retrieve the next subset of projects for the user.
#### Request parameters
 1. *page_offset* (int, optional, default 0): project to start at (integer, where first record is 0)
 2. *page_limit* (int, optional, default 50): number of projects to retrieve in single request (integer)
 3. *subaccount_id* (int, optional): if user is a distributor acting on behalf of subaccount, the subaccount_id is passed in as a request parameter.

#### Output
JSON object containing a list of projects (items) and pagination info. Example:
```
{
	"pagination": {
		"offset": 0,
		"limit": 50,
		"total_records": 1
	},
	"items": [{
		"project_id": 136,
		"project_code": "test_project_code",
		"project_name": "test_project_name_updated",
		"vendor_id": 12353,
		"subaccount_id": 0,
		"created_date_utc": "2016-03-10 18:14:36.418956Z",
		"updated_date_utc": "2016-03-10 18:27:29.171495Z",
		"product_count": 0
	}]
}
```

## Pagination
- Pagination limits the number of projects returned in a single request.
- *total_records*: total number of projects for account

Example series of requests if total_records is 130:
```
GET /projects/offset=0&limit=50  (get records 0 through 49)
GET /projects/offset=50&limit=50  (get records 50 through 99)
GET /projects/offset=100&limit=50  (get records 100 through 129)
```

## Headers
### Request Headers
Every request must contain the following headers:
 1. *Content-Type*: application/json
 2. *Correlation-Id*: uid to correlate logs
 3. *Grass-Account-Type*: header appended by Grass to indicate whether user is a vendor or subaccount.
 4. *Grass-Account-Id*: either the vendor_id or subaccount_id, depending on the value of Grass-Account-Type

Headers are validated by the [header validation schema](https://github.com/theorchard/ows-project-manager/blob/master/project_manager/validation_schema/header.py)

### Response Headers
Every response will contain the following headers:
 1. *Content-Type*: application/json
 2. *Correlation-Id*: uid to correlate logs (echoes back correlation_id from request header)

## Error Format
System and user errors will both be returned in JSON format containing the following fields:
 1. *code*: the class of error such as 'database_error', 'validation_error', 'authorization_error', or 'invalid_json_error'
 2. *message*: a more detailed error message or list of messages

Example of an error where the Grass-Account-Type in the header is invalid:
```
{
  "code": "validation_error",
  "message": "Value 'vendor1' for field '<obj>.Grass-Account-Type' is not in the enumeration: ['vendor', 'subaccount']"
}
```

## Authorization
The service does 2 types of authorization:
 1. Does the user have access to the given project_id? This is done by checking the project table to ensure that the vendor_id/subaccount_id is associated with that project_id.
 2. Does the vendor_id have access to the given subaccount_id? This will be done by calling the [OWS-Account Microservice](https://github.com/theorchard/ows-account).

If the user is unauthorized to perform the requested action, the service will return a status code of 403 (Forbidden) and an error json such as:
```
{
	"code": "authorization_error",
	"message": "Not the owner of this project"
}
```

## Types of Users

| Use case                                     | header.Grass-Account-Type | header.Grass-Account-Id           | request_json.subaccount_id        | Comment                                                                                                                               |
| -------------------------------------------  | ------------------------- | --------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Vendor acting on own behalf                  | vendor                    | vendor_id int                     | field not present in request json | ar.project.subaccount_id will be 0 (internal representation of NULL)                                                                  |
| Distributor acting on subaccount’s behalf    | vendor                    | vendor_id int                     | populated with subaccount_id      | Distributor is stored similarly to a vendor but is a different type of entity. Differentiated through the vendor.is_distributor flag. |
| Subaccount acting on own behalf              | subaccount                | subaccount_id int                 | field not present in request json | Service looks up parent vendor_id for given subaccount_id                                                                             |

## Jenkins Jobs
### PR Checker
* [Automatically run tests and flake8 when someone issues a Github PR](http://jenkins.theorchard.com:8080/job/ows-project-manager-pull-request/)

### Deploy
* [QA](http://jenkins.theorchard.com:8080/job/qa-ows-project-manager-deploy/)
* [Prod](http://jenkins.theorchard.com:8080/job/prod-ows-project-manager-deploy/)

## Logging and Alerting
- Logs are written to [Loggly](https://orchard.loggly.com/search)
- Exceptions are sent to [Sentry](https://app.getsentry.com/the-orchard/)

## Background Info
- [OWS-Project-Manager Tech Design](https://docs.google.com/document/d/1hR7xZp-9PqrglEZ4Ok1EQHt7afaivPXdIyxnpB1a2PY/edit#)
- [Project Q & A](https://docs.google.com/document/d/1R_Co3IKncMpFQALTtl93LFnmSzQaQ5WvOHLRlUNVdyE/edit)
- [Initial MVP](https://docs.google.com/document/d/1T6EbALpwRUrWi_mgSp9duuuO7PlxEgd3W43Ei7Ps5oM/edit?ts=56e1cb85)
