First commit 🎉

This commit is contained in:
Barnab 2024-03-27 17:04:51 +01:00
commit 6833cba08b
27 changed files with 788 additions and 0 deletions

BIN
Barnabe_DUPONT_TP1_R410.zip Normal file

Binary file not shown.

0
README.md Normal file
View file

Binary file not shown.

Binary file not shown.

84
app/athletes.py Normal file
View file

@ -0,0 +1,84 @@
#!/usr/bin/env python
# encoding: utf-8
import os
import json
from flask import Flask, request, jsonify
file = 'data/'+os.path.splitext(os.path.basename(__file__))[0]+'.json'
with open(file, 'r') as f:
j = json.load(f)
def save_json(new_json):
with open(file, 'w') as f:
f.write(json.dumps(new_json, indent=4))
with open(file, 'r') as f:
j = json.load(f)
app = Flask(__name__)
@app.route('/healthcheck', methods=['GET'])
def ping():
return jsonify({"conatiner_status": "healthy"})
@app.route('/athletes', methods=['GET'])
def list_athletes():
return jsonify({"status": "success", "data": j})
@app.route('/athletes', methods=['POST'])
def add_athlete():
try:
new = json.loads(request.data)
next_id = j[-1]['id']+1
j.append({
"id": next_id,
"name": new['name'],
"surname": new['surname'],
"email": new['email'],
"country": new['country'],
"isDisabled": new['isDisabled']
})
save_json(j)
return jsonify({"status": "success", "id": next_id})
except Exception as e:
print(e)
return jsonify({"status": "error", "message": "Please provide a valid JSON"}), 500
@app.route('/athletes/<id>', methods=['GET'])
def get_athlete(id):
for a in j:
print(a['id'])
if a['id'] == int(id):
return jsonify({"status": "success", "data": a})
return jsonify({"status": "error", "message": "Id not found"}), 404
@app.route('/athletes/<id>', methods=['PATCH'])
def edit_athlete(id):
try:
update = json.loads(request.data)
entries = ['name', 'surname', 'email', 'country', 'isDisabled']
for i, a in enumerate(j):
if a['id'] == int(id):
for e in entries:
if update.get(e) is not None:
j[i][e] = update[e]
save_json(j)
return jsonify({"status": "success"})
return jsonify({"status": "error", "message": "Id not found"}), 404
except Exception as e:
print(e)
return jsonify({"status": "error", "message": "Please provide a valid JSON"}), 500
@app.route('/athletes/<id>', methods=['DELETE'])
def rm_athlete(id):
for i, a in enumerate(j):
print(a['id'])
if a['id'] == int(id):
j.pop(i)
save_json(j)
return jsonify({"status": "success"})
return jsonify({"status": "error", "message": "Id not found"}), 404
app.run(host="0.0.0.0", debug=False)

5
app/data/athletes.json Normal file
View file

@ -0,0 +1,5 @@
[
{
"id": 0
}
]

5
app/data/medals.json Normal file
View file

@ -0,0 +1,5 @@
[
{
"id": 0
}
]

5
app/data/sports.json Normal file
View file

@ -0,0 +1,5 @@
[
{
"id": 0
}
]

25
app/docs.py Normal file
View file

@ -0,0 +1,25 @@
from flask import Flask, send_file
from flask_swagger_ui import get_swaggerui_blueprint
app = Flask(__name__)
SWAGGER_URL = '/docs' # URL for exposing Swagger UI (without trailing '/')
API_URL = '/docs/api/swagger.yml' # Our API url (can of course be a local resource)
@app.route(API_URL, methods=['GET'])
def swagger_yml():
# Read before use: http://flask.pocoo.org/docs/0.12/api/#flask.send_file
return send_file('/app/swagger.yml')
# Call factory function to create our blueprint
swaggerui_blueprint = get_swaggerui_blueprint(
SWAGGER_URL, # Swagger UI static files will be mapped to '{SWAGGER_URL}/dist/'
API_URL,
config={ # Swagger UI config overrides
'app_name': "JO"
},
)
app.register_blueprint(swaggerui_blueprint)
app.run(host="0.0.0.0", debug=False)

88
app/medals.py Normal file
View file

