hisham-cmd commited on
Commit
96ca8bc
·
verified ·
1 Parent(s): 639bd3a

Upload app/api/seed.py

Browse files
Files changed (1) hide show
  1. app/api/seed.py +145 -259
app/api/seed.py CHANGED
@@ -1,5 +1,8 @@
1
  from fastapi import APIRouter, Depends
2
  from sqlalchemy.orm import Session
 
 
 
3
 
4
  from app.db.base import get_db
5
  from app.models.product import Product, ProductImage, Category
@@ -10,222 +13,36 @@ from app.schemas.auth import AuthResponse
10
 
11
  router = APIRouter(prefix="/seed", tags=["Seed Data"])
12
 
13
-
14
- DEMO_CATEGORIES = [
15
- {"name_ar": "إلكترونيات", "name_en": "Electronics", "icon": "💻", "sort_order": 1},
16
- {"name_ar": "ملابس", "name_en": "Clothing", "icon": "👕", "sort_order": 2},
17
- {"name_ar": "أحذية", "name_en": "Shoes", "icon": "👟", "sort_order": 3},
18
- {"name_ar": "ساعات", "name_en": "Watches", "icon": "⌚", "sort_order": 4},
19
- {"name_ar": "حقائب", "name_en": "Bags", "icon": "👜", "sort_order": 5},
20
- {"name_ar": "إكسسوارات", "name_en": "Accessories", "icon": "💎", "sort_order": 6},
21
- ]
22
-
23
- DEMO_PRODUCTS = [
24
- {
25
- "name_ar": "سماعات لاسلكية برو",
26
- "name_en": "Wireless Pro Headphones",
27
- "description_ar": "سماعات رأس لاسلكية عالية الجودة مع إلغاء الضوضاء النشط وعمر بطارية يصل إلى 30 ساعة. صوت محيطي 360 درجة مع ميكروفون مدمج للمكالمات.",
28
- "description_en": "Premium wireless over-ear headphones with active noise cancellation and up to 30 hours battery life. Features 360° spatial audio and built-in microphone for crystal-clear calls.",
29
- "price": 299.99,
30
- "compare_price": 399.99,
31
- "stock": 50,
32
- "category_idx": 0,
33
- "rating": 4.8,
34
- "rating_count": 234,
35
- "is_featured": True,
36
- "images": [
37
- "https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=600",
38
- "https://images.unsplash.com/photo-1484704849700-f032a568e944?w=600",
39
- ],
40
- },
41
- {
42
- "name_ar": "ساعة ذكية ألترا",
43
- "name_en": "Ultra Smart Watch",
44
- "description_ar": "ساعة ذكية متقدمة مع شاشة AMOLED ومقاومة للماء حتى 50 متر. تتبع اللياقة البدنية ومراقبة معدل ضربات القلب والنوم.",
45
- "description_en": "Advanced smartwatch with AMOLED display and 50m water resistance. Comprehensive fitness tracking, heart rate monitoring, sleep analysis, and GPS navigation.",
46
- "price": 449.99,
47
- "compare_price": 599.99,
48
- "stock": 30,
49
- "category_idx": 3,
50
- "rating": 4.6,
51
- "rating_count": 156,
52
- "is_featured": True,
53
- "images": [
54
- "https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=600",
55
- "https://images.unsplash.com/photo-1546868871-af0de0ae72be?w=600",
56
- ],
57
- },
58
- {
59
- "name_ar": "حقيبة ظهر جلدية",
60
- "name_en": "Leather Backpack",
61
- "description_ar": "حقيبة ظهر من الجلد الطبيعي الفاخر مع جيوب متعددة وبطانة مقاومة للماء. مثالية للعمل والسفر.",
62
- "description_en": "Premium genuine leather backpack with multiple compartments and water-resistant lining. Perfect for work and travel with laptop sleeve up to 15 inches.",
63
- "price": 189.99,
64
- "compare_price": 249.99,
65
- "stock": 45,
66
- "category_idx": 4,
67
- "rating": 4.5,
68
- "rating_count": 89,
69
- "is_featured": True,
70
- "images": [
71
- "https://images.unsplash.com/photo-1553062407-98eeb64c6a62?w=600",
72
- "https://images.unsplash.com/photo-1622560480605-d83c853bc5c3?w=600",
73
- ],
74
- },
75
- {
76
- "name_ar": "حذاء رياضي إير ماكس",
77
- "name_en": "Air Max Running Shoes",
78
- "description_ar": "حذاء رياضي خفيف الوزن مع تقنية التهوية المتقدمة ونعل مرن يوفر أقصى درجات الراحة أثناء الجري.",
79
- "description_en": "Lightweight running shoes with advanced ventilation technology and flexible sole for maximum comfort during runs. Responsive cushioning and premium mesh upper.",
80
- "price": 159.99,
81
- "compare_price": 199.99,
82
- "stock": 80,
83
- "category_idx": 2,
84
- "rating": 4.7,
85
- "rating_count": 312,
86
- "is_featured": True,
87
- "images": [
88
- "https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=600",
89
- "https://images.unsplash.com/photo-1608231387042-66d1773070a5?w=600",
90
- ],
91
- },
92
- {
93
- "name_ar": "لابتوب ألتراسليم برو",
94
- "name_en": "UltraSlim Pro Laptop",
95
- "description_ar": "لابتوب احترافي بشاشة 14 بوصة 4K ومعالج من الجيل الأخير. خفيف الوزن 1.2 كغ مع بطارية تدوم 18 ساعة.",
96
- "description_en": "Professional laptop with 14-inch 4K display and latest-gen processor. Ultra-lightweight at 1.2kg with 18-hour battery life. 16GB RAM, 512GB SSD.",
97
- "price": 1299.99,
98
- "compare_price": 1599.99,
99
- "stock": 20,
100
- "category_idx": 0,
101
- "rating": 4.9,
102
- "rating_count": 178,
103
- "is_featured": True,
104
- "images": [
105
- "https://images.unsplash.com/photo-1496181133206-80ce9b88a853?w=600",
106
- "https://images.unsplash.com/photo-1525547719571-a2d4ac8945e2?w=600",
107
- ],
108
- },
109
- {
110
- "name_ar": "نظارات شمسية كلاسيك",
111
- "name_en": "Classic Sunglasses",
112
- "description_ar": "نظارات شمسية بتصميم كلاسيكي أنيق مع عدسات بولارايزد توفر حماية كاملة من أشعة UV. إطار معدني خفيف.",
113
- "description_en": "Classic design sunglasses with polarized lenses providing full UV protection. Lightweight metal frame with adjustable nose pads for perfect fit.",
114
- "price": 79.99,
115
- "compare_price": 129.99,
116
- "stock": 100,
117
- "category_idx": 5,
118
- "rating": 4.4,
119
- "rating_count": 67,
120
- "is_featured": False,
121
- "images": [
122
- "https://images.unsplash.com/photo-1572635196237-14b3f281503f?w=600",
123
- ],
124
- },
125
- {
126
- "name_ar": "قميص قطن بريميوم",
127
- "name_en": "Premium Cotton Shirt",
128
- "description_ar": "قميص من القطن المصري الفاخر بتصميم عصري مريح. مناسب للمناسبات الرسمية وغير الرسمية.",
129
- "description_en": "Premium Egyptian cotton shirt with modern comfortable design. Suitable for both formal and casual occasions. Breathable fabric with wrinkle-resistant finish.",
130
- "price": 89.99,
131
- "compare_price": None,
132
- "stock": 120,
133
- "category_idx": 1,
134
- "rating": 4.3,
135
- "rating_count": 45,
136
- "is_featured": False,
137
- "images": [
138
- "https://images.unsplash.com/photo-1602810318383-e386cc2a3ccf?w=600",
139
- ],
140
- },
141
- {
142
- "name_ar": "سماعات أذن لاسلكية",
143
- "name_en": "Wireless Earbuds",
144
- "description_ar": "سماعات أذن لاسلكية مع علبة شحن توفر 24 ساعة من الاستخدام. مقاومة للماء والعرق مع صوت عالي الدقة.",
145
- "description_en": "True wireless earbuds with charging case providing 24 hours of use. Water and sweat resistant (IPX5) with high-definition sound and active noise cancellation.",
146
- "price": 129.99,
147
- "compare_price": 179.99,
148
- "stock": 65,
149
- "category_idx": 0,
150
- "rating": 4.5,
151
- "rating_count": 198,
152
- "is_featured": False,
153
- "images": [
154
- "https://images.unsplash.com/photo-1590658268037-6bf12f032f55?w=600",
155
- ],
156
- },
157
- {
158
- "name_ar": "محفظة جلدية فاخرة",
159
- "name_en": "Luxury Leather Wallet",
160
- "description_ar": "محفظة من الجلد الإيطالي الفاخر مع حماية RFID. تصميم أنيق مع فتحات متعددة للبطاقات وجيب للعملات.",
161
- "description_en": "Premium Italian leather wallet with RFID protection. Elegant design with multiple card slots, bill compartment, and coin pocket. Gift box included.",
162
- "price": 69.99,
163
- "compare_price": 99.99,
164
- "stock": 90,
165
- "category_idx": 5,
166
- "rating": 4.6,
167
- "rating_count": 112,
168
- "is_featured": False,
169
- "images": [
170
- "https://images.unsplash.com/photo-1627123424574-724758594e93?w=600",
171
- ],
172
- },
173
- {
174
- "name_ar": "جاكيت شتوي مقاوم للماء",
175
- "name_en": "Waterproof Winter Jacket",
176
- "description_ar": "جاكيت شتوي دافئ ومقاوم للماء والرياح. حشوة داون طبيعية مع قلنسوة قابلة للإزالة وجيوب متعددة.",
177
- "description_en": "Warm waterproof and windproof winter jacket. Natural down insulation with removable hood and multiple pockets. Breathable membrane technology.",
178
- "price": 249.99,
179
- "compare_price": 349.99,
180
- "stock": 35,
181
- "category_idx": 1,
182
- "rating": 4.7,
183
- "rating_count": 76,
184
- "is_featured": False,
185
- "images": [
186
- "https://images.unsplash.com/photo-1544923246-77307dd270b1?w=600",
187
- ],
188
- },
189
- {
190
- "name_ar": "كاميرا ميرورليس احترافية",
191
- "name_en": "Professional Mirrorless Camera",
192
- "description_ar": "كاميرا ميرورليس احترافية بدقة 45 ميجابكسل وتصوير فيديو 8K. نظام تركيز تلقائي متقدم مع تثبيت صورة 5 محاور.",
193
- "description_en": "Professional mirrorless camera with 45MP sensor and 8K video recording. Advanced autofocus system with 5-axis image stabilization. Weather-sealed body.",
194
- "price": 2499.99,
195
- "compare_price": 2999.99,
196
- "stock": 10,
197
- "category_idx": 0,
198
- "rating": 4.9,
199
- "rating_count": 54,
200
- "is_featured": True,
201
- "images": [
202
- "https://images.unsplash.com/photo-1516035069371-29a1b244cc32?w=600",
203
- "https://images.unsplash.com/photo-1502920917128-1aa500764cbd?w=600",
204
- ],
205
- },
206
- {
207
- "name_ar": "حذاء رسمي جلدي",
208
- "name_en": "Formal Leather Shoes",
209
- "description_ar": "حذاء رسمي من الجلد الطبيعي المصقول يدوياً. نعل مطاطي مقاوم للانزلاق مع بطانة جلدية مريحة.",
210
- "description_en": "Hand-polished genuine leather formal shoes. Non-slip rubber sole with comfortable leather lining. Classic Oxford design with Goodyear welt construction.",
211
- "price": 199.99,
212
- "compare_price": None,
213
- "stock": 40,
214
- "category_idx": 2,
215
- "rating": 4.4,
216
- "rating_count": 38,
217
- "is_featured": False,
218
- "images": [
219
- "https://images.unsplash.com/photo-1614252369475-531eba835eb1?w=600",
220
- ],
221
- },
222
- ]
223
-
224
 
