ソースを参照

modify payment table & add token authentication

conradlan 3 年 前
コミット
a000f01efd
8 ファイル変更144 行追加65 行削除
  1. 3 1
      .vscode/launch.json
  2. 78 26
      main.py
  3. 17 9
      sql/crud.py
  4. 15 11
      sql/models.py
  5. 8 12
      sql/schemas.py
  6. 6 2
      templates/index.html
  7. 13 0
      templates/login.html
  8. 4 4
      templates/request.html

+ 3 - 1
.vscode/launch.json

@@ -15,7 +15,9 @@
                 "0.0.0.0",
                 "--port",
                 "8887",
-                "--reload"
+                "--reload",
+                "--ssl-keyfile=/etc/letsencrypt/live/api.ptt.cx/privkey.pem",
+                "--ssl-certfile=/etc/letsencrypt/live/api.ptt.cx/fullchain.pem"
             ],
             "jinja": true
         }

+ 78 - 26
main.py

@@ -1,27 +1,28 @@
-from re import U
 import uuid
 import os
+from datetime import datetime, timedelta
 from typing import Optional
-from fastapi import FastAPI, Form, Depends, HTTPException, status
+from fastapi import FastAPI, Depends, HTTPException, status
 from fastapi.templating import Jinja2Templates
+# from fastapi_jwt_auth import AuthJWT
 from dotenv import load_dotenv
 from os.path import join, dirname
 from linepay import LinePayApi
-from pydantic.networks import EmailStr
 from starlette.responses import HTMLResponse
 from sqlalchemy.orm import Session
 from fastapi.encoders import jsonable_encoder
 from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
 
 from sql.database import get_db_session
-from sql.crud import create_order, get_user
-from sql.models import order_info_linepay
-from sql.schemas import order_info_linepay as orderSechmas
-from sql.schemas import User
+from sql.crud import create_payment, get_user
+from sql.models import Payment, User
+from sql.schemas import PaymentSchema
+from sql.schemas import UserSchema
 
 from jose import JWTError, jwt
 from passlib.context import CryptContext
 
+
 # TBD load_env
 SECRET_KEY = "df2f77bd544240801a048bd4293afd8eeb7fff3cb7050e42c791db4b83ebadcd"
 ALGORITHM = "HS256"
@@ -36,12 +37,46 @@ def verify_password(plain_password, hashed_password):
 def get_password_hash(password):
     return pwd_context.hash(password)
 
-
+def authenticate_user(db_sesion: Session, username: str, password: str):
+    user = get_user(db_sesion, username)
+    if not user:
+        return False
+    if not verify_password(password, user.password):
+        return False
+    return user
+
+def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
+    to_encode = data.copy()
+    if expires_delta:
+        expire = datetime.utcnow() + expires_delta
+    else:
+        expire = datetime.utcnow() + timedelta(minutes=15)
+    to_encode.update({"exp": expire})
+    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
+    return encoded_jwt
+
+def get_current_user(db_sesion: Session, token: str = Depends(oauth2_scheme)):
+    credentials_exception = HTTPException(
+        status_code=status.HTTP_401_UNAUTHORIZED,
+        detail="Could not validate credentials",
+        headers={"WWW-Authenticate": "Bearer"},
+    )
+    try:
+        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+        username: str = payload.get("sub")
+        if username is None:
+            raise credentials_exception
+    except JWTError:
+        raise credentials_exception
+    user = get_user(db_sesion, username=username)
+    if user is None:
+        raise credentials_exception
+    return user
 
 
 # dotenv
-dotenv_path = join(dirname(__file__),'./env/.env')
-# dotenv_path = join(dirname(__file__),'./env/test.env') ## sandbox
+# dotenv_path = join(dirname(__file__),'./env/.env')
+dotenv_path = join(dirname(__file__),'./env/test.env') ## sandbox
 load_dotenv(dotenv_path)
 
 # logger (TBD)
@@ -53,8 +88,8 @@ templates = Jinja2Templates(directory="templates")
 LINE_PAY_CHANNEL_ID = os.environ.get("LINE_PAY_CHANNEL_ID")
 LINE_PAY_CHANNEL_SECRET = os.environ.get("LINE_PAY_CHANNEL_SECRET")
 LINE_PAY_REQEST_BASE_URL = "https://{}".format(os.environ.get("HOST_NAME"))
-line = LinePayApi(LINE_PAY_CHANNEL_ID, LINE_PAY_CHANNEL_SECRET, is_sandbox=False)
-# line = LinePayApi(LINE_PAY_CHANNEL_ID, LINE_PAY_CHANNEL_SECRET, is_sandbox=True)
+# line = LinePayApi(LINE_PAY_CHANNEL_ID, LINE_PAY_CHANNEL_SECRET, is_sandbox=False)
+line = LinePayApi(LINE_PAY_CHANNEL_ID, LINE_PAY_CHANNEL_SECRET, is_sandbox=True)
 
 # CACHE
 CACHE = {}
