first commit
This commit is contained in:
BIN
back/data/db.sqlite
Normal file
BIN
back/data/db.sqlite
Normal file
Binary file not shown.
306
back/main.py
Normal file
306
back/main.py
Normal file
@@ -0,0 +1,306 @@
|
||||
# 这里是Sycamore whisper的后端代码喵!
|
||||
# 但愿比V1写的好喵(逃
|
||||
from flask import Flask, jsonify, request
|
||||
from flask_cors import CORS
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
import os
|
||||
import uuid
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
app = Flask(__name__)
|
||||
# 跨域策略,仅/api/*允许所有来源的请求
|
||||
CORS(app, resources={r"/api/*": {"origins": "*"}})
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
DB_PATH = os.path.join(BASE_DIR, 'data', 'db.sqlite')
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{DB_PATH}'
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
# 全局配置变量
|
||||
NEED_AUDIT = True
|
||||
|
||||
# --- 定义数据库结构 ---
|
||||
class SiteSettings(db.Model):
|
||||
__tablename__ = 'site_settings'
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
title = db.Column(db.String(255))
|
||||
icon = db.Column(db.String(255))
|
||||
footer_text = db.Column(db.Text)
|
||||
repo_link = db.Column(db.String(255))
|
||||
enable_repo_button = db.Column(db.Boolean, default=False)
|
||||
enable_notice = db.Column(db.Boolean, default=False)
|
||||
need_audit = db.Column(db.Boolean, default=True)
|
||||
about = db.Column(db.Text)
|
||||
|
||||
class Identity(db.Model):
|
||||
__tablename__ = 'identity'
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
token = db.Column(db.String(36), unique=True, nullable=False)
|
||||
|
||||
class Submission(db.Model):
|
||||
__tablename__ = 'submissions'
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
content = db.Column(db.Text, nullable=False)
|
||||
hashtopic = db.Column(db.Text) # JSON string
|
||||
identity_token = db.Column(db.String(36), nullable=True)
|
||||
status = db.Column(db.String(20), default='Pending')
|
||||
created_at = db.Column(db.DateTime, default=lambda: datetime.now())
|
||||
upvotes = db.Column(db.Integer, default=0)
|
||||
downvotes = db.Column(db.Integer, default=0)
|
||||
|
||||
comments = db.relationship('Comment', backref='submission', lazy=True, cascade='all, delete-orphan')
|
||||
|
||||
class Comment(db.Model):
|
||||
__tablename__ = 'comments'
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
submission_id = db.Column(db.Integer, db.ForeignKey('submissions.id'), nullable=False)
|
||||
content = db.Column(db.Text, nullable=False)
|
||||
hashtopic = db.Column(db.Text) # JSON string
|
||||
identity_token = db.Column(db.String(36), nullable=True)
|
||||
created_at = db.Column(db.DateTime, default=lambda: datetime.now())
|
||||
upvotes = db.Column(db.Integer, default=0)
|
||||
downvotes = db.Column(db.Integer, default=0)
|
||||
parent_comment_id = db.Column(db.Integer, db.ForeignKey('comments.id'), nullable=True)
|
||||
|
||||
# 初始化数据库函数
|
||||
def init_db():
|
||||
if not os.path.exists('./data'):
|
||||
os.makedirs('./data')
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
|
||||
def load_config():
|
||||
global NEED_AUDIT
|
||||
with app.app_context():
|
||||
try:
|
||||
settings = SiteSettings.query.first()
|
||||
if settings:
|
||||
if hasattr(settings, 'need_audit') and settings.need_audit is not None:
|
||||
NEED_AUDIT = settings.need_audit
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to load settings: {e}")
|
||||
|
||||
# --- 用户普通api端点 ---
|
||||
@app.route('/api/settings', methods=['GET'])
|
||||
def get_settings():
|
||||
settings = SiteSettings.query.first()
|
||||
|
||||
if settings:
|
||||
data = {
|
||||
"title": settings.title,
|
||||
"icon": settings.icon,
|
||||
"footer_text": settings.footer_text,
|
||||
"repo_link": settings.repo_link,
|
||||
"enable_repo_button": settings.enable_repo_button,
|
||||
"enable_notice": settings.enable_notice
|
||||
}
|
||||
return jsonify({
|
||||
"code": 1000,
|
||||
"data": data
|
||||
})
|
||||
else:
|
||||
return jsonify({"code": 2002,"data":{}})
|
||||
|
||||
@app.route('/api/about', methods=['GET'])
|
||||
def get_about():
|
||||
settings = SiteSettings.query.first()
|
||||
if settings and settings.about:
|
||||
return jsonify({
|
||||
"code": 1000,
|
||||
"data": settings.about
|
||||
})
|
||||
else:
|
||||
# about在初始化时不会被设置,避免管理面板报错,返回默认文本
|
||||
return jsonify({
|
||||
"code": 1000,
|
||||
"data": "# 默认关于页面\n关于页面未设置,请前往管理面板。"
|
||||
})
|
||||
|
||||
@app.route('/api/get_id_token', methods=['GET'])
|
||||
def get_id_token():
|
||||
try:
|
||||
token = str(uuid.uuid4())
|
||||
new_identity = Identity(token=token)
|
||||
db.session.add(new_identity)
|
||||
db.session.commit()
|
||||
return jsonify({
|
||||
"code": 1000,
|
||||
"data": token
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
"code": 2003,
|
||||
"data": f"验证失败: {str(e)}"
|
||||
})
|
||||
|
||||
@app.route('/api/verify_token', methods=['POST'])
|
||||
def verify_token():
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data or 'token' not in data:
|
||||
return jsonify({
|
||||
"code": 2000,
|
||||
"data": "参数错误"
|
||||
})
|
||||
|
||||
token = data['token']
|
||||
identity = Identity.query.filter_by(token=token).first()
|
||||
|
||||
if identity:
|
||||
return jsonify({
|
||||
"code": 1000,
|
||||
"data": True
|
||||
})
|
||||
else:
|
||||
return jsonify({
|
||||
"code": 1000,
|
||||
"data": False
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
"code": 2003,
|
||||
"data": f"验证失败: {str(e)}"
|
||||
})
|
||||
|
||||
@app.route('/api/post', methods=['POST'])
|
||||
def submit_post():
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data or 'content' not in data:
|
||||
return jsonify({"code": 2000, "data": "内容不能为空"})
|
||||
|
||||
content = data['content']
|
||||
hashtopic = data.get('hashtopic', [])
|
||||
|
||||
if not isinstance(hashtopic, list):
|
||||
return jsonify({"code": 2000, "data": "hashtopic必须为列表"})
|
||||
|
||||
identity_token = data.get('identity')
|
||||
|
||||
# Identity 验证
|
||||
if identity_token:
|
||||
if not Identity.query.filter_by(token=identity_token).first():
|
||||
return jsonify({"code": 2004, "data": "无效的 Identity Token"})
|
||||
else:
|
||||
identity_token = None
|
||||
|
||||
# 保存
|
||||
new_post = Submission(
|
||||
content=content,
|
||||
hashtopic=json.dumps(hashtopic) if hashtopic else '[]',
|
||||
identity_token=identity_token,
|
||||
status='Pending' if NEED_AUDIT else 'Pass'
|
||||
)
|
||||
db.session.add(new_post)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({"code": 1000, "data": "投稿成功"})
|
||||
except Exception as e:
|
||||
return jsonify({"code": 2003, "data": f"投稿失败: {str(e)}"})
|
||||
|
||||
@app.route('/api/comment', methods=['POST'])
|
||||
def submit_comment():
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data or 'content' not in data or 'submission_id' not in data:
|
||||
return jsonify({"code": 2000, "data": "参数错误"})
|
||||
|
||||
submission_id = data['submission_id']
|
||||
content = data['content']
|
||||
hashtopic = data.get('hashtopic', [])
|
||||
|
||||
if not isinstance(hashtopic, list):
|
||||
return jsonify({"code": 2000, "data": "hashtopic必须为列表"})
|
||||
|
||||
identity_token = data.get('identity')
|
||||
|
||||
# 检查 submission 是否存在
|
||||
if not Submission.query.get(submission_id):
|
||||
return jsonify({"code": 2004, "data": "投稿不存在"})
|
||||
|
||||
# Identity 验证
|
||||
if identity_token:
|
||||
if not Identity.query.filter_by(token=identity_token).first():
|
||||
return jsonify({"code": 2004, "data": "无效的 Identity Token"})
|
||||
else:
|
||||
identity_token = None
|
||||
|
||||
new_comment = Comment(
|
||||
submission_id=submission_id,
|
||||
content=content,
|
||||
hashtopic=json.dumps(hashtopic) if hashtopic else '[]',
|
||||
identity_token=identity_token
|
||||
)
|
||||
db.session.add(new_comment)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({"code": 1000, "data": ""})
|
||||
except Exception as e:
|
||||
return jsonify({"code": 2003, "data": f"评论失败: {str(e)}"})
|
||||
|
||||
@app.route('/api/up', methods=['POST'])
|
||||
def upvote():
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data or 'id' not in data or 'type' not in data:
|
||||
return jsonify({"code": 2000, "data": "参数错误"})
|
||||
|
||||
item_id = data['id']
|
||||
item_type = data['type']
|
||||
|
||||
item = None
|
||||
if item_type == 'submission':
|
||||
item = db.session.get(Submission, item_id)
|
||||
elif item_type == 'comment':
|
||||
item = db.session.get(Comment, item_id)
|
||||
|
||||
if not item:
|
||||
return jsonify({"code": 2004, "data": "对象不存在"})
|
||||
|
||||
item.upvotes += 1
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({"code": 1000, "data": ""})
|
||||
except Exception as e:
|
||||
return jsonify({"code": 2003, "data": f"点赞失败: {str(e)}"})
|
||||
|
||||
@app.route('/api/down', methods=['POST'])
|
||||
def downvote():
|
||||
try:
|
||||
data = request.get_json()
|
||||
if not data or 'id' not in data or 'type' not in data:
|
||||
return jsonify({"code": 2000, "data": "参数错误"})
|
||||
|
||||
item_id = data['id']
|
||||
item_type = data['type']
|
||||
|
||||
item = None
|
||||
if item_type == 'submission':
|
||||
item = db.session.get(Submission, item_id)
|
||||
elif item_type == 'comment':
|
||||
item = db.session.get(Comment, item_id)
|
||||
|
||||
if not item:
|
||||
return jsonify({"code": 2004, "data": "对象不存在"})
|
||||
|
||||
item.downvotes += 1
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({"code": 1000, "data": ""})
|
||||
except Exception as e:
|
||||
return jsonify({"code": 2003, "data": f"点踩失败: {str(e)}"})
|
||||
|
||||
# --- 用户的管理api端点 ---
|
||||
# TODO: 用户管理端点
|
||||
|
||||
# --- 管理员api端点 ---
|
||||
# TODO: 添加管理员端点
|
||||
|
||||
# 主函数
|
||||
if __name__ == '__main__':
|
||||
init_db()
|
||||
load_config()
|
||||
app.run(debug=True, port=5000)
|
||||
Reference in New Issue
Block a user