#18 Introduce basic table setup

This commit is contained in:
Christoph Lienhard 2019-10-13 22:48:27 +02:00
parent 64c16a000a
commit 1015341009
15 changed files with 59 additions and 854 deletions

View file

@ -1,646 +1,11 @@
# Candymat Backend
The backend providing the data and managing changing the data.
## Setup dev environment
## Development setup
### Install
Run inside this folder
```
./dev-setup.sh
```
### Postgres via Docker
missing
#### For conda users:
```conda create -n candymat-userapp-api python=3.7.4 flask=1.1.1```
```conda activate candymat-userapp-api```
### Run
Start the flask server locally under http://127.0.0.1:5000 with the follwing script:
```
./dev-start.sh
```
Observe dummy data for http://127.0.0.1:5000/api/kandidaten
#### For Windows
```set set FLASK_APP=flask-server/main.py```
```flask run```
# API Dokumentation
This is the API documentation for the candymat backend.
It documents all resources (soon to be) available.
## Overview
| Description | GET (all) | GET (single) | POST | PUT |
|---|---|---|---|---|
| candidates | /api/kandidaten | /api/kandidaten/3 | /api/kandidaten | /api/kandidaten/3 |
| categories for questions | /api/kategorien | /api/kategorien/2 | /api/kategorien | /api/kategorien/2 |
| questions | /api/fragen | /api/fragen/1 | /api/fragen | /api/fragen/1 |
| answers from all candidates | /api/antworten | | | |
| answers of a single candidate | /api/kandidaten/3/antworten | /api/kandidaten/3/antworten/2 | /api/kandidaten/3/antworten | /api/kandidaten/3/antworten/2 |
For some endpoints such as `/api/kandiaten` or `/answers` there are additional filter options.
## Kandidaten
### Get all candidates
#### Request
```
GET /api/kandidaten
```
#### Example Response
```
200 OK
Content-Type: application/json
```
```json
[
{
"id": 0,
"login": "musterma",
"vorname": "Max",
"name": "Mustermann",
"email": "max.mustermann@yahoo.com"
},
{
"id": 1,
"login": "musterer",
"vorname": "Erika",
"name": "Mustermann",
"email": "erika.mustermann@yahoo.com"
}
]
```
### Get the candidate with id `<id>` (not implemented yet)
#### Request
```
GET /api/kandidaten/<id>
```
#### Example Response
```
200 OK
Content-Type: application/json
```
```json
{
"id": 0,
"login": "musterma",
"vorname": "Max",
"name": "Mustermann",
"email": "max.mustermann@yahoo.com"
}
```
### Get the canditates filtered by some key (not implemented yet)
In principle every available key can be used.
The filter consists of a key-value pair, e.g. `name=Mustermann`.
To concatenate filters, separeate them with `&`, e.g. `name=Mustermann&id=0`
#### Example Request
```
GET /api/kandidaten?name=Mustermann
```
#### Example Response
```
200 OK
Content-Type: application/json
```
```json
[
{
"id": 0,
"login": "musterma",
"vorname": "Max",
"name": "Mustermann",
"email": "max.mustermann@yahoo.com"
},
{
"id": 1,
"login": "musterer",
"vorname": "Erika",
"name": "Mustermann",
"email": "erika.mustermann@yahoo.com"
}
]
```
### Create a new candidate (not implemented yet)
#### Request
```
POST /api/kandidaten
Content-Type: application/json
```
```json
{
"login": "musterki",
"vorname": "Kind",
"name": "Mustermann",
"email": "kind.mustermann@yahoo.com"
}
```
Only `"login"` is required, the other keys are optional.
Additional `"key"`s (including `"id"`) are ignored.
#### Example Response
```
201 OK
Content-Type: application/json
```
```json
{
"id": 2,
"login": "musterki",
"vorname": "Kind",
"name": "Mustermann",
"email": "kind.mustermann@yahoo.com"
}
```
### Alter information of the candidate with id `<id>` (not implemented yet)
#### Request
```
PUT /api/kandidaten/<id>
Content-Type: application/json
```
```json
{
"email": "kind.mustermann@gmx.com"
}
```
The keys `"id"` and `"login"` are immutable.
#### Example Response
```
200 OK
Content-Type: application/json
```
```json
{
"id": 2,
"login": "musterki",
"vorname": "Kind",
"name": "Mustermann",
"email": "kind.mustermann@gmx.com"
}
```
## Kategorien
### Get all categories
#### Request
```
GET /api/kategorien
```
#### Example Response
```
200 OK
Content-Type: application/json
```
```json
[
{
"id": 0,
"name": "Umwelt"
},
{
"id": 1,
"name": "Soziales"
}
]
```
### Get the category with id `<id>` (not implemented yet)
#### Request
```
GET /api/kategorien/<id>
```
#### Example Response
```
200 OK
Content-Type: application/json
```
```json
{
"id": 0,
"name": "Umwelt",
}
```
### Create a new category (not implemented yet)
#### Request
```
POST /api/kategorien
Content-Type: application/json
```
```json
{
"name": "Digitales",
}
```
#### Example Response
```
201 OK
Content-Type: application/json
```
```json
{
"id": 2,
"name": "Digitales",
}
```
## Fragen
### Get all questions
#### Request
```
GET /api/fragen
```
#### Example Response
```
200 OK
Content-Type: application/json
```
```json
[
{
"id": 0,
"text": "Dies ist eine Dummy Frage für Testzwecke",
"kategorie": {
"id": 0,
"name": "Umwelt"
}
},
{
"id": 1,
"text": "Eine weitere Testfrage",
"kategorie": {
"id": 1,
"name": "Soziales"
}
}
]
```
### Get the question with id `<id>` (not implemented yet)
#### Request
```
GET /api/fragen/<id>
```
#### Example Response
```
200 OK
Content-Type: application/json
```
```json
{
"id": 1,
"text": "Eine weitere Testfrage",
"kategorie": {
"id": 0,
"name": "Soziales"
}
}
```
### Get questions filtered by category (not implemented yet)
#### Request
```
GET /api/fragen?kategorie=<category_id>
```
#### Example Response
```
200 OK
Content-Type: application/json
```
```json
[
{
"id": 0,
"text": "Dies ist eine Dummy Frage für Testzwecke",
"kategorie": {
"id": 0,
"name": "Umwelt"
}
}
]
```
### Create a new question (not implemented yet)
#### Request
```
POST /api/fragen
Content-Type: application/json
```
```json
{
"text": "Dies ist noch eine Dummy Frage für Testzwecke",
"kategorie_id": 0,
}
```
#### Example Response
```
201 OK
Content-Type: application/json
```
```json
{
"id": 2,
"text": "Dies ist noch eine Dummy Frage für Testzwecke",
"kategorie": {
"id": 0,
"name": "Umwelt"
}
}
```
### Alter the question with id `<id>` (not implemented yet)
#### Request
```
PUT /api/fragen/<id>
Content-Type: application/json
```
```json
{
"text": "Dies ist eine geaenderte Dummy Frage",
"kategorie_id": 0,
}
```
#### Example Response
```
200 OK
Content-Type: application/json
```
```json
{
"id": 2,
"text": "Dies ist eine geaenderte Dummy Frage",
"kategorie": {
"id": 3,
"name": "Digitales"
}
}
```
## Antworten
This represents the answers of the candidates to the questions.
Each answer consists of two parts:
1. The position: `-1`, `0`, `1` (negative, neutral, positive)
2. The statement: Some (optional) additional context the candidate gave on the question.
### Get all answers from every candidate
#### Request
```
GET /api/antworten
```
#### Example Response
```
200 OK
Content-Type: application/json
```
```json
[
{
"id": 0,
"kandidat": {
"id": 0,
"login": "musterma",
"vorname": "Max",
"name": "Mustermann",
"email": "max.mustermann@yahoo.com"
},
"frage": {
"id": 0,
"text": "Dies ist eine Dummy Frage für Testzwecke",
"kategorie": {
"id": 0,
"name": "Umwelt"
}
},
"position": -1,
"statement": "Lorem ipsum"
},
{
"id": 1,
"kandidat": {
"id": 0,
"login": "musterma",
"vorname": "Max",
"name": "Mustermann",
"email": "max.mustermann@yahoo.com"
},
"frage": {
"id": 1,
"text": "Eine weitere Testfrage",
"kategorie": {
"id": 1,
"name": "Soziales"
}
},
"position": 1,
"statement": "Lorem ipsum"
}
]
```
### Get answers filtered by question and/or candidate (not implemented yet)
To filter for a question with id `<question_id>` append `?frage=<question_id>` to the URI.
To filter for a candidate with id `<candidate_id>` to key-value pair is `kandidat=<candidate_id>`.
To use both filters at the same time concatinate them with an `&`.
The ordering of the filters in unimportant.
#### Example Request
```
GET /api/antworten?frage=1&kandidat=0
```
#### Example Response
```
200 OK
Content-Type: application/json
```
```json
[
{
"id": 1,
"kandidat": {
"id": 0,
"login": "musterma",
"vorname": "Max",
"name": "Mustermann",
"email": "max.mustermann@yahoo.com"
},
"frage": {
"id": 1,
"text": "Eine weitere Testfrage",
"kategorie": {
"id": 1,
"name": "Soziales"
}
},
"position": 1,
"statement": "Lorem ipsum"
},
]
```
### Get answers of candidate with id `<candidate_id>`(not implemented yet)
To get the answers of a single candidate it is also possible to use the API endpoint `/api/kandidaten/<candidate_id>/answers`.
This reflects the philosophy that answers belong first and foremost to the candidate.
There is a difference in the response though:
The `"kandidat"` key is missing.
It is possible to get the answer for a specific question with the respective querry parameter,
e.g. `/api/kandidaten/<candidate_id>/antworten?frage=0`
#### Example Request
```
GET /kandidaten/<candidate_id>/antworten
```
#### Example Response
```
200 OK
Content-Type: application/json
```
```json
[
{
"id": 0,
"frage": {
"id": 0,
"text": "Dies ist eine Dummy Frage für Testzwecke",
"kategorie": {
"id": 0,
"name": "Umwelt"
}
},
"position": -1,
"statement": "Lorem ipsum"
},
{
"id": 1,
"frage": {
"id": 1,
"text": "Eine weitere Testfrage",
"kategorie": {
"id": 1,
"name": "Soziales"
}
},
"position": 1,
"statement": "Lorem ipsum"
}
]
```
### Get the answer with id `<answer_id>` of candidate with id `<candidate_id>` (not implemented yet)
#### Request
```
GET /api/kandiaten/<candidate_id>/antworten/<answer_id>
```
#### Example Response
```
200 OK
Content-Type: application/json
```
```json
{
"id": 0,
"frage": {
"id": 0,
"text": "Dies ist eine Dummy Frage für Testzwecke",
"kategorie": {
"id": 0,
"name": "Umwelt"
}
},
"position": -1,
"statement": "Lorem ipsum"
}
```
### Create a new answer for candidate with id <candidate_id> (not implemented yet)
#### Request
```
POST /api/kandidaten/<candidate_id>/antworten
Content-Type: application/json
```
```json
{
"question_id": 2,
"position": 1,
"statement": "Lorem ipsum ....."
}
```
The key `"statement"` is optional.
#### Example Response
```
201 OK
Content-Type: application/json
```
```json
{
"id": 3,
"frage": {
"id": 2,
"text": "Dies ist noch eine Dummy Frage für Testzwecke",
"kategorie": {
"id": 0,
"name": "Umwelt"
}
},
"position": 1,
"statement": "Lorem ipsum ....."
}
```
### Alter the answer with id <answer_id> of candidate with the id `<candidate_id>` (not implemented yet)
#### Request
```
PUT /api/kandidaten/<candidate_id>/antworten/<answer_id>
Content-Type: application/json
```
```json
{
"position": 0,
"statement": "Hab's mir anders ueberlegt..."
}
```
#### Example Response
```
200 OK
Content-Type: application/json
```
```json
{
"id": 3,
"frage": {
"id": 2,
"text": "Dies ist noch eine Dummy Frage für Testzwecke",
"kategorie": {
"id": 0,
"name": "Umwelt"
}
},
"position": 0,
"statement": "Hab's mir anders ueberlegt..."
}
```
### Postgres on your machine
* Install postgres and start it
* Create a new database
* Execute the scripts in the `./sql` folder in chronological order