@@ -63,27 +98,45 @@ CACHE = {}
 app = FastAPI()
 oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
 
+
 @app.get('/')
-def hello(token: str = Depends(oauth2_scheme)):
+def hello():
+    return templates.TemplateResponse("login.html",{"request": {}})
+
+
+@app.post('/')
+def hello(form_data: OAuth2PasswordRequestForm = Depends(), db_sesion: Session = Depends(get_db_session)):
+    user = authenticate_user(db_sesion, form_data.username, form_data.password)
+    if not user:
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail="Incorrect username or password",
+            headers={"WWW-Authenticate": "Bearer"},
+        )
+    access_token_expires = timedelta(days=ACCESS_TOKEN_EXPIRE_DAYS)
+    access_token = create_access_token(
+        data={"sub": user.username}, expires_delta=access_token_expires
+    )
     index = {}
     index["product"] = "早鳥方案"
     index["amount"] = 1200
     index["url"] = "/request"
+    index["name"] = user.username
     # return {"product" : ,"amount" : 1200}
-    return templates.TemplateResponse("index.html", {"request":index})
+    # return templates.TemplateResponse("index.html", {"request":index, "access_token": access_token, "token_type": "bearer"})
+    return {"access_token": access_token, "token_type": "bearer"}
 
 ## Request
 @app.post('/request', response_class=HTMLResponse)
-async def pay_request(email: EmailStr= Form(...), phone: str= Form(...),full_name: str= Form(...)):
+async def pay_request(token: str = Depends(oauth2_scheme), db_sesion: Session = Depends(get_db_session)):
+    user = get_current_user(db_sesion, token)
     order_id = str(uuid.uuid4())
     amount = 1200
     currency = "TWD"
-    CACHE["email"] = email
-    CACHE["phone"] = phone
-    CACHE["full_name"] = full_name
     CACHE["orderid"] = order_id
     CACHE["amount"] = amount
     CACHE["currency"] = currency