225
  @router.post("", response_model=AuthResponse)
226
  async def seed_database(db: Session = Depends(get_db)):
227
- """Populate database with demo categories, products, and an admin user."""
228
- # Always ensure admin user exists (even on re-seed)
229
  existing_admin = db.query(User).filter(User.email == "admin@vortex.com").first()
230
  if not existing_admin:
231
  admin_user = User(
@@ -247,54 +64,123 @@ async def seed_database(db: Session = Depends(get_db)):
247
  statusCode=200,
248
  )
249
 
250
- # Create categories
251
- categories = []
252
- for cat_data in DEMO_CATEGORIES:
253
- cat = Category(**cat_data)
254
- db.add(cat)
255
- categories.append(cat)
256
- db.flush()
257
-
258
- # Create products
259
- for prod_data in DEMO_PRODUCTS:
260
- images_data = prod_data.pop("images")
261
- category_idx = prod_data.pop("category_idx")
262
-
263
- product = Product(
264
- **prod_data,
265
- category_id=categories[category_idx].id,
266
  )
267
- db.add(product)
268
- db.flush()
269
-
270
- for i, img_url in enumerate(images_data):
271
- img = ProductImage(
272
- product_id=product.id,
273
- image_url=img_url,
274
- sort_order=i,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
  )
