Skip to content

Commit 49387de

Browse files
committed
feat: migrate gift collection indexing from slug to integer ID
- Update GiftCollection model: replace slug PK with integer id, add options (JSONB) and blockchain_address fields - Update GiftUnique model: replace collection_slug FK with collection_id FK - Update TelegramChatGiftCollection model: replace collection_slug FK with collection_id FK - Create Alembic migration with schema changes, data migration from JSON mapping, and cleanup - Refactor DTOs, services, actions, and API routes to use integer IDs - Update chat eligibility logic (find_relevant_gift_items) to use collection_id - Temporarily disable gift indexer tasks and comment out slug-dependent indexer methods
1 parent 5f3ec4b commit 49387de

File tree

20 files changed

+632
-274
lines changed

20 files changed

+632
-274
lines changed

backend/api/pos/chat.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,15 +254,15 @@ def validate_category_or_collection(self) -> Self:
254254

255255

256256
class TelegramChatGiftRuleCPO(BaseTelegramChatQuantityRuleCPO):
257-
collection_slug: str | None
257+
collection_id: int | None
258258
model: str | None = None
259259
backdrop: str | None = None
260260
pattern: str | None = None
261261

262262
@model_validator(mode="after")
263263
def validate_category_or_collection(self) -> Self:
264-
if not self.category and not self.collection_slug:
265-
raise ValueError("At least category of collection must be specified")
264+
if not self.category and not self.collection_id:
265+
raise ValueError("At least category or collection must be specified")
266266

267267
return self
268268

backend/api/pos/gift.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ def from_dto(cls, dto: GiftCollectionsMetadataDTO) -> Self:
4040
class GiftFilterPO(GiftFilterDTO):
4141
@classmethod
4242
def from_query_string(cls, value: str) -> Self:
43-
return cls.model_validate_json(unquote(value))
43+
data = cls.model_validate_json(unquote(value))
44+
return data
4445

4546

4647
class GiftUniqueInfoFDO(BaseFDO):

