my0919175 commited on
Commit
d36aa0c
·
verified ·
1 Parent(s): e38d85f

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +72 -5
app.py CHANGED
@@ -244,6 +244,16 @@ def init_db():
244
  FOREIGN KEY(user_id) REFERENCES users(id)
245
  )
246
  """)
 
 
 
 
 
 
 
 
 
 
247
  db.execute("""
248
  CREATE TABLE IF NOT EXISTS projects (
249
  id TEXT PRIMARY KEY,
@@ -350,6 +360,7 @@ def init_db():
350
  db.execute("ALTER TABLE users ADD COLUMN id_card TEXT") if not _column_exists(db,'users','id_card') else None
351
  db.execute("ALTER TABLE users ADD COLUMN id_status TEXT DEFAULT 'none'") if not _column_exists(db,'users','id_status') else None
352
  db.execute("ALTER TABLE users ADD COLUMN active INTEGER DEFAULT 1") if not _column_exists(db,'users','active') else None
 
353
  db.execute("CREATE INDEX IF NOT EXISTS idx_interests_project ON interests(project_id)")
354
  db.execute("CREATE INDEX IF NOT EXISTS idx_files_project ON project_files(project_id)")
355
  db.execute("CREATE INDEX IF NOT EXISTS idx_appt_project ON appointments(project_id)")
@@ -380,6 +391,25 @@ def create_session(db, user_id: str) -> str:
380
  return token
381
 
382
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
  def current_user(authorization: Optional[str]) -> dict:
384
  if not authorization or not authorization.startswith("Bearer "):
385
  raise HTTPException(401, "لازم تسجل دخول")
@@ -436,6 +466,14 @@ class ResetPasswordIn(BaseModel):
436
  new_password: str = Field(min_length=6, max_length=100)
437
 
438
 
 
 
 
 
 
 
 
 
439
  class ProjectIn(BaseModel):
440
  name: str = Field(min_length=1, max_length=200)
441
  sector: str
@@ -503,7 +541,7 @@ def row_to_public_user(row: sqlite3.Row) -> dict:
503
 
504
 
505
  @app.post("/api/auth/signup")
506
- def signup(body: SignupIn, request: Request):
507
  rate_limit(request, "signup", max_calls=5, window_sec=3600)
508
  if body.role not in ("owner", "investor", "both", "broker"):
509
  raise HTTPException(400, "نوع الحساب لازم يكون owner أو investor أو both")
@@ -515,13 +553,13 @@ def signup(body: SignupIn, request: Request):
515
  raise HTTPException(409, "الإيميل ده مسجل قبل كده")
516
  db.execute(
517
  "INSERT INTO users (id,name,email,phone,password_hash,salt,role,created_at,bio,owner_field,"
518
- "investor_budget_min,investor_budget_max,investor_sectors) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)",
519
  (uid, body.name, body.email, body.phone, h, salt, body.role, int(time.time()), body.bio,
520
  body.owner_field, body.investor_budget_min, body.investor_budget_max, body.investor_sectors),
521
  )
522
- token = create_session(db, uid)
523
- row = db.execute("SELECT * FROM users WHERE id=?", (uid,)).fetchone()
524
- return {"token": token, "user": row_to_public_user(row)}
525
 
526
 
527
  @app.post("/api/auth/login")
@@ -533,10 +571,39 @@ def login(body: LoginIn, request: Request):
533
  raise HTTPException(401, "الإيميل أو الباسورد غلط")
534
  if not row["active"]:
535
  raise HTTPException(403, "الحساب ده معطّل. لو ده غلط، تواصل معانا")
 
 
536
  token = create_session(db, row["id"])
537
  return {"token": token, "user": row_to_public_user(row)}
538
 
539
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
540
  @app.post("/api/auth/forgot-password")
541
  def forgot_password(body: ForgotPasswordIn, request: Request, background_tasks: BackgroundTasks):
542
  rate_limit(request, "forgot_password", max_calls=4, window_sec=900)
 
244
  FOREIGN KEY(user_id) REFERENCES users(id)
245
  )
246
  """)
247
+ db.execute("""
248
+ CREATE TABLE IF NOT EXISTS email_verifications (
249
+ token TEXT PRIMARY KEY,
250
+ user_id TEXT NOT NULL,
251
+ created_at INTEGER NOT NULL,
252
+ expires_at INTEGER NOT NULL,
253
+ used INTEGER DEFAULT 0,
254
+ FOREIGN KEY(user_id) REFERENCES users(id)
255
+ )
256
+ """)
257
  db.execute("""
