commit 88ced77b235d56864e32a767da361f8bc1c8af48 Author: Marius Robert Macamete Date: Wed Mar 4 09:52:43 2026 +0200 init commit diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d9e1668 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.12-slim +WORKDIR /app +# Instalăm dependențele +RUN pip install --no-cache-dir flask flask-cors apscheduler +# Copiem fișierele +COPY . . +# Expunem portul +EXPOSE 5000 +# Rulăm aplicația +CMD ["python", "server.py"] \ No newline at end of file diff --git a/data/aquila_forms.db b/data/aquila_forms.db new file mode 100644 index 0000000..ebf8f15 Binary files /dev/null and b/data/aquila_forms.db differ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b508343 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,28 @@ +version: "3.9" + +networks: + reverse-proxy: + external: true + +volumes: + mailform_db: + mailform_assets: + +services: + mailform-app: + build: . + container_name: mailform-aquila + restart: unless-stopped + environment: + - TZ=Europe/Bucharest + - VIRTUAL_HOST=mailform.northdanubesoft.eu + - VIRTUAL_PORT=5000 + - LETSENCRYPT_HOST=mailform.northdanubesoft.eu + - LETSENCRYPT_EMAIL=macamete.robert@gmail.com + expose: + - "5000" + volumes: + - mailform_db:/app/data + - mailform_assets:/app/static + networks: + - reverse-proxy \ No newline at end of file diff --git a/helpers/__pycache__/send_email.cpython-313.pyc b/helpers/__pycache__/send_email.cpython-313.pyc new file mode 100644 index 0000000..e5a533e Binary files /dev/null and b/helpers/__pycache__/send_email.cpython-313.pyc differ diff --git a/helpers/send_email.py b/helpers/send_email.py new file mode 100644 index 0000000..22a4be8 --- /dev/null +++ b/helpers/send_email.py @@ -0,0 +1,17 @@ +import smtplib + +# --- HELPER TRIMITE EMAIL (GMAIL) --- +def send_gmail(to_email, subject, body): + # Folosește App Password-ul tău de la Google aici + user = "macamete.robert@gmail.com" + pw = "advx yqlv jkaa czvr" + msg = f"Subject: {subject}\nContent-Type: text/html\n\n{body}" + try: + server = smtplib.SMTP_SSL('smtp.gmail.com', 465) + server.login(user, pw) + server.sendmail(user, to_email, msg) + server.quit() + return True + except Exception as e: + print(f"Eroare mail: {e}") + return False \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..756c1cf --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +Flask==3.1.3 +APScheduler==3.11.2 \ No newline at end of file diff --git a/server.py b/server.py new file mode 100644 index 0000000..5f4107c --- /dev/null +++ b/server.py @@ -0,0 +1,274 @@ +import sqlite3, uuid, random +import atexit +from datetime import datetime, timedelta +from flask import Flask, request, jsonify, render_template_string, session, redirect, send_from_directory +from apscheduler.schedulers.background import BackgroundScheduler +from helpers.send_email import send_gmail + +app = Flask(__name__, static_folder='static', static_url_path='/static') +app.secret_key = "cheie_ultra_secreta_aquila" +DB_FILE = "data/aquila_forms.db" +ADMIN_EMAIL = "macamete.robert@gmail.com" + +# --- LOGICA BAZĂ DE DATE --- +def init_db(): + with sqlite3.connect(DB_FILE) as conn: + cursor = conn.cursor() + cursor.execute('''CREATE TABLE IF NOT EXISTS clients + (id TEXT PRIMARY KEY, + nume TEXT, + email_destinatie TEXT, + creat_la DATETIME, + data_expirare DATETIME)''') + cursor.execute('''CREATE TABLE IF NOT EXISTS otps (cod TEXT, expira_la DATETIME)''') + conn.commit() + +# --- RUTE ADMIN --- +@app.route('/admin') +def admin_home(): + if not session.get('logged_in'): + return render_template_string(LOGIN_HTML) + + with sqlite3.connect(DB_FILE) as conn: + conn.row_factory = sqlite3.Row + clients = conn.execute("SELECT * FROM clients").fetchall() + return render_template_string(DASHBOARD_HTML, clients=clients) + +@app.route('/admin/login', methods=['POST']) +def do_login(): + email = request.form.get('email') + if email == ADMIN_EMAIL: + otp = str(random.randint(100000, 999999)) + expira = datetime.now() + timedelta(minutes=10) + with sqlite3.connect(DB_FILE) as conn: + conn.execute("INSERT INTO otps VALUES (?, ?)", (otp, expira)) + send_gmail(ADMIN_EMAIL, "Cod Acces Panou", f"Codul tau: {otp}") + return render_template_string(VERIFY_HTML) + return "Acces interzis", 403 + +@app.route('/admin/verify', methods=['POST']) +def do_verify(): + cod = request.form.get('cod') + with sqlite3.connect(DB_FILE) as conn: + res = conn.execute("SELECT * FROM otps WHERE cod=? AND expira_la > ?", (cod, datetime.now())).fetchone() + if res: + session['logged_in'] = True + conn.execute("DELETE FROM otps") # Curățăm codurile folosite + return redirect('/admin') + return "Cod invalid", 401 + +@app.route('/admin/add', methods=['POST']) +def add_client(): + if not session.get('logged_in'): return "No", 401 + uid = str(uuid.uuid4())[:8] + nume = request.form.get('nume') + email = request.form.get('email') + acum = datetime.now().isoformat() + # Calculăm data de expirare (peste 365 zile) + data_expirare = (datetime.now() + timedelta(days=365)).isoformat() + + with sqlite3.connect(DB_FILE) as conn: + conn.execute("INSERT INTO clients VALUES (?, ?, ?, ?, ?)", + (uid, nume, email, acum, data_expirare)) + return redirect('/admin') + +@app.route('/admin/delete/') +def delete_client(id): + if not session.get('logged_in'): return "No", 401 + with sqlite3.connect(DB_FILE) as conn: + conn.execute("DELETE FROM clients WHERE id=?", (id,)) + return redirect('/admin') + +# --- ENDPOINT PUBLIC PENTRU SITE-URI --- +@app.route('/api/v1/send', methods=['POST']) +def public_api(): + cid = request.form.get('client_id') + with sqlite3.connect(DB_FILE) as conn: + conn.row_factory = sqlite3.Row + client = conn.execute("SELECT * FROM clients WHERE id=?", (cid,)).fetchone() + + if client: + body = f"Mesaj nou de la {request.form.get('nume')}
Email: {request.form.get('email')}