@ -0,0 +1,88 @@
#!/usr/bin/env python
# encoding: utf-8
import os
import json
from flask import Flask, request, jsonify
file = 'data/'+os.path.splitext(os.path.basename(__file__))[0]+'.json'
with open(file, 'r') as f:
j = json.load(f)
def save_json(new_json):
with open(file, 'w') as f:
f.write(json.dumps(new_json, indent=4))
with open(file, 'r') as f:
j = json.load(f)
valid_ranks = ['gold', 'silver', 'bronze']
app = Flask(__name__)
@app.route('/healthcheck', methods=['GET'])
def ping():
return jsonify({"conatiner_status": "healthy"})
@app.route('/medals', methods=['GET'])
def list_medals():
return jsonify({"status": "success", "data": j})
@app.route('/medals', methods=['POST'])
def add_medal():
try:
new = json.loads(request.data)
if new['rank'] not in valid_ranks:
return jsonify({"status": "error", "message": "The valid ranks are gold, silver and bronze"}), 400
next_id = j[-1]['id']+1
j.append({
"id": next_id,
"rank": new['rank'],
"sportID": new['sportID'],
"athleteID": new['athleteID']
})
save_json(j)
return jsonify({"status": "success", "id": next_id})
except Exception as e:
print(e)
return jsonify({"status": "error", "message": "Please provide a valid JSON"}), 500
@app.route('/medals/<id>', methods=['GET'])
def get_medal(id):
for m in j:
print(m['id'])
if m['id'] == int(id):
return jsonify({"status": "success", "data": m})
return jsonify({"status": "error", "message": "Id not found"}), 404
@app.route('/medals/<id>', methods=['PATCH'])
def edit_medal(id):
try:
update = json.loads(request.data)
entries = ['rank', 'sportID', 'athleteID']
for i, m in enumerate(j):
if m['id'] == int(id):
for e in entries:
if update.get(e) is not None:
if e == "rank":
if update[e] not in valid_ranks:
return jsonify({"status": "error", "message": "The valid ranks are gold, silver and bronze"}), 400
j[i][e] = update[e]
save_json(j)
return jsonify({"status": "success"})
return jsonify({"status": "error", "message": "Id not found"}), 404
except Exception as e:
print(e)
return jsonify({"status": "error", "message": "Please provide a valid JSON"}), 500
@app.route('/medals/<id>', methods=['DELETE'])
def rm_medal(id):
for i, m in enumerate(j):
print(m['id'])
if m['id'] == int(id):
j.pop(i)
save_json(j)
return jsonify({"status": "success"})
return jsonify({"status": "error", "message": "Id not found"}), 404
app.run(host="0.0.0.0", debug=False)

80
app/sports.py Normal file
View file

@ -0,0 +1,80 @@
#!/usr/bin/env python
# encoding: utf-8
import os
import json
from flask import Flask, request, jsonify
file = 'data/'+os.path.splitext(os.path.basename(__file__))[0]+'.json'
with open(file, 'r') as f:
j = json.load(f)
def save_json(new_json):
with open(file, 'w') as f:
f.write(json.dumps(new_json, indent=4))
with open(file, 'r') as f:
j = json.load(f)
app = Flask(__name__)
@app.route('/healthcheck', methods=['GET'])
def ping():
return jsonify({"conatiner_status": "healthy"})
@app.route('/sports', methods=['GET'])
def list_sports():
return jsonify({"status": "success", "data": j})
@app.route('/sports', methods=['POST'])
def add_sport():
try:
new = json.loads(request.data)
next_id = j[-1]['id']+1
j.append({
"id": next_id,
"name": new['name'],
"place": new['place'],
"category": new['category'],
})
save_json(j)
return jsonify({"status": "success", "id": next_id})
except Exception as e:
print(e)
return jsonify({"status": "error", "message": "Please provide a valid JSON"}), 500
@app.route('/sports/<id>', methods=['GET'])
def get_sport(id):
for s in j:
if s['id'] == int(id):
return jsonify({"status": "success", "data": s})
return jsonify({"status": "error", "message": "Id not found"}), 404
@app.route('/sports/<id>', methods=['PATCH'])
def edit_sport(id):
try:
update = json.loads(request.data)
entries = ['name', 'place', 'category']
for i, s in enumerate(j):
if s['id'] == int(id):
for e in entries:
if update.get(e) is not None:
j[i][e] = update[e]
save_json(j)
return jsonify({"status": "success"})
return jsonify({"status": "error", "message": "Id not found"}), 404
except Exception as e:
print(e)
return jsonify({"status": "error", "message": "Please provide a valid JSON"}), 500
@app.route('/sports/<id>', methods=['DELETE'])
def rm_sport(id):
for i, s in enumerate(j):
if s['id'] == int(id):
j.pop(i)
save_json(j)
return jsonify({"status": "success"})
return jsonify({"status": "error", "message": "Id not found"}), 404
app.run(host="0.0.0.0", debug=False)

