import-exercises-with-attributes.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. import path from "path";
  2. import fs from "fs";
  3. import csv from "csv-parser";
  4. import { ExerciseAttributeNameEnum, ExerciseAttributeValueEnum, PrismaClient } from "@prisma/client";
  5. const prisma = new PrismaClient();
  6. interface ExerciseAttributeCSVRow {
  7. id: string;
  8. name: string;
  9. name_en: string;
  10. description: string;
  11. description_en: string;
  12. full_video_url: string;
  13. full_video_image_url: string;
  14. introduction: string;
  15. introduction_en: string;
  16. slug: string;
  17. slug_en: string;
  18. attribute_name: string;
  19. attribute_value: string;
  20. }
  21. function cleanValue(value: string): string | null {
  22. if (!value || value === "NULL" || value.trim() === "") return null;
  23. return value.trim();
  24. }
  25. function groupExercisesByOriginalId(rows: ExerciseAttributeCSVRow[]) {
  26. const exercisesMap = new Map();
  27. for (const row of rows) {
  28. const exerciseId = row.id;
  29. if (!exercisesMap.has(exerciseId)) {
  30. exercisesMap.set(exerciseId, {
  31. originalId: exerciseId,
  32. name: row.name,
  33. nameEn: cleanValue(row.name_en),
  34. description: cleanValue(row.description),
  35. descriptionEn: cleanValue(row.description_en),
  36. fullVideoUrl: cleanValue(row.full_video_url),
  37. fullVideoImageUrl: cleanValue(row.full_video_image_url),
  38. introduction: cleanValue(row.introduction),
  39. introductionEn: cleanValue(row.introduction_en),
  40. slug: cleanValue(row.slug),
  41. slugEn: cleanValue(row.slug_en),
  42. attributes: [],
  43. });
  44. }
  45. const exercise = exercisesMap.get(exerciseId);
  46. if (row.attribute_name && row.attribute_value) {
  47. exercise.attributes.push({
  48. attributeName: row.attribute_name,
  49. attributeValue: row.attribute_value,
  50. });
  51. }
  52. }
  53. return Array.from(exercisesMap.values());
  54. }
  55. async function ensureAttributeNameExists(name: ExerciseAttributeNameEnum) {
  56. let attributeName = await prisma.exerciseAttributeName.findFirst({
  57. where: { name },
  58. });
  59. if (!attributeName) {
  60. attributeName = await prisma.exerciseAttributeName.create({
  61. data: { name },
  62. });
  63. }
  64. return attributeName;
  65. }
  66. async function ensureAttributeValueExists(attributeNameId: string, value: ExerciseAttributeValueEnum) {
  67. let attributeValue = await prisma.exerciseAttributeValue.findFirst({
  68. where: {
  69. attributeNameId,
  70. value,
  71. },
  72. });
  73. if (!attributeValue) {
  74. attributeValue = await prisma.exerciseAttributeValue.create({
  75. data: {
  76. attributeNameId,
  77. value,
  78. },
  79. });
  80. }
  81. return attributeValue;
  82. }
  83. async function importExercisesFromCSV(filePath: string) {
  84. const rows: ExerciseAttributeCSVRow[] = [];
  85. return new Promise<void>((resolve, reject) => {
  86. fs.createReadStream(filePath)
  87. .pipe(csv())
  88. .on("data", (row: ExerciseAttributeCSVRow) => {
  89. rows.push(row);
  90. })
  91. .on("end", async () => {
  92. console.log(`📋 ${rows.length} lines found in the CSV`);
  93. try {
  94. const exercises = groupExercisesByOriginalId(rows);
  95. console.log(`🏋️ ${exercises.length} unique exercises found`);
  96. let imported = 0;
  97. let errors = 0;
  98. for (const exercise of exercises) {
  99. try {
  100. console.log(`\n🔄 Traitement de "${exercise.name}"...`);
  101. // Créer ou mettre à jour l'exercice (version simplifiée)
  102. const createdExercise = await prisma.exercise.upsert({
  103. where: { slug: exercise.slug || `exercise-${exercise.originalId}` },
  104. update: {
  105. name: exercise.name,
  106. nameEn: exercise.nameEn,
  107. description: exercise.description,
  108. descriptionEn: exercise.descriptionEn,
  109. fullVideoUrl: exercise.fullVideoUrl,
  110. fullVideoImageUrl: exercise.fullVideoImageUrl,
  111. introduction: exercise.introduction,
  112. introductionEn: exercise.introductionEn,
  113. slugEn: exercise.slugEn,
  114. },
  115. create: {
  116. name: exercise.name,
  117. nameEn: exercise.nameEn,
  118. description: exercise.description,
  119. descriptionEn: exercise.descriptionEn,
  120. fullVideoUrl: exercise.fullVideoUrl,
  121. fullVideoImageUrl: exercise.fullVideoImageUrl,
  122. introduction: exercise.introduction,
  123. introductionEn: exercise.introductionEn,
  124. slug: exercise.slug || `exercise-${exercise.originalId}`,
  125. slugEn: exercise.slugEn,
  126. },
  127. });
  128. // Supprimer les anciens attributs
  129. await prisma.exerciseAttribute.deleteMany({
  130. where: { exerciseId: createdExercise.id },
  131. });
  132. // Créer les nouveaux attributs
  133. for (const attr of exercise.attributes) {
  134. try {
  135. const attributeName = await ensureAttributeNameExists(attr.attributeName);
  136. const attributeValue = await ensureAttributeValueExists(attributeName.id, attr.attributeValue);
  137. await prisma.exerciseAttribute.create({
  138. data: {
  139. exerciseId: createdExercise.id,
  140. attributeNameId: attributeName.id,
  141. attributeValueId: attributeValue.id,
  142. },
  143. });
  144. console.log(` ✅ Attribute: ${attr.attributeName} = ${attr.attributeValue}`);
  145. } catch (attrError) {
  146. console.error(" ❌ Attribute error:", attrError);
  147. }
  148. }
  149. console.log(`✅ "${exercise.name}" imported with ${exercise.attributes.length} attributes`);
  150. imported++;
  151. } catch (error) {
  152. console.error(`❌ Error for "${exercise.name}":`, error);
  153. errors++;
  154. }
  155. }
  156. console.log("\n📊 Summary:");
  157. console.log(` ✅ Imported: ${imported}`);
  158. console.log(` ❌ Errors: ${errors}`);
  159. resolve();
  160. } catch (error) {
  161. reject(error);
  162. }
  163. })
  164. .on("error", reject);
  165. });
  166. }
  167. async function main() {
  168. try {
  169. console.log("🚀 Import exercises (simplified version)...\n");
  170. const csvFilePath = process.argv[2];
  171. if (!csvFilePath) {
  172. console.error("❌ Please provide a CSV file path as argument");
  173. console.log("Usage: npm run import:exercises-full <path-to-csv-file>");
  174. process.exit(1);
  175. }
  176. if (!fs.existsSync(csvFilePath)) {
  177. console.error(`❌ File not found: ${csvFilePath}`);
  178. process.exit(1);
  179. }
  180. if (path.extname(csvFilePath).toLowerCase() !== ".csv") {
  181. console.error(`❌ File must be a CSV file, got: ${path.extname(csvFilePath)}`);
  182. process.exit(1);
  183. }
  184. console.log(`📁 Importing from: ${csvFilePath}`);
  185. await importExercisesFromCSV(csvFilePath);
  186. // Stats finales
  187. const totalExercises = await prisma.exercise.count();
  188. const totalAttributes = await prisma.exerciseAttribute.count();
  189. console.log("\n📈 Final database:");
  190. console.log(` 🏋️ Exercises: ${totalExercises}`);
  191. console.log(` 🏷️ Attributes: ${totalAttributes}`);
  192. console.log("\n🎉 Import completed!");
  193. } catch (error) {
  194. console.error("💥 Error:", error);
  195. process.exit(1);
  196. } finally {
  197. await prisma.$disconnect();
  198. }
  199. }
  200. main();