{request.form.get('mesaj')}" + send_gmail(client['email_destinatie'], f"Contact: {client['nume']}", body) + return jsonify({"status": "ok"}), 200 + return jsonify({"status": "error"}), 404 + +# --- LOGICA DE VERIFICARE BILLING --- +def check_billing_reminders(): + print(f"[{datetime.now()}] Se verifică termenele de facturare...") + + # Calculăm data de peste fix 30 de zile (doar data, fără oră) + tinta = (datetime.now() + timedelta(days=30)).strftime('%Y-%m-%d') + + try: + with sqlite3.connect(DB_FILE) as conn: + conn.row_factory = sqlite3.Row + # Verificăm cine expiră în fereastra de 30 de zile + query = "SELECT * FROM clients WHERE date(data_expirare) = ?" + clienti = conn.execute(query, (tinta,)).fetchall() + + for client in clienti: + subiect = f"💰 Facturare: {client['nume']}" + corp = f""" +

Reminder Facturare

+

Clientul {client['nume']} are data de expirare pe {client['data_expirare']}.

+

Trebuie emisă factura pentru reînnoire.

+
+ ID Intern: {client['id']} + """ + send_gmail(ADMIN_EMAIL, subiect, corp) + print(f"Notificare trimisă pentru {client['nume']}") + + except Exception as e: + print(f"Eroare la verificarea billing-ului: {e}") + +# --- CONFIGURARE SCHEDULER --- +scheduler = BackgroundScheduler() +# Setează ora la care vrei să primești mail-ul (ex: ora 09:00 dimineața) +scheduler.add_job(func=check_billing_reminders, trigger="cron", hour=9, minute=0) +scheduler.start() + +# Închide scheduler-ul când aplicația se oprește +atexit.register(lambda: scheduler.shutdown()) + +@app.route('/logo.png') +def serve_logo(): + return send_from_directory(app.static_folder, 'logo.png') + +# --- TEMPLATE-URI HTML (Simplificate) --- +LOGIN_HTML = """ + + + + + + + Login Admin - AquilaSoft + + +
+

Admin Access

+
+
+ + +
+ +
+
+ + +""" + +VERIFY_HTML = """ + + + + + + + Verificare Cod - AquilaSoft + + +
+

Verificare Cod

+

Introdu codul primit pe email.

+
+ + +
+
+ + +""" +DASHBOARD_HTML = """ + + + + + + + Dashboard - AquilaSoft + + + + +
+
+

+ + Adaugă Client Nou +

+
+ + + +
+
+ +
+
+

Clienți Activi

+ {{ clients|length }} înregistrați +
+ +
+ + + + + + + + + + + + {% for c in clients %} + + + + + + + + {% endfor %} + +
ID (client_id)Nume SiteEmail NotificăriExpirareAcțiuni
{{ c['id'] }}{{ c['nume'] }}{{ c['email_destinatie'] }}{{ c['data_expirare'] }} + Şterge +
+
+ {% if not clients %} +
+ + Nu există clienți înregistrați. Adaugă primul client folosind formularul de mai sus. +
+ {% endif %} +
+ + +
+ + +""" + +if __name__ == '__main__': + init_db() + app.run(host='0.0.0.0', port=5000) \ No newline at end of file diff --git a/static/logo.png b/static/logo.png new file mode 100644 index 0000000..e18d152 Binary files /dev/null and b/static/logo.png differ