View file

@ -1,16 +0,0 @@
[
{
"id": 0,
"kandidaten_id": 0,
"fragen_id": 0,
"kategorien_id": 0,
"text": "Lorem ipsum"
},
{
"id": 1,
"kandidaten_id": 0,
"fragen_id": 1,
"kategorien_id": 0,
"text": "Lorem ipsum..."
}
]

View file

@ -1,17 +0,0 @@
[
{
"id": 0,
"text": "Dies ist eine Dummy Frage für Testzwecke",
"kategorie_id": 0
},
{
"id": 1,
"text": "Eine weitere Testfrage",
"kategorie_id": 0
},
{
"id": 2,
"text": "Die besondere Frage.",
"kategorie_id": 1
}
]

View file

@ -1,14 +0,0 @@
[
{
"id": 0,
"vorname": "Max",
"name": "Mustermann",
"email": "max.mustermann@yahoo.com"
},
{
"id": 1,
"vorname": "Erika",
"name": "Mustermann",
"email": "erika.mustermann@yahoo.com"
}
]

View file

@ -1,10 +0,0 @@
[
{
"id": 0,
"name": "Umwelt"
},
{
"id": 1,
"name": "Soziales"
}
]

View file

@ -1,8 +0,0 @@
#!/usr/bin/env bash
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
pip install -r requirements_dev.txt