+    CACHE["userid"] = user.id
     request_options ={
         "amount" : amount,
         "currency" : currency,
@@ -115,9 +168,7 @@ async def pay_request(email: EmailStr= Form(...), phone: str= Form(...),full_nam
     response["transaction_id"] = transaction_id
     response["paymentStatusCheckReturnCode"] = check_result.get("returnCode", None)
     response["paymentStatusCheckReturnMessage"] = check_result.get("returnMessage", None)
-    response["email"] = email
-    response["phone"] = phone
-    response["full_name"] = full_name
+    response["full_name"] = user.username
     return templates.TemplateResponse("request.html", {"request":response})
     # return response
 
@@ -135,11 +186,12 @@ async def pay_confirm(transactionId: int, orderId: Optional[str] = None, db_sesi
     response["payment_details"] = payment_details
     if(response["paymentStatusCheckReturnCode"] == '0123'):
         orderin = {}
-        orderin["orderid"] = CACHE["orderid"]
-        orderin["email"] = CACHE["email"]
-        orderin["phone"] = CACHE["phone"]
-        orderin["full_name"] = CACHE["full_name"]
-        create_order(db_sesion, order_in= orderin)
+        orderin["OrderId"] = CACHE["orderid"]
+        orderin["UserId"] = CACHE["userid"]
+        orderin["TransactionId"] = CACHE["transaction_id"]
+        orderin["Source"] = "linepay"
+        orderin["UpdateTime"] = datetime.now()
+        create_payment(db_sesion, order_in= orderin)
     return templates.TemplateResponse("confirm.html", {"request":response})
     # return {"transactionId" : str(transactionId), "orderId" : orderId}
 ## Capture

+ 17 - 9
sql/crud.py

@@ -1,19 +1,27 @@
+from pydantic.networks import import_email_validator
 from sqlalchemy.orm import Session
-from .models import order_info_linepay
-from .schemas import order_info_linepay as orderschema
+from .models import Payment, User
+from .schemas import PaymentSchema
+from .schemas import UserSchema
 
 def get_order(db: Session, id: int):
-    return db.query(order_info_linepay).filter(order_info_linepay.id == id)
+    return db.query(PaymentSchema).filter(PaymentSchema.id == id)
 
 def get_orders(db: Session, skip: int = 0, limit: int = 100):
-    return db.query(order_info_linepay).offset(skip).limit(limit).all()
+    return db.query(PaymentSchema).offset(skip).limit(limit).all()
 
-def create_order(db: Session, order_in: orderschema):
-    create_order = order_info_linepay(**order_in)
-    db.add(create_order)
+def create_payment(db: Session, order_in: PaymentSchema):
+    create_payment = Payment(**order_in)
+    db.add(create_payment)
     db.commit()
-    db.refresh(create_order)
-    return create_order
+    db.refresh(create_payment)
+    return create_payment
+
+def get_user(db: Session, username: str):
+    for i in db.query(User).filter(User.username == username).order_by(User.id.desc()).limit(1):
+        return UserSchema(**i.__dict__)
+
+
 
 
 ## this is for crud method

+ 15 - 11
sql/models.py

@@ -1,32 +1,36 @@
 from sqlalchemy import Column, Integer, String, DateTime, Text
 from sqlalchemy.engine.base import Transaction
-
+from sqlalchemy.sql.schema import ForeignKey
+from sqlalchemy.orm import relation, relationship
 from .database import Base
 from datetime import datetime
 
 
-class order_info_linepay(Base):
-    __tablename__ = "order_info_linepay"
+class Payment(Base):
+    __tablename__ = "payment"
     
-    id = Column(Integer, primary_key=True, nullable=False)
-    orderid = Column(String(255), unique=True, nullable=False)
-    email = Column(String(255), nullable=False)
-    phone = Column(String(20))
-    full_name = Column(String(50))
-    transaction_date = Column(DateTime, nullable=False, default=datetime.now())
-
+    Id = Column(Integer, primary_key=True, nullable=False)
+    UserId = Column(String(255), ForeignKey("users.id"), nullable=False)
+    OrderId = Column(String(255))
+    TransactionId = Column(String(255))
+    Source = Column(String(30))
+    CreateTime = Column(DateTime, nullable=False, default=datetime.now())
+    UpdateTime = Column(DateTime, nullable=False)
 
 class User(Base):
     __tablename__ = "users"
+    
     id = Column(Integer, primary_key=True, nullable= False)
     username = Column(String(45))
     email = Column(String(60))
     password = Column(String(128))
     token = Column(String(256), default=None)
     left_time = Column(Integer, default=None)
-    last_stored = Column(datetime, default=None)
+    last_stored = Column(DateTime, default=None)
     line_token = Column(String(256), default=None)
     veri_url = Column(Text, default=None)
     invite_code = Column(Text, default=None)
 
+    payment = relation("Payment")
+
 ## this is for ORM model  define database model

+ 8 - 12
sql/schemas.py

@@ -1,7 +1,6 @@
-from logging import disable
 from typing import Optional
 
-from pydantic import BaseModel, validator, EmailStr
+from pydantic import BaseModel, EmailStr
 from datetime import datetime
 
 from sqlalchemy.sql.expression import text
@@ -27,22 +26,19 @@ class UserSchema(BaseModel):
 
 
 
-class order_info_linepay(BaseModel):
+class PaymentSchema(BaseModel):
     """[summary]
 
     Args:
         BaseModel ([type]): [description]
     """
     id: int
-    # UUID4 validation???
-    orderid: str
-    email: EmailStr
-    # phone: phone??
-    # @validator('phone')
-    # def check_phone()
-    phone: Optional[str] = None
-    full_name: Optional[str] = None
-    transaction_date: datetime = datetime.now()
+    userid: int
+    orderid: Optional[str] = None
+    TaransactionId: Optional[str] = None
+    Source: str
+    CreateTime: Optional[datetime] = None
+    UpdateTime: datetime = datetime.now()
     ## this is to avoid lazy loading problem
     class Config:
         orm_mode = True

+ 6 - 2
templates/index.html

@@ -9,14 +9,18 @@
 			<dd>{{ request.product }}</dd>
 			<dt>金額</dt>
 			<dd>{{ request.amount }}</dd>
+			<dt>email</dt>
+			<dd>{{ request.email }}</dd>
+			<dt>name</dt>
+			<dd>{{ request.username }}</dd>
 			<!-- <dd>
 				<a href="{{ request.url }}" rel="noopener noreferrer">填寫資料</a>
 			</dd> -->
 		</dl>
 		<form action = "/request" method = "POST">
-			<p>email <input type = "email" name = "email" /></p>
+			<!-- <p>email <input type = "email" name = "email" /></p>
 			<p>phone <input type = "tel" name = "phone" /></p>
-			<p>full_name <input type = "text" name = "full_name" /></p>
+			<p>full_name <input type = "text" name = "full_name" /></p> -->
 			<p><input type = "submit" value = "submit" /></p>
 		</form>
     </body>

+ 13 - 0
templates/login.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+    <head>
+		<title>index</title>
+    </head>
+    <body>
+		<form action = "/" method = "POST">
+			<p>email <input type = "text" name = "username" /></p>
+			<p>password <input type = "password" name = "password" /></p>
+			<p><input type = "submit" value = "submit" /></p>
+		</form>
+    </body>
+</html>

+ 4 - 4
templates/request.html

@@ -5,10 +5,10 @@
     </head>
     <body>
 		<dl>
-			<dt>email</dt>
-			<dd>{{ request.email }}</dd>
-			<dt>phone</dt>
-			<dd>{{ request.phone }}</dd>
+			<!-- <dt>email</dt>
+			<dd>{{ request.email }}</dd> -->
+			<!-- <dt>phone</dt>
+			<dd>{{ request.phone }}</dd> -->
 			<dt>full_name</dt>
 			<dd>{{ request.full_name}}</dd>
 			<dt>paymentAccessToken</dt>