document_operation_logs.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. """
  2. Document Operation Logs API
  3. Query document operation history for audit purposes
  4. """
  5. from datetime import datetime
  6. from flask import request
  7. from flask_login import current_user
  8. from flask_restx import Resource, fields, marshal_with, reqparse
  9. from werkzeug.exceptions import BadRequest, NotFound
  10. from controllers.console import api
  11. from controllers.console.wraps import account_initialization_required, setup_required
  12. from libs.login import login_required
  13. from services.dataset_operation_log_service import DatasetOperationLogService, DatasetOperationType
  14. from services.dataset_service import DatasetService, DocumentService
  15. # Response fields for operation log
  16. operation_log_fields = {
  17. "id": fields.String,
  18. "action": fields.String,
  19. "created_at": fields.DateTime(dt_format="iso8601"),
  20. "created_ip": fields.String,
  21. "content": fields.Raw,
  22. }
  23. operation_log_list_fields = {
  24. "logs": fields.List(fields.Nested(operation_log_fields)),
  25. "total": fields.Integer,
  26. "page": fields.Integer,
  27. "per_page": fields.Integer,
  28. "pages": fields.Integer,
  29. }
  30. class DocumentOperationLogsResource(Resource):
  31. """Base resource for document operation logs"""
  32. pass
  33. class DocumentOperationLogsApi(DocumentOperationLogsResource):
  34. """API for querying document operation logs"""
  35. @setup_required
  36. @login_required
  37. @account_initialization_required
  38. @marshal_with(operation_log_list_fields)
  39. def get(self, dataset_id: str, document_id: str):
  40. """
  41. Get operation logs for a specific document
  42. Args:
  43. dataset_id: Dataset ID
  44. document_id: Document ID
  45. Query Parameters:
  46. - action: Filter by action type (e.g., document.enable, document.disable)
  47. - start_date: Filter by start date (ISO 8601 format)
  48. - end_date: Filter by end date (ISO 8601 format)
  49. - page: Page number (default: 1)
  50. - per_page: Items per page (default: 20, max: 100)
  51. Returns:
  52. dict: Paginated list of operation logs
  53. """
  54. dataset_id_str = str(dataset_id)
  55. document_id_str = str(document_id)
  56. # Get dataset and check permissions
  57. dataset = DatasetService.get_dataset(dataset_id_str)
  58. if not dataset:
  59. raise NotFound("Dataset not found.")
  60. DatasetService.check_dataset_permission(dataset, current_user)
  61. # Get document to verify it exists
  62. document = DocumentService.get_document(dataset_id_str, document_id_str)
  63. if not document:
  64. raise NotFound("Document not found.")
  65. # Parse query parameters
  66. parser = reqparse.RequestParser()
  67. parser.add_argument("action", type=str, location="args")
  68. parser.add_argument("start_date", type=str, location="args")
  69. parser.add_argument("end_date", type=str, location="args")
  70. parser.add_argument("page", type=int, default=1, location="args")
  71. parser.add_argument("per_page", type=int, default=20, location="args")
  72. args = parser.parse_args()
  73. # Validate and parse dates
  74. start_date = None
  75. end_date = None
  76. if args["start_date"]:
  77. try:
  78. start_date = datetime.fromisoformat(args["start_date"].replace("Z", "+00:00"))
  79. except ValueError:
  80. raise BadRequest("Invalid start_date format. Use ISO 8601 format (e.g., 2024-01-01T00:00:00Z)")
  81. if args["end_date"]:
  82. try:
  83. end_date = datetime.fromisoformat(args["end_date"].replace("Z", "+00:00"))
  84. except ValueError:
  85. raise BadRequest("Invalid end_date format. Use ISO 8601 format (e.g., 2024-01-01T00:00:00Z)")
  86. # Validate action type
  87. action_type = None
  88. if args["action"]:
  89. try:
  90. action_type = DatasetOperationType(args["action"])
  91. except ValueError:
  92. raise BadRequest(f"Invalid action type: {args['action']}")
  93. # Limit per_page to prevent abuse
  94. per_page = min(args["per_page"], 100)
  95. # Query operation logs
  96. result = DatasetOperationLogService.get_dataset_operation_logs(
  97. tenant_id=str(dataset.tenant_id),
  98. dataset_id=dataset_id_str,
  99. resource_type="document",
  100. resource_id=document_id_str,
  101. action=action_type,
  102. start_date=start_date,
  103. end_date=end_date,
  104. page=args["page"],
  105. per_page=per_page,
  106. )
  107. return result
  108. class DatasetOperationLogsApi(DocumentOperationLogsResource):
  109. """API for querying all operation logs in a dataset"""
  110. @setup_required
  111. @login_required
  112. @account_initialization_required
  113. @marshal_with(operation_log_list_fields)
  114. def get(self, dataset_id: str):
  115. """
  116. Get operation logs for an entire dataset
  117. Args:
  118. dataset_id: Dataset ID
  119. Query Parameters:
  120. - resource_type: Filter by resource type (document, segment, metadata)
  121. - action: Filter by action type
  122. - start_date: Filter by start date (ISO 8601 format)
  123. - end_date: Filter by end date (ISO 8601 format)
  124. - page: Page number (default: 1)
  125. - per_page: Items per page (default: 20, max: 100)
  126. Returns:
  127. dict: Paginated list of operation logs
  128. """
  129. dataset_id_str = str(dataset_id)
  130. # Get dataset and check permissions
  131. dataset = DatasetService.get_dataset(dataset_id_str)
  132. if not dataset:
  133. raise NotFound("Dataset not found.")
  134. DatasetService.check_dataset_permission(dataset, current_user)
  135. # Parse query parameters
  136. parser = reqparse.RequestParser()
  137. parser.add_argument("resource_type", type=str, location="args")
  138. parser.add_argument("action", type=str, location="args")
  139. parser.add_argument("start_date", type=str, location="args")
  140. parser.add_argument("end_date", type=str, location="args")
  141. parser.add_argument("page", type=int, default=1, location="args")
  142. parser.add_argument("per_page", type=int, default=20, location="args")
  143. args = parser.parse_args()
  144. # Validate resource type
  145. if args["resource_type"] and args["resource_type"] not in ["document", "segment", "metadata", "dataset"]:
  146. raise BadRequest(f"Invalid resource_type: {args['resource_type']}")
  147. # Validate and parse dates
  148. start_date = None
  149. end_date = None
  150. if args["start_date"]:
  151. try:
  152. start_date = datetime.fromisoformat(args["start_date"].replace("Z", "+00:00"))
  153. except ValueError:
  154. raise BadRequest("Invalid start_date format. Use ISO 8601 format (e.g., 2024-01-01T00:00:00Z)")
  155. if args["end_date"]:
  156. try:
  157. end_date = datetime.fromisoformat(args["end_date"].replace("Z", "+00:00"))
  158. except ValueError:
  159. raise BadRequest("Invalid end_date format. Use ISO 8601 format (e.g., 2024-01-01T00:00:00Z)")
  160. # Validate action type
  161. action_type = None
  162. if args["action"]:
  163. try:
  164. action_type = DatasetOperationType(args["action"])
  165. except ValueError:
  166. raise BadRequest(f"Invalid action type: {args['action']}")
  167. # Limit per_page to prevent abuse
  168. per_page = min(args["per_page"], 100)
  169. # Query operation logs
  170. result = DatasetOperationLogService.get_dataset_operation_logs(
  171. tenant_id=str(dataset.tenant_id),
  172. dataset_id=dataset_id_str,
  173. resource_type=args["resource_type"],
  174. action=action_type,
  175. start_date=start_date,
  176. end_date=end_date,
  177. page=args["page"],
  178. per_page=per_page,
  179. )
  180. return result
  181. # Register API routes
  182. api.add_resource(
  183. DocumentOperationLogsApi,
  184. "/datasets/<uuid:dataset_id>/documents/<uuid:document_id>/operation-logs",
  185. )
  186. api.add_resource(
  187. DatasetOperationLogsApi,
  188. "/datasets/<uuid:dataset_id>/operation-logs",
  189. )