View file

@ -1,5 +0,0 @@
#!/usr/bin/env bash
source venv/bin/activate
env FLASK_APP=flask-server/main.py flask run

View file

@ -1,64 +0,0 @@
from flask import Flask
from flask import request
import json
#########
# Helper Functions
def get_kat_n(k_id):
with open('data/kategorien.json', 'r', encoding="utf-8") as json_file:
kategorien = json.load(json_file)
for kategorie in kategorien:
if json.dumps(kategorie["id"]) == k_id:
return kategorie["name"]
app = Flask(__name__)
@app.route('/api/')
def root():
return "Candymat Data Backend"
@app.route('/api/fragen')
def fragen():
with open('data/fragen.json', 'r', encoding="utf-8") as json_file:
fragen = json.load(json_file)
# Get the url Request Parameter "kat_id"
kat_id = request.args.get('kat_id')
if kat_id == None:
answer = []
for frage in fragen:
frage.update({"kategorie_name": get_kat_n(json.dumps(frage["kategorie_id"]))})
frage.pop("kategorie_id")
answer.append(frage)
return json.dumps(answer)
else:
answer = []
for frage in fragen:
if json.dumps(frage["kategorie_id"]) == kat_id:
frage.update({"kategorie_name": get_kat_n(json.dumps(frage["kategorie_id"]))})
frage.pop("kategorie_id")
answer.append(frage)
return json.dumps(answer)
@app.route('/api/kandidaten')
def kandidaten():
with open('data/kandidaten.json', 'r') as json_file:
return json_file.read()
@app.route('/api/kategorien')
def kategorien():
with open('data/kategorien.json', 'r') as json_file:
return json_file.read()
@app.route('/api/antworten')
def antworten():
with open('data/antworten.json','r') as json_file:
return json_file.read()

