ibrohm commited on
Commit
9b5a86f
Β·
verified Β·
1 Parent(s): 8674650

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. database/db.py +287 -0
database/db.py ADDED
@@ -0,0 +1,287 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ from datetime import datetime
4
+ from motor.motor_asyncio import AsyncIOMotorClient
5
+ from config import MONGO_URI, PRODUCTS
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ # MongoDB Client
10
+ client = AsyncIOMotorClient(MONGO_URI)
11
+ db = client.get_database("telegram_bot")
12
+
13
+
14
+ async def init_db():
15
+ """MongoDB ulanishini tekshirish va dastlabki sozlashlarni bajarish."""
16
+ try:
17
+ # Indexlarni yaratish
18
+ await db.users.create_index("id", unique=True)
19
+ await db.products.create_index("id", unique=True)
20
+ await db.gift_codes.create_index("id", unique=True)
21
+ await db.gift_codes.create_index("gift_link", unique=True)
22
+ await db.orders.create_index("id", unique=True)
23
+
24
+ # Standart mahsulotlarni qo'shish (agar mavjud bo'lmasa)
25
+ for p in PRODUCTS:
26
+ await db.products.update_one(
27
+ {"id": p["id"]},
28
+ {"$setOnInsert": {
29
+ "id": p["id"],
30
+ "name": p["name"],
31
+ "description": p["description"],
32
+ "duration_months": p["duration_months"],
33
+ "stars_price": p["stars_price"],
34
+ "emoji": p["emoji"],
35
+ "is_active": 1
36
+ }},
37
+ upsert=True
38
+ )
39
+ logger.info("βœ… MongoDB indekslari va boshlang'ich mahsulotlar tayyor.")
40
+ except Exception as e:
41
+ logger.error(f"❌ MongoDB init_db xatosi: {e}", exc_info=True)
42
+
43
+
44
+ async def get_next_sequence_value(sequence_name: str) -> int:
45
+ """Auto-increment integer ID yaratish (SQLite o'rniga ishlatiladi)."""
46
+ result = await db.counters.find_one_and_update(
47
+ {"_id": sequence_name},
48
+ {"$inc": {"sequence_value": 1}},
49
+ upsert=True,
50
+ return_document=True
51
+ )
52
+ return result["sequence_value"]
53
+
54
+
55
+ # ─────────────────────── USERS ───────────────────────
56
+
57
+ async def upsert_user(user_id: int, username: str | None, full_name: str):
58
+ """Foydalanuvchini qo'shish yoki yangilash."""
59
+ await db.users.update_one(
60
+ {"id": user_id},
61
+ {"$set": {
62
+ "username": username,
63
+ "full_name": full_name,
64
+ "created_at": datetime.now().isoformat()
65
+ }},
66
+ upsert=True
67
+ )
68
+
69
+
70
+ async def get_all_user_ids() -> list[int]:
71
+ """Barcha foydalanuvchilar ID larini olish (broadcast uchun)."""
72
+ cursor = db.users.find({}, {"id": 1})
73
+ rows = await cursor.to_list(length=None)
74
+ return [row["id"] for row in rows]
75
+
76
+
77
+ async def count_users() -> int:
78
+ """Foydalanuvchilar sonini hisoblash."""
79
+ return await db.users.count_documents({})
80
+
81
+
82
+ # ─────────────────────── PRODUCTS ───────────────────────
83
+
84
+ async def get_active_products() -> list[dict]:
85
+ """Faol mahsulotlar ro'yxatini olish."""
86
+ cursor = db.products.find({"is_active": 1}).sort("duration_months", 1)
87
+ rows = await cursor.to_list(length=None)
88
+ return rows
89
+
90
+
91
+ async def get_product_by_id(product_id: int) -> dict | None:
92
+ """ID bo'yicha mahsulotni olish."""
93
+ return await db.products.find_one({"id": product_id})
94
+
95
+
96
+ async def update_product_price(product_id: int, new_price: int):
97
+ """Mahsulot narxini yangilash."""
98
+ await db.products.update_one(
99
+ {"id": product_id},
100
+ {"$set": {"stars_price": new_price}}
101
+ )
102
+
103
+
104
+ # ─────────────────────── GIFT CODES ───────────────────────
105
+
106
+ async def add_gift_code(product_id: int, gift_link: str) -> bool:
107
+ """Yangi sovg'a linkini zaxiraga qo'shish. Agar mavjud bo'lsa False qaytaradi."""
108
+ try:
109
+ # O'xshash link borligini tekshiramiz (unique constraint)
110
+ exists = await db.gift_codes.find_one({"gift_link": gift_link})
111
+ if exists:
112
+ return False
113
+
114
+ new_id = await get_next_sequence_value("gift_code_id")
115
+ await db.gift_codes.insert_one({
116
+ "id": new_id,
117
+ "product_id": product_id,
118
+ "gift_link": gift_link,
119
+ "is_used": 0,
120
+ "used_by": None,
121
+ "order_id": None,
122
+ "added_at": datetime.now().isoformat(),
123
+ "used_at": None
124
+ })
125
+ return True
126
+ except Exception:
127
+ return False
128
+
129
+
130
+ async def get_available_code(product_id: int) -> dict | None:
131
+ """Berilgan mahsulot uchun birinchi ishlatilmagan linkni olish."""
132
+ return await db.gift_codes.find_one({
133
+ "product_id": product_id,
134
+ "is_used": 0
135
+ }, sort=[("id", 1)])
136
+
137
+
138
+ async def mark_code_used(code_id: int, user_id: int, order_id: int):
139
+ """Linkni ishlatilgan deb belgilash."""
140
+ await db.gift_codes.update_one(
141
+ {"id": code_id},
142
+ {"$set": {
143
+ "is_used": 1,
144
+ "used_by": user_id,
145
+ "order_id": order_id,
146
+ "used_at": datetime.now().isoformat()
147
+ }}
148
+ )
149
+
150
+
151
+ async def count_available_codes(product_id: int) -> int:
152
+ """Mahsulot uchun mavjud linklarni sanash."""
153
+ return await db.gift_codes.count_documents({
154
+ "product_id": product_id,
155
+ "is_used": 0
156
+ })
157
+
158
+
159
+ async def get_stock_summary() -> list[dict]:
160
+ """Har bir mahsulot uchun zaxira holati."""
161
+ products = await get_active_products()
162
+ summary = []
163
+ for p in products:
164
+ available = await db.gift_codes.count_documents({"product_id": p["id"], "is_used": 0})
165
+ used = await db.gift_codes.count_documents({"product_id": p["id"], "is_used": 1})
166
+ total = available + used
167
+ summary.append({
168
+ "name": p["name"],
169
+ "emoji": p.get("emoji", "⭐"),
170
+ "stars_price": p["stars_price"],
171
+ "available": available,
172
+ "used": used,
173
+ "total": total
174
+ })
175
+ return summary
176
+
177
+
178
+ # ─────────────────────── ORDERS ───────────────────────
179
+
180
+ async def create_order(user_id: int, product_id: int, stars_amount: int) -> int:
181
+ """Yangi buyurtma yaratish va uning ID sini qaytarish."""
182
+ new_id = await get_next_sequence_value("order_id")
183
+ await db.orders.insert_one({
184
+ "id": new_id,
185
+ "user_id": user_id,
186
+ "product_id": product_id,
187
+ "gift_code_id": None,
188
+ "status": "pending",
189
+ "charge_id": None,
190
+ "stars_amount": stars_amount,
191
+ "created_at": datetime.now().isoformat(),
192
+ "completed_at": None
193
+ })
194
+ return new_id
195
+
196
+
197
+ async def complete_order(order_id: int, gift_code_id: int, charge_id: str):
198
+ """Buyurtmani yakunlash."""
199
+ await db.orders.update_one(
200
+ {"id": order_id},
201
+ {"$set": {
202
+ "status": "completed",
203
+ "gift_code_id": gift_code_id,
204
+ "charge_id": charge_id,
205
+ "completed_at": datetime.now().isoformat()
206
+ }}
207
+ )
208
+
209
+
210
+ async def fail_order(order_id: int, charge_id: str):
211
+ """Buyurtmani (zaxira yo'qligi sababli) pending_delivery holatiga o'tkazish."""
212
+ await db.orders.update_one(
213
+ {"id": order_id},
214
+ {"$set": {
215
+ "status": "pending_delivery",
216
+ "charge_id": charge_id
217
+ }}
218
+ )
219
+
220
+
221
+ async def get_user_orders(user_id: int) -> list[dict]:
222
+ """Foydalanuvchi buyurtmalari tarixi (oxirgi 10 ta)."""
223
+ cursor = db.orders.find({"user_id": user_id}).sort("created_at", -1).limit(10)
224
+ orders = await cursor.to_list(length=None)
225
+
226
+ result = []
227
+ for o in orders:
228
+ product = await db.products.find_one({"id": o["product_id"]})
229
+ gift_link = None
230
+ if o.get("gift_code_id"):
231
+ code = await db.gift_codes.find_one({"id": o["gift_code_id"]})
232
+ if code:
233
+ gift_link = code["gift_link"]
234
+
235
+ result.append({
236
+ "id": o["id"],
237
+ "status": o["status"],
238
+ "stars_amount": o.get("stars_amount"),
239
+ "created_at": o["created_at"],
240
+ "product_name": product["name"] if product else "Noma'lum",
241
+ "gift_link": gift_link
242
+ })
243
+ return result
244
+
245
+
246
+ async def get_total_stats() -> dict:
247
+ """Umumiy statistika."""
248
+ total_completed = await db.orders.count_documents({"status": "completed"})
249
+
250
+ # Stars summasini hisoblash
251
+ pipeline = [
252
+ {"$match": {"status": "completed"}},
253
+ {"$group": {"_id": None, "total": {"$sum": "$stars_amount"}}}
254
+ ]
255
+ cursor = db.orders.aggregate(pipeline)
256
+ agg_result = await cursor.to_list(length=1)
257
+ total_stars = agg_result[0]["total"] if agg_result else 0
258
+
259
+ pending_delivery = await db.orders.count_documents({"status": "pending_delivery"})
260
+
261
+ return {
262
+ "total_completed": total_completed,
263
+ "total_stars": total_stars,
264
+ "pending_delivery": pending_delivery
265
+ }
266
+
267
+
268
+ async def get_pending_delivery_orders() -> list[dict]:
269
+ """Zaxira bo'lmaganligi sababli kutilayotgan buyurtmalar."""
270
+ cursor = db.orders.find({"status": "pending_delivery"}).sort("created_at", 1)
271
+ orders = await cursor.to_list(length=None)
272
+
273
+ result = []
274
+ for o in orders:
275
+ product = await db.products.find_one({"id": o["product_id"]})
276
+ user = await db.users.find_one({"id": o["user_id"]})
277
+ result.append({
278
+ "id": o["id"],
279
+ "user_id": o["user_id"],
280
+ "stars_amount": o.get("stars_amount"),
281
+ "created_at": o["created_at"],
282
+ "product_name": product["name"] if product else "Noma'lum",
283
+ "product_id": o["product_id"],
284
+ "username": user.get("username") if user else None,
285
+ "full_name": user.get("full_name") if user else "Noma'lum"
286
+ })
287
+ return result