Initial commit - EcoCharge backend API + admin

This commit is contained in:
Safouane Bazzi
2026-03-09 14:11:28 +01:00
commit 6b025aeacb
6 changed files with 612 additions and 0 deletions

106
app.py Normal file
View File

@@ -0,0 +1,106 @@
from flask import Flask, jsonify, render_template, request, redirect, url_for, session
from flask_cors import CORS
from config import Config
import psycopg2
app = Flask(__name__)
app.config.from_object(Config)
app.secret_key = app.config["SECRET_KEY"]
CORS(app)
conn = psycopg2.connect(
host="localhost",
port=5432,
database="ecocharge",
user="eco",
password="eco_pass"
)
@app.route("/")
def home():
return jsonify({
"message": "API EcoCharge en ligne"
})
@app.route("/api/status")
def status():
return jsonify({
"project": "EcoCharge",
"api": "ok",
"database": "postgresql_connected"
})
@app.route("/admin/login", methods=["GET", "POST"])
def admin_login():
if request.method == "POST":
username = request.form.get("username")
password = request.form.get("password")
if username == "admin" and password == "admin123":
session["admin_logged_in"] = True
return redirect(url_for("admin_dashboard"))
return render_template("login.html", error="Identifiants incorrects")
return render_template("login.html")
@app.route("/admin/dashboard", methods=["GET", "POST"])
def admin_dashboard():
if not session.get("admin_logged_in"):
return redirect(url_for("admin_login"))
cur = conn.cursor()
if request.method == "POST":
min_battery = request.form.get("min_battery")
max_temp = request.form.get("max_temp")
min_power = request.form.get("min_power")
cur.execute("""
INSERT INTO settings (key, value)
VALUES ('min_battery_voltage', %s)
ON CONFLICT (key)
DO UPDATE SET value = EXCLUDED.value
""", (min_battery,))
cur.execute("""
INSERT INTO settings (key, value)
VALUES ('max_battery_temperature', %s)
ON CONFLICT (key)
DO UPDATE SET value = EXCLUDED.value
""", (max_temp,))
cur.execute("""
INSERT INTO settings (key, value)
VALUES ('min_solar_power', %s)
ON CONFLICT (key)
DO UPDATE SET value = EXCLUDED.value
""", (min_power,))
conn.commit()
cur.execute("SELECT value FROM settings WHERE key = 'min_battery_voltage'")
row1 = cur.fetchone()
cur.execute("SELECT value FROM settings WHERE key = 'max_battery_temperature'")
row2 = cur.fetchone()
cur.execute("SELECT value FROM settings WHERE key = 'min_solar_power'")
row3 = cur.fetchone()
settings = {
"min_battery": row1[0] if row1 else "10.5",
"max_temp": row2[0] if row2 else "45",
"min_power": row3[0] if row3 else "5"
}
return render_template("admin_dashboard.html", settings=settings)
@app.route("/admin/logout")
def admin_logout():
session.pop("admin_logged_in", None)
return redirect(url_for("admin_login"))
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)

13
config.py Normal file
View File

@@ -0,0 +1,13 @@
import os
from dotenv import load_dotenv
load_dotenv()
class Config:
SECRET_KEY = os.getenv("SECRET_KEY", "ecocharge_secret_2026")
DB_HOST = os.getenv("DB_HOST", "localhost")
DB_PORT = int(os.getenv("DB_PORT", 3306))
DB_NAME = os.getenv("DB_NAME", "ecocharge")
DB_USER = os.getenv("DB_USER", "root")
DB_PASSWORD = os.getenv("DB_PASSWORD", "root")

323
database.sql Normal file
View File

