Selfsolver project provides a platform for support self-service.
Requires poetry
and python>=3.8
.
Run poetry install
to install dependencies.
Copy the .env.example
to .env
for development environments.
Use poetry run flask generate-secret
to generate entropic enough secrets. (see .env.example
)
Finally, run poetry run flask run
to run the app.
Run poetry run pytest --cov
to run all the tests and see a basic coverage report.
Make sure the TEST_DATABASE_URL
environment variable is set correctly.
Run poetry run flask database setup
.
After any changes to the models, you may need to recreate the tables.
Run poetry run flask database reset
. THIS WILL DESTROY ALL APP DATA.
There's no http api to create companies (by design).
Run poetry run flask company create
to create a company.
The id will be mentioned in the command output.
There's no http api for signup (by design).
Run poetry run flask user create {COMPANY_ID} {EMAIL} {PASSWORD}
to create a user with those credentials.
E.g. poetry run flask user create 1 email@example.com correct-horse-battery-staple
.
Run poetry run flask seed
to create user, company, devices, models, brands, defects and locations.
Run poetry run flask seed {COMPANY_ID}
if you want to tie this data to a specific company.
This project exposes a (mostly) RESTful API. Request and response contents are expected to be JSON. Authentication through JWT should go in the Authorization
header, under the bearer token scheme. Authentication is required except where indicated.
The implemented endpoints are documented below:
Requires credentials:
email
: the user email.password
: the user password.
Responds with 400 if data is missing (or not json).
Responds with 401 if credentials don't match any user.
Responds with 200, JWT available under access_token
property.
Responds 200 with an array of tickets and related data.
[
{
"closed": null,
"created": "2020-11-12T21:48:01.132227",
"device": {
"family": {
"brand": {
"id": 1,
"name": "Valdez, Wilson and Montgomery"
},
"brand_id": 1,
"id": 1,
"name": "DarkGreen"
},
"family_id": 1,
"id": 1,
"location": {
"company_id": 1,
"id": 1,
"label": "Vazquezfurt"
},
"location_id": 1,
"serial": "2927009444124"
},
"device_id": 1,
"forwarded": null,
"id": 1,
"solution_id": null
},
{ ... },
...
]
Responds with an array of device objects with related data (not normalized).
[
{
"family": {
"brand": {
"id": 1,
"name": "Valdez, Wilson and Montgomery"
},
"brand_id": 1,
"id": 1,
"name": "DarkGreen"
},
"family_id": 1,
"id": 1,
"location": {
"company_id": 1,
"id": 1,
"label": "Vazquezfurt"
},
"location_id": 1,
"serial": "2927009444124"
},
{ ... },
...
]
Responds with an array of defect objects.
[
{
"description": "O aparelho n\u00e3o liga.",
"id": 1
},
{ ... },
...
]
Requires new ticket data:
defect
: defect id (int) for defects in this ticket.device
: device id (int) for the device this ticket is about.
Responds with 400 if data is missing (or not json). Responds with 404 if either defect or device are not found in database. Responds with 403 if device is not allocated to user's company. Responds with 200 and the new ticket data and related data (not normalized).
{
"closed": null,
"created": "2020-11-22T21:53:12.766012",
"device": {
"family": {
"brand": {
"id": 2,
"name": "Thompson, Turner and Love"
},
"brand_id": 2,
"id": 2,
"name": "Ivory"
},
"family_id": 2,
"id": 2,
"location": {
"company_id": 1,
"id": 2,
"label": "Meganmouth"
},
"location_id": 2,
"serial": "0279271851103"
},
"device_id": 2,
"forwarded": null,
"id": 15,
"solution_id": null
}
Responds with an array of possible solutions for ticket. Solution description is NON-SANITIZED markdown.
[
{
"description": "\nPrimeiro, confira se a **tomada** est\u00e1 ligada.\n\n\nAl\u00e9m disso, confira tamb\u00e9m:\n\n* o cabo\n* a tomada\n* a energia da casa\n",
"id": 1
},
{
"description": "\n* Remova os atolamentos e elimine o erro para continuar imprimindo.\n* Verifique a bandeja de entrada, bandeja de sa\u00edda, \u00e1rea de acesso ao cartucho de tinta e parte de baixo da impressora, e remova todo papel atolado.\n* Remova todo o papel da bandeja de entrada.\n\nhttps://www.youtube.com/watch?v=9Y-HYkMggwg\n",
"id": 5
},
{ ... },
...
]
Requires closing ticket info. One of the following is required:
- solution: id of the solution that closed the ticket.
- forwarded: true if the ticket was forwarded to support team.
Responds with 400 if data is missing (or not json).
Responds with 404 if either ticket_id
or solution
is not in database.
Responds with 403 if ticket was not attached to device allocated to current user's company.
Responds with 200 and the updated ticket and related data (not normalized).
{
"closed": "2020-11-22T19:04:45.206516",
"created": "2020-11-12T21:48:01.132227",
"device": {
"family": {
"brand": {
"id": 1,
"name": "Valdez, Wilson and Montgomery"
},
"brand_id": 1,
"id": 1,
"name": "DarkGreen"
},
"family_id": 1,
"id": 1,
"location": {
"company_id": 1,
"id": 1,
"label": "Vazquezfurt"
},
"location_id": 1,
"serial": "2927009444124"
},
"device_id": 1,
"forwarded": null,
"id": 1,
"solution_id": 1
}
Requires heroku command line.
# First, login to heroku.
$ heroku login
# Proceed to create an app
$ heroku create
# Set up a poetry-enabled buildpack
$ heroku buildpacks:add https://github.com/moneymeets/python-poetry-buildpack.git
$ heroku buildpacks:add heroku/python
# Enable the postgresql addon
$ heroku addons:create heroku-postgresql:hobby-dev
# Set buildpack-specific environment variables
$ heroku config:set POETRY_VERSION=1.0.10
$ heroku config:set DISABLE_POETRY_CREATE_RUNTIME_FILE=1
# Set app-specific environment variables
$ heroku config:set FLASK_APP=selfsolver/app.py
$ heroku config:set JWT_SECRET_KEY=`poetry run flask generate-secret`
$ heroku config:set PASSWORD_PEPPER=`poetry run flask generate-secret`
# Deploy to heroku using git
$ git push heroku main
# Don't forget to setup database once the app is running
$ heroku run flask database setup