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_notif(n): return { 'id': n.id, 'title': n.title or '', 'message': n.message or '', 'type': n.type or 'info', 'is_read': n.is_read, 'read_at': str(n.read_at) if n.read_at else None, 'reference_model': n.reference_model or None, 'reference_id': n.reference_id or None, 'created_at': str(n.create_date) if n.create_date else '', } def _ser_rule(r): return { 'id': r.id, 'name': r.name or '', 'event_type': r.event_type or '', 'template': r.template or '', # Admin UI uses `active`; legacy callers still read `is_active`. 'active': bool(r.is_active), 'is_active': bool(r.is_active), 'recipients': r.recipients or 'all', 'channels': r.channels or '', 'days_before': r.days_before or 0, 'frequency': r.frequency or 'once', 'channel': r.channel or 'in_app', 'entity_id': r.entity_id.id or None, 'entity_name': r.entity_id.name if r.entity_id else None, } def _apply_rule_writes(body, vals): """Translate the incoming JSON body into ORM write values. Accepts both the new (`active`, `channel`, `days_before`, `frequency`) and legacy (`is_active`, `channels`) contracts. """ for k in ('name', 'event_type', 'template', 'recipients', 'frequency', 'channel'): if k in body and body[k] is not None: vals[k] = body[k] if 'channels' in body and body['channels'] is not None: vals['channels'] = body['channels'] if 'days_before' in body and body['days_before'] is not None: try: vals['days_before'] = int(body['days_before']) except (TypeError, ValueError): pass if 'entity_id' in body: vals['entity_id'] = int(body['entity_id']) if body['entity_id'] else False if 'active' in body: vals['is_active'] = bool(body['active']) elif 'is_active' in body: vals['is_active'] = bool(body['is_active']) return vals class NotificationsController(http.Controller): @http.route('/api/notifications', type='http', auth='public', methods=['GET'], csrf=False) @jwt_required def list_notifications(self, **kw): try: M = request.env['encoach.notification'].sudo() domain = [('user_id', '=', request.env.uid)] if kw.get('type'): domain.append(('type', '=', kw['type'])) if kw.get('is_read') == 'false': domain.append(('is_read', '=', False)) elif kw.get('is_read') == 'true': domain.append(('is_read', '=', True)) offset, limit, page = _paginate(kw) total = M.search_count(domain) recs = M.search(domain, offset=offset, limit=limit, order='create_date desc') items = [_ser_notif(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/notifications//read', type='http', auth='public', methods=['POST'], csrf=False) @jwt_required def mark_read(self, nid, **kw): try: rec = request.env['encoach.notification'].sudo().browse(nid) if not rec.exists(): return _error_response('Not found', 404) from datetime import datetime rec.write({'is_read': True, 'read_at': str(datetime.now())}) return _json_response(_ser_notif(rec)) except Exception as e: return _error_response(str(e), 500) @http.route('/api/notifications/read-all', type='http', auth='public', methods=['POST'], csrf=False) @jwt_required def mark_all_read(self, **kw): try: from datetime import datetime recs = request.env['encoach.notification'].sudo().search([ ('user_id', '=', request.env.uid), ('is_read', '=', False) ]) recs.write({'is_read': True, 'read_at': str(datetime.now())}) return _json_response({'success': True}) except Exception as e: return _error_response(str(e), 500) @http.route('/api/notifications/unread-count', type='http', auth='public', methods=['GET'], csrf=False) @jwt_required def unread_count(self, **kw): try: count = request.env['encoach.notification'].sudo().search_count([ ('user_id', '=', request.env.uid), ('is_read', '=', False) ]) return _json_response({'count': count}) except Exception as e: return _error_response(str(e), 500) # ── Rules ──────────────────────────────────────────────────────── @http.route('/api/notification-rules', type='http', auth='public', methods=['GET'], csrf=False) @jwt_required def list_rules(self, **kw): try: recs = request.env['encoach.notification.rule'].sudo().search([], order='id desc') return _json_response([_ser_rule(r) for r in recs]) except Exception as e: return _error_response(str(e), 500) @http.route('/api/notification-rules', type='http', auth='public', methods=['POST'], csrf=False) @jwt_required def create_rule(self, **kw): try: body = _get_json_body() if not body.get('name'): return _error_response('name is required', 400) if not body.get('event_type'): return _error_response('event_type is required', 400) vals = { 'name': body.get('name', ''), 'event_type': body.get('event_type', ''), } _apply_rule_writes(body, vals) rec = request.env['encoach.notification.rule'].sudo().create(vals) return _json_response(_ser_rule(rec)) except Exception as e: _logger.exception('create notification rule failed') return _error_response(str(e), 500) @http.route('/api/notification-rules/', type='http', auth='public', methods=['PATCH', 'PUT'], csrf=False) @jwt_required def update_rule(self, rid, **kw): try: rec = request.env['encoach.notification.rule'].sudo().browse(rid) if not rec.exists(): return _error_response('Not found', 404) body = _get_json_body() vals = {} _apply_rule_writes(body, vals) if vals: rec.write(vals) return _json_response(_ser_rule(rec)) except Exception as e: _logger.exception('update notification rule failed') return _error_response(str(e), 500) @http.route('/api/notification-rules/', type='http', auth='public', methods=['DELETE'], csrf=False) @jwt_required def delete_rule(self, rid, **kw): try: rec = request.env['encoach.notification.rule'].sudo().browse(rid) if rec.exists(): rec.unlink() return _json_response({'success': True}) except Exception as e: return _error_response(str(e), 500) # ── Preferences ────────────────────────────────────────────────── @http.route('/api/notification-preferences', type='http', auth='public', methods=['GET'], csrf=False) @jwt_required def get_preferences(self, **kw): try: M = request.env['encoach.notification.preference'].sudo() rec = M.search([('user_id', '=', request.env.uid)], limit=1) if not rec: rec = M.create({'user_id': request.env.uid}) return _json_response({ 'email_enabled': rec.email_enabled, 'push_enabled': rec.push_enabled, 'in_app_enabled': rec.in_app_enabled, 'digest_frequency': rec.digest_frequency, }) except Exception as e: return _error_response(str(e), 500) @http.route('/api/notification-preferences', type='http', auth='public', methods=['PATCH', 'PUT'], csrf=False) @jwt_required def update_preferences(self, **kw): try: M = request.env['encoach.notification.preference'].sudo() rec = M.search([('user_id', '=', request.env.uid)], limit=1) if not rec: rec = M.create({'user_id': request.env.uid}) body = _get_json_body() vals = {} for k in ('email_enabled', 'push_enabled', 'in_app_enabled'): if k in body: vals[k] = bool(body[k]) if 'digest_frequency' in body: vals['digest_frequency'] = body['digest_frequency'] if vals: rec.write(vals) return _json_response({ 'email_enabled': rec.email_enabled, 'push_enabled': rec.push_enabled, 'in_app_enabled': rec.in_app_enabled, 'digest_frequency': rec.digest_frequency, }) except Exception as e: return _error_response(str(e), 500)