Coverage for controllers / main.py: 23%
105 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-28 01:16 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-28 01:16 +0000
1# -*- coding: utf-8 -*-
3from odoo import http
4from odoo.http import request
5import json
6import logging
7import hmac
8import hashlib
10_logger = logging.getLogger(__name__)
13class LoxBackupController(http.Controller):
14 """Controller for LOX Backup webhooks and API endpoints"""
16 def _verify_signature(self, api_key, signature, body):
17 """Verify webhook signature"""
18 if not signature:
19 return False
20 expected = hmac.new(
21 api_key.encode('utf-8'),
22 body,
23 hashlib.sha256
24 ).hexdigest()
25 return hmac.compare_digest(signature, expected)
27 def _get_config(self):
28 """Get active LOX backup configuration"""
29 return request.env['lox.backup.config'].sudo().search([
30 ('active', '=', True),
31 ('source_registered', '=', True),
32 ], limit=1)
34 @http.route('/lox/webhook/backup-status', type='json', auth='public', methods=['POST'], csrf=False)
35 def webhook_backup_status(self, **kwargs):
36 """Webhook endpoint for backup status updates from LOX"""
37 try:
38 data = request.get_json_data()
39 backup_uuid = data.get('backup_id') or data.get('uuid')
40 status = data.get('status')
42 if not backup_uuid or not status:
43 return {'error': 'Missing backup_id or status'}
45 # Find the backup log
46 log = request.env['lox.backup.log'].sudo().search([
47 ('backup_uuid', '=', backup_uuid),
48 ], limit=1)
50 if not log:
51 _logger.warning(f'Webhook: Backup log not found for UUID {backup_uuid}')
52 return {'error': 'Backup not found'}
54 # Map status
55 status_mapping = {
56 'PENDING': 'pending',
57 'UPLOADING': 'in_progress',
58 'VALIDATING': 'validating',
59 'COMPLETED': 'completed',
60 'FAILED': 'failed',
61 'CANCELLED': 'cancelled',
62 }
64 updates = {
65 'status': status_mapping.get(status, 'pending'),
66 }
68 if data.get('size'):
69 updates['size_bytes'] = data['size']
70 if data.get('checksum'):
71 updates['checksum'] = data['checksum']
72 if data.get('completed_at'):
73 updates['completed_at'] = data['completed_at']
74 if data.get('error'):
75 updates['error_message'] = data['error']
77 log.write(updates)
78 _logger.info(f'Webhook: Updated backup {backup_uuid} status to {status}')
80 return {'success': True}
82 except Exception as e:
83 _logger.exception('Webhook error')
84 return {'error': str(e)}
86 @http.route('/lox/webhook/restore-ready', type='json', auth='public', methods=['POST'], csrf=False)
87 def webhook_restore_ready(self, **kwargs):
88 """Webhook endpoint for restore ready notifications"""
89 try:
90 data = request.get_json_data()
91 backup_uuid = data.get('backup_id') or data.get('uuid')
92 download_url = data.get('download_url') or data.get('url')
93 expires_at = data.get('expires_at')
95 if not backup_uuid:
96 return {'error': 'Missing backup_id'}
98 # Find the backup log
99 log = request.env['lox.backup.log'].sudo().search([
100 ('backup_uuid', '=', backup_uuid),
101 ], limit=1)
103 if not log:
104 _logger.warning(f'Webhook: Backup log not found for UUID {backup_uuid}')
105 return {'error': 'Backup not found'}
107 log.write({
108 'restore_status': 'ready',
109 'restore_url': download_url,
110 'restore_expires_at': expires_at,
111 })
113 _logger.info(f'Webhook: Restore ready for backup {backup_uuid}')
114 return {'success': True}
116 except Exception as e:
117 _logger.exception('Webhook error')
118 return {'error': str(e)}
120 @http.route('/lox/api/status', type='json', auth='public', methods=['GET', 'POST'], csrf=False)
121 def api_status(self, **kwargs):
122 """API endpoint to check module status"""
123 config = self._get_config()
124 return {
125 'status': 'active' if config else 'not_configured',
126 'source_id': config.source_id if config else None,
127 'module_version': '18.0.1.0.0',
128 }
130 @http.route('/lox/api/trigger-backup', type='json', auth='public', methods=['POST'], csrf=False)
131 def api_trigger_backup(self, **kwargs):
132 """API endpoint to trigger a backup remotely"""
133 try:
134 data = request.get_json_data() or {}
135 api_key = request.httprequest.headers.get('X-API-Key')
137 config = self._get_config()
138 if not config:
139 return {'error': 'LOX Backup not configured'}
141 # Verify API key
142 if not api_key or api_key != config.api_key:
143 return {'error': 'Invalid API key'}
145 component = data.get('component', 'full')
146 if component not in ('full', 'database', 'filestore'):
147 component = 'full'
149 # Create backup via wizard
150 wizard = request.env['lox.backup.wizard'].sudo().create({
151 'config_id': config.id,
152 'component': component,
153 })
155 # Run backup
156 wizard.action_run_backup()
158 return {
159 'success': True,
160 'message': 'Backup triggered',
161 }
163 except Exception as e:
164 _logger.exception('API trigger backup error')
165 return {'error': str(e)}
167 @http.route('/lox/api/backups', type='json', auth='public', methods=['GET', 'POST'], csrf=False)
168 def api_list_backups(self, **kwargs):
169 """API endpoint to list backups"""
170 try:
171 api_key = request.httprequest.headers.get('X-API-Key')
173 config = self._get_config()
174 if not config:
175 return {'error': 'LOX Backup not configured'}
177 # Verify API key
178 if not api_key or api_key != config.api_key:
179 return {'error': 'Invalid API key'}
181 data = request.get_json_data() or {}
182 limit = data.get('limit', 50)
183 offset = data.get('offset', 0)
185 logs = request.env['lox.backup.log'].sudo().search([
186 ('config_id', '=', config.id),
187 ], limit=limit, offset=offset, order='create_date desc')
189 backups = []
190 for log in logs:
191 backups.append({
192 'uuid': log.backup_uuid,
193 'component': log.component,
194 'status': log.status,
195 'size': log.size_bytes,
196 'created_at': log.create_date.isoformat() if log.create_date else None,
197 'completed_at': log.completed_at.isoformat() if log.completed_at else None,
198 })
200 return {
201 'backups': backups,
202 'total': request.env['lox.backup.log'].sudo().search_count([
203 ('config_id', '=', config.id),
204 ]),
205 }
207 except Exception as e:
208 _logger.exception('API list backups error')
209 return {'error': str(e)}