@@ -0,0 +1,323 @@
--
-- PostgreSQL database dump
--
\restrict ijnV8LNUvXkT7zfNUtK8rYjTPMZ7E4a8Dp7BES0I4ggucAotiMgPl90yvhq0XQq
-- Dumped from database version 18.3 (Debian 18.3-1.pgdg13+1)
-- Dumped by pg_dump version 18.3 (Debian 18.3-1.pgdg13+1)
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET transaction_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;
SET default_tablespace = '';
SET default_table_access_method = heap;
--
-- Name: settings; Type: TABLE; Schema: public; Owner: eco
--
CREATE TABLE public.settings (
id integer NOT NULL,
key character varying(50),
value character varying(100)
);
ALTER TABLE public.settings OWNER TO eco;
--
-- Name: settings_id_seq; Type: SEQUENCE; Schema: public; Owner: eco
--
CREATE SEQUENCE public.settings_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.settings_id_seq OWNER TO eco;
--
-- Name: settings_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: eco
--
ALTER SEQUENCE public.settings_id_seq OWNED BY public.settings.id;
--
-- Name: system_status; Type: TABLE; Schema: public; Owner: eco
--
CREATE TABLE public.system_status (
id integer NOT NULL,
device_name character varying(50),
online boolean,
last_seen timestamp without time zone
);
ALTER TABLE public.system_status OWNER TO eco;
--
-- Name: system_status_id_seq; Type: SEQUENCE; Schema: public; Owner: eco
--
CREATE SEQUENCE public.system_status_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.system_status_id_seq OWNER TO eco;
--
-- Name: system_status_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: eco
--
ALTER SEQUENCE public.system_status_id_seq OWNED BY public.system_status.id;
--
-- Name: telemetry; Type: TABLE; Schema: public; Owner: eco
--
CREATE TABLE public.telemetry (
id integer NOT NULL,
"timestamp" timestamp without time zone DEFAULT CURRENT_TIMESTAMP,
upv double precision,
ipv double precision,
ubat double precision,
ibat double precision,
power double precision,
temperature double precision,
luminosity double precision,
humidity double precision
);
ALTER TABLE public.telemetry OWNER TO eco;
--
-- Name: telemetry_id_seq; Type: SEQUENCE; Schema: public; Owner: eco
--
CREATE SEQUENCE public.telemetry_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.telemetry_id_seq OWNER TO eco;
--
-- Name: telemetry_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: eco
--
ALTER SEQUENCE public.telemetry_id_seq OWNED BY public.telemetry.id;
--
-- Name: users; Type: TABLE; Schema: public; Owner: eco
--
CREATE TABLE public.users (
id integer NOT NULL,
username character varying(50),
password character varying(255),
is_admin boolean DEFAULT false
);
ALTER TABLE public.users OWNER TO eco;
--
-- Name: users_id_seq; Type: SEQUENCE; Schema: public; Owner: eco
--
CREATE SEQUENCE public.users_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.users_id_seq OWNER TO eco;
--
-- Name: users_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: eco
--
ALTER SEQUENCE public.users_id_seq OWNED BY public.users.id;
--
-- Name: settings id; Type: DEFAULT; Schema: public; Owner: eco
--
ALTER TABLE ONLY public.settings ALTER COLUMN id SET DEFAULT nextval('public.settings_id_seq'::regclass);
--
-- Name: system_status id; Type: DEFAULT; Schema: public; Owner: eco
--
ALTER TABLE ONLY public.system_status ALTER COLUMN id SET DEFAULT nextval('public.system_status_id_seq'::regclass);
--
-- Name: telemetry id; Type: DEFAULT; Schema: public; Owner: eco
--
ALTER TABLE ONLY public.telemetry ALTER COLUMN id SET DEFAULT nextval('public.telemetry_id_seq'::regclass);
--
-- Name: users id; Type: DEFAULT; Schema: public; Owner: eco
--
ALTER TABLE ONLY public.users ALTER COLUMN id SET DEFAULT nextval('public.users_id_seq'::regclass);
--
-- Data for Name: settings; Type: TABLE DATA; Schema: public; Owner: eco
--
COPY public.settings (id, key, value) FROM stdin;
1 temp_max 60
2 battery_min 11
3 solar_max 22
\.
--
-- Data for Name: system_status; Type: TABLE DATA; Schema: public; Owner: eco
--
COPY public.system_status (id, device_name, online, last_seen) FROM stdin;
1 esp32 f 2026-03-05 15:07:28.827398
\.
--
-- Data for Name: telemetry; Type: TABLE DATA; Schema: public; Owner: eco
--
COPY public.telemetry (id, "timestamp", upv, ipv, ubat, ibat, power, temperature, luminosity, humidity) FROM stdin;
1 2026-03-05 14:51:17.119119 18.5 0.7 12.4 0.5 13 32 500 45
2 2026-03-05 15:07:19.878569 19 0.8 12.5 0.5 14 31 600 40
\.
--
-- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: eco
--
COPY public.users (id, username, password, is_admin) FROM stdin;
1 admin admin123 t
\.
--
-- Name: settings_id_seq; Type: SEQUENCE SET; Schema: public; Owner: eco
--
SELECT pg_catalog.setval('public.settings_id_seq', 3, true);
--
-- Name: system_status_id_seq; Type: SEQUENCE SET; Schema: public; Owner: eco
--
SELECT pg_catalog.setval('public.system_status_id_seq', 1, true);
--
-- Name: telemetry_id_seq; Type: SEQUENCE SET; Schema: public; Owner: eco
--
SELECT pg_catalog.setval('public.telemetry_id_seq', 2, true);
--
-- Name: users_id_seq; Type: SEQUENCE SET; Schema: public; Owner: eco
--
SELECT pg_catalog.setval('public.users_id_seq', 1, true);
--
-- Name: settings settings_key_key; Type: CONSTRAINT; Schema: public; Owner: eco
--
ALTER TABLE ONLY public.settings
ADD CONSTRAINT settings_key_key UNIQUE (key);
--
-- Name: settings settings_pkey; Type: CONSTRAINT; Schema: public; Owner: eco
--
ALTER TABLE ONLY public.settings
ADD CONSTRAINT settings_pkey PRIMARY KEY (id);
--
-- Name: system_status system_status_pkey; Type: CONSTRAINT; Schema: public; Owner: eco
--
ALTER TABLE ONLY public.system_status
ADD CONSTRAINT system_status_pkey PRIMARY KEY (id);
--
-- Name: telemetry telemetry_pkey; Type: CONSTRAINT; Schema: public; Owner: eco
--
ALTER TABLE ONLY public.telemetry
ADD CONSTRAINT telemetry_pkey PRIMARY KEY (id);
--
-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: eco
--
ALTER TABLE ONLY public.users
ADD CONSTRAINT users_pkey PRIMARY KEY (id);
--
-- Name: users users_username_key; Type: CONSTRAINT; Schema: public; Owner: eco
--
ALTER TABLE ONLY public.users
ADD CONSTRAINT users_username_key UNIQUE (username);
--
-- PostgreSQL database dump complete
--
\unrestrict ijnV8LNUvXkT7zfNUtK8rYjTPMZ7E4a8Dp7BES0I4ggucAotiMgPl90yvhq0XQq

