blob: 6fd2a89df5d362d646af7097c411e68e4c2a6fdd [file] [log] [blame]
TRM-codingd1cbf672025-06-18 15:15:08 +08001# routes/posts.py
wu90da17b2025-06-19 12:45:29 +08002
TRM-codingd1cbf672025-06-18 15:15:08 +08003from flask import Blueprint, request, jsonify, abort
wu52491532025-06-24 22:47:35 +08004from extensions import db
5from models.post import Post
TRM-codingd1cbf672025-06-18 15:15:08 +08006from models.behavior import Behavior
wu52491532025-06-24 22:47:35 +08007from utils.Fpost import Fpost
TRM-codingf55d2372025-06-20 16:22:37 +08008import json
TRM-codingd1cbf672025-06-18 15:15:08 +08009
10posts_bp = Blueprint('posts', __name__)
11
12@posts_bp.route('', methods=['POST'])
13def create_post():
TRM-codingf55d2372025-06-20 16:22:37 +080014 try:
wu52491532025-06-24 22:47:35 +080015 # 获取文本字段
16 user_id = request.form.get('user_id')
17 title = request.form.get('title')
18 content = request.form.get('content')
19 status = request.form.get('status', 'published')
20 topic_id = request.form.get('topic_id')
TRM-codingf55d2372025-06-20 16:22:37 +080021 media_count = int(request.form.get('media_count', 0))
wu52491532025-06-24 22:47:35 +080022
TRM-codingf55d2372025-06-20 16:22:37 +080023 if not user_id or not title or not content:
24 return jsonify({'error': '缺少必要字段'}), 400
wu52491532025-06-24 22:47:35 +080025
26 # 获取上传的文件
TRM-codingf55d2372025-06-20 16:22:37 +080027 files = []
28 for i in range(media_count):
wu52491532025-06-24 22:47:35 +080029 file_key = f'media_{i}'
30 if file_key in request.files:
31 files.append(request.files[file_key])
32
33 # 使用 Fpost 创建帖子
TRM-codingf55d2372025-06-20 16:22:37 +080034 fpost = Fpost(db.session)
35 new_post = fpost.create_post_with_files(
36 user_id=int(user_id),
37 title=title,
38 content=content,
39 topic_id=int(topic_id) if topic_id else None,
40 status=status,
41 files=files
42 )
wu52491532025-06-24 22:47:35 +080043
TRM-codingf55d2372025-06-20 16:22:37 +080044 return jsonify({'id': new_post.id}), 201
wu52491532025-06-24 22:47:35 +080045
TRM-codingf55d2372025-06-20 16:22:37 +080046 except Exception as e:
47 return jsonify({'error': str(e)}), 500
TRM-codingd1cbf672025-06-18 15:15:08 +080048
49@posts_bp.route('', methods=['GET'])
50def list_posts():
wu90da17b2025-06-19 12:45:29 +080051 """
wu52491532025-06-24 22:47:35 +080052 获取帖子列表,支持:
53 - GET /posts 返回所有已发布帖子
54 - GET /posts?user_id=123 返回指定用户 user_id 的所有帖子
wu90da17b2025-06-19 12:45:29 +080055 """
56 user_id = request.args.get('user_id', type=int)
57 query = Post.query
58 if user_id is not None:
59 query = query.filter_by(user_id=user_id)
60 else:
61 query = query.filter_by(status='published')
62
63 posts = query.all()
TRM-codingd1cbf672025-06-18 15:15:08 +080064
wu52491532025-06-24 22:47:35 +080065 return jsonify([{
66 'id': p.id,
67 'title': p.title,
68 'status': p.status, # 新增 status 字段
69 'heat': p.heat,
70 'created_at': p.created_at.isoformat()
71 } for p in posts])
wu5a4acf72025-06-24 15:38:29 +080072
TRM-codingd1cbf672025-06-18 15:15:08 +080073@posts_bp.route('/<int:post_id>', methods=['GET'])
74def get_post(post_id):
75 post = Post.query.get_or_404(post_id)
76 return jsonify({
wu52491532025-06-24 22:47:35 +080077 'id': post.id,
78 'user_id': post.user_id,
79 'topic_id': post.topic_id,
80 'title': post.title,
81 'content': post.content,
82 'media_urls': post.media_urls,
83 'status': post.status,
84 'heat': post.heat,
85 'created_at': post.created_at.isoformat(),
86 'updated_at': post.updated_at.isoformat()
TRM-codingd1cbf672025-06-18 15:15:08 +080087 })
88
89@posts_bp.route('/<int:post_id>', methods=['PUT'])
90def update_post(post_id):
91 """
wu52491532025-06-24 22:47:35 +080092 修改帖子字段(可选字段:title, content, topic_id, media_urls, status)
93 支持FormData和JSON两种格式
TRM-codingd1cbf672025-06-18 15:15:08 +080094 """
TRM-codingf55d2372025-06-20 16:22:37 +080095 try:
96 fpost = Fpost(db.session)
wu52491532025-06-24 22:47:35 +080097
98 # 检查是否是FormData请求
TRM-codingf55d2372025-06-20 16:22:37 +080099 if request.content_type and 'multipart/form-data' in request.content_type:
wu52491532025-06-24 22:47:35 +0800100 # FormData请求
101 title = request.form.get('title')
102 content = request.form.get('content')
103 status = request.form.get('status')
TRM-codingf55d2372025-06-20 16:22:37 +0800104 topic_id = request.form.get('topic_id')
wu52491532025-06-24 22:47:35 +0800105 media_count = int(request.form.get('media_count', 0))
106 existing_media_urls_str = request.form.get('existing_media_urls')
107
108 # 解析现有媒体URLs
TRM-codingf55d2372025-06-20 16:22:37 +0800109 existing_media_urls = None
wu52491532025-06-24 22:47:35 +0800110 if existing_media_urls_str:
TRM-codingf55d2372025-06-20 16:22:37 +0800111 try:
wu52491532025-06-24 22:47:35 +0800112 existing_media_urls = json.loads(existing_media_urls_str)
TRM-codingf55d2372025-06-20 16:22:37 +0800113 except:
114 existing_media_urls = None
wu52491532025-06-24 22:47:35 +0800115
116 # 获取新上传的文件
TRM-codingf55d2372025-06-20 16:22:37 +0800117 files = []
wu52491532025-06-24 22:47:35 +0800118 for i in range(media_count):
119 file_key = f'media_{i}'
120 if file_key in request.files:
121 files.append(request.files[file_key])
122
123 # 更新帖子
124 updated_post = fpost.update_post_with_files(
TRM-codingf55d2372025-06-20 16:22:37 +0800125 post_id=post_id,
126 title=title,
127 content=content,
128 topic_id=int(topic_id) if topic_id else None,
129 status=status,
wu52491532025-06-24 22:47:35 +0800130 files=files if files else None,
TRM-codingf55d2372025-06-20 16:22:37 +0800131 existing_media_urls=existing_media_urls
132 )
wu52491532025-06-24 22:47:35 +0800133
TRM-codingf55d2372025-06-20 16:22:37 +0800134 else:
wu52491532025-06-24 22:47:35 +0800135 # JSON请求(保持原有逻辑)
TRM-codingf55d2372025-06-20 16:22:37 +0800136 post = Post.query.get_or_404(post_id)
137 data = request.get_json() or {}
wu52491532025-06-24 22:47:35 +0800138 for key in ('title', 'content', 'topic_id', 'media_urls', 'status'):
139 if key in data:
140 setattr(post, key, data[key])
TRM-codingf55d2372025-06-20 16:22:37 +0800141 db.session.commit()
wu52491532025-06-24 22:47:35 +0800142 updated_post = post
143
144 if not updated_post:
TRM-codingf55d2372025-06-20 16:22:37 +0800145 return jsonify({'error': '帖子不存在'}), 404
wu52491532025-06-24 22:47:35 +0800146
TRM-codingf55d2372025-06-20 16:22:37 +0800147 return '', 204
wu52491532025-06-24 22:47:35 +0800148
TRM-codingf55d2372025-06-20 16:22:37 +0800149 except Exception as e:
150 return jsonify({'error': str(e)}), 500
TRM-codingd1cbf672025-06-18 15:15:08 +0800151
152@posts_bp.route('/<int:post_id>', methods=['DELETE'])
153def delete_post(post_id):
154 post = Post.query.get_or_404(post_id)
155 db.session.delete(post)
156 db.session.commit()
157 return '', 204
158
wu52491532025-06-24 22:47:35 +0800159@posts_bp.route('/<int:post_id>/<action>', methods=['POST'])
160def post_action(post_id, action):
161 """
162 支持的 action: like, favorite, view, share
163 对于 like 和 favorite,保证每个用户每帖只做一次。
164 """
165 if action not in ('like', 'favorite', 'view', 'share'):
166 abort(400, 'Invalid action')
TRM-codingd1cbf672025-06-18 15:15:08 +0800167
wu52491532025-06-24 22:47:35 +0800168 data = request.get_json() or {}
169 user_id = data.get('user_id')
170 if not user_id:
171 abort(400, 'user_id required')
wu5934be42025-06-24 11:50:34 +0800172
wu52491532025-06-24 22:47:35 +0800173 # 对 like/favorite 做去重检查
174 if action in ('like', 'favorite'):
175 exists = Behavior.query.filter_by(
176 user_id=user_id,
177 post_id=post_id,
178 type=action
179 ).first()
180 if exists:
181 return jsonify({'error': f'already {action}d'}), 400
182
183 # 创建行为记录
184 beh = Behavior(user_id=user_id, post_id=post_id, type=action)
185 db.session.add(beh)
186
187 # 更新热度
188 post = Post.query.get_or_404(post_id)
189 post.heat += 1
190
191 db.session.commit()
192 return '', 201
193
194@posts_bp.route('/<int:post_id>/like', methods=['DELETE'])
195def unlike(post_id):
196 user_id = request.get_json(silent=True) and request.get_json().get('user_id')
197 if not user_id:
198 abort(400, 'user_id required')
199 # 查找已有的 like 行为
200 beh = Behavior.query.filter_by(
201 user_id=user_id,
202 post_id=post_id,
203 type='like'
204 ).first()
205 if not beh:
206 return jsonify({'error': 'not liked yet'}), 400
207
208 db.session.delete(beh)
209 # 更新热度,确保不降到负数
210 post = Post.query.get_or_404(post_id)
211 post.heat = max(post.heat - 1, 0)
212 db.session.commit()
213 return '', 204
214
215@posts_bp.route('/<int:post_id>/favorite', methods=['DELETE'])
216def unfavorite(post_id):
217 user_id = request.get_json(silent=True) and request.get_json().get('user_id')
218 if not user_id:
219 abort(400, 'user_id required')
220 # 查找已有的 favorite 行为
221 beh = Behavior.query.filter_by(
222 user_id=user_id,
223 post_id=post_id,
224 type='favorite'
225 ).first()
226 if not beh:
227 return jsonify({'error': 'not favorited yet'}), 400
228
229 db.session.delete(beh)
230 # 更新热度
231 post = Post.query.get_or_404(post_id)
232 post.heat = max(post.heat - 1, 0)
233 db.session.commit()
234 return '', 204
235
236@posts_bp.route('/<int:post_id>/like/status', methods=['GET'])
wu5934be42025-06-24 11:50:34 +0800237def has_liked(post_id):
238 """
wu52491532025-06-24 22:47:35 +0800239 检查指定 user_id 是否对 post_id 点过赞。
240 GET /posts/<post_id>/like/status?user_id=123
241 返回 { "liked": true } 或 { "liked": false }
wu5934be42025-06-24 11:50:34 +0800242 """
243 user_id = request.args.get('user_id', type=int)
244 if not user_id:
245 abort(400, 'user_id required')
246
247 exists = Behavior.query.filter_by(
248 user_id=user_id,
249 post_id=post_id,
250 type='like'
251 ).first() is not None
252
253 return jsonify({'liked': exists}), 200