David Prince commited on
Commit
69381eb
·
0 Parent(s):

Initial dolor3v backend

Browse files
Files changed (16) hide show
  1. .gitignore +8 -0
  2. Dockerfile +22 -0
  3. add_balance.py +8 -0
  4. admin.py +35 -0
  5. ai_review.py +35 -0
  6. app.py +33 -0
  7. database.py +9 -0
  8. debug_login.py +12 -0
  9. endpoints.py +157 -0
  10. find_db_name.py +7 -0
  11. fix_user.py +8 -0
  12. init_db.py +3 -0
  13. models.py +30 -0
  14. requirements.txt +5 -0
  15. reset.py +17 -0
  16. 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())