71
docker-compose.yml Normal file
View file

@ -0,0 +1,71 @@
version: "3.3"
services:
traefik:
image: "traefik:v2.11"
container_name: "traefik"
command:
- "--log.level=DEBUG"
- "--api.insecure=true"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
ports:
- "80:80"
- "8080:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
networks:
- backend
docs:
image: "git.gnous.eu/anrab35/sae410_docs:latest"
container_name: "JO_docs"
networks:
- backend
labels:
- "traefik.enable=true"
- "traefik.http.routers.docs.rule=PathPrefix(`/docs`)"
athletes:
image: "git.gnous.eu/anrab35/sae410_athletes:latest"
container_name: "JO_athletes"
networks:
- backend
volumes:
- "JO_athletes_data:/app/data:rw"
labels:
- "traefik.enable=true"
- "traefik.http.routers.athletes.rule=PathPrefix(`/athletes`)"
medals:
image: "git.gnous.eu/anrab35/sae410_medals:latest"
container_name: "JO_medals"
networks:
- backend
volumes:
- "JO_medals_data:/app/data:rw"
labels:
- "traefik.enable=true"
- "traefik.http.routers.medals.rule=PathPrefix(`/medals`)"
sports:
image: "git.gnous.eu/anrab35/sae410_sports:latest"
container_name: "JO_sports"
networks:
- backend
volumes:
- "JO_sports_data:/app/data:rw"
labels:
- "traefik.enable=true"
- "traefik.http.routers.sports.rule=PathPrefix(`/sports`)"
networks:
backend:
name: backend
external: false
volumes:
JO_athletes_data:
JO_medals_data:
JO_sports_data:

View file

@ -0,0 +1,29 @@
FROM python:3-alpine
MAINTAINER Barnabé Dupont <contact@bdupont.tech>
LABEL version="1.0.0"
RUN apk add curl
RUN adduser -D worker
USER worker
WORKDIR /home/worker
COPY --chown=worker:worker docker/requirements.txt /home/worker
RUN pip install --user -r requirements.txt
WORKDIR /app
COPY --chown=worker:worker app/athletes.py /app/
COPY --chown=worker:worker app/data/athletes.json /app/data/
EXPOSE 5000
HEALTHCHECK --interval=15s --timeout=30s --start-period=10s --retries=3 CMD curl --fail http://localhost:5000/healthcheck || exit 1
CMD ["python3", "athletes.py"]

26
docker/docs/Dockerfile Normal file
View file

@ -0,0 +1,26 @@
FROM python:3-alpine
MAINTAINER Barnabé Dupont <contact@bdupont.tech>
LABEL version="1.0.0"
RUN adduser -D worker
USER worker
WORKDIR /home/worker
COPY --chown=worker:worker docker/docs/requirements.txt /home/worker
RUN pip install --user -r requirements.txt
WORKDIR /app
COPY --chown=worker:worker app/docs.py /app/
COPY --chown=worker:worker swagger.yml /app/
EXPOSE 5000
CMD ["python3", "docs.py"]

View file

@ -0,0 +1,2 @@
flask==3.0.0
flask-swagger-ui==4.11.1

29
docker/medals/Dockerfile Normal file
View file