258
  CREATE TABLE IF NOT EXISTS projects (
259
  id TEXT PRIMARY KEY,
 
360
  db.execute("ALTER TABLE users ADD COLUMN id_card TEXT") if not _column_exists(db,'users','id_card') else None
361
  db.execute("ALTER TABLE users ADD COLUMN id_status TEXT DEFAULT 'none'") if not _column_exists(db,'users','id_status') else None
362
  db.execute("ALTER TABLE users ADD COLUMN active INTEGER DEFAULT 1") if not _column_exists(db,'users','active') else None
363
+ db.execute("ALTER TABLE users ADD COLUMN email_verified INTEGER DEFAULT 0") if not _column_exists(db,'users','email_verified') else None
364
  db.execute("CREATE INDEX IF NOT EXISTS idx_interests_project ON interests(project_id)")
365
  db.execute("CREATE INDEX IF NOT EXISTS idx_files_project ON project_files(project_id)")
366
  db.execute("CREATE INDEX IF NOT EXISTS idx_appt_project ON appointments(project_id)")
 
391
  return token
392
 
393
 
394
+ def send_verification_email(db, background_tasks: BackgroundTasks, user_id: str, name: str, email: str):
395
+ token = secrets.token_urlsafe(32)
396
+ now = int(time.time())
397
+ db.execute(
398
+ "INSERT INTO email_verifications (token,user_id,created_at,expires_at) VALUES (?,?,?,?)",
399
+ (token, user_id, now, now + 86400), # صالح 24 ساعة
400
+ )
401
+ link = f"{SITE_URL}/verify-email.html?token={token}"
402
+ html = email_html(
403
+ "أكّد إيميلك",
404
+ f"أهلاً {name}،",
405
+ ["شكرًا إنك عملت حساب على نيّة. خطوة واحدة باقية بس عشان تفعّل حسابك.",
406
+ "دوس على الزرار تحت عشان تأكّد إيميلك وتدخل حسابك على طول. الرابط صالح لمدة 24 ساعة.",
407
+ "لو ملطلبتش تسجيل حساب على نيّة، تجاهل الرسالة دي ببساطة."],
408
+ "تأكيد الإيميل والدخول", link,
409
+ )
410
+ background_tasks.add_task(send_email, email, "أكّد إيميلك — نيّة", html)
411
+
412
+
413
  def current_user(authorization: Optional[str]) -> dict:
414
  if not authorization or not authorization.startswith("Bearer "):
415
  raise HTTPException(401, "لازم تسجل دخول")
 
466
  new_password: str = Field(min_length=6, max_length=100)
467
 
468
 
469
+ class VerifyEmailIn(BaseModel):
470
+ token: str
471
+
472
+
473
+ class ResendVerifyIn(BaseModel):
474
+ email: EmailStr
475
+
476
+
477
  class ProjectIn(BaseModel):
478
  name: str = Field(min_length=1, max_length=200)
479
  sector: str
 
541
 
542
 
543
  @app.post("/api/auth/signup")
544
+ def signup(body: SignupIn, request: Request, background_tasks: BackgroundTasks):
545
  rate_limit(request, "signup", max_calls=5, window_sec=3600)
546
  if body.role not in ("owner", "investor", "both", "broker"):
547
  raise HTTPException(400, "نوع الحساب لازم يكون owner أو investor أو both")
 
553
  raise HTTPException(409, "الإيميل ده مسجل قبل كده")
554
  db.execute(
555
  "INSERT INTO users (id,name,email,phone,password_hash,salt,role,created_at,bio,owner_field,"
556
+ "investor_budget_min,investor_budget_max,investor_sectors,email_verified) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,0)",
557
  (uid, body.name, body.email, body.phone, h, salt, body.role, int(time.time()), body.bio,
558
  body.owner_field, body.investor_budget_min, body.investor_budget_max, body.investor_sectors),
559
  )
560
+ send_verification_email(db, background_tasks, uid, body.name, body.email)
561
+ return {"needsVerification": True, "email": body.email,
562
+ "message": "اتبعت رسالة تأكيد لإيميلك. افتح الرابط اللي فيها عشان تفعّل حسابك وتدخل على طول."}
563
 
564
 
565
  @app.post("/api/auth/login")
 
571
  raise HTTPException(401, "الإيميل أو الباسورد غلط")
572
  if not row["active"]:
573
  raise HTTPException(403, "الحساب ده معطّل. لو ده غلط، تواصل معانا")
574
+ if "email_verified" in row.keys() and not row["email_verified"]:
575
+ raise HTTPException(403, "لازم تأكّد إيميلك الأول. اتبعتلك رسالة فيها رابط التفعيل — لو مش لاقيها استخدم زرار إعادة الإرسال")
576
  token = create_session(db, row["id"])
577
  return {"token": token, "user": row_to_public_user(row)}
578
 
579
 
580
+ @app.post("/api/auth/verify-email")
581
+ def verify_email(body: VerifyEmailIn):
582
+ with get_db() as db:
583
+ row = db.execute(
584
+ "SELECT * FROM email_verifications WHERE token=? AND used=0", (body.token,)
585
+ ).fetchone()
586
+ if not row or row["expires_at"] < time.time():
587
+ raise HTTPException(400, "رابط التأكيد غير صالح أو منتهي — اطلب رابط جديد")
588
+ db.execute("UPDATE email_verifications SET used=1 WHERE token=?", (body.token,))
589
+ db.execute("UPDATE users SET email_verified=1 WHERE id=?", (row["user_id"],))
590
+ user = db.execute("SELECT * FROM users WHERE id=?", (row["user_id"],)).fetchone()
591
+ if not user["active"]:
592
+ raise HTTPException(403, "الحساب ده معطّل. لو ده غلط، تواصل معانا")
593
+ token = create_session(db, user["id"])
594
+ return {"token": token, "user": row_to_public_user(user)}
595
+
596
+
597
+ @app.post("/api/auth/resend-verification")
598
+ def resend_verification(body: ResendVerifyIn, request: Request, background_tasks: BackgroundTasks):
599
+ rate_limit(request, "resend_verification", max_calls=4, window_sec=900)
600
+ with get_db() as db:
601
+ user = db.execute("SELECT id,name,email_verified FROM users WHERE email=?", (body.email,)).fetchone()
602
+ if user and not user["email_verified"]:
603
+ send_verification_email(db, background_tasks, user["id"], user["name"], body.email)
604
+ return {"ok": True, "message": "لو الإيميل مسجل ومش مفعّل، هيوصلك رابط تأكيد جديد"}
605
+
606
+
607
  @app.post("/api/auth/forgot-password")
608
  def forgot_password(body: ForgotPasswordIn, request: Request, background_tasks: BackgroundTasks):
609
  rate_limit(request, "forgot_password", max_calls=4, window_sec=900)