276
- db.add(img)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
 
278
- # Seed Coupons
279
- DEMO_COUPONS = [
280
- {"code": "WELCOME10", "discount_percent": 10.0, "is_active": True},
281
- {"code": "VORTEX20", "discount_percent": 20.0, "is_active": True},
282
- {"code": "SUMMER50", "discount_percent": 50.0, "is_active": False},
283
- ]
284
-
285
- for coupon_data in DEMO_COUPONS:
286
- coupon = Coupon(**coupon_data)
287
- db.add(coupon)
288
 
289
- db.commit()
 
 
 
 
 
 
 
 
 
290
 
291
- return AuthResponse(
292
- isSuccess=True,
293
- value={
294
- "message": "Database seeded successfully",
295
- "categories_count": len(DEMO_CATEGORIES),
296
- "products_count": len(DEMO_PRODUCTS),
297
- "coupons_count": len(DEMO_COUPONS),
298
- },
299
- statusCode=201,
300
- )
 
1
  from fastapi import APIRouter, Depends
2
  from sqlalchemy.orm import Session
3
+ import json
4
+ import random
5
+ import os
6
 
7
  from app.db.base import get_db
8
  from app.models.product import Product, ProductImage, Category
 
13
 
14
  router = APIRouter(prefix="/seed", tags=["Seed Data"])
