-
-
Notifications
You must be signed in to change notification settings - Fork 50
Expand file tree
/
Copy pathclick.py
More file actions
67 lines (51 loc) · 2.06 KB
/
Copy pathclick.py
File metadata and controls
67 lines (51 loc) · 2.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
"""
Click document model.
Maps to the `clicks` MongoDB time-series collection.
Time-series schema:
timeField = "clicked_at"
metaField = "meta"
granularity = "hours"
The `meta` subdocument groups clicks by URL for efficient range queries.
owner_id always holds an ObjectId — anonymous clicks use ANONYMOUS_OWNER_ID
to avoid bucket churn from mixed None/ObjectId types.
"""
from __future__ import annotations
from datetime import datetime
from pydantic import BaseModel
from schemas.models.base import MongoBaseModel, PyObjectId
class ClickMeta(BaseModel):
"""The metaField subdocument for the time-series collection."""
url_id: PyObjectId
short_code: str
owner_id: PyObjectId # ANONYMOUS_OWNER_ID for unowned URLs
# Nullable because time-series buckets created before this field existed
# can't be backfilled — they keep their original shape forever.
domain: str | None = None
class ClickDoc(MongoBaseModel):
"""Document model for the `clicks` time-series collection."""
# Time-series timeField
clicked_at: datetime
# Time-series metaField
meta: ClickMeta
# Analytics fields
ip_address: str
country: str = "Unknown"
city: str = "Unknown"
browser: str
os: str
redirect_ms: int
referrer: str | None = None # sanitised referrer domain, nullable
bot_name: str | None = None # nullable
# Nullable like meta.domain: clicks recorded before these fields existed
# keep their original shape forever (time-series buckets can't be
# backfilled). device is "mobile" | "tablet" | "desktop" | "unknown".
device: str | None = None
# UTM tags captured from the short link's own query string, sanitised
# at event construction (services.click.events).
utm_source: str | None = None
utm_medium: str | None = None
utm_campaign: str | None = None
def to_mongo(self) -> dict:
# None must be absent, not null: null↔string type flips hard-close
# time-series buckets (schema-change rollover).
return self.model_dump(by_alias=True, exclude_none=True)