Skip to content

Commit b157cfc

Browse files
committed
refactor: update Postgres audit log model
1 parent b80bf3a commit b157cfc

9 files changed

+597
-277
lines changed

todo/migrations/0001_initial_postgres_models.py

Lines changed: 384 additions & 201 deletions
Large diffs are not rendered by default.

todo/migrations/0002_remove_old_watchlist_tables.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,16 @@
44

55

66
class Migration(migrations.Migration):
7-
87
dependencies = [
9-
('todo', '0001_initial_postgres_models'),
8+
("todo", "0001_initial_postgres_models"),
109
]
1110

1211
operations = [
1312
# Remove the old watchlist tables
1413
migrations.DeleteModel(
15-
name='PostgresWatchlistTask',
14+
name="PostgresWatchlistTask",
1615
),
1716
migrations.DeleteModel(
18-
name='PostgresWatchlist',
17+
name="PostgresWatchlist",
1918
),
2019
]

todo/migrations/0003_create_new_watchlist_table.py

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,45 @@
55

66

77
class Migration(migrations.Migration):
8-
98
dependencies = [
10-
('todo', '0002_remove_old_watchlist_tables'),
9+
("todo", "0002_remove_old_watchlist_tables"),
1110
]
1211

1312
operations = [
1413
migrations.CreateModel(
15-
name='PostgresWatchlist',
14+
name="PostgresWatchlist",
1615
fields=[
17-
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18-
('mongo_id', models.CharField(blank=True, max_length=24, null=True, unique=True)),
19-
('task_id', models.CharField(max_length=24)),
20-
('user_id', models.CharField(max_length=24)),
21-
('is_active', models.BooleanField(default=True)),
22-
('created_by', models.CharField(max_length=24)),
23-
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
24-
('updated_by', models.CharField(blank=True, max_length=24, null=True)),
25-
('updated_at', models.DateTimeField(blank=True, null=True)),
26-
('last_sync_at', models.DateTimeField(auto_now=True)),
27-
('sync_status', models.CharField(choices=[('SYNCED', 'Synced'), ('PENDING', 'Pending'), ('FAILED', 'Failed')], default='SYNCED', max_length=20)),
28-
('sync_error', models.TextField(blank=True, null=True)),
16+
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
17+
("mongo_id", models.CharField(blank=True, max_length=24, null=True, unique=True)),
18+
("task_id", models.CharField(max_length=24)),
19+
("user_id", models.CharField(max_length=24)),
20+
("is_active", models.BooleanField(default=True)),
21+
("created_by", models.CharField(max_length=24)),
22+
("created_at", models.DateTimeField(default=django.utils.timezone.now)),
23+
("updated_by", models.CharField(blank=True, max_length=24, null=True)),
24+
("updated_at", models.DateTimeField(blank=True, null=True)),
25+
("last_sync_at", models.DateTimeField(auto_now=True)),
26+
(
27+
"sync_status",
28+
models.CharField(
29+
choices=[("SYNCED", "Synced"), ("PENDING", "Pending"), ("FAILED", "Failed")],
30+
default="SYNCED",
31+
max_length=20,
32+
),
33+
),
34+
("sync_error", models.TextField(blank=True, null=True)),
2935
],
3036
options={
31-
'db_table': 'postgres_watchlist',
32-
'indexes': [models.Index(fields=['mongo_id'], name='postgres_wa_mongo_i_5c0868_idx'), models.Index(fields=['task_id'], name='postgres_wa_task_id_adb0e4_idx'), models.Index(fields=['user_id'], name='postgres_wa_user_id_71c384_idx'), models.Index(fields=['is_active'], name='postgres_wa_is_acti_ae4d9b_idx'), models.Index(fields=['sync_status'], name='postgres_wa_sync_st_29bd9a_idx'), models.Index(fields=['user_id', 'task_id'], name='postgres_wa_user_id_c1421a_idx')],
33-
'unique_together': {('user_id', 'task_id')},
37+
"db_table": "postgres_watchlist",
38+
"indexes": [
39+
models.Index(fields=["mongo_id"], name="postgres_wa_mongo_i_5c0868_idx"),
40+
models.Index(fields=["task_id"], name="postgres_wa_task_id_adb0e4_idx"),
41+
models.Index(fields=["user_id"], name="postgres_wa_user_id_71c384_idx"),
42+
models.Index(fields=["is_active"], name="postgres_wa_is_acti_ae4d9b_idx"),
43+
models.Index(fields=["sync_status"], name="postgres_wa_sync_st_29bd9a_idx"),
44+
models.Index(fields=["user_id", "task_id"], name="postgres_wa_user_id_c1421a_idx"),
45+
],
46+
"unique_together": {("user_id", "task_id")},
3447
},
3548
),
3649
]
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Generated by Django 5.1.5 on 2025-08-23 18:43
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
dependencies = [
8+
("todo", "0003_create_new_watchlist_table"),
9+
]
10+
11+
operations = [
12+
migrations.RemoveIndex(
13+
model_name="postgresauditlog",
14+
name="postgres_au_collect_4f3415_idx",
15+
),
16+
migrations.RemoveIndex(
17+
model_name="postgresauditlog",
18+
name="postgres_au_documen_2dd530_idx",
19+
),
20+
migrations.RemoveIndex(
21+
model_name="postgresauditlog",
22+
name="postgres_au_user_mo_20b3ab_idx",
23+
),
24+
migrations.RenameField(
25+
model_name="postgresauditlog",
26+
old_name="user_mongo_id",
27+
new_name="assignee_from",
28+
),
29+
migrations.RemoveField(
30+
model_name="postgresauditlog",
31+
name="collection_name",
32+
),
33+
migrations.RemoveField(
34+
model_name="postgresauditlog",
35+
name="document_id",
36+
),
37+
migrations.RemoveField(
38+
model_name="postgresauditlog",
39+
name="ip_address",
40+
),
41+
migrations.RemoveField(
42+
model_name="postgresauditlog",
43+
name="new_values",
44+
),
45+
migrations.RemoveField(
46+
model_name="postgresauditlog",
47+
name="old_values",
48+
),
49+
migrations.RemoveField(
50+
model_name="postgresauditlog",
51+
name="user_agent",
52+
),
53+
migrations.AddField(
54+
model_name="postgresauditlog",
55+
name="assignee_to",
56+
field=models.CharField(blank=True, max_length=24, null=True),
57+
),
58+
migrations.AddField(
59+
model_name="postgresauditlog",
60+
name="new_executor_id",
61+
field=models.CharField(blank=True, max_length=24, null=True),
62+
),
63+
migrations.AddField(
64+
model_name="postgresauditlog",
65+
name="performed_by",
66+
field=models.CharField(blank=True, max_length=24, null=True),
67+
),
68+
migrations.AddField(
69+
model_name="postgresauditlog",
70+
name="previous_executor_id",
71+
field=models.CharField(blank=True, max_length=24, null=True),
72+
),
73+
migrations.AddField(
74+
model_name="postgresauditlog",
75+
name="spoc_id",
76+
field=models.CharField(blank=True, max_length=24, null=True),
77+
),
78+
migrations.AddField(
79+
model_name="postgresauditlog",
80+
name="status_from",
81+
field=models.CharField(blank=True, max_length=20, null=True),
82+
),
83+
migrations.AddField(
84+
model_name="postgresauditlog",
85+
name="status_to",
86+
field=models.CharField(blank=True, max_length=20, null=True),
87+
),
88+
migrations.AddField(
89+
model_name="postgresauditlog",
90+
name="task_id",
91+
field=models.CharField(blank=True, max_length=24, null=True),
92+
),
93+
migrations.AddField(
94+
model_name="postgresauditlog",
95+
name="team_id",
96+
field=models.CharField(blank=True, max_length=24, null=True),
97+
),
98+
migrations.AlterField(
99+
model_name="postgresauditlog",
100+
name="action",
101+
field=models.CharField(max_length=100),
102+
),
103+
migrations.AddIndex(
104+
model_name="postgresauditlog",
105+
index=models.Index(fields=["task_id"], name="postgres_au_task_id_76f799_idx"),
106+
),
107+
migrations.AddIndex(
108+
model_name="postgresauditlog",
109+
index=models.Index(fields=["team_id"], name="postgres_au_team_id_aaca90_idx"),
110+
),
111+
migrations.AddIndex(
112+
model_name="postgresauditlog",
113+
index=models.Index(fields=["performed_by"], name="postgres_au_perform_f08d1f_idx"),
114+
),
115+
]