15
 
16
+ # Mapping distinct categories from Excel to a hierarchical structure
17
+ CATEGORY_MAP = {
18
+ # Parents (Keys) : Subcategories (Values)
19
+ "الأزياء": ["الملابس الرجالية", "الملابس النسائية"],
20
+ "الأحذية": ["الأحذية"],
21
+ "الأطفال والألعاب": ["الأطفال"],
22
+ "المنزل والمطبخ": ["المنزل والمطبخ"],
23
+ "العطور والتجميل": ["العطور", "التجميل والعناية"],
24
+ "الأجهزة الإلكترونية": ["الهواتف والإلكترونيات"],
25
+ }
26
+
27
+ # Image placeholders by Parent Category
28
+ IMAGE_KEYWORDS = {
29
+ "الأزياء": "clothing,fashion,apparel",
30
+ "الأحذية": "shoes,sneakers",
31
+ "الأطفال والألعاب": "kids,toys,baby",
32
+ "المنزل والمطبخ": "kitchen,home,appliance",
33
+ "العطور والتجميل": "perfume,beauty,skincare",
34
+ "الأجهزة الإلكترونية": "electronics,smartphone,gadget",
35
+ }
36
+
37
+ def get_image_url(parent_category: str, image_index: int):
38
+ # Generating consistent but different images based on a seed
39
+ seed = random.randint(1000, 9999) + image_index
40
+ return f"https://picsum.photos/seed/{seed}/800/800"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
  @router.post("", response_model=AuthResponse)
43
  async def seed_database(db: Session = Depends(get_db)):
44
+ """Populate database with proper product data from json dump and an admin user."""
45
+ # Always ensure admin user exists
46
  existing_admin = db.query(User).filter(User.email == "admin@vortex.com").first()
47
  if not existing_admin:
48
  admin_user = User(
 
64
  statusCode=200,
65
  )
66
 
67
+ json_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "products_dump.json")
68
+ if not os.path.exists(json_path):
69
+ return AuthResponse(
70
+ isSuccess=False,
71
+ value={"message": f"Source file {json_path} not found."},
72
+ statusCode=404,
73
+ error="File not found"
 
 
 
 
 
 
 
 
 
74
  )
