api impl
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
.env
|
.env
|
||||||
|
summerizer.py
|
||||||
1
api/__init__.py
Normal file
1
api/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#api/__init__.py
|
||||||
42
api/auth.py
Normal file
42
api/auth.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#api/auth.py
|
||||||
|
from flask import Blueprint, session, redirect, url_for, jsonify
|
||||||
|
from authlib.integrations.flask_client import OAuth
|
||||||
|
import env_provider
|
||||||
|
auth_bp = Blueprint('auth', __name__, url_prefix='/api')
|
||||||
|
|
||||||
|
oauth = OAuth()
|
||||||
|
keycloak = oauth.register(
|
||||||
|
'keycloak',
|
||||||
|
client_id=env_provider.CLIENT_ID,
|
||||||
|
client_secret=env_provider.CLIENT_SECRET,
|
||||||
|
server_metadata_url="https://login.hangman-lab.top/auth/realms/Hangman-Lab/.well-known/openid-configuration",
|
||||||
|
client_kwargs={"scope": "openid email profile"},
|
||||||
|
)
|
||||||
|
|
||||||
|
@auth_bp.route('/login', methods=['GET'])
|
||||||
|
def login():
|
||||||
|
redirect_uri = url_for("auth.authorize", _external=True)
|
||||||
|
return keycloak.authorize_redirect(redirect_uri)
|
||||||
|
|
||||||
|
@auth_bp.route('/authorize', methods=['GET'])
|
||||||
|
def authorize():
|
||||||
|
try:
|
||||||
|
token = keycloak.authorize_access_token()
|
||||||
|
user_info = keycloak.parse_id_token(token)
|
||||||
|
session['user'] = user_info
|
||||||
|
return jsonify({"message": "login successful", "user": user_info})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": "Authorization failed"}), 401
|
||||||
|
@auth_bp.route('/logout', methods=['GET'])
|
||||||
|
def logout():
|
||||||
|
session.pop('user', None)
|
||||||
|
logout_url = "https://login.hangman-lab.top/auth/realms/Hangman-Lab/protocol/openid-connect/logout"
|
||||||
|
return redirect(logout_url)
|
||||||
|
|
||||||
|
@auth_bp.route("/user", methods=["GET"])
|
||||||
|
def user():
|
||||||
|
u = session.get('user')
|
||||||
|
if not u:
|
||||||
|
return jsonify({"username": "guest", "role": "guest"})
|
||||||
|
return jsonify(u)
|
||||||
|
|
||||||
51
api/markdown.py
Normal file
51
api/markdown.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#api/markdown.py
|
||||||
|
from flask import Blueprint, request, jsonify
|
||||||
|
from db import get_db
|
||||||
|
from db.models.Markdown import Markdown
|
||||||
|
|
||||||
|
markdown_bp = Blueprint('markdown', __name__, url_prefix='/api/markdown')
|
||||||
|
|
||||||
|
@markdown_bp.route('/<int:markdown_id>', methods=['GET'])
|
||||||
|
def get_markdown(markdown_id):
|
||||||
|
with get_db() as db:
|
||||||
|
markdown = db.query(Markdown).get(markdown_id)
|
||||||
|
if markdown is None:
|
||||||
|
return jsonify({"error": "file not found"}), 404
|
||||||
|
return jsonify(markdown.to_dict())
|
||||||
|
|
||||||
|
@markdown_bp.route('/', methods=['POST'])
|
||||||
|
def create_markdown():
|
||||||
|
data = request.json
|
||||||
|
title = data.get('title')
|
||||||
|
content = data.get('content')
|
||||||
|
path = data.get('path')
|
||||||
|
if not title or not content:
|
||||||
|
return jsonify({"error": "missing required fields"}), 400
|
||||||
|
new_markdown = Markdown(title=title, content=content, path=path)
|
||||||
|
with get_db() as db:
|
||||||
|
db.add(new_markdown)
|
||||||
|
db.commit()
|
||||||
|
return jsonify(new_markdown.to_dict()), 201
|
||||||
|
|
||||||
|
@markdown_bp.route('/<int:markdown_id>', methods=['PUT'])
|
||||||
|
def update_markdown(markdown_id):
|
||||||
|
with get_db() as db:
|
||||||
|
markdown = db.query(Markdown).get(markdown_id)
|
||||||
|
if markdown is None:
|
||||||
|
return jsonify({"error": "file not found"}), 404
|
||||||
|
data = request.json
|
||||||
|
markdown.title = data.get('title')
|
||||||
|
markdown.content = data.get('content')
|
||||||
|
markdown.path = data.get('path')
|
||||||
|
db.commit()
|
||||||
|
return jsonify(markdown.to_dict()), 200
|
||||||
|
|
||||||
|
@markdown_bp.route('/<int:markdown_id>', methods=['DELETE'])
|
||||||
|
def delete_markdown(markdown_id):
|
||||||
|
with get_db() as db:
|
||||||
|
markdown = db.query(Markdown).get(markdown_id)
|
||||||
|
if markdown is None:
|
||||||
|
return jsonify({"error": "file not found"}), 404
|
||||||
|
db.delete(markdown)
|
||||||
|
db.commit()
|
||||||
|
return jsonify({"message": "deleted"}), 200
|
||||||
73
app.py
73
app.py
@@ -1,51 +1,30 @@
|
|||||||
from authlib.integrations import flask_client
|
# app.py
|
||||||
from flask import Flask, jsonify, request, url_for, redirect, session
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
from authlib.integrations.flask_client import OAuth
|
import env_provider
|
||||||
import os
|
import db
|
||||||
|
from api.auth import auth_bp
|
||||||
|
from api.markdown import markdown_bp
|
||||||
|
|
||||||
|
|
||||||
|
def is_allowed_origin(origin):
|
||||||
|
parsed_origin = urlparse(origin)
|
||||||
|
if parsed_origin.hostname in ['localhost', '127.0.0.1']:
|
||||||
|
return True
|
||||||
|
allowed_origins = [
|
||||||
|
"https://login.hangman-lab.top",
|
||||||
|
"https://git.hangman-lab.top",
|
||||||
|
]
|
||||||
|
return origin in allowed_origins
|
||||||
|
|
||||||
|
db.create_all()
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
CORS(app)
|
app.secret_key = env_provider.SESSION_SECRET_KEY
|
||||||
oauth = OAuth(app)
|
CORS(app, resources={r"/api/*": {"origins": is_allowed_origin}})
|
||||||
|
|
||||||
keycloak = oauth.register(
|
|
||||||
'keycloak',
|
|
||||||
client_id="main",
|
|
||||||
client_secret="<KEY>",
|
|
||||||
server_metadata_url="https://login.hangman-lab.top/auth/realms/Hangman-Lab/.well-known/openid-configuration",
|
|
||||||
client_kwargs={"scope": "openid email profile"},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/login')
|
|
||||||
def login():
|
|
||||||
redirect_uri = url_for("authorize", _external=True)
|
|
||||||
return keycloak.authorize_redirect(redirect_uri)
|
|
||||||
|
|
||||||
@app.route('/api/authorize')
|
|
||||||
def authorize():
|
|
||||||
token = keycloak.authorize_access_token()
|
|
||||||
user_info = keycloak.parse_id_token(token)
|
|
||||||
session['user'] = user_info
|
|
||||||
|
|
||||||
@app.route('/api/logout')
|
|
||||||
def logout():
|
|
||||||
session.pop('user', None)
|
|
||||||
logout_url = "https://login.hangman-lab.top/auth/realms/Hangman-Lab/protocol/openid-connect/logout"
|
|
||||||
return redirect(logout_url)
|
|
||||||
|
|
||||||
@app.route("/api/user")
|
|
||||||
def user():
|
|
||||||
u = session.get('user')
|
|
||||||
if not u:
|
|
||||||
return jsonify({"username": "guest", "role": "guest"})
|
|
||||||
return jsonify(user)
|
|
||||||
|
|
||||||
@app.route('/api/get_note_list', methods=['GET'])
|
|
||||||
def get_note_list():
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@app.route('/api/get_note', methods=['GET'])
|
|
||||||
def get_note(idx):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
app.register_blueprint(markdown_bp)
|
||||||
|
app.register_blueprint(auth_bp)
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(host='0.0.0.0', port=5000)
|
||||||
|
|||||||
23
db/__init__.py
Normal file
23
db/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#db/__init__.py
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from models import Base
|
||||||
|
|
||||||
|
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
import env_provider
|
||||||
|
|
||||||
|
engine = create_engine(env_provider.DB_CONNECT_STRING)
|
||||||
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
@contextmanager
|
||||||
|
def get_db():
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
yield db
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
def create_all():
|
||||||
|
with engine.begin() as conn:
|
||||||
|
Base.metadata.create_all(bind=conn)
|
||||||
21
db/models/BackendLog.py
Normal file
21
db/models/BackendLog.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#db/models/BackendLog.py
|
||||||
|
from sqlalchemy import Column, Integer, String, DateTime, Text
|
||||||
|
from db.models import Base
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class BackendLog(Base):
|
||||||
|
__tablename__ = 'backend_log'
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
level = Column(String(50), nullable=False)
|
||||||
|
message = Column(Text, nullable=False)
|
||||||
|
timestamp = Column(DateTime, nullable=False)
|
||||||
|
extra = Column(Text, nullable=True)
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.id,
|
||||||
|
"level": self.level,
|
||||||
|
"message": self.message,
|
||||||
|
"timestamp": self.timestamp,
|
||||||
|
"extra": self.extra,
|
||||||
|
}
|
||||||
21
db/models/Markdown.py
Normal file
21
db/models/Markdown.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#db/models/Markdown.py
|
||||||
|
from sqlalchemy import Column, Text, Integer, String, DateTime
|
||||||
|
from db.models import Base
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
class Markdown(Base):
|
||||||
|
__tablename__ = 'markdown'
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
title = Column(String(255), nullable=False)
|
||||||
|
content = Column(Text, nullable=False)
|
||||||
|
created_at = Column(DateTime, default=datetime.datetime.utcnow)
|
||||||
|
path = Column(String(255), nullable=False)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'id': self.id,
|
||||||
|
'title': self.title,
|
||||||
|
'content': self.content,
|
||||||
|
'created_at': self.created_at,
|
||||||
|
'path': self.path,
|
||||||
|
}
|
||||||
10
db/models/__init__.py
Normal file
10
db/models/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#db/models/__init__.py
|
||||||
|
|
||||||
|
import pkgutil
|
||||||
|
import importlib
|
||||||
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
Base = declarative_base()
|
||||||
|
package_name = "db.models"
|
||||||
|
for _, module_name, _ in pkgutil.iter_modules(__path__):
|
||||||
|
importlib.import_module(f"{package_name}.{module_name}")
|
||||||
|
|
||||||
9
db/utils.py
Normal file
9
db/utils.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#db/utils.py
|
||||||
|
from db import get_db
|
||||||
|
from db.models.BackendLog import BackendLog
|
||||||
|
|
||||||
|
def insert_backend_log(level, message, extra=None):
|
||||||
|
log_entry = BackendLog(level=level, message=message, extra=extra)
|
||||||
|
with get_db() as db:
|
||||||
|
db.add(log_entry)
|
||||||
|
db.commit()
|
||||||
10
env_provider.py
Normal file
10
env_provider.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#env_provider.py
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
CLIENT_ID = os.getenv("CLIENT_ID")
|
||||||
|
CLIENT_SECRET = os.getenv("CLIENT_SECRET")
|
||||||
|
DB_CONNECT_STRING = os.getenv("DB_CONNECT_STRING")
|
||||||
|
SESSION_SECRET_KEY = os.getenv("SESSION_SECRET_KEY")
|
||||||
19
logging_handlers/DatabaseLogHandler.py
Normal file
19
logging_handlers/DatabaseLogHandler.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#logging_handlers/DatabaseLogHandler.py
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import traceback
|
||||||
|
from db import get_db
|
||||||
|
from db.models.BackendLog import BackendLog
|
||||||
|
class DatabaseLogHandler(logging.Handler):
|
||||||
|
def emit(self, record):
|
||||||
|
message = self.format(record)
|
||||||
|
level = record.levelname
|
||||||
|
extra = getattr(record, 'extra', None)
|
||||||
|
|
||||||
|
log_entry = BackendLog(message=message, level=level, extra=extra)
|
||||||
|
try:
|
||||||
|
with get_db() as db:
|
||||||
|
db.add(log_entry)
|
||||||
|
db.commit()
|
||||||
|
except Exception:
|
||||||
|
print(f"Failed to log to database: {traceback.format_exc()}")
|
||||||
1
logging_handlers/__init__.py
Normal file
1
logging_handlers/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#logging_handlers/__init__.py
|
||||||
0
requirements.txt
Normal file
0
requirements.txt
Normal file
Reference in New Issue
Block a user