todo/models/postgres/audit_log.py

Lines changed: 15 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,21 @@
33

44

55
class PostgresAuditLog(models.Model):
6-
"""
7-
Postgres model for audit logs.
8-
"""
9-
10-
# MongoDB ObjectId as string for reference
116
mongo_id = models.CharField(max_length=24, unique=True, null=True, blank=True)
127

13-
# Audit log fields
14-
ACTION_CHOICES = [
15-
("CREATE", "Create"),
16-
("UPDATE", "Update"),
17-
("DELETE", "Delete"),
18-
("READ", "Read"),
19-
("LOGIN", "Login"),
20-
("LOGOUT", "Logout"),
21-
]
22-
23-
action = models.CharField(max_length=20, choices=ACTION_CHOICES)
24-
collection_name = models.CharField(max_length=100)
25-
document_id = models.CharField(max_length=24) # MongoDB ObjectId as string
26-
27-
# User who performed the action
28-
user_mongo_id = models.CharField(max_length=24, null=True, blank=True) # MongoDB ObjectId as string
29-
30-
# Changes made
31-
old_values = models.JSONField(null=True, blank=True)
32-
new_values = models.JSONField(null=True, blank=True)
33-
34-
# Metadata
35-
ip_address = models.GenericIPAddressField(null=True, blank=True)
36-
user_agent = models.TextField(null=True, blank=True)
37-
38-
# Timestamps
8+
task_id = models.CharField(max_length=24, null=True, blank=True)
9+
team_id = models.CharField(max_length=24, null=True, blank=True)
10+
previous_executor_id = models.CharField(max_length=24, null=True, blank=True)
11+
new_executor_id = models.CharField(max_length=24, null=True, blank=True)
12+
spoc_id = models.CharField(max_length=24, null=True, blank=True)
13+
action = models.CharField(max_length=100)
3914
timestamp = models.DateTimeField(default=timezone.now)
15+
status_from = models.CharField(max_length=20, null=True, blank=True)
16+
status_to = models.CharField(max_length=20, null=True, blank=True)
17+
assignee_from = models.CharField(max_length=24, null=True, blank=True)
18+
assignee_to = models.CharField(max_length=24, null=True, blank=True)
19+
performed_by = models.CharField(max_length=24, null=True, blank=True)
4020