View file

@ -1,69 +0,0 @@
import json
from main import kandidaten, fragen, kategorien, antworten
def test_kandidaten():
expected_result = [
{
"id": 0,
"vorname": "Max",
"name": "Mustermann",
"email": "max.mustermann@yahoo.com"
},
{
"id": 1,
"vorname": "Erika",
"name": "Mustermann",
"email": "erika.mustermann@yahoo.com"
}
]
assert json.loads(kandidaten()) == expected_result
def test_fragen():
expected_result = [
{
"id": 2,
"text": "Die besondere Frage.",
"kategorie_name": "Soziales"
}
]
assert json.loads(fragen()) == expected_result
def test_kategorien():
expected_result = [{
"id": 0,
"name": "Umwelt"
}, {
"id": 1,
"name": "Soziales"
}]
assert json.loads(kategorien()) == expected_result
def test_antworten():
expected_result = [{
{
"id": 0,
"kandidat_vorname": "Max",
"kandidat_name": "Mustermann",
"kandidat_email": "max.mustermann@yahoo.com"
"frage_text": "Dies ist eine Dummy Frage für Testzwecke",
"kategorie_name": "Umwelt",
"antwort_text": "Lorem ipsum"
},
{
"id": 1,
"kandidat_vorname": "Max",
"kandidat_name": "Mustermann",
"kandidat_email": "max.mustermann@yahoo.com"
"frage_text": "Eine weitere Testfrage"
"kategorie_name": "Umwelt",
"antwort_text": "Lorem ipsum..."
}
}]
assert json.loads(antworten()) == expected_result

View file

@ -1 +0,0 @@
Flask==1.1.1

View file

@ -1 +0,0 @@
pytest==5.1.2

View file

@ -1,7 +0,0 @@
import sys
import pytest
sys.path.append("flask_server")
pytest.main()

View file

@ -0,0 +1,52 @@
-- Create table for users
CREATE TABLE public."user"
(
login character varying(8) primary key,
name character varying(300),
surname character varying(300),
email character varying(320)
);
-- Create table for user groups
CREATE TABLE public."group"
(
id serial primary key,
name character varying(300) UNIQUE,
access_right character varying(1000)
);
-- Create table for relation of users and groups
CREATE TABLE public.user_group
(
group_id integer REFERENCES public."group" (id) ON UPDATE CASCADE ON DELETE CASCADE,
user_login character varying(8) REFERENCES public."user" (login) ON UPDATE CASCADE ON DELETE CASCADE,
primary key (group_id, user_login)
);
-- Create table for catgeories
CREATE TABLE public.category
(
id serial primary key,
title character varying(300) UNIQUE NOT NULL,
description character varying(5000)
);
-- Create table for questions
CREATE TABLE public.question
(
id serial primary key,
category_id integer REFERENCES public."category" (id) ON UPDATE CASCADE ON DELETE SET NULL,
text character varying(3000) NOT NULL,
description character varying(5000)
);
-- Create table for answers
CREATE TABLE public.answer
(
question_id integer REFERENCES public."question" (id) ON UPDATE CASCADE ON DELETE CASCADE,
user_login character varying(8) REFERENCES public."user" ON UPDATE CASCADE ON DELETE CASCADE,
postition integer NOT NULL,
text character varying(5000),
primary key (question_id, user_login)
);