push
This commit is contained in:
commit
b8fde88b58
12
Dockerfile
Normal file
12
Dockerfile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
FROM python:3.11-slim-bookworm
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE 1
|
||||||
|
ENV PYTHONUNBUFFERED 1
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY requirements.txt requirements.txt
|
||||||
|
|
||||||
|
RUN pip3 install -r requirements.txt
|
||||||
|
COPY app/. .
|
||||||
|
|
||||||
|
CMD ["gunicorn", "-w", "4", "wsgi:app", "--bind", "0.0.0.0:8000"]
|
||||||
96
README.md
Normal file
96
README.md
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# BRANCH API
|
||||||
|
|
||||||
|
Base for create an API for french quotes in flask with SQL Alchemy for backend
|
||||||
|
|
||||||
|
## CONFIGURATION :wrench:
|
||||||
|
|
||||||
|
- In app/app.py configure admin_api_key and user_api_key
|
||||||
|
|
||||||
|
## UTILISATION :checkered_flag:
|
||||||
|
|
||||||
|
- créer un evironnement virtuel:
|
||||||
|
```bash
|
||||||
|
sudo apt install python3-venv
|
||||||
|
python3 -m venv venv
|
||||||
|
```
|
||||||
|
|
||||||
|
- Installation des dépendances
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
cd app
|
||||||
|
flask run
|
||||||
|
```
|
||||||
|
|
||||||
|
> Récupérer l'api_key admin qui s'affiche dans le terminal
|
||||||
|
|
||||||
|
- Ajouter les deux citations test:
|
||||||
|
```bash
|
||||||
|
curl -X POST -H "Content-Type: application/json; charset=utf-8" -H "API-Key: <admin_api_key>" -d '{"auteur": "François de La Rochefoucauld", "citation": "Il est plus aisé d’etre sage pour les autres que de l’etre pour soi-même."}' http://localhost:5000/api/citations/add
|
||||||
|
|
||||||
|
curl -X POST -H "Content-Type: application/json; charset=utf-8" -H "API-Key: <admin_api_key>" -d '{"auteur": "Pierre De Montesquieu", "citation": "On ne justifie pas une injustice par une autre."}' http://localhost:5000/api/citations/add
|
||||||
|
```
|
||||||
|
|
||||||
|
- Ajouter une liste de citations:
|
||||||
|
```bash
|
||||||
|
curl -X POST -H "Content-Type: application/json; charset=utf-8" -H "API-Key: <admin_api_key>" -d '{
|
||||||
|
"citations": [
|
||||||
|
{"auteur": "Auteur1", "citation": "Citation1"},
|
||||||
|
{"auteur": "Auteur2", "citation": "Citation2"},
|
||||||
|
{"auteur": "Auteur3", "citation": "Citation3"}
|
||||||
|
]
|
||||||
|
}' http://localhost:5000/api/citations/add_list
|
||||||
|
```
|
||||||
|
|
||||||
|
- API admin:
|
||||||
|
```bash
|
||||||
|
curl -H "API-Key: <admin_api_key>" http://localhost:5000/api/citations/all | jq
|
||||||
|
curl -H "API-Key: <admin_api_key>" http://localhost:5000/api/citations/random | jq
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
- Ajouter un utilisateur non admin:
|
||||||
|
```bash
|
||||||
|
curl -X POST -H "Content-Type: application/json; charset=utf-8" -H "API-Key: <admin_api_key>" -d '{"username": "nouvel_utilisateur", "is_admin": false}' http://localhost:5000/api/users/add
|
||||||
|
```
|
||||||
|
|
||||||
|
- Supprimer un utilisateur:
|
||||||
|
```bash
|
||||||
|
curl -X DELETE -H "Content-Type: application/json; charset=utf-8" -H "Api-Key: <admin_api_key>" -d '{"username": "nom_utilisateur_a_supprimer"}' http://localhost:5000/api/users/delete
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
> Récupérer l'api_key qui s'affiche pour le nouvel utilisateur
|
||||||
|
|
||||||
|
- API user:
|
||||||
|
```bash
|
||||||
|
curl -H "API-Key: <nouvel_utilisateur_api_key>" http://localhost:5000/api/citations/random | jq
|
||||||
|
```
|
||||||
|
|
||||||
|
:bulb: Utiliser le script ajout_citations.sh pour ajouter plusieurs citations en une fois
|
||||||
|
|
||||||
|
## DOCKER :whale:
|
||||||
|
|
||||||
|
- Construire l'image Docker et démarrer un conteneur:
|
||||||
|
```bash
|
||||||
|
docker build -t api:1.0 .
|
||||||
|
docker run -d -p 8000:8000 api:1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
## DOCKER COMPOSE
|
||||||
|
|
||||||
|
- Démarrer l'API:
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## TO DO :bookmark_tabs:
|
||||||
|
|
||||||
|
- [X] fonction check si citation existe deja
|
||||||
|
- [X] fonction add user
|
||||||
|
- [X] test added user
|
||||||
|
- [X] fonction ajout_citations(list)
|
||||||
|
- [X] tester Docker
|
||||||
|
- [X] gérer l'UTF8
|
||||||
|
- [X] endpoint del user
|
||||||
|
|
||||||
|
|
||||||
32
ajout_citations.sh
Executable file
32
ajout_citations.sh
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# ADMIN KEY
|
||||||
|
API_KEY="A RECUPERER DES LOGS DOCKER"
|
||||||
|
|
||||||
|
########################## LIST CITATIONS ##########################
|
||||||
|
|
||||||
|
API_ENDPOINT="http://localhost:8000/api/citations/add_list"
|
||||||
|
CITATIONS='{
|
||||||
|
"citations": [
|
||||||
|
{"auteur": "Maya Angelo", "citation": "La vie n´est pas mesurée par le nombre de respirations que nous prenons, mais par les moments qui nous coupent le souffle."},
|
||||||
|
{"auteur": "Leonardo da Vinci", "citation": "La simplicité est la sophistication ultime."},
|
||||||
|
{"auteur": "Albert Einstein", "citation": "La folie, c´est de faire la même chose et de s´attendre à un résultat différent."},
|
||||||
|
{"auteur": "Mark Twain", "citation": "La gentillesse est la langue que les sourds peuvent entendre et les aveugles peuvent voir."},
|
||||||
|
{"auteur": "Henry Ford", "citation": "L´échec est simplement l´opportunité de recommencer, cette fois d´une manière plus intelligente."},
|
||||||
|
{"auteur": "Oscar Wilde", "citation": "La vie est trop importante pour être prise au sérieux."},
|
||||||
|
{"auteur": "Neale Donald Walsch", "citation": "La vie commence à la fin de votre zone de confort."},
|
||||||
|
{"auteur": "Mahatma Gandhi", "citation": "Soyez le changement que vous voulez voir dans le monde."},
|
||||||
|
{"auteur": "Muhammad Ali", "citation": "Ne comptez pas les jours, faites que les jours comptent."},
|
||||||
|
{"auteur": "Sénèque", "citation": "La vie n´est pas d´attendre que l´orage passe, mais d´apprendre à danser sous la pluie."},
|
||||||
|
{"auteur": "Steve Jobs", "citation": "Votre temps est limité, ne le gaspillez pas à vivre la vie de quelqu´un d´autre."},
|
||||||
|
{"auteur": "Ralph Marston", "citation": "Le bonheur est un choix, pas une résultante. Rien ne vous rend heureux à moins que vous ne décidiez de l´être."},
|
||||||
|
{"auteur": "Vidal Sassoon", "citation": "Le seul endroit où le succès vient avant le travail, c´est dans le dictionnaire."},
|
||||||
|
{"auteur": "Dalaï Lama", "citation": "Il n´y a personne qui soit né sous une mauvaise étoile, il n´y a que des gens qui ne savent pas lire le ciel."}
|
||||||
|
]
|
||||||
|
}'
|
||||||
|
|
||||||
|
# AJOUT CITATION
|
||||||
|
curl -X POST -H "Content-Type: application/json; charset=utf-8" -H "API-Key: $API_KEY" -d "$CITATIONS" "$API_ENDPOINT"
|
||||||
|
|
||||||
|
# VERIF ALL CITATIONS
|
||||||
|
curl -H "API-Key: $API_KEY" http://localhost:8000/api/citations/all | jq
|
||||||
203
app/app.py
Normal file
203
app/app.py
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
from flask import Flask, jsonify, request
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from sqlalchemy.sql.expression import func
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
|
||||||
|
################################ CONFIG ################################
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///citations.db'
|
||||||
|
db = SQLAlchemy(app)
|
||||||
|
|
||||||
|
################################ CLASS ################################
|
||||||
|
# CLASS CITATION
|
||||||
|
class Citation(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
auteur = db.Column(db.String(50), nullable=False)
|
||||||
|
citation = db.Column(db.String(500), nullable=False)
|
||||||
|
|
||||||
|
# CLASS USER
|
||||||
|
class User(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
username = db.Column(db.String(50), unique=True, nullable=False)
|
||||||
|
api_key = db.Column(db.String(64), unique=True, nullable=False)
|
||||||
|
is_admin = db.Column(db.Boolean, default=False)
|
||||||
|
|
||||||
|
|
||||||
|
# CREATE ADMIN FIRST TIME
|
||||||
|
def initialize_admin_api_key():
|
||||||
|
# Verif si l'admin existe déjà
|
||||||
|
admin_user = User.query.filter_by(username='admin').first()
|
||||||
|
|
||||||
|
if not admin_user:
|
||||||
|
# Si l'admin n'existe pas, générer une nouvelle clé API et l'ajouter à la bdd
|
||||||
|
admin_api_key = secrets.token_urlsafe(32)
|
||||||
|
admin_user = User(username='admin', api_key=admin_api_key, is_admin=True)
|
||||||
|
db.session.add(admin_user)
|
||||||
|
db.session.commit()
|
||||||
|
print(f"Clé API de l'administrateur : {admin_api_key}")
|
||||||
|
|
||||||
|
|
||||||
|
# REQUIRE API KEY WRAPPER
|
||||||
|
def require_api_key(*api_types):
|
||||||
|
def decorator(func):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
|
||||||
|
provided_key = request.headers.get('API-Key')
|
||||||
|
user = User.query.filter_by(api_key=provided_key).first()
|
||||||
|
|
||||||
|
if user:
|
||||||
|
if 'admin' in api_types and user.is_admin:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
elif 'user' in api_types:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
|
return jsonify({"error": "Invalid API key or insufficient permissions"}), 401
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
# CREATE DB
|
||||||
|
with app.app_context():
|
||||||
|
db.create_all()
|
||||||
|
initialize_admin_api_key()
|
||||||
|
|
||||||
|
|
||||||
|
################################ ROUTES ################################
|
||||||
|
## RANDOM CITATION
|
||||||
|
@app.route('/api/citations/random', methods=['GET'], endpoint='random')
|
||||||
|
@require_api_key('user', 'admin')
|
||||||
|
def get_citation():
|
||||||
|
citation = Citation.query.order_by(func.random()).first()
|
||||||
|
citation = {"auteur": citation.auteur, "citation": citation.citation}
|
||||||
|
|
||||||
|
response = jsonify({"citations": citation})
|
||||||
|
response.headers['Content-Type'] = 'application/json; charset=utf-8'
|
||||||
|
return response
|
||||||
|
|
||||||
|
## GET ALL CITATION
|
||||||
|
@app.route('/api/citations/all', methods=['GET'], endpoint='all')
|
||||||
|
@require_api_key('admin')
|
||||||
|
def get_all_citations():
|
||||||
|
citations = Citation.query.all()
|
||||||
|
citations_list = [{"auteur": citation.auteur, "citation": citation.citation} for citation in citations]
|
||||||
|
|
||||||
|
response = jsonify({"citations": citations_list})
|
||||||
|
response.headers['Content-Type'] = 'application/json; charset=utf-8'
|
||||||
|
return response
|
||||||
|
|
||||||
|
## AJOUT CITATION
|
||||||
|
@app.route('/api/citations/add', methods=['POST'], endpoint='ajout')
|
||||||
|
@require_api_key('admin')
|
||||||
|
def add_citation():
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
new_auteur = data['auteur']
|
||||||
|
new_citation_text = data['citation']
|
||||||
|
existing_citation = Citation.query.filter_by(auteur=new_auteur, citation=new_citation_text).first()
|
||||||
|
|
||||||
|
if existing_citation:
|
||||||
|
return jsonify({"error": "Cette citation existe deja dans la base de donnees"}), 400
|
||||||
|
|
||||||
|
new_citation = Citation(auteur=data['auteur'], citation=data['citation'])
|
||||||
|
db.session.add(new_citation)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
response = jsonify({"message": "Citation ajoutee avec succes!"})
|
||||||
|
response.headers['Content-Type'] = 'application/json; charset=utf-8'
|
||||||
|
return response
|
||||||
|
|
||||||
|
## AJOUT LISTE DE CITATIONS
|
||||||
|
@app.route('/api/citations/add_list', methods=['POST'], endpoint='ajout_liste')
|
||||||
|
@require_api_key('admin')
|
||||||
|
def add_citations_from_list():
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
citations_to_add = data.get('citations')
|
||||||
|
|
||||||
|
if not citations_to_add or not isinstance(citations_to_add, list):
|
||||||
|
response = jsonify({"error": "Veuillez fournir une liste valide de citations"}), 400
|
||||||
|
return response
|
||||||
|
|
||||||
|
citations = []
|
||||||
|
for citation_data in citations_to_add:
|
||||||
|
new_auteur = citation_data.get('auteur')
|
||||||
|
new_citation_text = citation_data.get('citation')
|
||||||
|
|
||||||
|
if not new_auteur or not new_citation_text:
|
||||||
|
response = jsonify({"error": "Chaque element de la liste doit avoir des cles 'auteur' et 'citation'"}), 400
|
||||||
|
return response
|
||||||
|
|
||||||
|
existing_citation = Citation.query.filter_by(auteur=new_auteur, citation=new_citation_text).first()
|
||||||
|
if existing_citation:
|
||||||
|
response = jsonify({"error": f"La citation de {new_auteur} existe deja dans la base de donnees"}), 400
|
||||||
|
return response
|
||||||
|
|
||||||
|
new_citation = Citation(auteur=new_auteur, citation=new_citation_text)
|
||||||
|
citations.append(new_citation)
|
||||||
|
|
||||||
|
db.session.add_all(citations)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
response = jsonify({"message": "Citations ajoutees avec succes depuis la liste!"})
|
||||||
|
response.headers['Content-Type'] = 'application/json; charset=utf-8'
|
||||||
|
return response
|
||||||
|
|
||||||
|
################################ AJOUT USER ################################
|
||||||
|
@app.route('/api/users/add', methods=['POST'], endpoint='add_user')
|
||||||
|
@require_api_key('admin')
|
||||||
|
def add_user():
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
new_username = data['username']
|
||||||
|
is_admin = data.get('is_admin', False)
|
||||||
|
existing_user = User.query.filter_by(username=new_username).first()
|
||||||
|
|
||||||
|
if existing_user:
|
||||||
|
return jsonify({"error": "Cet utilisateur existe déjà"}), 400
|
||||||
|
|
||||||
|
new_api_key = secrets.token_urlsafe(32)
|
||||||
|
|
||||||
|
new_user = User(username=new_username, api_key=new_api_key, is_admin=is_admin)
|
||||||
|
db.session.add(new_user)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({"username": new_username, "api_key": new_api_key, "is_admin": is_admin})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
################################ DEL USER ################################
|
||||||
|
@app.route('/api/users/delete', methods=['DELETE'], endpoint='delete_user')
|
||||||
|
@require_api_key('admin')
|
||||||
|
def delete_user():
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
username_to_delete = data.get('username')
|
||||||
|
|
||||||
|
if not username_to_delete:
|
||||||
|
return jsonify({"error": "Le nom d'utilisateur est requis"}), 400
|
||||||
|
|
||||||
|
user_to_delete = User.query.filter_by(username=username_to_delete).first()
|
||||||
|
|
||||||
|
if not user_to_delete:
|
||||||
|
return jsonify({"error": "Cet utilisateur n'existe pas"}), 404
|
||||||
|
|
||||||
|
db.session.delete(user_to_delete)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({"message": "Utilisateur " + username_to_delete + " supprimé avec succès"})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
################################ MAIN ################################
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True)
|
||||||
4
app/wsgi.py
Normal file
4
app/wsgi.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from app import app
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True)
|
||||||
11
docker-compose.yml
Normal file
11
docker-compose.yml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
api:
|
||||||
|
build: .
|
||||||
|
container_name: ostro
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- 8000:8000
|
||||||
|
volumes:
|
||||||
|
- ./app:/app/instance
|
||||||
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
Flask==2.2.5
|
||||||
|
Flask-SQLalchemy==3.1.1
|
||||||
|
requests==2.28.1
|
||||||
|
gunicorn==21.2.0
|
||||||
|
Werkzeug==2.3.7
|
||||||
Loading…
x
Reference in New Issue
Block a user