41-
# Sync metadata
4221
last_sync_at = models.DateTimeField(auto_now=True)
4322
sync_status = models.CharField(
4423
max_length=20,
@@ -55,13 +34,13 @@ class Meta:
5534
db_table = "postgres_audit_logs"
5635
indexes = [
5736
models.Index(fields=["mongo_id"]),
37+
models.Index(fields=["task_id"]),
38+
models.Index(fields=["team_id"]),
5839
models.Index(fields=["action"]),
59-
models.Index(fields=["collection_name"]),
60-
models.Index(fields=["document_id"]),
61-
models.Index(fields=["user_mongo_id"]),
40+
models.Index(fields=["performed_by"]),
6241
models.Index(fields=["timestamp"]),
6342
models.Index(fields=["sync_status"]),
6443
]
6544

6645
def __str__(self):
67-
return f"{self.action} on {self.collection_name}:{self.document_id}"
46+
return f"{self.action} on task {self.task_id}"

todo/models/postgres/watchlist.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class PostgresWatchlist(models.Model):
1515
task_id = models.CharField(max_length=24) # MongoDB ObjectId as string
1616
user_id = models.CharField(max_length=24) # MongoDB ObjectId as string
1717
is_active = models.BooleanField(default=True)
18-
18+
1919
# Audit fields
2020
created_by = models.CharField(max_length=24) # MongoDB ObjectId as string
2121
created_at = models.DateTimeField(default=timezone.now)

todo/repositories/audit_log_repository.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from todo.models.audit_log import AuditLogModel
22
from todo.repositories.common.mongo_repository import MongoRepository
33
from datetime import datetime, timezone
4+
from todo.services.enhanced_dual_write_service import EnhancedDualWriteService
45

56

67
class AuditLogRepository(MongoRepository):
@@ -13,6 +14,33 @@ def create(cls, audit_log: AuditLogModel) -> AuditLogModel:
1314
audit_log_dict = audit_log.model_dump(mode="json", by_alias=True, exclude_none=True)
1415
insert_result = collection.insert_one(audit_log_dict)
1516
audit_log.id = insert_result.inserted_id
17+
18+
dual_write_service = EnhancedDualWriteService()
19+
audit_log_data = {
20+
"task_id": str(audit_log.task_id) if audit_log.task_id else None,
21+
"team_id": str(audit_log.team_id) if audit_log.team_id else None,
22+
"previous_executor_id": str(audit_log.previous_executor_id) if audit_log.previous_executor_id else None,
23+
"new_executor_id": str(audit_log.new_executor_id) if audit_log.new_executor_id else None,
24+
"spoc_id": str(audit_log.spoc_id) if audit_log.spoc_id else None,
25+
"action": audit_log.action,
26+
"timestamp": audit_log.timestamp,
27+
"status_from": audit_log.status_from,
28+
"status_to": audit_log.status_to,
29+
"assignee_from": str(audit_log.assignee_from) if audit_log.assignee_from else None,
30+
"assignee_to": str(audit_log.assignee_to) if audit_log.assignee_to else None,
31+
"performed_by": str(audit_log.performed_by) if audit_log.performed_by else None,
32+
}
33+
34+
dual_write_success = dual_write_service.create_document(
35+
collection_name="audit_logs", data=audit_log_data, mongo_id=str(audit_log.id)
36+
)
37+
38+
if not dual_write_success:
39+
import logging
40+
41+
logger = logging.getLogger(__name__)
42+
logger.warning(f"Failed to sync audit log {audit_log.id} to Postgres")
43+
1644
return audit_log
1745

1846
@classmethod

todo/services/dual_write_service.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -419,17 +419,21 @@ def _transform_user_role_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
419419
}
420420