backend/api/routes/admin/chat/rule/gift.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ async def add_chat_gift_rule(
3939
)
4040
new_rule = await action.create(
4141
group_id=rule.group_id,
42-
collection_slug=rule.collection_slug,
42+
collection_id=rule.collection_id,
4343
model=rule.model,
4444
backdrop=rule.backdrop,
4545
pattern=rule.pattern,
@@ -64,7 +64,7 @@ async def update_chat_gift_rule(
6464
)
6565
updated_rule = await action.update(
6666
rule_id=rule_id,
67-
collection_slug=rule.collection_slug,
67+
collection_id=rule.collection_id,
6868
model=rule.model,
6969
backdrop=rule.backdrop,
7070
pattern=rule.pattern,

backend/api/routes/gift.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ async def get_gifts_owners(
4747

4848

4949
@gift_router.get(
50-
"/{collection_slug}",
50+
"/{collection_id}",
5151
description="Returns an object of gift with their holders (telegram ID and blockchain address)",
5252
responses={
5353
HTTP_200_OK: {"model": GiftUniqueItemsFDO},
@@ -58,11 +58,11 @@ async def get_gifts_owners(
5858
},
5959
)
6060
async def get_collection_holders(
61-
collection_slug: str,
61+
collection_id: int,
6262
db_session: Session = Depends(get_db_session),
6363
) -> GiftUniqueItemsFDO:
6464
gift_unique_action = GiftUniqueAction(db_session=db_session)
65-
items = gift_unique_action.get_all(collection_slug=collection_slug)
65+
items = gift_unique_action.get_all(collection_id=collection_id)
6666
return GiftUniqueItemsFDO(
6767
items=[GiftUniqueInfoFDO.from_dto(item) for item in items]
6868
)

backend/core/src/core/actions/chat/rule/gift.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def check_duplicates(
3636
self,
3737
chat_id: int,
3838
group_id: int,
39-
collection_slug: str | None,
39+
collection_id: int | None,
4040
category: str | None,
4141
entity_id: int | None = None,
4242
) -> None:
@@ -47,7 +47,7 @@ def check_duplicates(
4747
4848
:param chat_id: The unique identifier for the chat where the rule applies.
4949
:param group_id: The unique identifier for the group where the rule applies.
50-
:param collection_slug: The slug identifying the collection; can be None if not applicable.
50+
:param collection_id: The id identifying the collection; can be None if not applicable.
5151
:param category: The category to which the rule applies; can be None if not applicable.
5252
:param entity_id: Optional identifier for the specific entity to exclude from duplicate checks.
5353
@@ -56,7 +56,7 @@ def check_duplicates(
5656
existing_rules = self.service.find(
5757
chat_id=chat_id,
5858
group_id=group_id,
59-
collection_slug=collection_slug,
59+
collection_id=collection_id,
6060
category=category,
6161
)
6262
if next(filter(lambda rule: rule.id != entity_id, existing_rules), None):
@@ -67,41 +67,41 @@ def check_duplicates(
6767

6868
def validate_params(
6969
self,
70-
collection_slug: str | None,
70+
collection_id: int | None,
7171
model: str | None,
7272
backdrop: str | None,
7373
pattern: str | None,
7474
) -> None:
75-
# If the collection slug is not set or attributes are not selected – no need to validate them
76-
if not collection_slug or not any((model, backdrop, pattern)):
75+
# If the collection id is not set or attributes are not selected – no need to validate them
76+
if not collection_id or not any((model, backdrop, pattern)):
7777
return
7878

79-
options = self.gift_unique_service.get_unique_options(
80-
collection_slug=collection_slug
81-
)
79+
# FIXME: Rewrite disabled for now since it needs refactoring
80+
# options = self.gift_unique_service.get_unique_options("...")
81+
options = {}
8282

8383
if model and model not in options.get("models", []):
8484
raise HTTPException(
8585
status_code=HTTP_400_BAD_REQUEST,
86-
detail=f"Model {model!r} is not available for the collection {collection_slug!r}.",
86+
detail=f"Model {model!r} is not available for the collection {collection_id!r}.",
8787
)
8888

8989
if backdrop and backdrop not in options.get("backdrops", []):
9090
raise HTTPException(
9191
status_code=HTTP_400_BAD_REQUEST,
92-
detail=f"Backdrop {backdrop!r} is not available for the collection {collection_slug!r}.",
92+
detail=f"Backdrop {backdrop!r} is not available for the collection {collection_id!r}.",
9393
)
9494

9595
if pattern and pattern not in options.get("patterns", []):
9696
raise HTTPException(
9797
status_code=HTTP_400_BAD_REQUEST,
98-
detail=f"Pattern {pattern!r} is not available for the collection {collection_slug!r}.",
98+
detail=f"Pattern {pattern!r} is not available for the collection {collection_id!r}.",
9999
)
100100

101101
async def create(
102102
self,
103103
group_id: int | None,
104-
collection_slug: str | None,
104+
collection_id: int | None,
105105
model: str | None,
106106
backdrop: str | None,
107107
pattern: str | None,
@@ -113,16 +113,16 @@ async def create(
113113
self.check_duplicates(
114114
chat_id=self.chat.id,
115115
group_id=group_id,
116-
collection_slug=collection_slug,
116+
collection_id=collection_id,
117117
category=category,
118118
)
119-
self.validate_params(collection_slug, model, backdrop, pattern)
119+
self.validate_params(collection_id, model, backdrop, pattern)
120120

121121
new_rule = self.service.create(
122122
CreateTelegramChatGiftCollectionRuleDTO(
123123
chat_id=self.chat.id,
124124
group_id=group_id,
125-
collection_slug=collection_slug,
125+
collection_id=collection_id,
126126
model=model,
127127
backdrop=backdrop,
128128
pattern=pattern,
@@ -139,7 +139,7 @@ async def create(
139139
async def update(
140140
self,
141141
rule_id: int,
142-
collection_slug: str | None,
142+
collection_id: int | None,
143143
category: str | None,
144144
model: str | None,
145145
backdrop: str | None,
@@ -155,16 +155,16 @@ async def update(
155155
self.check_duplicates(
156156
chat_id=self.chat.id,
157157
group_id=rule.group_id,
158-
collection_slug=collection_slug,
158+
collection_id=collection_id,
159159
category=category,
160160
entity_id=rule_id,
161161
)
162-
self.validate_params(collection_slug, model, backdrop, pattern)
162+
self.validate_params(collection_id, model, backdrop, pattern)
163163

164164
updated_rule = self.service.update(
165165
rule=rule,
166166
dto=UpdateTelegramChatGiftCollectionRuleDTO(
167-
collection_slug=collection_slug,
167+
collection_id=collection_id,
168168
category=category,
169169
threshold=threshold,
170170
is_enabled=is_enabled,

backend/core/src/core/actions/gift.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ def get_metadata(self) -> GiftCollectionsMetadataDTO:
4343
collections_with_options = []
4444

4545
for collection in all_collections:
46-
options = self.service.get_unique_options(collection_slug=collection.slug)
46+
options = collection.options
4747
collections_with_options.append(
4848
GiftCollectionMetadataDTO(
49-
slug=collection.slug,
49+
id=collection.id,
5050
title=collection.title,
5151
preview_url=collection.preview_url,
5252
supply=collection.supply,
@@ -89,7 +89,7 @@ def __construct_filter_options_query(
8989
for option in options:
9090
# Basic filtering logic (collection, model, backdrop, pattern)
9191
base_filter = and_(
92-
GiftUnique.collection_slug == option.collection,
92+
GiftUnique.collection_id == option.collection_id,
9393
GiftUnique.telegram_owner_id.isnot(None),
9494
*filter(
9595
None.__ne__,
@@ -144,18 +144,18 @@ def get_collections_holders(self, options: list[GiftFilterDTO]) -> Sequence[int]
144144
result = self.db_session.execute(query).scalars().all()
145145
return result
146146

147-
def get_all(self, collection_slug: str) -> Sequence[GiftUniqueDTO]:
147+
def get_all(self, collection_id: int) -> Sequence[GiftUniqueDTO]:
148148
"""
149149
Fetches all unique items in a given collection.
150150
"""
151151
try:
152-
self.collection_service.get(slug=collection_slug)
152+
self.collection_service.get(id=collection_id)
153153
except NoResultFound:
154154
raise HTTPException(
155155
status_code=HTTP_404_NOT_FOUND,
156-
detail=f"Collection {collection_slug!r} not found",
156+
detail=f"Collection {collection_id!r} not found",
157157
)
158158
return [
159159
GiftUniqueDTO.from_orm(gift)
160-
for gift in self.service.get_all(collection_slug=collection_slug)
160+
for gift in self.service.get_all(collection_id=collection_id)
161161
]

backend/core/src/core/dtos/chat/rule/gift.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,17 @@
1212
class BaseTelegramChatGiftCollectionRuleDTO(BaseModel):
1313
threshold: int
1414
is_enabled: bool
15-
collection_slug: str | None
15+
collection_id: int | None
1616
model: str | None
1717
backdrop: str | None
1818
pattern: str | None
1919
category: str | None
2020

2121
@model_validator(mode="after")
22-
def validate_slug_or_category(self) -> Self:
23-
if (self.category is None) == (self.collection_slug is None):
22+
def validate_id_or_category(self) -> Self:
23+
if (self.category is None) == (self.collection_id is None):
2424
raise ValueError(
25-
"Either category or collection_slug must be provided and not both."
25+
"Either category or collection_id must be provided and not both."
2626
)
2727

2828
return self
@@ -73,9 +73,8 @@ def promote_url(self) -> str | None:
7373
# FIXME: Turn on when market is released
7474
# if self.collection:
7575
# return PROMOTE_GIFT_COLLECTION_TEMPLATE.format(
76-
# collection_slug=self.collection.slug
76+
# collection_id=self.collection.id
7777
# )
78-
7978
return None
8079

8180
@classmethod

backend/core/src/core/dtos/gift/collection.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99

1010
class GiftCollectionDTO(BaseModel):
11-
slug: str
11+
id: int
1212
title: str
1313
preview_url: str | None
1414
supply: int
@@ -18,7 +18,7 @@ class GiftCollectionDTO(BaseModel):
1818
@classmethod
1919
def from_orm(cls, obj: GiftCollection) -> Self:
2020
return cls(
21-
slug=obj.slug,
21+
id=obj.id,
2222
title=obj.title,
2323
preview_url=obj.preview_url,
2424
supply=obj.supply,
@@ -27,9 +27,9 @@ def from_orm(cls, obj: GiftCollection) -> Self:
2727
)
2828

2929
@classmethod
30-
def from_telethon(cls, slug: str, obj: StarGiftUnique, preview_url: str) -> Self:
30+
def from_telethon(cls, id: int, obj: StarGiftUnique, preview_url: str) -> Self:
3131
return cls(
32-
slug=slug,
32+
id=id,
3333
title=obj.title,
3434
preview_url=preview_url,
3535
supply=obj.availability_total,
@@ -39,7 +39,7 @@ def from_telethon(cls, slug: str, obj: StarGiftUnique, preview_url: str) -> Self
3939

4040

4141
class GiftCollectionMetadataDTO(BaseModel):
42-
slug: str
42+
id: int
4343
title: str
4444
preview_url: str | None
4545
supply: int
@@ -54,7 +54,7 @@ class GiftCollectionsMetadataDTO(BaseModel):
5454

5555

5656
class GiftFilterDTO(BaseModel):
57-
collection: str
57+
collection_id: int
5858
model: str | None = None
5959
backdrop: str | None = None
6060
pattern: str | None = None
@@ -68,26 +68,28 @@ class GiftFiltersDTO(BaseModel):
6868
def validate_with_context(
6969
cls, objs: list[GiftFilterDTO], context: GiftCollectionsMetadataDTO
7070
) -> Self:
71-
context_by_slug = {
72-
collection.slug: collection for collection in context.collections
71+
context_by_id = {
72+
collection.id: collection for collection in context.collections
7373
}
7474
for obj in objs:
75-
if not (collection_metadata := context_by_slug.get(obj.collection)):
76-
raise ValueError(f"Collection {obj.collection} not found in metadata")
75+
if not (collection_metadata := context_by_id.get(obj.collection_id)):
76+
raise ValueError(
77+
f"Collection {obj.collection_id} not found in metadata"
78+
)
7779

7880
if obj.model and obj.model not in collection_metadata.models:
7981
raise ValueError(
80-
f"Model {obj.model} not found in collection {obj.collection}"
82+
f"Model {obj.model} not found in collection {obj.collection_id}"
8183
)
8284

8385
if obj.backdrop and obj.backdrop not in collection_metadata.backdrops:
8486
raise ValueError(
85-
f"Backdrop {obj.backdrop} not found in collection {obj.collection}"
87+
f"Backdrop {obj.backdrop} not found in collection {obj.collection_id}"
8688
)
8789

8890
if obj.pattern and obj.pattern not in collection_metadata.patterns:
8991
raise ValueError(
90-
f"Pattern {obj.pattern} not found in collection {obj.collection}"
92+
f"Pattern {obj.pattern} not found in collection {obj.collection_id}"
9193
)
9294

9395
return cls(filters=objs)

backend/core/src/core/dtos/gift/item.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
class GiftUniqueDTO(BaseModel):
1616
slug: str
17-
collection_slug: str
17+
collection_id: int
1818
telegram_owner_id: int | None
1919
number: int
2020
blockchain_address: str | None
@@ -28,7 +28,7 @@ class GiftUniqueDTO(BaseModel):
2828
def from_orm(cls, obj: GiftUnique) -> Self:
2929
return cls(
3030
slug=obj.slug,
31-
collection_slug=obj.collection_slug,
31+
collection_id=obj.collection_id,
3232
telegram_owner_id=obj.telegram_owner_id,
3333
number=obj.number,
3434
blockchain_address=obj.blockchain_address,
@@ -40,7 +40,7 @@ def from_orm(cls, obj: GiftUnique) -> Self:
4040
)
4141

4242
@classmethod
43-
def from_telethon(cls, collection_slug: str, obj: StarGiftUnique) -> Self:
43+
def from_telethon(cls, collection_id: int, obj: StarGiftUnique) -> Self:
4444
model_attribute = next(
4545
(
4646
attribute
@@ -73,7 +73,7 @@ def from_telethon(cls, collection_slug: str, obj: StarGiftUnique) -> Self:
7373

7474
return cls(
7575
slug=obj.slug,
76-
collection_slug=collection_slug,
76+
collection_id=collection_id,
7777
telegram_owner_id=getattr(obj.owner_id, "user_id", None),
7878
number=obj.num,
7979
blockchain_address=obj.gift_address,

0 commit comments

Comments
 (0)