75
+
76
+ try:
77
+ with open(json_path, "r", encoding="utf-8") as f:
78
+ products_data = json.load(f)
79
+
80
+ # 1. Create Parent Categories
81
+ parent_cats = {}
82
+ for parent_name, sub_names in CATEGORY_MAP.items():
83
+ parent_cat = db.query(Category).filter(Category.name_ar == parent_name).first()
84
+ if not parent_cat:
85
+ parent_cat = Category(
86
+ name_ar=parent_name,
87
+ name_en=parent_name,
88
+ icon="grid",
89
+ )
90
+ db.add(parent_cat)
91
+ db.commit()
92
+ db.refresh(parent_cat)
93
+ parent_cats[parent_name] = parent_cat
94
+
95
+ # 2. Create Subcategories
96
+ sub_cats = {}
97
+ for parent_name, sub_names in CATEGORY_MAP.items():
98
+ for sub_name in sub_names:
99
+ sub_cat = db.query(Category).filter(Category.name_ar == sub_name).first()
100
+ if not sub_cat:
101
+ sub_cat = Category(
102
+ name_ar=sub_name,
103
+ name_en=sub_name,
104
+ icon="list",
105
+ parent_id=parent_cats[parent_name].id
106
+ )
107
+ db.add(sub_cat)
108
+ db.commit()
109
+ db.refresh(sub_cat)
110
+ sub_cats[sub_name] = {"model": sub_cat, "parent_name": parent_name}
111
+
112
+ # 3. Create Products
113
+ inserted_count = 0
114
+ for item in products_data:
115
+ cat_name = item.get("الفئة")
116
+ if not cat_name or cat_name not in sub_cats:
117
+ continue
118
+
119
+ sub_cat_info = sub_cats[cat_name]
120
+
121
+ price = float(item.get("سعر البيع (ريال)", 0))
122
+ compare_price = price * 1.2
123
+
124
+ prod = Product(
125
+ name_ar=item.get("اسم المنتج", ""),
126
+ name_en=item.get("اسم المنتج", ""),
127
+ description_ar=item.get("الوصف التسويقي", ""),
128
+ description_en=item.get("الوصف التسويقي", ""),
129
+ price=price,
130
+ compare_price=round(compare_price, 2),
131
+ stock=random.randint(10, 100),
132
+ category_id=sub_cat_info["model"].id,
133
+ rating=round(random.uniform(3.5, 5.0), 1),
134
+ rating_count=random.randint(0, 50),
135
+ is_featured=random.choice([True, False]),
136
+ is_active=True
137
  )
138
+ db.add(prod)
139
+ db.flush()
140
+
141
+ # 4. Create multiple images
142
+ num_images = random.randint(2, 4)
143
+ for i in range(num_images):
144
+ img_url = get_image_url(sub_cat_info["parent_name"], i)
145
+ prod_img = ProductImage(
146
+ product_id=prod.id,
147
+ image_url=img_url,
148
+ alt_text=prod.name_ar,
149
+ sort_order=i
150
+ )
151
+ db.add(prod_img)
152
+
153
+ inserted_count += 1
154
+
155
+ # Seed Coupons
156
+ DEMO_COUPONS = [
157
+ {"code": "WELCOME10", "discount_percent": 10.0, "is_active": True},
158
+ {"code": "VORTEX20", "discount_percent": 20.0, "is_active": True},
159
+ {"code": "SUMMER50", "discount_percent": 50.0, "is_active": False},
160
+ ]
161
+
162
+ for coupon_data in DEMO_COUPONS:
163
+ coupon = Coupon(**coupon_data)
164
+ db.add(coupon)
165
 
166
+ db.commit()
 
 
 
 
 
 
 
 
 
167
 
168
+ return AuthResponse(
169
+ isSuccess=True,
170
+ value={
171
+ "message": "Database seeded successfully",
172
+ "categories_count": db.query(Category).count(),
173
+ "products_count": inserted_count,
174
+ "coupons_count": len(DEMO_COUPONS),
175
+ },
176
+ statusCode=201,
177
+ )
178
 
179
+ except Exception as e:
180
+ db.rollback()
181
+ return AuthResponse(
182
+ isSuccess=False,
183
+ value={"message": str(e)},
184
+ statusCode=500,
185
+ error=str(e)
186
+ )