Spaces:
Runtime error
Runtime error
David Prince commited on
Commit ·
69381eb
0
Parent(s):
Initial dolor3v backend
Browse files- .gitignore +8 -0
- Dockerfile +22 -0
- add_balance.py +8 -0
- admin.py +35 -0
- ai_review.py +35 -0
- app.py +33 -0
- database.py +9 -0
- debug_login.py +12 -0
- endpoints.py +157 -0
- find_db_name.py +7 -0
- fix_user.py +8 -0
- init_db.py +3 -0
- models.py +30 -0
- requirements.txt +5 -0
- reset.py +17 -0
- verify_db.py +7 -0
.gitignore
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.pyc
|
| 3 |
+
*.db
|
| 4 |
+
.env
|
| 5 |
+
instance/
|
| 6 |
+
storage/
|
| 7 |
+
app.app_context
|
| 8 |
+
core
|
Dockerfile
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
COPY requirements.txt .
|
| 6 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 7 |
+
|
| 8 |
+
COPY . .
|
| 9 |
+
|
| 10 |
+
RUN python -c "
|
| 11 |
+
import os
|
| 12 |
+
os.environ['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///dolor.db'
|
| 13 |
+
from app import app
|
| 14 |
+
from models import db
|
| 15 |
+
with app.app_context():
|
| 16 |
+
db.create_all()
|
| 17 |
+
print('DB initialized')
|
| 18 |
+
"
|
| 19 |
+
|
| 20 |
+
EXPOSE 7860
|
| 21 |
+
|
| 22 |
+
CMD ["python", "app.py"]
|
add_balance.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from app import app
|
| 2 |
+
from models import db, User
|
| 3 |
+
|
| 4 |
+
with app.app_context():
|
| 5 |
+
u = User.query.filter_by(email="dolor@test.com").first()
|
| 6 |
+
u.balance = 10000.0
|
| 7 |
+
db.session.commit()
|
| 8 |
+
print(f"Balance set to {u.balance} for {u.account_number}")
|
admin.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Flask, request, jsonify
|
| 2 |
+
from flask_sqlalchemy import SQLAlchemy
|
| 3 |
+
from flask_cors import CORS
|
| 4 |
+
|
| 5 |
+
app = Flask(__name__)
|
| 6 |
+
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///admin.db"
|
| 7 |
+
db = SQLAlchemy(app)
|
| 8 |
+
CORS(app)
|
| 9 |
+
|
| 10 |
+
class Admin(db.Model):
|
| 11 |
+
id = db.Column(db.Integer, primary_key=True)
|
| 12 |
+
username = db.Column(db.String(100), nullable=False)
|
| 13 |
+
password = db.Column(db.String(100), nullable=False)
|
| 14 |
+
|
| 15 |
+
@app.route("/login", methods=["POST"])
|
| 16 |
+
def login():
|
| 17 |
+
data = request.get_json()
|
| 18 |
+
admin = Admin.query.filter_by(username=data["username"], password=data["password"]).first()
|
| 19 |
+
if admin:
|
| 20 |
+
return jsonify({"token": "login successful"})
|
| 21 |
+
return jsonify({"message": "Invalid credentials"})
|
| 22 |
+
|
| 23 |
+
@app.route("/users", methods=["GET"])
|
| 24 |
+
def users():
|
| 25 |
+
users = User.query.all()
|
| 26 |
+
return jsonify([{"id": user.id, "name": user.name, "email": user.email, "account_number": user.account_number, "balance": user.balance} for user in users])
|
| 27 |
+
|
| 28 |
+
@app.route("/transactions", methods=["GET"])
|
| 29 |
+
def transactions():
|
| 30 |
+
transactions = Transaction.query.all()
|
| 31 |
+
return jsonify([{"id": transaction.id, "sender_account_number": transaction.sender_account_number, "recipient_account_number": transaction.recipient_account_number, "amount": transaction.amount} for transaction in transactions])
|
| 32 |
+
|
| 33 |
+
if __name__ == "__main__":
|
| 34 |
+
app.run(debug=True)
|
| 35 |
+
|
ai_review.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def ai_review_transaction(sender, recipient, amount, is_external):
|
| 2 |
+
reasons = []
|
| 3 |
+
decision = "approve"
|
| 4 |
+
|
| 5 |
+
# Rule 1: Amount exceeds balance by 80%
|
| 6 |
+
if amount > sender.balance * 0.8:
|
| 7 |
+
reasons.append("amount exceeds 80% of balance")
|
| 8 |
+
decision = "hold"
|
| 9 |
+
|
| 10 |
+
# Rule 2: Large transaction
|
| 11 |
+
if amount > 10000:
|
| 12 |
+
reasons.append("large transaction above 10,000")
|
| 13 |
+
decision = "hold"
|
| 14 |
+
|
| 15 |
+
# Rule 3: External transfers over 5000 are suspicious
|
| 16 |
+
if is_external and amount > 5000:
|
| 17 |
+
reasons.append("high-value external transfer")
|
| 18 |
+
decision = "hold"
|
| 19 |
+
|
| 20 |
+
# Rule 4: Round large numbers externally (common fraud pattern)
|
| 21 |
+
if is_external and amount >= 1000 and amount % 100 == 0:
|
| 22 |
+
reasons.append("round-number external transfer flagged for review")
|
| 23 |
+
decision = "hold"
|
| 24 |
+
|
| 25 |
+
# Rule 5: Very small amounts (potential test/probe transactions)
|
| 26 |
+
if amount < 1:
|
| 27 |
+
reasons.append("suspiciously small amount")
|
| 28 |
+
decision = "hold"
|
| 29 |
+
|
| 30 |
+
if decision == "approve":
|
| 31 |
+
reason = "Transaction looks normal"
|
| 32 |
+
else:
|
| 33 |
+
reason = "; ".join(reasons)
|
| 34 |
+
|
| 35 |
+
return decision, reason
|
app.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from flask import Flask, jsonify
|
| 3 |
+
from database import init_db, create_tables
|
| 4 |
+
from endpoints import register, login, transfer, external_transfer, balance, history, alerts, jwt
|
| 5 |
+
from flask_cors import CORS
|
| 6 |
+
|
| 7 |
+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 8 |
+
DB_PATH = os.path.join(BASE_DIR, 'dolor.db')
|
| 9 |
+
|
| 10 |
+
app = Flask(__name__)
|
| 11 |
+
app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{DB_PATH}"
|
| 12 |
+
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
|
| 13 |
+
app.config["JWT_SECRET_KEY"] = "dolor3v-secret-2026"
|
| 14 |
+
|
| 15 |
+
init_db(app)
|
| 16 |
+
jwt.init_app(app)
|
| 17 |
+
CORS(app)
|
| 18 |
+
|
| 19 |
+
app.add_url_rule("/register", "register", register, methods=["POST"])
|
| 20 |
+
app.add_url_rule("/login", "login", login, methods=["POST"])
|
| 21 |
+
app.add_url_rule("/transfer", "transfer", transfer, methods=["POST"])
|
| 22 |
+
app.add_url_rule("/external-transfer", "external_transfer", external_transfer, methods=["POST"])
|
| 23 |
+
app.add_url_rule("/balance", "balance", balance, methods=["GET"])
|
| 24 |
+
app.add_url_rule("/history", "history", history, methods=["GET"])
|
| 25 |
+
app.add_url_rule("/alerts", "alerts", alerts, methods=["GET"])
|
| 26 |
+
|
| 27 |
+
@app.route("/health")
|
| 28 |
+
def health():
|
| 29 |
+
return jsonify({"status": "ok", "app": "dolor3v", "version": "1.0.0"})
|
| 30 |
+
|
| 31 |
+
if __name__ == "__main__":
|
| 32 |
+
create_tables(app)
|
| 33 |
+
app.run(host="0.0.0.0", port=7860, debug=False)
|
database.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from models import db
|
| 2 |
+
|
| 3 |
+
def init_db(app):
|
| 4 |
+
db.init_app(app)
|
| 5 |
+
|
| 6 |
+
def create_tables(app):
|
| 7 |
+
with app.app_context():
|
| 8 |
+
db.create_all()
|
| 9 |
+
print("Tables created!")
|
debug_login.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from app import app
|
| 2 |
+
from models import db, User
|
| 3 |
+
from werkzeug.security import check_password_hash
|
| 4 |
+
|
| 5 |
+
with app.app_context():
|
| 6 |
+
print("DB URI:", app.config["SQLALCHEMY_DATABASE_URI"])
|
| 7 |
+
users = User.query.all()
|
| 8 |
+
print("Users in DB:", len(users))
|
| 9 |
+
for u in users:
|
| 10 |
+
print(f" email={u.email} password_hash={u.password[:30]}...")
|
| 11 |
+
result = check_password_hash(u.password, "1234")
|
| 12 |
+
print(f" password check for '1234': {result}")
|
endpoints.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import request, jsonify
|
| 2 |
+
from models import db, User, Transaction, Alert
|
| 3 |
+
from flask_jwt_extended import JWTManager, jwt_required, create_access_token, get_jwt_identity
|
| 4 |
+
from werkzeug.security import generate_password_hash, check_password_hash
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
from ai_review import ai_review_transaction
|
| 7 |
+
import random, string, uuid
|
| 8 |
+
|
| 9 |
+
jwt = JWTManager()
|
| 10 |
+
|
| 11 |
+
def generate_account_number():
|
| 12 |
+
while True:
|
| 13 |
+
number = "ACC" + "".join(random.choices(string.digits, k=10))
|
| 14 |
+
if not User.query.filter_by(account_number=number).first():
|
| 15 |
+
return number
|
| 16 |
+
|
| 17 |
+
def create_alert(account_number, message):
|
| 18 |
+
alert = Alert(account_number=account_number, message=message)
|
| 19 |
+
db.session.add(alert)
|
| 20 |
+
|
| 21 |
+
def register():
|
| 22 |
+
data = request.get_json()
|
| 23 |
+
if User.query.filter_by(email=data["email"]).first():
|
| 24 |
+
return jsonify({"message": "Email already exists"}), 400
|
| 25 |
+
user = User(
|
| 26 |
+
name=data["name"],
|
| 27 |
+
email=data["email"],
|
| 28 |
+
password=generate_password_hash(data["password"]),
|
| 29 |
+
account_number=generate_account_number(),
|
| 30 |
+
)
|
| 31 |
+
db.session.add(user)
|
| 32 |
+
db.session.commit()
|
| 33 |
+
create_alert(user.account_number, f"Welcome to dolor3v, {user.name}! Your account {user.account_number} is ready.")
|
| 34 |
+
db.session.commit()
|
| 35 |
+
return jsonify({"message": "User registered successfully", "account_number": user.account_number})
|
| 36 |
+
|
| 37 |
+
def login():
|
| 38 |
+
data = request.get_json()
|
| 39 |
+
user = User.query.filter_by(email=data["email"]).first()
|
| 40 |
+
if not user or not check_password_hash(user.password, data["password"]):
|
| 41 |
+
return jsonify({"message": "Invalid credentials"}), 401
|
| 42 |
+
access_token = create_access_token(identity=user.account_number)
|
| 43 |
+
return jsonify({"access_token": access_token, "account_number": user.account_number})
|
| 44 |
+
|
| 45 |
+
@jwt_required()
|
| 46 |
+
def transfer():
|
| 47 |
+
data = request.get_json()
|
| 48 |
+
sender_acc = get_jwt_identity()
|
| 49 |
+
sender = User.query.filter_by(account_number=sender_acc).first()
|
| 50 |
+
recipient = User.query.filter_by(account_number=data["recipient_account_number"]).first()
|
| 51 |
+
if not sender or not recipient:
|
| 52 |
+
return jsonify({"message": "Invalid account numbers"}), 404
|
| 53 |
+
if sender.account_number == recipient.account_number:
|
| 54 |
+
return jsonify({"message": "Cannot transfer to same account"}), 400
|
| 55 |
+
amount = float(data["amount"])
|
| 56 |
+
if sender.balance < amount:
|
| 57 |
+
return jsonify({"message": "Insufficient funds"}), 400
|
| 58 |
+
|
| 59 |
+
decision, reason = ai_review_transaction(sender, recipient, amount, False)
|
| 60 |
+
status = "pending" if decision == "hold" else "completed"
|
| 61 |
+
|
| 62 |
+
if status == "completed":
|
| 63 |
+
sender.balance -= amount
|
| 64 |
+
recipient.balance += amount
|
| 65 |
+
|
| 66 |
+
t = Transaction(
|
| 67 |
+
sender_account_number=sender.account_number,
|
| 68 |
+
recipient_account_number=recipient.account_number,
|
| 69 |
+
amount=amount,
|
| 70 |
+
status=status,
|
| 71 |
+
is_external=False,
|
| 72 |
+
reference=str(uuid.uuid4())[:8].upper(),
|
| 73 |
+
ai_review=reason,
|
| 74 |
+
)
|
| 75 |
+
db.session.add(t)
|
| 76 |
+
create_alert(sender.account_number, f"Transfer of {amount} to {recipient.account_number} — {status}. Ref: {t.reference}")
|
| 77 |
+
create_alert(recipient.account_number, f"You received {amount} from {sender.account_number}. Ref: {t.reference}")
|
| 78 |
+
db.session.commit()
|
| 79 |
+
return jsonify({"message": f"Transfer {status}", "reference": t.reference, "ai_review": reason})
|
| 80 |
+
|
| 81 |
+
@jwt_required()
|
| 82 |
+
def external_transfer():
|
| 83 |
+
data = request.get_json()
|
| 84 |
+
sender_acc = get_jwt_identity()
|
| 85 |
+
sender = User.query.filter_by(account_number=sender_acc).first()
|
| 86 |
+
if not sender:
|
| 87 |
+
return jsonify({"message": "User not found"}), 404
|
| 88 |
+
amount = float(data["amount"])
|
| 89 |
+
if sender.balance < amount:
|
| 90 |
+
return jsonify({"message": "Insufficient funds"}), 400
|
| 91 |
+
|
| 92 |
+
bank_name = data.get("bank_name", "External Bank")
|
| 93 |
+
recipient_name = data.get("recipient_name", "Unknown")
|
| 94 |
+
|
| 95 |
+
decision, reason = ai_review_transaction(sender, recipient_name, amount, True)
|
| 96 |
+
status = "pending" if decision == "hold" else "processing"
|
| 97 |
+
|
| 98 |
+
if status == "processing":
|
| 99 |
+
sender.balance -= amount
|
| 100 |
+
|
| 101 |
+
ref = str(uuid.uuid4())[:8].upper()
|
| 102 |
+
t = Transaction(
|
| 103 |
+
sender_account_number=sender.account_number,
|
| 104 |
+
recipient_account_number=f"EXT:{bank_name}:{data.get('recipient_account')}",
|
| 105 |
+
amount=amount,
|
| 106 |
+
status=status,
|
| 107 |
+
is_external=True,
|
| 108 |
+
reference=ref,
|
| 109 |
+
ai_review=reason,
|
| 110 |
+
)
|
| 111 |
+
db.session.add(t)
|
| 112 |
+
create_alert(sender.account_number, f"External transfer of {amount} to {recipient_name} at {bank_name} — {status}. Ref: {ref}")
|
| 113 |
+
db.session.commit()
|
| 114 |
+
return jsonify({"message": f"External transfer {status}", "reference": ref, "ai_review": reason})
|
| 115 |
+
|
| 116 |
+
@jwt_required()
|
| 117 |
+
def balance():
|
| 118 |
+
account_number = get_jwt_identity()
|
| 119 |
+
user = User.query.filter_by(account_number=account_number).first()
|
| 120 |
+
if not user:
|
| 121 |
+
return jsonify({"message": "Invalid account number"}), 404
|
| 122 |
+
return jsonify({"balance": user.balance, "account_number": user.account_number})
|
| 123 |
+
|
| 124 |
+
@jwt_required()
|
| 125 |
+
def history():
|
| 126 |
+
account_number = get_jwt_identity()
|
| 127 |
+
txns = Transaction.query.filter(
|
| 128 |
+
(Transaction.sender_account_number == account_number) |
|
| 129 |
+
(Transaction.recipient_account_number == account_number)
|
| 130 |
+
).order_by(Transaction.timestamp.desc()).all()
|
| 131 |
+
|
| 132 |
+
result = []
|
| 133 |
+
for t in txns:
|
| 134 |
+
result.append({
|
| 135 |
+
"id": t.id,
|
| 136 |
+
"type": "sent" if t.sender_account_number == account_number else "received",
|
| 137 |
+
"amount": t.amount,
|
| 138 |
+
"sender": t.sender_account_number,
|
| 139 |
+
"recipient": t.recipient_account_number,
|
| 140 |
+
"status": t.status,
|
| 141 |
+
"reference": t.reference,
|
| 142 |
+
"is_external": t.is_external,
|
| 143 |
+
"ai_review": t.ai_review,
|
| 144 |
+
"timestamp": t.timestamp.isoformat(),
|
| 145 |
+
})
|
| 146 |
+
return jsonify({"transactions": result})
|
| 147 |
+
|
| 148 |
+
@jwt_required()
|
| 149 |
+
def alerts():
|
| 150 |
+
account_number = get_jwt_identity()
|
| 151 |
+
unread = Alert.query.filter_by(account_number=account_number, read=False)\
|
| 152 |
+
.order_by(Alert.timestamp.desc()).all()
|
| 153 |
+
result = [{"id": a.id, "message": a.message, "timestamp": a.timestamp.isoformat()} for a in unread]
|
| 154 |
+
for a in unread:
|
| 155 |
+
a.read = True
|
| 156 |
+
db.session.commit()
|
| 157 |
+
return jsonify({"alerts": result})
|
find_db_name.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from app import app
|
| 2 |
+
|
| 3 |
+
def main():
|
| 4 |
+
print(app.config['SQLALCHEMY_DATABASE_URI'])
|
| 5 |
+
|
| 6 |
+
if __name__ == "__main__":
|
| 7 |
+
main()
|
fix_user.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from app import app
|
| 2 |
+
from models import db, User
|
| 3 |
+
|
| 4 |
+
with app.app_context():
|
| 5 |
+
u = User.query.filter_by(email="dolor@test.com").first()
|
| 6 |
+
db.session.delete(u)
|
| 7 |
+
db.session.commit()
|
| 8 |
+
print("Deleted old user")
|
init_db.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from app import app
|
| 2 |
+
from database import create_tables
|
| 3 |
+
create_tables(app)
|
models.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask_sqlalchemy import SQLAlchemy
|
| 2 |
+
from datetime import datetime
|
| 3 |
+
|
| 4 |
+
db = SQLAlchemy()
|
| 5 |
+
|
| 6 |
+
class User(db.Model):
|
| 7 |
+
id = db.Column(db.Integer, primary_key=True)
|
| 8 |
+
name = db.Column(db.String(100), nullable=False)
|
| 9 |
+
email = db.Column(db.String(100), nullable=False, unique=True)
|
| 10 |
+
password = db.Column(db.String(100), nullable=False)
|
| 11 |
+
account_number = db.Column(db.String(20), nullable=False, unique=True)
|
| 12 |
+
balance = db.Column(db.Float, nullable=False, default=0.0)
|
| 13 |
+
|
| 14 |
+
class Transaction(db.Model):
|
| 15 |
+
id = db.Column(db.Integer, primary_key=True)
|
| 16 |
+
sender_account_number = db.Column(db.String(20), nullable=False)
|
| 17 |
+
recipient_account_number = db.Column(db.String(20), nullable=False)
|
| 18 |
+
amount = db.Column(db.Float, nullable=False)
|
| 19 |
+
status = db.Column(db.String(20), nullable=False, default="completed")
|
| 20 |
+
is_external = db.Column(db.Boolean, default=False)
|
| 21 |
+
reference = db.Column(db.String(50), nullable=True)
|
| 22 |
+
ai_review = db.Column(db.String(500), nullable=True)
|
| 23 |
+
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
|
| 24 |
+
|
| 25 |
+
class Alert(db.Model):
|
| 26 |
+
id = db.Column(db.Integer, primary_key=True)
|
| 27 |
+
account_number = db.Column(db.String(20), nullable=False)
|
| 28 |
+
message = db.Column(db.String(500), nullable=False)
|
| 29 |
+
read = db.Column(db.Boolean, default=False)
|
| 30 |
+
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
flask
|
| 2 |
+
flask-sqlalchemy
|
| 3 |
+
flask-cors
|
| 4 |
+
flask-jwt-extended
|
| 5 |
+
werkzeug
|
reset.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os, sqlite3
|
| 2 |
+
from app import app
|
| 3 |
+
from models import db
|
| 4 |
+
|
| 5 |
+
db_path = '/data/data/com.termux/files/home/dolor.db'
|
| 6 |
+
|
| 7 |
+
# Delete old db
|
| 8 |
+
if os.path.exists(db_path):
|
| 9 |
+
os.remove(db_path)
|
| 10 |
+
print("Deleted old DB")
|
| 11 |
+
|
| 12 |
+
# Create fresh with all tables
|
| 13 |
+
with app.app_context():
|
| 14 |
+
db.create_all()
|
| 15 |
+
from sqlalchemy import inspect
|
| 16 |
+
tables = inspect(db.engine).get_table_names()
|
| 17 |
+
print("Tables created:", tables)
|
verify_db.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from app import app
|
| 2 |
+
from sqlalchemy import inspect
|
| 3 |
+
|
| 4 |
+
with app.app_context():
|
| 5 |
+
from models import db
|
| 6 |
+
inspector = inspect(db.engine)
|
| 7 |
+
print("Tables:", inspector.get_table_names())
|