@ -0,0 +1,29 @@
FROM python:3-alpine
MAINTAINER Barnabé Dupont <contact@bdupont.tech>
LABEL version="1.0.0"
RUN apk add curl
RUN adduser -D worker
USER worker
WORKDIR /home/worker
COPY --chown=worker:worker docker/requirements.txt /home/worker
RUN pip install --user -r requirements.txt
WORKDIR /app
COPY --chown=worker:worker app/medals.py /app/
COPY --chown=worker:worker app/data/medals.json /app/data/
EXPOSE 5000
HEALTHCHECK --interval=15s --timeout=30s --start-period=10s --retries=3 CMD curl --fail http://localhost:5000/healthcheck || exit 1
CMD ["python3", "medals.py"]

1
docker/requirements.txt Normal file
View file

@ -0,0 +1 @@
flask==3.0.0

29
docker/sports/Dockerfile Normal file
View file

@ -0,0 +1,29 @@
FROM python:3-alpine
MAINTAINER Barnabé Dupont <contact@bdupont.tech>
LABEL version="1.0.0"
RUN apk add curl
RUN adduser -D worker
USER worker
WORKDIR /home/worker
COPY --chown=worker:worker docker/requirements.txt /home/worker
RUN pip install --user -r requirements.txt
WORKDIR /app
COPY --chown=worker:worker app/sports.py /app/
COPY --chown=worker:worker app/data/sports.json /app/data/
EXPOSE 5000
HEALTHCHECK --interval=15s --timeout=30s --start-period=10s --retries=3 CMD curl --fail http://localhost:5000/healthcheck || exit 1
CMD ["python3", "sports.py"]

27
pytest/test_athletes.py Normal file
View file

@ -0,0 +1,27 @@
import requests
import random
import pytest
def pytest_namespace():
return {'current_id': 0}
def test_add_new_athlete():
test_id = str(random.randint(1000,9999))
x = requests.post('http://localhost/athletes', json={"name": "TEST", "surname": test_id, "email": "test@test.org", "country": "Listenbourg", "isDisabled": True})
pytest.current_id = x.json()['id']
assert x.status_code == 200
def test_get_athlete():
assert requests.get("http://localhost/athletes/"+str(pytest.current_id)).status_code == 200
def test_edit_athlete():
assert requests.patch("http://localhost/athletes/"+str(pytest.current_id), json={"name": "HELLO", "isDisabled": False}).status_code == 200
def test_get_modified_athlete():
x = requests.get("http://localhost/athletes/"+str(pytest.current_id))
json = x.json()['data']
assert x.status_code == 200 and json['name'] == "HELLO" and json['isDisabled'] == False
def test_delete_athlete():
assert requests.delete("http://localhost/athletes/"+str(pytest.current_id)).status_code == 200
def test_deleted_athlete():
assert requests.get("http://localhost/athletes/"+str(pytest.current_id)).status_code == 404
def test_add_fake_athlete():
requests.post('http://localhost/athletes', json={"PEBCAK": True}).status_code == 400

27
pytest/test_medals.py Normal file
View file

@ -0,0 +1,27 @@
import requests
import random
import pytest
def pytest_namespace():
return {'current_id': 0}
def test_add_new_medal():
test_id = random.randint(1000,9999)
x = requests.post('http://localhost/medals', json={"rank": "gold", "sportID": test_id, "athleteID": 1})
pytest.current_id = x.json()['id']
assert x.status_code == 200
def test_get_medal():
assert requests.get("http://localhost/medals/"+str(pytest.current_id)).status_code == 200
def test_edit_medal():
assert requests.patch("http://localhost/medals/"+str(pytest.current_id), json={"athleteID": 88}).status_code == 200
def test_get_modified_medal():
x = requests.get("http://localhost/medals/"+str(pytest.current_id))
json = x.json()['data']
assert x.status_code == 200 and json['rank'] == "gold" and json['athleteID'] == 88
def test_delete_medal():
assert requests.delete("http://localhost/medals/"+str(pytest.current_id)).status_code == 200
def test_deleted_medal():
assert requests.get("http://localhost/medals/"+str(pytest.current_id)).status_code == 404
def test_add_fake_medal():
requests.post('http://localhost/medals', json={"rank": "Loris"}).status_code == 400

28
pytest/test_sports.py Normal file
View file

