import-exercises-with-attributes.ts 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  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. function normalizeAttributeValue(value: string): ExerciseAttributeValueEnum {
  67. const cleaned = value.trim().toUpperCase();
  68. if (["N/A", "NA", "NONE", "NULL", ""].includes(cleaned)) return "NA";
  69. if ((Object.values(ExerciseAttributeValueEnum) as string[]).includes(cleaned)) {
  70. return cleaned as ExerciseAttributeValueEnum;
  71. }
  72. throw new Error(`Unknown attribute value: ${value}`);
  73. }
  74. async function ensureAttributeValueExists(attributeNameId: string, value: ExerciseAttributeValueEnum) {
  75. let attributeValue = await prisma.exerciseAttributeValue.findFirst({
  76. where: {
  77. attributeNameId,
  78. value,
  79. },
  80. });
  81. if (!attributeValue) {
  82. attributeValue = await prisma.exerciseAttributeValue.create({
  83. data: {
  84. attributeNameId,
  85. value,
  86. },
  87. });
  88. }
  89. return attributeValue;
  90. }
  91. async function importExercisesFromCSV(filePath: string) {
  92. const rows: ExerciseAttributeCSVRow[] = [];
  93. return new Promise<void>((resolve, reject) => {
  94. fs.createReadStream(filePath)
  95. .pipe(csv())
  96. .on("data", (row: ExerciseAttributeCSVRow) => {
  97. rows.push(row);
  98. })
  99. .on("end", async () => {
  100. console.log(`📋 ${rows.length} lines found in the CSV`);
  101. try {
  102. const exercises = groupExercisesByOriginalId(rows);
  103. console.log(`🏋️ ${exercises.length} unique exercises found`);
  104. let imported = 0;
  105. let errors = 0;
  106. for (const exercise of exercises) {
  107. try {
  108. console.log(`\n🔄 Processing "${exercise.name}"...`);
  109. // Create or update the exercise (simplified version)
  110. const createdExercise = await prisma.exercise.upsert({
  111. where: { slug: exercise.slug || `exercise-${exercise.originalId}` },
  112. update: {
  113. name: exercise.name,
  114. nameEn: exercise.nameEn,
  115. description: exercise.description,
  116. descriptionEn: exercise.descriptionEn,
  117. fullVideoUrl: exercise.fullVideoUrl,
  118. fullVideoImageUrl: exercise.fullVideoImageUrl,
  119. introduction: exercise.introduction,
  120. introductionEn: exercise.introductionEn,
  121. slugEn: exercise.slugEn,
  122. },
  123. create: {
  124. name: exercise.name,
  125. nameEn: exercise.nameEn,
  126. description: exercise.description,
  127. descriptionEn: exercise.descriptionEn,
  128. fullVideoUrl: exercise.fullVideoUrl,
  129. fullVideoImageUrl: exercise.fullVideoImageUrl,
  130. introduction: exercise.introduction,
  131. introductionEn: exercise.introductionEn,
  132. slug: exercise.slug || `exercise-${exercise.originalId}`,
  133. slugEn: exercise.slugEn,
  134. },
  135. });
  136. // Remove old attributes
  137. await prisma.exerciseAttribute.deleteMany({
  138. where: { exerciseId: createdExercise.id },
  139. });
  140. // Create new attributes
  141. for (const attr of exercise.attributes) {
  142. try {
  143. const attributeName = await ensureAttributeNameExists(attr.attributeName);
  144. const attributeValue = await ensureAttributeValueExists(attributeName.id, normalizeAttributeValue(attr.attributeValue));
  145. await prisma.exerciseAttribute.create({
  146. data: {
  147. exerciseId: createdExercise.id,
  148. attributeNameId: attributeName.id,
  149. attributeValueId: attributeValue.id,
  150. },
  151. });
  152. console.log(` ✅ Attribute: ${attr.attributeName} = ${attr.attributeValue}`);
  153. } catch (attrError) {
  154. console.error(" ❌ Attribute error:", attrError);
  155. }
  156. }
  157. console.log(`✅ "${exercise.name}" imported with ${exercise.attributes.length} attributes`);
  158. imported++;
  159. } catch (error) {
  160. console.error(`❌ Error for "${exercise.name}":`, error);
  161. errors++;
  162. }
  163. }
  164. console.log("\n📊 Summary:");
  165. console.log(` ✅ Imported: ${imported}`);
  166. console.log(` ❌ Errors: ${errors}`);
  167. resolve();
  168. } catch (error) {
  169. reject(error);
  170. }
  171. })
  172. .on("error", reject);
  173. });
  174. }
  175. async function main() {
  176. try {
  177. console.log("🚀 Import exercises (simplified version)...\n");
  178. const csvFilePath = process.argv[2];
  179. if (!csvFilePath) {
  180. console.error("❌ Please provide a CSV file path as argument");
  181. console.log("Usage: npm run import:exercises-full <path-to-csv-file>");
  182. process.exit(1);
  183. }
  184. if (!fs.existsSync(csvFilePath)) {
  185. console.error(`❌ File not found: ${csvFilePath}`);
  186. process.exit(1);
  187. }
  188. if (path.extname(csvFilePath).toLowerCase() !== ".csv") {
  189. console.error(`❌ File must be a CSV file, got: ${path.extname(csvFilePath)}`);
  190. process.exit(1);
  191. }
  192. console.log(`📁 Importing from: ${csvFilePath}`);
  193. await importExercisesFromCSV(csvFilePath);
  194. // Final stats
  195. const totalExercises = await prisma.exercise.count();
  196. const totalAttributes = await prisma.exerciseAttribute.count();
  197. console.log("\n📈 Final database:");
  198. console.log(` 🏋️ Exercises: ${totalExercises}`);
  199. console.log(` 🏷️ Attributes: ${totalAttributes}`);
  200. console.log("\n🎉 Import completed!");
  201. } catch (error) {
  202. console.error("💥 Error:", error);
  203. process.exit(1);
  204. } finally {
  205. await prisma.$disconnect();
  206. }
  207. }
  208. main();