import logging from odoo import http from odoo.http import request from odoo.addons.encoach_api.controllers.base import ( jwt_required, _json_response, _error_response, _get_json_body, _paginate, ) _logger = logging.getLogger(__name__) def _ser_board(b): return { 'id': b.id, 'name': b.name or '', 'course_id': b.course_id.id if b.course_id else 0, 'course_name': b.course_id.name if b.course_id else '', 'batch_id': b.batch_id.id if b.batch_id else None, 'batch_name': b.batch_id.name if b.batch_id else None, 'chapter_id': b.chapter_id.id if b.chapter_id else None, 'chapter_name': b.chapter_id.name if b.chapter_id else None, 'is_enabled': b.is_enabled, 'allow_student_posts': b.allow_student_posts, 'post_count': b.post_count or 0, 'assignment_id': None, } def _ser_post(p): return { 'id': p.id, 'board_id': p.board_id.id, 'parent_id': p.parent_id.id if p.parent_id else None, 'author_id': p.author_id.id, 'author_name': p.author_id.name or '', 'author_role': '', 'title': p.title or '', 'content': p.content or '', 'attachment_ids': [], 'attachment_names': [], 'is_pinned': p.is_pinned, 'is_resolved': p.is_resolved, 'reply_count': len(p.child_ids), 'created_at': str(p.create_date) if p.create_date else '', } def _ser_announcement(a): return { 'id': a.id, 'title': a.title or '', 'content': a.content or '', 'author_id': a.author_id.id, 'author_name': a.author_id.name or '', 'course_id': a.course_id.id if a.course_id else None, 'course_name': a.course_id.name if a.course_id else None, 'batch_id': a.batch_id.id if a.batch_id else None, 'batch_name': a.batch_id.name if a.batch_id else None, 'priority': a.priority or 'normal', 'is_published': a.is_published, 'published_at': str(a.published_at) if a.published_at else None, 'expires_at': str(a.expires_at) if a.expires_at else None, 'send_email': a.send_email, 'attachment_ids': [], 'attachment_names': [], } def _ser_message(m): return { 'id': m.id, 'sender_id': m.sender_id.id, 'sender_name': m.sender_id.name or '', 'recipient_id': m.recipient_id.id, 'recipient_name': m.recipient_id.name or '', 'subject': m.subject or '', 'content': m.content or '', 'is_read': m.is_read, 'read_at': str(m.read_at) if m.read_at else None, 'attachment_ids': [], 'attachment_names': [], 'send_email_copy': m.send_email_copy, 'created_at': str(m.create_date) if m.create_date else '', } class CommunicationController(http.Controller): # ── Discussion Boards ──────────────────────────────────────────── @http.route('/api/discussion-boards', type='http', auth='public', methods=['GET'], csrf=False) @jwt_required def list_boards(self, **kw): try: M = request.env['encoach.discussion.board'].sudo() domain = [] if kw.get('course_id'): domain.append(('course_id', '=', int(kw['course_id']))) if kw.get('batch_id'): domain.append(('batch_id', '=', int(kw['batch_id']))) recs = M.search(domain, limit=200, order='id desc') return _json_response([_ser_board(r) for r in recs]) except Exception as e: return _error_response(str(e), 500) @http.route('/api/discussion-boards', type='http', auth='public', methods=['POST'], csrf=False) @jwt_required def create_board(self, **kw): try: body = _get_json_body() vals = {'name': body.get('name', '')} if body.get('course_id'): vals['course_id'] = int(body['course_id']) if body.get('batch_id'): vals['batch_id'] = int(body['batch_id']) rec = request.env['encoach.discussion.board'].sudo().create(vals) return _json_response(_ser_board(rec)) except Exception as e: return _error_response(str(e), 500) @http.route('/api/discussion-boards/', type='http', auth='public', methods=['PATCH', 'PUT'], csrf=False) @jwt_required def update_board(self, bid, **kw): try: rec = request.env['encoach.discussion.board'].sudo().browse(bid) if not rec.exists(): return _error_response('Not found', 404) body = _get_json_body() vals = {} if 'name' in body: vals['name'] = body['name'] if 'is_enabled' in body: vals['is_enabled'] = bool(body['is_enabled']) if 'allow_student_posts' in body: vals['allow_student_posts'] = bool(body['allow_student_posts']) if vals: rec.write(vals) return _json_response(_ser_board(rec)) except Exception as e: return _error_response(str(e), 500) # ── Posts ──────────────────────────────────────────────────────── @http.route('/api/discussion-boards//posts', type='http', auth='public', methods=['GET'], csrf=False) @jwt_required def list_posts(self, bid, **kw): try: M = request.env['encoach.discussion.post'].sudo() domain = [('board_id', '=', bid), ('parent_id', '=', False)] offset, limit, page = _paginate(kw) total = M.search_count(domain) recs = M.search(domain, offset=offset, limit=limit, order='is_pinned desc, create_date desc') items = [_ser_post(r) for r in recs] return _json_response({'items': items, 'data': items, 'total': total, 'page': page, 'size': limit}) except Exception as e: return _error_response(str(e), 500) @http.route('/api/discussion-boards//posts', type='http', auth='public', methods=['POST'], csrf=False) @jwt_required def create_post(self, bid, **kw): try: body = _get_json_body() vals = { 'board_id': bid, 'author_id': request.env.uid, 'content': body.get('content', ''), } if body.get('title'): vals['title'] = body['title'] if body.get('parent_id'): vals['parent_id'] = int(body['parent_id']) rec = request.env['encoach.discussion.post'].sudo().create(vals) return _json_response(_ser_post(rec)) except Exception as e: return _error_response(str(e), 500) @http.route('/api/posts/', type='http', auth='public', methods=['PATCH', 'PUT'], csrf=False) @jwt_required def update_post(self, pid, **kw): try: rec = request.env['encoach.discussion.post'].sudo().browse(pid) if not rec.exists(): return _error_response('Not found', 404) body = _get_json_body() vals = {} if 'content' in body: vals['content'] = body['content'] if 'title' in body: vals['title'] = body['title'] if vals: rec.write(vals) return _json_response(_ser_post(rec)) except Exception as e: return _error_response(str(e), 500) @http.route('/api/posts/', type='http', auth='public', methods=['DELETE'], csrf=False) @jwt_required def delete_post(self, pid, **kw): try: rec = request.env['encoach.discussion.post'].sudo().browse(pid) if rec.exists(): rec.unlink() return _json_response({'success': True}) except Exception as e: return _error_response(str(e), 500) @http.route('/api/posts//pin', type='http', auth='public', methods=['POST'], csrf=False) @jwt_required def pin_post(self, pid, **kw): try: rec = request.env['encoach.discussion.post'].sudo().browse(pid) if not rec.exists(): return _error_response('Not found', 404) body = _get_json_body() pinned = body.get('pinned', not rec.is_pinned) rec.write({'is_pinned': pinned}) return _json_response(_ser_post(rec)) except Exception as e: return _error_response(str(e), 500) @http.route('/api/posts//resolve', type='http', auth='public', methods=['POST'], csrf=False) @jwt_required def resolve_post(self, pid, **kw): try: rec = request.env['encoach.discussion.post'].sudo().browse(pid) if not rec.exists(): return _error_response('Not found', 404) rec.write({'is_resolved': True}) return _json_response(_ser_post(rec)) except Exception as e: return _error_response(str(e), 500) # ── Announcements ──────────────────────────────────────────────── @http.route('/api/announcements', type='http', auth='public', methods=['GET'], csrf=False) @jwt_required def list_announcements(self, **kw): try: M = request.env['encoach.announcement'].sudo() domain = [] if kw.get('course_id'): domain.append(('course_id', '=', int(kw['course_id']))) if kw.get('priority'): domain.append(('priority', '=', kw['priority'])) recs = M.search(domain, limit=200, order='create_date desc') return _json_response([_ser_announcement(r) for r in recs]) except Exception as e: return _error_response(str(e), 500) @http.route('/api/announcements', type='http', auth='public', methods=['POST'], csrf=False) @jwt_required def create_announcement(self, **kw): try: body = _get_json_body() vals = { 'title': body.get('title', ''), 'content': body.get('content', ''), 'author_id': request.env.uid, 'priority': body.get('priority', 'normal'), 'send_email': body.get('send_email', False), } if body.get('course_id'): vals['course_id'] = int(body['course_id']) if body.get('batch_id'): vals['batch_id'] = int(body['batch_id']) if body.get('expires_at'): vals['expires_at'] = body['expires_at'] rec = request.env['encoach.announcement'].sudo().create(vals) return _json_response(_ser_announcement(rec)) except Exception as e: return _error_response(str(e), 500) @http.route('/api/announcements/', type='http', auth='public', methods=['PATCH', 'PUT'], csrf=False) @jwt_required def update_announcement(self, aid, **kw): try: rec = request.env['encoach.announcement'].sudo().browse(aid) if not rec.exists(): return _error_response('Not found', 404) body = _get_json_body() vals = {} for k in ('title', 'content', 'priority', 'expires_at'): if k in body: vals[k] = body[k] if 'send_email' in body: vals['send_email'] = bool(body['send_email']) if vals: rec.write(vals) return _json_response(_ser_announcement(rec)) except Exception as e: return _error_response(str(e), 500) @http.route('/api/announcements/', type='http', auth='public', methods=['DELETE'], csrf=False) @jwt_required def delete_announcement(self, aid, **kw): try: rec = request.env['encoach.announcement'].sudo().browse(aid) if rec.exists(): rec.unlink() return _json_response({'success': True}) except Exception as e: return _error_response(str(e), 500) @http.route('/api/announcements//publish', type='http', auth='public', methods=['POST'], csrf=False) @jwt_required def publish_announcement(self, aid, **kw): try: rec = request.env['encoach.announcement'].sudo().browse(aid) if not rec.exists(): return _error_response('Not found', 404) from datetime import datetime rec.write({'is_published': True, 'published_at': str(datetime.now())}) return _json_response(_ser_announcement(rec)) except Exception as e: return _error_response(str(e), 500) # ── Messages ───────────────────────────────────────────────────── @http.route('/api/messages', type='http', auth='public', methods=['GET'], csrf=False) @jwt_required def list_messages(self, **kw): try: M = request.env['encoach.message'].sudo() uid = request.env.uid domain = [('recipient_id', '=', uid)] if kw.get('is_read') == 'false': domain.append(('is_read', '=', False)) offset, limit, page = _paginate(kw) total = M.search_count(domain) recs = M.search(domain, offset=offset, limit=limit, order='create_date desc') items = [_ser_message(r) for r in recs] return _json_response({'items': items, 'data': items, 'total': total, 'page': page, 'size': limit}) except Exception as e: return _error_response(str(e), 500) @http.route('/api/messages/sent', type='http', auth='public', methods=['GET'], csrf=False) @jwt_required def list_sent_messages(self, **kw): try: M = request.env['encoach.message'].sudo() uid = request.env.uid offset, limit, page = _paginate(kw) domain = [('sender_id', '=', uid)] total = M.search_count(domain) recs = M.search(domain, offset=offset, limit=limit, order='create_date desc') items = [_ser_message(r) for r in recs] return _json_response({'items': items, 'data': items, 'total': total, 'page': page, 'size': limit}) except Exception as e: return _error_response(str(e), 500) @http.route('/api/messages', type='http', auth='public', methods=['POST'], csrf=False) @jwt_required def send_message(self, **kw): try: body = _get_json_body() vals = { 'sender_id': request.env.uid, 'recipient_id': int(body.get('recipient_id', 0)), 'subject': body.get('subject', ''), 'content': body.get('content', ''), 'send_email_copy': body.get('send_email_copy', False), } rec = request.env['encoach.message'].sudo().create(vals) return _json_response(_ser_message(rec)) except Exception as e: return _error_response(str(e), 500) @http.route('/api/messages/', type='http', auth='public', methods=['GET'], csrf=False) @jwt_required def get_message(self, mid, **kw): try: rec = request.env['encoach.message'].sudo().browse(mid) if not rec.exists(): return _error_response('Not found', 404) if not rec.is_read and rec.recipient_id.id == request.env.uid: from datetime import datetime rec.write({'is_read': True, 'read_at': str(datetime.now())}) return _json_response(_ser_message(rec)) except Exception as e: return _error_response(str(e), 500) @http.route('/api/messages/', type='http', auth='public', methods=['DELETE'], csrf=False) @jwt_required def delete_message(self, mid, **kw): try: rec = request.env['encoach.message'].sudo().browse(mid) if rec.exists(): rec.unlink() return _json_response({'success': True}) except Exception as e: return _error_response(str(e), 500) @http.route('/api/messages/unread-count', type='http', auth='public', methods=['GET'], csrf=False) @jwt_required def unread_count(self, **kw): try: count = request.env['encoach.message'].sudo().search_count([ ('recipient_id', '=', request.env.uid), ('is_read', '=', False), ]) return _json_response({'count': count}) except Exception as e: return _error_response(str(e), 500)