5
requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
Flask==3.0.3
flask-cors==4.0.1
python-dotenv==1.0.1
mysql-connector-python==9.0.0
Werkzeug==3.0.3

View File

@@ -0,0 +1,78 @@
<!DOCTYPE html>
<html>
<head>
<title>Admin EcoCharge</title>
<style>
body{
font-family: Arial;
background:#f5f7fb;
padding:40px;
}
.card{
background:white;
padding:30px;
border-radius:10px;
box-shadow:0 4px 12px rgba(0,0,0,0.1);
max-width:500px;
}
h2{
margin-top:0;
}
label{
display:block;
margin-top:15px;
}
input{
width:100%;
padding:8px;
margin-top:5px;
}
button{
margin-top:20px;
padding:10px;
background:#2563eb;
color:white;
border:none;
border-radius:6px;
cursor:pointer;
}
a{
display:block;
margin-top:20px;
}
</style>
</head>
<body>
<div class="card">
<h2>Administration EcoCharge</h2>
<form method="POST">
<label>Seuil batterie minimum</label>
<input type="number" step="0.1" name="min_battery" value="{{ settings.min_battery }}">
<label>Température max batterie</label>
<input type="number" step="0.1" name="max_temp" value="{{ settings.max_temp }}">
<label>Puissance solaire minimum</label>
<input type="number" step="0.1" name="min_power" value="{{ settings.min_power }}">
<button type="submit">Sauvegarder</button>
</form>
<a href="/admin/logout">Se déconnecter</a>
</div>
</body>
</html>

87
templates/login.html Normal file
View File

@@ -0,0 +1,87 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Connexion Admin - EcoCharge</title>
<style>
body {
font-family: Arial, sans-serif;
background: #f4f7fb;
margin: 0;
padding: 0;
}
.login-container {
width: 360px;
margin: 100px auto;
background: white;
padding: 30px;
border-radius: 12px;
box-shadow: 0 4px 18px rgba(0,0,0,0.12);
}
h2 {
text-align: center;
margin-bottom: 25px;
color: #1f2937;
}
label {
display: block;
margin-bottom: 6px;
margin-top: 15px;
color: #374151;
font-weight: bold;
}
input {
width: 100%;
padding: 10px;
border: 1px solid #d1d5db;
border-radius: 8px;
box-sizing: border-box;
}
button {
width: 100%;
margin-top: 20px;
padding: 12px;
border: none;
border-radius: 8px;
background: #2563eb;
color: white;
font-size: 16px;
cursor: pointer;
}
button:hover {
background: #1d4ed8;
}
.error {
color: red;
margin-top: 15px;
text-align: center;
}
</style>
</head>
<body>
<div class="login-container">
<h2>Connexion Admin</h2>
<form method="POST" action="/admin/login">
<label for="username">Nom d'utilisateur</label>
<input type="text" id="username" name="username" required>
<label for="password">Mot de passe</label>
<input type="password" id="password" name="password" required>
<button type="submit">Se connecter</button>
</form>
{% if error %}
<p class="error">{{ error }}</p>
{% endif %}
</div>
</body>
</html>