421421
def _transform_audit_log_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
422-
"""Transform audit log data for Postgres."""
423422
return {
423+
"task_id": str(data.get("task_id", "")) if data.get("task_id") else None,
424+
"team_id": str(data.get("team_id", "")) if data.get("team_id") else None,
425+
"previous_executor_id": str(data.get("previous_executor_id", ""))
426+
if data.get("previous_executor_id")
427+
else None,
428+
"new_executor_id": str(data.get("new_executor_id", "")) if data.get("new_executor_id") else None,
429+
"spoc_id": str(data.get("spoc_id", "")) if data.get("spoc_id") else None,
424430
"action": data.get("action"),
425-
"collection_name": data.get("collection_name"),
426-
"document_id": str(data.get("document_id", "")),
427-
"user_mongo_id": str(data.get("user_id", "")) if data.get("user_id") else None,
428-
"old_values": data.get("old_values"),
429-
"new_values": data.get("new_values"),
430-
"ip_address": data.get("ip_address"),
431-
"user_agent": data.get("user_agent"),
432431
"timestamp": data.get("timestamp"),
432+
"status_from": data.get("status_from"),
433+
"status_to": data.get("status_to"),
434+
"assignee_from": str(data.get("assignee_from", "")) if data.get("assignee_from") else None,
435+
"assignee_to": str(data.get("assignee_to", "")) if data.get("assignee_to") else None,
436+
"performed_by": str(data.get("performed_by", "")) if data.get("performed_by") else None,
433437
}
434438

435439
def _transform_team_creation_invite_code_data(self, data: Dict[str, Any]) -> Dict[str, Any]:

todo/services/task_service.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -680,23 +680,21 @@ def defer_task(cls, task_id: str, deferred_till: datetime, user_id: str) -> Task
680680

681681
try:
682682
postgres_task = PostgresTask.objects.get(mongo_id=task_id)
683-
683+
684684
deferred_details_data = {
685685
"task": postgres_task,
686686
"deferred_at": deferred_details.deferredAt,
687687
"deferred_till": deferred_details.deferredTill,
688688
"deferred_by": str(deferred_details.deferredBy),
689689
}
690-
691-
PostgresDeferredDetails.objects.update_or_create(
692-
task=postgres_task,
693-
defaults=deferred_details_data
694-
)
695-
690+
691+
PostgresDeferredDetails.objects.update_or_create(task=postgres_task, defaults=deferred_details_data)
692+
696693
except PostgresTask.DoesNotExist:
697694
pass
698695
except Exception as e:
699696
import logging
697+
700698
logger = logging.getLogger(__name__)
701699
logger.warning(f"Failed to sync deferred details to PostgreSQL for task {task_id}: {str(e)}")
702700

@@ -853,12 +851,13 @@ def get_tasks_for_user(
853851
def _remove_deferred_details_from_postgres(cls, task_id: str) -> None:
854852
try:
855853
postgres_task = PostgresTask.objects.get(mongo_id=task_id)
856-
854+
857855
PostgresDeferredDetails.objects.filter(task=postgres_task).delete()
858-
856+
859857
except PostgresTask.DoesNotExist:
860858
pass
861859
except Exception as e:
862860
import logging
861+
863862
logger = logging.getLogger(__name__)
864863
logger.warning(f"Failed to remove deferred details from PostgreSQL for task {task_id}: {str(e)}")

0 commit comments

Comments
 (0)