@ -0,0 +1,28 @@
import requests
import random
import pytest
def pytest_namespace():
return {'current_id': 0}
def test_add_new_sport():
test_id = str(random.randint(1000,9999))
x = requests.post('http://localhost/sports', json={"name": "TEST", "place": test_id, "category": "Multiplayer"})
pytest.current_id = x.json()['id']
assert x.status_code == 200
def test_get_sport():
print("http://localhost/sports/"+str(pytest.current_id))
assert requests.get("http://localhost/sports/"+str(pytest.current_id)).status_code == 200
def test_edit_sport():
assert requests.patch("http://localhost/sports/"+str(pytest.current_id), json={"category": "Aquatic"}).status_code == 200
def test_get_modified_sport():
x = requests.get("http://localhost/sports/"+str(pytest.current_id))
json = x.json()['data']
assert x.status_code == 200 and json['category'] == "Aquatic"
def test_delete_sport():
assert requests.delete("http://localhost/sports/"+str(pytest.current_id)).status_code == 200
def test_deleted_sport():
assert requests.get("http://localhost/sports/"+str(pytest.current_id)).status_code == 404
def test_add_fake_sport():
requests.post('http://localhost/sports', json={"rank": "gold", "sportID": 666, "athleteID": 1}).status_code == 400

227
swagger.yml Normal file
View file

@ -0,0 +1,227 @@
openapi: '3.0.2'
info:
title: API Jo
version: '1.0'
servers:
- url: http://localhost/
components:
schemas:
Athlete:
type: object
properties:
name:
type: string
surname:
type: string
email:
type: string
country:
type: string
isDisabled:
type: boolean
default: false
Sport:
type: object
properties:
name:
type: string
place:
type: string
category:
type: string
Medal:
type: object
properties:
rank:
type: string
enum:
- gold
- silver
- bronze
sportID:
type: integer
athleteID:
type: integer
paths:
/athletes:
get:
tags:
- Athletes
summary: Display athletes list
responses:
'200':
description: OK
post:
tags:
- Athletes
summary: Add a new athlete
responses:
'200':
description: OK
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Athlete'
/athletes/{id}:
parameters:
- name: id
in: path
required: true
schema:
type: integer
example: 1
get:
tags:
- Athletes
summary: Display one athlete using the ID
responses:
'200':
description: 'OK'
content:
application/json:
schema:
$ref: '#/components/schemas/Athlete'
'404':
description: 'Not found'
patch:
tags:
- Athletes
summary: Modify a athlete using the ID
responses:
'200':
description: 'OK'
delete:
tags:
- Athletes
summary: Delete an athlete using the ID
responses:
'200':
description: 'OK'
'404':
description: 'Athete not found'
/sports:
get:
tags:
- Sports
summary: List all available sports
responses:
'200':
description: OK
post:
tags:
- Sports
summary: Add a sport
responses:
'200':
description: OK
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Sport'
/sports/{id}:
get:
tags:
- Sports
summary: Display a specific sport using the ID
responses:
'200':
description: 'OK'
content:
application/json:
schema:
$ref: '#/components/schemas/Sport'
'404':
description: 'Not found'
parameters:
- name: id
in: path
required: true
schema:
type: integer
example: 1
patch:
tags:
- Sports
summary: Edit a specific sport using the ID
responses:
'200':
description: 'OK'
delete:
tags:
- Sports
summary: Delete a specific sport using the ID
responses:
'200':
description: 'OK'
'404':
description: 'Sport not found'
/medal:
get:
tags:
- Medal
summary: Display medals list
responses:
'200':
description: OK
post:
tags:
- Medal
summary: Add a new medal
responses:
'200':
description: OK
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/Medal'
/medals/{id}:
get:
tags:
- Medal
summary: Display a medal
responses:
'200':
description: 'OK'
content:
application/json:
schema:
$ref: '#/components/schemas/Medal'
'404':
description: 'Not found'
parameters:
- name: id
in: path
required: true
schema:
type: integer
example: 1
patch:
tags:
- Medal
summary: Modify element from medal
responses:
'200':
description: 'OK'
'404':
description: 'Medal not found'
delete:
tags:
- Medal
summary: Delete medal from Id
responses:
'200':
description: 'OK'
'404':
description: 'Medal not found'