@rep2recall/r2r-mongo
Version:
R2r format engine for Rep2Recall
1,070 lines (1,069 loc) • 38.3 kB
JavaScript
"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;