blob: 4d8be1eb58f7247cff4b65b597cf58ea9a402fb7 [file] [log] [blame]
# routes/posts.py
from flask import Blueprint, request, jsonify, abort
from extensions import db
from models.post import Post
from models.behavior import Behavior
from utils.Fpost import Fpost
import json
posts_bp = Blueprint('posts', __name__)
@posts_bp.route('', methods=['POST'])
def create_post():
try:
user_id = request.form.get('user_id')
title = request.form.get('title')
content = request.form.get('content')
status = request.form.get('status', 'published')
topic_id = request.form.get('topic_id')
media_count = int(request.form.get('media_count', 0))
if not user_id or not title or not content:
return jsonify({'error': '缺少必要字段'}), 400
files = []
for i in range(media_count):
key = f'media_{i}'
if key in request.files:
files.append(request.files[key])
fpost = Fpost(db.session)
new_post = fpost.create_post_with_files(
user_id=int(user_id),
title=title,
content=content,
topic_id=int(topic_id) if topic_id else None,
status=status,
files=files
)
return jsonify({'id': new_post.id}), 201
except Exception as e:
return jsonify({'error': str(e)}), 500
@posts_bp.route('', methods=['GET'])
def list_posts():
"""
GET /posts -> 全部已发布帖子
GET /posts?user_id=xx -> 指定用户的所有帖子
"""
user_id = request.args.get('user_id', type=int)
query = Post.query
if user_id is not None:
query = query.filter_by(user_id=user_id)
else:
query = query.filter_by(status='published')
posts = query.all()
return jsonify([{
'id' : p.id,
'title' : p.title,
'status' : p.status,
'heat' : p.heat,
'created_at' : p.created_at.isoformat()
} for p in posts])
@posts_bp.route('/<int:post_id>', methods=['GET'])
def get_post(post_id):
post = Post.query.get_or_404(post_id)
return jsonify({
'id' : post.id,
'user_id' : post.user_id,
'topic_id' : post.topic_id,
'title' : post.title,
'content' : post.content,
'media_urls' : post.media_urls,
'status' : post.status,
'heat' : post.heat,
'created_at' : post.created_at.isoformat(),
'updated_at' : post.updated_at.isoformat()
})
@posts_bp.route('/<int:post_id>', methods=['PUT'])
def update_post(post_id):
"""
支持 FormData 和 JSON 两种格式更新:
- multipart/form-data 时可上传新文件并保留 existing_media_urls
- application/json 时只修改字段
"""
try:
fpost = Fpost(db.session)
if request.content_type and 'multipart/form-data' in request.content_type:
title = request.form.get('title')
content = request.form.get('content')
status = request.form.get('status')
topic_id = request.form.get('topic_id')
count = int(request.form.get('media_count', 0))
existing = request.form.get('existing_media_urls')
existing_media_urls = None
if existing:
try:
existing_media_urls = json.loads(existing)
except:
existing_media_urls = None
files = []
for i in range(count):
key = f'media_{i}'
if key in request.files:
files.append(request.files[key])
updated = fpost.update_post_with_files(
post_id=post_id,
title=title,
content=content,
topic_id=int(topic_id) if topic_id else None,
status=status,
files=files or None,
existing_media_urls=existing_media_urls
)
else:
post = Post.query.get_or_404(post_id)
data = request.get_json() or {}
for field in ('title','content','topic_id','media_urls','status'):
if field in data:
setattr(post, field, data[field])
db.session.commit()
updated = post
if not updated:
return jsonify({'error': '帖子不存在'}), 404
return '', 204
except Exception as e:
return jsonify({'error': str(e)}), 500
@posts_bp.route('/<int:post_id>', methods=['DELETE'])
def delete_post(post_id):
post = Post.query.get_or_404(post_id)
db.session.delete(post)
db.session.commit()
return '', 204
# —— 显式的 like/favorite 删除和查询路由,放在泛用 action 路由之前 —— #
@posts_bp.route('/<int:post_id>/like', methods=['GET'])
def has_liked(post_id):
"""
GET /posts/<post_id>/like?user_id=xx
返回 { "liked": true/false }
"""
user_id = request.args.get('user_id', type=int)
if not user_id:
abort(400, 'user_id required')
exists = Behavior.query.filter_by(
user_id=user_id,
post_id=post_id,
type='like'
).first() is not None
return jsonify({'liked': exists}), 200
@posts_bp.route('/<int:post_id>/like', methods=['DELETE'])
def unlike(post_id):
data = request.get_json(silent=True) or {}
user_id = data.get('user_id')
if not user_id:
abort(400, 'user_id required')
beh = Behavior.query.filter_by(
user_id=user_id,
post_id=post_id,
type='like'
).first()
if not beh:
return jsonify({'error': 'not liked yet'}), 400
db.session.delete(beh)
post = Post.query.get_or_404(post_id)
post.heat = max(post.heat - 1, 0)
db.session.commit()
return '', 204
@posts_bp.route('/<int:post_id>/favorite', methods=['DELETE'])
def unfavorite(post_id):
data = request.get_json(silent=True) or {}
user_id = data.get('user_id')
if not user_id:
abort(400, 'user_id required')
beh = Behavior.query.filter_by(
user_id=user_id,
post_id=post_id,
type='favorite'
).first()
if not beh:
return jsonify({'error': 'not favorited yet'}), 400
db.session.delete(beh)
post = Post.query.get_or_404(post_id)
post.heat = max(post.heat - 1, 0)
db.session.commit()
return '', 204
# —— 泛用 action 路由,仅处理 POST /posts/<id>/(like|favorite|view|share) —— #
@posts_bp.route('/<int:post_id>/<action>', methods=['POST'])
def post_action(post_id, action):
"""
支持 action: like, favorite, view, share,
对 like/favorite 做幂等去重检查。
"""
if action not in ('like','favorite','view','share'):
abort(400, 'Invalid action')
data = request.get_json() or {}
user_id = data.get('user_id')
if not user_id:
abort(400, 'user_id required')
# 幂等检查
if action in ('like','favorite'):
exists = Behavior.query.filter_by(
user_id=user_id,
post_id=post_id,
type=action
).first()
if exists:
return jsonify({'error': f'already {action}d'}), 400
# 记录行为
beh = Behavior(user_id=user_id, post_id=post_id, type=action)
db.session.add(beh)
# 更新热度
post = Post.query.get_or_404(post_id)
post.heat += 1
db.session.commit()
return '', 201