UNPKG

@rep2recall/r2r-mongo

Version:
1,070 lines (1,069 loc) 38.3 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); const spark_md5_1 = __importDefault(require("spark-md5")); const quiz_1 = require("./quiz"); const q2filter_1 = __importDefault(require("q2filter")); const util_1 = require("./util"); const fast_json_stable_stringify_1 = __importDefault(require("fast-json-stable-stringify")); const typegoose_1 = require("@hasezoey/typegoose"); const mongoose_1 = __importDefault(require("mongoose")); const moment_1 = __importDefault(require("moment")); const bson_1 = require("bson"); const r2r_format_1 = require("@rep2recall/r2r-format"); let User = class User extends typegoose_1.Typegoose { }; __decorate([ typegoose_1.prop({ required: true, unique: true }), __metadata("design:type", String) ], User.prototype, "email", void 0); __decorate([ typegoose_1.prop(), __metadata("design:type", String) ], User.prototype, "picture", void 0); __decorate([ typegoose_1.prop({ required: true }), __metadata("design:type", String) ], User.prototype, "secret", void 0); User = __decorate([ typegoose_1.pre("save", async function () { if (!this.secret) { this.secret = await util_1.generateSecret(); } }) ], User); const UserModel = new User().getModelForClass(User); let Deck = class Deck extends typegoose_1.Typegoose { }; __decorate([ typegoose_1.prop({ required: true }), __metadata("design:type", String) ], Deck.prototype, "name", void 0); __decorate([ typegoose_1.prop({ ref: User, required: true }), __metadata("design:type", Object) ], Deck.prototype, "user", void 0); Deck = __decorate([ typegoose_1.index({ name: 1, user: 1 }, { unique: true }) ], Deck); const DeckModel = new Deck().getModelForClass(Deck); let Source = class Source extends typegoose_1.Typegoose { }; __decorate([ typegoose_1.prop({ required: true }), __metadata("design:type", String) ], Source.prototype, "h", void 0); __decorate([ typegoose_1.prop({ required: true }), __metadata("design:type", String) ], Source.prototype, "name", void 0); __decorate([ typegoose_1.prop({ required: true, default: new Date() }), __metadata("design:type", Date) ], Source.prototype, "created", void 0); __decorate([ typegoose_1.prop({ ref: User, required: true }), __metadata("design:type", Object) ], Source.prototype, "user", void 0); Source = __decorate([ typegoose_1.index({ h: 1, user: 1 }, { unique: true }) ], Source); const SourceModel = new Source().getModelForClass(Source); let Template = class Template extends typegoose_1.Typegoose { }; __decorate([ typegoose_1.prop({ required: true }), __metadata("design:type", String) ], Template.prototype, "name", void 0); __decorate([ typegoose_1.prop({ ref: Source }), __metadata("design:type", Object) ], Template.prototype, "source", void 0); __decorate([ typegoose_1.prop({ required: true }), __metadata("design:type", String) ], Template.prototype, "key", void 0); __decorate([ typegoose_1.prop({ required: true }), __metadata("design:type", String) ], Template.prototype, "front", void 0); __decorate([ typegoose_1.prop(), __metadata("design:type", String) ], Template.prototype, "back", void 0); __decorate([ typegoose_1.prop(), __metadata("design:type", String) ], Template.prototype, "css", void 0); __decorate([ typegoose_1.prop(), __metadata("design:type", String) ], Template.prototype, "js", void 0); __decorate([ typegoose_1.prop({ ref: User, required: true }), __metadata("design:type", Object) ], Template.prototype, "user", void 0); Template = __decorate([ typegoose_1.index({ key: 1, user: 1 }, { unique: true }), typegoose_1.pre("save", async function () { if (!this.key) { const { front, back, css, js } = this; this.key = spark_md5_1.default.hash(fast_json_stable_stringify_1.default({ front, back, css, js })); } }) ], Template); const TemplateModel = new Template().getModelForClass(Template); let Note = class Note extends typegoose_1.Typegoose { }; __decorate([ typegoose_1.prop({ required: true }), __metadata("design:type", String) ], Note.prototype, "key", void 0); __decorate([ typegoose_1.prop({ required: true }), __metadata("design:type", String) ], Note.prototype, "name", void 0); __decorate([ typegoose_1.prop({ ref: Source }), __metadata("design:type", Object) ], Note.prototype, "source", void 0); __decorate([ typegoose_1.prop({ required: true }), __metadata("design:type", Object) ], Note.prototype, "data", void 0); __decorate([ typegoose_1.prop({ required: true }), __metadata("design:type", Object) ], Note.prototype, "order", void 0); __decorate([ typegoose_1.prop({ ref: User, required: true }), __metadata("design:type", Object) ], Note.prototype, "user", void 0); Note = __decorate([ typegoose_1.index({ key: 1, user: 1 }, { unique: true }), typegoose_1.pre("save", function () { if (!this.key) { this.key = spark_md5_1.default.hash(fast_json_stable_stringify_1.default(this.data)); } }) ], Note); const NoteModel = new Note().getModelForClass(Note); let Media = class Media extends typegoose_1.Typegoose { }; __decorate([ typegoose_1.prop({ required: true }), __metadata("design:type", String) ], Media.prototype, "h", void 0); __decorate([ typegoose_1.prop({ ref: Source }), __metadata("design:type", Object) ], Media.prototype, "source", void 0); __decorate([ typegoose_1.prop({ required: true }), __metadata("design:type", String) ], Media.prototype, "name", void 0); __decorate([ typegoose_1.prop({ required: true }), __metadata("design:type", ArrayBuffer) ], Media.prototype, "data", void 0); __decorate([ typegoose_1.prop({ ref: User, required: true }), __metadata("design:type", Object) ], Media.prototype, "user", void 0); Media = __decorate([ typegoose_1.index({ h: 1, user: 1 }, { unique: true }), typegoose_1.pre("save", function () { if (!this.h) { this.h = spark_md5_1.default.ArrayBuffer.hash(this.data); } }) ], Media); const MediaModel = new Media().getModelForClass(Media); let Card = class Card extends typegoose_1.Typegoose { }; __decorate([ typegoose_1.prop({ ref: Deck, required: true }), __metadata("design:type", Object) ], Card.prototype, "deck", void 0); __decorate([ typegoose_1.prop({ ref: Template }), __metadata("design:type", Object) ], Card.prototype, "template", void 0); __decorate([ typegoose_1.prop({ ref: Note }), __metadata("design:type", Object) ], Card.prototype, "note", void 0); __decorate([ typegoose_1.prop({ required: true }), __metadata("design:type", String) ], Card.prototype, "front", void 0); __decorate([ typegoose_1.prop(), __metadata("design:type", String) ], Card.prototype, "back", void 0); __decorate([ typegoose_1.prop(), __metadata("design:type", String) ], Card.prototype, "mnemonic", void 0); __decorate([ typegoose_1.prop(), __metadata("design:type", Number) ], Card.prototype, "srsLevel", void 0); __decorate([ typegoose_1.prop(), __metadata("design:type", Date) ], Card.prototype, "nextReview", void 0); __decorate([ typegoose_1.prop({ default: [] }), __metadata("design:type", Array) ], Card.prototype, "tag", void 0); __decorate([ typegoose_1.prop({ default: new Date() }), __metadata("design:type", Date) ], Card.prototype, "created", void 0); __decorate([ typegoose_1.prop(), __metadata("design:type", Date) ], Card.prototype, "modified", void 0); __decorate([ typegoose_1.prop(), __metadata("design:type", Object) ], Card.prototype, "stat", void 0); __decorate([ typegoose_1.prop({ ref: User, required: true }), __metadata("design:type", Object) ], Card.prototype, "user", void 0); Card = __decorate([ typegoose_1.pre("save", function () { this.modified = new Date(); }) ], Card); const CardModel = new Card().getModelForClass(Card); class R2rMongo extends r2r_format_1.R2rOnline { constructor(mongoUri) { super(); this.mongoUri = mongoUri; } async build() { mongoose_1.default.set('useCreateIndex', true); await mongoose_1.default.connect(this.mongoUri, { useNewUrlParser: true, useUnifiedTopology: true }); return this; } async signup(email, password, options = {}) { const u = await UserModel.findOne({ email }); if (u) { this.user = u; return u.secret; } else { const secret = await util_1.generateSecret(); this.user = await UserModel.create({ email, secret, ...options }); return secret; } } async getSecret() { return this.user ? this.user.secret : null; } async newSecret() { if (this.user) { const secret = await util_1.generateSecret(); this.user.secret = secret; await this.user.save(); return secret; } return null; } async parseSecret(secret) { const u = await UserModel.findOne({ secret }); if (u) { this.user = u; return true; } return false; } async login(email, secret) { const u = await UserModel.findOne({ email, secret }); if (u) { this.user = u; return true; } return false; } async logout() { this.user = undefined; return true; } async close() { await mongoose_1.default.disconnect(); return this; } async reset() { if (this.user) { await Promise.all([ SourceModel.deleteMany({ user: this.user }), MediaModel.deleteMany({ user: this.user }), TemplateModel.deleteMany({ user: this.user }), NoteModel.deleteMany({ user: this.user }), CardModel.deleteMany({ user: this.user }) ]); await this.user.remove(); await this.logout(); } else { await Promise.all([ SourceModel.deleteMany({ user: { $exists: false } }), MediaModel.deleteMany({ user: { $exists: false } }), TemplateModel.deleteMany({ user: { $exists: false } }), NoteModel.deleteMany({ user: { $exists: false } }), CardModel.deleteMany({ user: { $exists: false } }) ]); } return this; } async parseCond(q, options = {}) { if (options.sortBy === "random") { q += " is:random"; delete options.sortBy; } const parser = new q2filter_1.default(q, { anyOf: new Set(["template", "front", "mnemonic", "deck", "tag"]), isString: new Set(["template", "front", "back", "mnemonic", "deck", "tag"]), isDate: new Set(["created", "modified", "nextReview"]), transforms: { "is:due": () => { return { nextReview: { $lt: new Date() } }; } }, noParse: new Set(["is:distinct", "is:duplicate", "is:random"]), sortBy: { key: options.sortBy || "created", desc: options.desc || true } }); const fullCond = parser.getCondFull(); if (!options.fields) { return { data: [], count: 0 }; } else if (options.fields === "*") { options.fields = ["data", "source", "deck", "front", "js", "mnemonic", "modified", "nextReview", "sCreated", "sH", "srsLevel", "stat", "tBack", "tFront", "tag", "template", "back", "created", "css", "_id"]; } const allFields = new Set(options.fields || []); for (const f of (fullCond.fields || [])) { allFields.add(f); } if (q.includes("is:distinct") || q.includes("is:duplicate")) { allFields.add("key"); } const proj = {}; if (["data", "order"].some((k) => allFields.has(k))) { proj.note = 1; } if (["deck"].some((k) => allFields.has(k))) { proj.deck = 1; } if (["sCreated", "sH", "source"].some((k) => allFields.has(k))) { proj.source = 1; } if (["tFront", "tBack", "template", "model", "css", "js"].some((k) => allFields.has(k))) { proj.template = 1; } for (const f of allFields) { proj[f] = 1; } const outputProj = { id: 1 }; for (const f of options.fields) { proj[f] = 1; outputProj[f] = 1; } let aggArray = [ { $match: { user: this.user._id } }, { $project: proj } ]; if (["data", "order", "key"].some((k) => allFields.has(k))) { aggArray.push({ $lookup: { from: "notes", localField: "note", foreignField: "_id", as: "n" } }, { $unwind: { path: "$n", preserveNullAndEmptyArrays: true } }, { $project: { ...outputProj, data: "$n.data", order: "$n.order", key: "$n.key" } }); } if (["deck"].some((k) => allFields.has(k))) { aggArray.push({ $lookup: { from: "decks", localField: "deck", foreignField: "_id", as: "d" } }, { $unwind: { path: "$d", preserveNullAndEmptyArrays: true } }, { $project: { ...outputProj, deck: "$d.name" } }); } if (["sCreated", "sH", "source"].some((k) => allFields.has(k))) { aggArray.push({ $lookup: { from: "sources", localField: "source", foreignField: "_id", as: "s" } }, { $unwind: { path: "$s", preserveNullAndEmptyArrays: true } }, { $project: { ...outputProj, source: "$s.name", sH: "$s.h", sCreated: "$s.created" } }); } if (["tFront", "tBack", "template", "css", "js"].some((k) => allFields.has(k))) { aggArray.push({ $lookup: { from: "templates", localField: "template", foreignField: "_id", as: "t" } }, { $unwind: { path: "$t", preserveNullAndEmptyArrays: true } }, { $project: { ...outputProj, tFront: "$t.front", tBack: "$t.back", template: "$t.name", css: "$t.css", js: "$t.js" } }); } aggArray.push({ $match: fullCond.cond }); const getGroupStmt = (k0) => { return { $group: { _id: `$${k0}`, repeat: { $sum: 1 }, data: { $addToSet: (() => { const newProj = {}; for (const k of Object.keys(outputProj)) { newProj[k] = `$${k}`; } return newProj; })() } } }; }; const projStmt = (() => { const newProj = {}; for (const k of Object.keys(outputProj)) { newProj[k] = `$data.${k}`; } return newProj; })(); if (fullCond.noParse.has("is:distinct")) { aggArray.push(getGroupStmt("key"), { $project: { data: { $arrayElemAt: ["$data", 0] } } }, { $project: projStmt }); } if (fullCond.noParse.has("is:duplicate")) { aggArray.push(getGroupStmt("front"), { $match: { repeat: { $gt: 1 } } }, { $unwind: "$data" }, { $project: projStmt }); } const ids = await CardModel.aggregate([ ...aggArray, { $project: { _id: 1 } } ]); aggArray.push({ $project: outputProj }); if (fullCond.noParse.has("is:random")) { util_1.shuffle(ids); aggArray = [ { $match: { _id: { $in: ids.slice(0, options.limit) } } }, ...aggArray ]; } else { const sortBy = fullCond.sortBy ? fullCond.sortBy.key : options.sortBy; const desc = fullCond.sortBy ? fullCond.sortBy.desc : options.desc; if (sortBy) { aggArray.push({ $sort: { [sortBy]: desc ? -1 : 1 } }); } if (options.offset) { aggArray.push({ $skip: options.offset }); } if (options.limit) { aggArray.push({ $limit: options.limit }); } } const data = await CardModel.aggregate(aggArray); return { data, count: ids.length }; } async insertMany(entries) { entries = await entries.mapAsync(async (e) => await this.transformCreateOrUpdate(null, e)); const now = new Date(); const sIdMap = {}; await entries.filter((e) => e.sH).distinctBy((el) => el.sH).mapAsync(async (el) => { try { sIdMap[el.sH] = (await SourceModel.create({ name: el.source, created: el.sCreated ? moment_1.default(el.sCreated).toDate() : now, h: el.sH, user: this.user })); } catch (err) { } }); const tIdMap = {}; await entries.filter((e) => e.tFront).mapAsync(async (el) => { try { tIdMap[el.template] = (await TemplateModel.create({ name: el.template, front: el.tFront, back: el.tBack, css: el.css, js: el.js, source: el.sH ? sIdMap[el.sH] : undefined, user: this.user })); } catch (e) { } }); const nDataMap = {}; const ns = entries.filter((e) => e.data).map((el) => { const data = {}; const order = {}; let index = 1; for (const { key, value } of el.data) { data[key] = value; order[key] = index; index++; } const key = spark_md5_1.default.hash(fast_json_stable_stringify_1.default(data)); nDataMap[spark_md5_1.default.hash(fast_json_stable_stringify_1.default(el.data))] = key; return { key, name: `${el.sH}/${el.template}/${el.data[0].value}`, data, order, source: el.sH ? sIdMap[el.sH] : undefined, user: this.user }; }); try { await NoteModel.insertMany(ns, { ordered: false }, (err, docs) => { for (const el of entries) { for (const d of docs) { if (nDataMap[spark_md5_1.default.hash(fast_json_stable_stringify_1.default(el.data))] === d.key) { el.noteId = d; break; } } } }); } catch (e) { } const dMap = {}; const decks = entries.map((e) => e.deck); const deckIds = await decks.mapAsync((d) => this.getOrCreateDeck(d)); decks.forEach((d, i) => { dMap[d] = deckIds[i]; }); return (await CardModel.insertMany(entries.map((e) => { return { front: e.front, back: e.back, mnemonic: e.mnemonic, srsLevel: e.srsLevel, nextReview: e.nextReview, deck: dMap[e.deck], note: e.noteId, template: e.template ? tIdMap[e.template] : undefined, created: now, tag: e.tag, user: this.user }; }))).map((el) => el._id.toString()); } async updateMany(ids, u) { const cs = await (await CardModel.find({ _id: { $in: ids.map((id) => new bson_1.ObjectID(id)) } })).mapAsync(async (c) => { for (const k of Object.keys(c)) { if (!Object.keys(u).includes(k) && k !== "_id") { delete c[k]; } } const c0 = Object.assign(c, await this.transformCreateOrUpdate(c._id, u)); const c1 = { _id: c._id }; for (let [k, v] of Object.entries(c0)) { switch (k) { case "deck": v = await this.getOrCreateDeck(v); c1[k] = v; break; case "tFront": case "tBack": k = k.substr(1).toLocaleLowerCase(); case "css": case "js": const { template } = (await CardModel.findOne({ _id: c._id })); await TemplateModel.updateOne({ _id: template }, { $set: { [k]: v } }); break; case "data": const { note } = (await CardModel.findOne({ _id: c._id }).populate("note")); if (note) { const { order, data } = note; for (const { key, value } of v) { if (!order[key]) { order[key] = Math.max(...Object.values(order)) + 1; } data[key] = value; } await NoteModel.updateOne({ _id: note._id }, { $set: { order, data } }); } else { const order = {}; const data = {}; for (const { key, value } of v) { if (!order[key]) { order[key] = Math.max(-1, ...Object.values(order)) + 1; } data[key] = value; } const key = spark_md5_1.default.hash(fast_json_stable_stringify_1.default(data)); const name = `${key}/${Object.values(data)[0]}`; c1.note = await NoteModel.create({ key, name, order, data, user: this.user }); } break; default: c1[k] = v; } } return c1; }); await cs.mapAsync(async (c) => { if (Object.keys(c).length > 1) { const { _id, ...$set } = c; await CardModel.updateOne({ _id }, { $set }); } }); } async addTags(ids, tags) { await CardModel.updateMany({ _id: { $in: ids.map((id) => new bson_1.ObjectID(id)) } }, { $addToSet: { tag: { $each: tags } } }); } async removeTags(ids, tags) { await CardModel.updateMany({ _id: { $in: ids.map((id) => new bson_1.ObjectID(id)) } }, { $pull: { tag: { $in: tags } } }); } async deleteMany(ids) { await CardModel.deleteMany({ _id: { $in: ids.map((id) => new bson_1.ObjectID(id)) } }); } async render(cardId) { const r = await this.parseCond(`_id=${cardId}`, { limit: 1, fields: ["front", "back", "mnemonic", "tFront", "tBack", "data", "css", "js"] }); const c = r.data[0]; const { tFront, tBack, data } = c; if (/@md5\n/.test(c.front)) { c.front = r2r_format_1.ankiMustache(tFront || "", data); } if (c.back && /@md5\n/.test(c.back)) { c.back = r2r_format_1.ankiMustache(tBack || "", data, c.front); } return c; } async updateSrsLevel(dSrsLevel, cardId) { const card = await CardModel.findOne({ _id: new bson_1.ObjectID(cardId) }); if (!card) { return; } card.srsLevel = card.srsLevel || 0; card.stat = card.stat || { streak: { right: 0, wrong: 0 } }; card.stat.streak = card.stat.streak || { right: 0, wrong: 0 }; if (dSrsLevel > 0) { card.stat.streak.right = (card.stat.streak.right || 0) + 1; } else if (dSrsLevel < 0) { card.stat.streak.wrong = (card.stat.streak.wrong || 0) + 1; } card.srsLevel += dSrsLevel; if (card.srsLevel >= quiz_1.srsMap.length) { card.srsLevel = quiz_1.srsMap.length - 1; } if (card.srsLevel < 0) { card.srsLevel = 0; } if (dSrsLevel > 0) { card.nextReview = quiz_1.getNextReview(card.srsLevel); } else { card.nextReview = quiz_1.repeatReview(); } const { srsLevel, stat, nextReview } = card; this.updateMany([cardId], { srsLevel, stat, nextReview }); } async transformCreateOrUpdate(cardId, u) { let data = null; let front = ""; if (u.front && u.front.startsWith("@template\n")) { if (!data) { if (cardId) { data = this.getData(cardId); } else { data = u.data || []; } } u.tFront = u.front.substr("@template\n".length); } if (u.tFront) { front = r2r_format_1.ankiMustache(u.tFront, data || {}); u.front = "@md5\n" + spark_md5_1.default.hash(front); } if (u.back && u.back.startsWith("@template\n")) { if (!data) { if (cardId) { data = this.getData(cardId); } else { data = u.data || []; } } u.tBack = (u.front || "").substr("@template\n".length); if (!front && cardId) { front = await this.getFront(cardId); } } if (u.tBack) { const back = r2r_format_1.ankiMustache(u.tBack, data || {}, front); u.back = "@md5\n" + spark_md5_1.default.hash(back); } return u; } async getOrCreateDeck(name) { try { return (await DeckModel.create({ name, user: this.user })); } catch (e) { return (await DeckModel.findOne({ name, user: this.user })); } } async getData(cardId) { const c = await CardModel.findOne({ _id: new bson_1.ObjectID(cardId) }).populate("note", "data"); if (c && c.note) { const { data, order } = c.note; return r2r_format_1.toSortedData({ data, order }); } return null; } async getFront(cardId) { const c = await CardModel.findOne({ _id: new bson_1.ObjectID(cardId) }).populate("note", "data").populate("template", "front"); if (c && c.note && c.template) { if (c.front.startsWith("@md5\n")) { return r2r_format_1.ankiMustache(c.template.front, c.note.data || {}); } return c.front; } return ""; } async export(r2r, q = "", options) { const callback = options ? options.callback : undefined; let current = 1; const ms = await MediaModel.find({ user: this.user }); for (const m of ms) { if (callback) callback({ text: "Inserting media", current, max: ms.length }); try { await r2r.createMedia(m); } catch (e) { } current++; } if (callback) callback({ text: "Parsing q" }); const cs = await this.parseCond(q, { fields: "*" }); current = 1; for (const c of util_1.chunk(cs.data, 1000)) { if (callback) callback({ text: "Inserting cards", current, max: cs.count }); await r2r.insertMany(c); current += 1000; } await r2r.close(); } async getMedia(h) { const m = await MediaModel.findOne({ h, user: this.user }); return m || null; } async allMedia() { return (await MediaModel.find({ user: this.user })); } async createMedia(m) { const h = spark_md5_1.default.ArrayBuffer.hash(m.data); await MediaModel.create({ ...m, h, user: this.user }); return h; } async deleteMedia(h) { await MediaModel.deleteOne({ h }); return true; } async fromR2r(r2r, options) { const filename = options ? options.filename : undefined; const callback = options ? options.callback : undefined; if (callback) callback({ text: "Reading R2r file" }); const data = fs_1.default.readFileSync(r2r.filename); const sourceH = spark_md5_1.default.ArrayBuffer.hash(data); const now = new Date(); let source; try { source = (await SourceModel.create({ name: filename || r2r.filename, h: sourceH, created: now, user: this.user })); } catch (e) { if (callback) callback({ text: "Duplicated Anki resource" }); return; } await (await r2r.allMedia()).mapAsync(async (m) => { try { MediaModel.create({ ...m, source, user: this.user }); } catch (e) { } }); await this.insertMany((await r2r.parseCond("", { fields: "*" })).data); } async fromAnki(anki, options) { const filename = options ? options.filename : undefined; const callback = options ? options.callback : undefined; if (callback) callback({ text: "Reading Anki file" }); const data = fs_1.default.readFileSync(anki.filePath); let source; const sourceH = spark_md5_1.default.ArrayBuffer.hash(data); const now = new Date(); try { source = (await SourceModel.create({ name: filename || anki.filePath, h: spark_md5_1.default.ArrayBuffer.hash(data), created: now, user: this.user })); } catch (e) { if (callback) callback({ text: "Duplicated Anki resource" }); return; } let current; let max; const media = await anki.apkg.tables.media.all(); current = 0; max = media.length; for (const el of media) { if (callback) callback({ text: "Inserting media", current, max }); try { await MediaModel.create({ h: el.h, name: el.name, data: el.data, source, user: this.user }); } catch (e) { } current++; } const card = await anki.apkg.tables.cards.all(); const dIdMap = {}; const tIdMap = {}; const nIdMap = {}; current = 0; max = card.length; for (const cs of util_1.chunk(card, 1000)) { if (callback) callback({ text: "Inserting cards", current, max }); const decks = []; for (const c of cs) { if (!dIdMap[c.deck.name]) { decks.push({ name: c.deck.name, user: this.user }); } } if (decks.length > 0) { await decks.mapAsync(async (d) => { try { dIdMap[d.name] = (await DeckModel.create({ name: d.name, user: this.user })); } catch (e) { dIdMap[d.name] = (await DeckModel.findOne({ name: d.name, user: this.user })); } }); } const templates = []; for (const c of cs) { const key = spark_md5_1.default.hash(fast_json_stable_stringify_1.default({ front: c.template.qfmt, back: c.template.afmt, css: c.note.model.css })); if (!tIdMap[key]) { templates.push({ name: `${sourceH}/${c.note.model.name}/${c.template.name}`, source, front: c.template.qfmt, back: c.template.afmt, css: c.note.model.css, key, user: this.user }); } } if (templates.length > 0) { await TemplateModel.insertMany(templates, { ordered: false }, (err, docs) => { console.log(docs); for (const d of docs) { tIdMap[d.key] = d; } }); } const notes = []; for (const c of cs) { const data = {}; const order = {}; c.template.model.flds.forEach((k, i) => { data[k] = c.note.flds[i]; order[k] = i; }); const key = spark_md5_1.default.hash(fast_json_stable_stringify_1.default(data)); if (!nIdMap[key]) { notes.push({ key, name: `${sourceH}/${c.note.model.name}/${c.template.name}/${c.note.flds[0]}`, data, order, user: this.user }); } } if (notes.length > 0) { await NoteModel.insertMany(notes, { ordered: false }, (err, docs) => { for (const d of docs) { nIdMap[d.key] = d; } }); } try { await CardModel.insertMany(cs.map((c) => { const data = {}; c.template.model.flds.forEach((k, i) => { data[k] = c.note.flds[i]; }); const key = spark_md5_1.default.hash(fast_json_stable_stringify_1.default(data)); const front = r2r_format_1.ankiMustache(c.template.qfmt, data); const back = r2r_format_1.ankiMustache(c.template.afmt, data, front); return { deck: dIdMap[c.deck.name], template: tIdMap[spark_md5_1.default.hash(fast_json_stable_stringify_1.default({ front: c.template.qfmt, back: c.template.afmt, css: c.note.model.css }))], note: nIdMap[key], front: `@md5\n${spark_md5_1.default.hash(front)}`, back: `@md5\n${spark_md5_1.default.hash(back)}`, tag: c.note.tags, created: now, user: this.user }; }), { ordered: false }); } catch (e) { } current += 1000; } } } exports.default = R2rMongo;