@opentermsarchive/engine 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +3 -0
- package/.eslintrc.yaml +116 -0
- package/.github/workflows/deploy.yml +50 -0
- package/.github/workflows/release.yml +64 -0
- package/.github/workflows/test.yml +77 -0
- package/CHANGELOG.md +14 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/CONTRIBUTING.md +143 -0
- package/LICENSE +153 -0
- package/MIGRATING.md +42 -0
- package/README.fr.md +110 -0
- package/README.md +438 -0
- package/Vagrantfile +38 -0
- package/ansible.cfg +13 -0
- package/bin/.env.js +1 -0
- package/bin/lint-declarations.js +31 -0
- package/bin/track.js +26 -0
- package/bin/validate-declarations.js +68 -0
- package/config/ci.json +5 -0
- package/config/contrib.json +35 -0
- package/config/dating.json +37 -0
- package/config/default.json +71 -0
- package/config/france.json +40 -0
- package/config/p2b-compliance.json +40 -0
- package/config/pga.json +40 -0
- package/config/production.json +27 -0
- package/config/test.json +49 -0
- package/config/vagrant.json +24 -0
- package/decision-records/0001-service-name-and-id.md +73 -0
- package/decision-records/0002-service-history.md +212 -0
- package/decision-records/0003-snapshots-database.md +123 -0
- package/ops/README.md +280 -0
- package/ops/app.yml +5 -0
- package/ops/infra.yml +6 -0
- package/ops/inventories/dev.yml +7 -0
- package/ops/inventories/production.yml +27 -0
- package/ops/roles/infra/defaults/main.yml +2 -0
- package/ops/roles/infra/files/.gitconfig +3 -0
- package/ops/roles/infra/files/mongod.conf +18 -0
- package/ops/roles/infra/files/ota-bot-key.private_key +26 -0
- package/ops/roles/infra/tasks/main.yml +78 -0
- package/ops/roles/infra/tasks/mongo.yml +40 -0
- package/ops/roles/infra/templates/ssh_config.j2 +5 -0
- package/ops/roles/ota/defaults/main.yml +14 -0
- package/ops/roles/ota/files/.env +21 -0
- package/ops/roles/ota/tasks/database.yml +65 -0
- package/ops/roles/ota/tasks/main.yml +110 -0
- package/ops/site.yml +6 -0
- package/package.json +101 -0
- package/pm2.config.cjs +20 -0
- package/scripts/dataset/README.md +37 -0
- package/scripts/dataset/assets/LICENSE +540 -0
- package/scripts/dataset/assets/README.template.js +65 -0
- package/scripts/dataset/export/index.js +106 -0
- package/scripts/dataset/export/index.test.js +155 -0
- package/scripts/dataset/export/test/fixtures/dataset/LICENSE +540 -0
- package/scripts/dataset/export/test/fixtures/dataset/README.md +40 -0
- package/scripts/dataset/export/test/fixtures/dataset/ServiceA/Terms of Service/2021-01-01T11-27-00Z.md +1 -0
- package/scripts/dataset/export/test/fixtures/dataset/ServiceA/Terms of Service/2021-01-11T11-32-47Z.md +1 -0
- package/scripts/dataset/export/test/fixtures/dataset/ServiceB/Privacy Policy/2022-01-01T12-12-24Z.md +1 -0
- package/scripts/dataset/export/test/fixtures/dataset/ServiceB/Terms of Service/2022-01-06T11-32-47Z.md +1 -0
- package/scripts/dataset/index.js +40 -0
- package/scripts/dataset/logger/index.js +17 -0
- package/scripts/dataset/main.js +25 -0
- package/scripts/dataset/publish/index.js +39 -0
- package/scripts/declarations/lint/index.js +36 -0
- package/scripts/declarations/utils/index.js +81 -0
- package/scripts/declarations/validate/definitions.js +63 -0
- package/scripts/declarations/validate/index.mocha.js +262 -0
- package/scripts/declarations/validate/service.history.schema.js +86 -0
- package/scripts/declarations/validate/service.schema.js +91 -0
- package/scripts/history/logger/index.js +39 -0
- package/scripts/history/migrate-services.js +212 -0
- package/scripts/history/update-to-full-hash.js +61 -0
- package/scripts/history/utils/index.js +23 -0
- package/scripts/import/README.md +59 -0
- package/scripts/import/config/import.json +12 -0
- package/scripts/import/index.js +224 -0
- package/scripts/import/loadCommits.js +66 -0
- package/scripts/import/logger/index.js +43 -0
- package/scripts/rewrite/README.md +131 -0
- package/scripts/rewrite/config/rewrite-snapshots.json +32 -0
- package/scripts/rewrite/config/rewrite-versions.json +32 -0
- package/scripts/rewrite/initializer/files/license +428 -0
- package/scripts/rewrite/initializer/files/readme.md +8 -0
- package/scripts/rewrite/initializer/index.js +44 -0
- package/scripts/rewrite/rewrite-snapshots.js +108 -0
- package/scripts/rewrite/rewrite-versions.js +160 -0
- package/scripts/rewrite/utils.js +33 -0
- package/scripts/utils/renamer/README.md +49 -0
- package/scripts/utils/renamer/index.js +45 -0
- package/scripts/utils/renamer/rules/documentTypes.json +25 -0
- package/scripts/utils/renamer/rules/documentTypesByService.json +170 -0
- package/scripts/utils/renamer/rules/serviceNames.json +92 -0
- package/src/archivist/errors.js +9 -0
- package/src/archivist/fetcher/errors.js +6 -0
- package/src/archivist/fetcher/exports.js +18 -0
- package/src/archivist/fetcher/fullDomFetcher.js +84 -0
- package/src/archivist/fetcher/htmlOnlyFetcher.js +62 -0
- package/src/archivist/fetcher/index.js +35 -0
- package/src/archivist/fetcher/index.test.js +239 -0
- package/src/archivist/filter/exports.js +3 -0
- package/src/archivist/filter/index.js +178 -0
- package/src/archivist/filter/index.test.js +561 -0
- package/src/archivist/index.js +276 -0
- package/src/archivist/index.test.js +600 -0
- package/src/archivist/recorder/index.js +77 -0
- package/src/archivist/recorder/index.test.js +463 -0
- package/src/archivist/recorder/record.js +35 -0
- package/src/archivist/recorder/record.test.js +91 -0
- package/src/archivist/recorder/repositories/factory.js +23 -0
- package/src/archivist/recorder/repositories/git/dataMapper.js +83 -0
- package/src/archivist/recorder/repositories/git/git.js +122 -0
- package/src/archivist/recorder/repositories/git/git.test.js +86 -0
- package/src/archivist/recorder/repositories/git/index.js +182 -0
- package/src/archivist/recorder/repositories/git/index.test.js +714 -0
- package/src/archivist/recorder/repositories/interface.js +108 -0
- package/src/archivist/recorder/repositories/mongo/dataMapper.js +32 -0
- package/src/archivist/recorder/repositories/mongo/index.js +121 -0
- package/src/archivist/recorder/repositories/mongo/index.test.js +721 -0
- package/src/archivist/services/documentDeclaration.js +26 -0
- package/src/archivist/services/documentDeclaration.test.js +85 -0
- package/src/archivist/services/documentTypes.json +386 -0
- package/src/archivist/services/index.js +255 -0
- package/src/archivist/services/index.test.js +327 -0
- package/src/archivist/services/pageDeclaration.js +51 -0
- package/src/archivist/services/pageDeclaration.test.js +224 -0
- package/src/archivist/services/service.js +60 -0
- package/src/archivist/services/service.test.js +164 -0
- package/src/exports.js +3 -0
- package/src/index.js +59 -0
- package/src/logger/README.md +1 -0
- package/src/logger/index.js +131 -0
- package/src/main.js +18 -0
- package/src/notifier/README.md +1 -0
- package/src/notifier/index.js +150 -0
- package/src/tracker/README.md +1 -0
- package/src/tracker/index.js +215 -0
- package/test/fixtures/service_A.js +22 -0
- package/test/fixtures/service_A_terms.md +10 -0
- package/test/fixtures/service_A_terms_snapshot.html +14 -0
- package/test/fixtures/service_B.js +22 -0
- package/test/fixtures/service_with_declaration_history.js +65 -0
- package/test/fixtures/service_with_filters_history.js +155 -0
- package/test/fixtures/service_with_history.js +188 -0
- package/test/fixtures/service_with_multipage_document.js +100 -0
- package/test/fixtures/service_without_history.js +31 -0
- package/test/fixtures/services.js +19 -0
- package/test/fixtures/terms.pdf +0 -0
- package/test/fixtures/termsFromPDF.md +25 -0
- package/test/fixtures/termsModified.pdf +0 -0
- package/test/services/service_A.json +9 -0
- package/test/services/service_B.json +9 -0
- package/test/services/service_with_declaration_history.filters.js +7 -0
- package/test/services/service_with_declaration_history.history.json +17 -0
- package/test/services/service_with_declaration_history.json +13 -0
- package/test/services/service_with_filters_history.filters.history.js +29 -0
- package/test/services/service_with_filters_history.filters.js +7 -0
- package/test/services/service_with_filters_history.json +13 -0
- package/test/services/service_with_history.filters.history.js +29 -0
- package/test/services/service_with_history.filters.js +7 -0
- package/test/services/service_with_history.history.json +26 -0
- package/test/services/service_with_history.json +17 -0
- package/test/services/service_with_multipage_document.filters.js +7 -0
- package/test/services/service_with_multipage_document.history.json +37 -0
- package/test/services/service_with_multipage_document.json +28 -0
- package/test/services/service_without_history.filters.js +7 -0
- package/test/services/service_without_history.json +13 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import fsApi from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
import simpleGit from 'simple-git';
|
|
5
|
+
|
|
6
|
+
process.env.LC_ALL = 'en_GB'; // Ensure git messages will be in English as some errors are handled by analysing the message content
|
|
7
|
+
|
|
8
|
+
const fs = fsApi.promises;
|
|
9
|
+
|
|
10
|
+
export default class Git {
|
|
11
|
+
constructor({ path: repositoryPath, author }) {
|
|
12
|
+
this.path = repositoryPath;
|
|
13
|
+
this.author = author;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async initialize() {
|
|
17
|
+
if (!fsApi.existsSync(this.path)) {
|
|
18
|
+
await fs.mkdir(this.path, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
this.git = simpleGit(this.path, { maxConcurrentProcesses: 1 });
|
|
22
|
+
|
|
23
|
+
await this.git.init();
|
|
24
|
+
|
|
25
|
+
return this.git
|
|
26
|
+
.addConfig('core.autocrlf', false)
|
|
27
|
+
.addConfig('push.default', 'current')
|
|
28
|
+
.addConfig('user.name', this.author.name)
|
|
29
|
+
.addConfig('user.email', this.author.email);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async add(filePath) {
|
|
33
|
+
return this.git.add(this.relativePath(filePath));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async commit({ filePath, message, date = new Date() }) {
|
|
37
|
+
const commitDate = new Date(date).toISOString();
|
|
38
|
+
let summary;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
process.env.GIT_AUTHOR_DATE = commitDate;
|
|
42
|
+
process.env.GIT_COMMITTER_DATE = commitDate;
|
|
43
|
+
|
|
44
|
+
summary = await this.git.commit(message, filePath);
|
|
45
|
+
} finally {
|
|
46
|
+
process.env.GIT_AUTHOR_DATE = '';
|
|
47
|
+
process.env.GIT_COMMITTER_DATE = '';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!summary.commit) { // Nothing committed, no hash to return
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return summary.commit;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async pushChanges() {
|
|
58
|
+
return this.git.push();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async listCommits(options = []) {
|
|
62
|
+
return this.log([ '--reverse', '--no-merges', '--name-only', ...options ]);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async getCommit(options) {
|
|
66
|
+
const [commit] = await this.listCommits([ '-1', ...options ]);
|
|
67
|
+
|
|
68
|
+
return commit;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async log(options = []) {
|
|
72
|
+
try {
|
|
73
|
+
const logSummary = await this.git.log(options);
|
|
74
|
+
|
|
75
|
+
return logSummary.all;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (/unknown revision or path not in the working tree|does not have any commits yet/.test(error.message)) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async isTracked(filePath) {
|
|
86
|
+
const result = await this.git.raw('ls-files', this.relativePath(filePath));
|
|
87
|
+
|
|
88
|
+
return Boolean(result);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async checkout(options) {
|
|
92
|
+
return this.git.checkout(options);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async show(options) {
|
|
96
|
+
return this.git.show(options);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async cleanUp() {
|
|
100
|
+
await this.git.reset('hard');
|
|
101
|
+
|
|
102
|
+
return this.git.clean('f', '-d');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async getFullHash(shortHash) {
|
|
106
|
+
return (await this.git.show([ shortHash, '--pretty=%H', '-s' ])).trim();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async restore(path, commit) {
|
|
110
|
+
return this.git.raw([ 'restore', '-s', commit, '--', path ]);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async destroyHistory() {
|
|
114
|
+
await fs.rm(this.path, { recursive: true });
|
|
115
|
+
|
|
116
|
+
return this.initialize();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
relativePath(absolutePath) {
|
|
120
|
+
return path.relative(this.path, absolutePath); // Git needs a path relative to the .git directory, not an absolute one
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
import { expect } from 'chai';
|
|
6
|
+
import config from 'config';
|
|
7
|
+
|
|
8
|
+
import Git from './git.js';
|
|
9
|
+
|
|
10
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const RECORDER_PATH = path.resolve(__dirname, '../../../../../', config.get('recorder.versions.storage.git.path'));
|
|
12
|
+
|
|
13
|
+
describe('Git', () => {
|
|
14
|
+
const DEFAULT_CONTENT = 'default content';
|
|
15
|
+
const DEFAULT_COMMIT_MESSAGE = 'default commit message';
|
|
16
|
+
let subject;
|
|
17
|
+
|
|
18
|
+
before(() => {
|
|
19
|
+
subject = new Git({
|
|
20
|
+
path: RECORDER_PATH,
|
|
21
|
+
author: {
|
|
22
|
+
name: config.get('recorder.versions.storage.git.author.name'),
|
|
23
|
+
email: config.get('recorder.versions.storage.git.author.email'),
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return subject.initialize();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('#commit', () => {
|
|
31
|
+
const expectedFilePath = `${RECORDER_PATH}/test.md`;
|
|
32
|
+
|
|
33
|
+
let commitId;
|
|
34
|
+
|
|
35
|
+
before(async () => {
|
|
36
|
+
await fs.writeFile(expectedFilePath, DEFAULT_CONTENT);
|
|
37
|
+
|
|
38
|
+
await subject.add(expectedFilePath);
|
|
39
|
+
commitId = await subject.commit({ filePath: expectedFilePath, message: DEFAULT_COMMIT_MESSAGE });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
after(() => subject.destroyHistory());
|
|
43
|
+
|
|
44
|
+
it('returns a full length SHA1 commit ID', () => {
|
|
45
|
+
expect(commitId).to.match(/\b[0-9a-f]{40}\b/);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
context('when stage area is dirty', () => {
|
|
49
|
+
const expectedFileName = 'file-to-commit.md';
|
|
50
|
+
const expectedFilePath = `${RECORDER_PATH}/${expectedFileName}`;
|
|
51
|
+
const unwantedFilePath = `${RECORDER_PATH}/unwanted-file.md`;
|
|
52
|
+
let commit;
|
|
53
|
+
let committedFiles;
|
|
54
|
+
let committedFileName;
|
|
55
|
+
|
|
56
|
+
before(async () => {
|
|
57
|
+
await fs.writeFile(expectedFilePath, DEFAULT_CONTENT);
|
|
58
|
+
await fs.writeFile(unwantedFilePath, DEFAULT_CONTENT);
|
|
59
|
+
|
|
60
|
+
await subject.add(expectedFilePath);
|
|
61
|
+
await subject.add(unwantedFilePath);
|
|
62
|
+
|
|
63
|
+
const commitId = await subject.commit({ filePath: expectedFilePath, message: DEFAULT_COMMIT_MESSAGE });
|
|
64
|
+
|
|
65
|
+
commit = await subject.getCommit([commitId]);
|
|
66
|
+
|
|
67
|
+
if (!commit) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
({ files: committedFiles } = commit.diff);
|
|
72
|
+
([{ file: committedFileName }] = committedFiles);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
after(() => subject.destroyHistory());
|
|
76
|
+
|
|
77
|
+
it('commits the specified file', () => {
|
|
78
|
+
expect(committedFileName).to.equal(expectedFileName);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('commits only one file', () => {
|
|
82
|
+
expect(committedFiles).to.have.lengthOf(1);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This module is the boundary beyond which the usage of git is abstracted.
|
|
3
|
+
* Commit SHAs are used as opaque unique IDs.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fsApi from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
|
|
9
|
+
import mime from 'mime';
|
|
10
|
+
|
|
11
|
+
import RepositoryInterface from '../interface.js';
|
|
12
|
+
|
|
13
|
+
import * as DataMapper from './dataMapper.js';
|
|
14
|
+
import Git from './git.js';
|
|
15
|
+
|
|
16
|
+
const fs = fsApi.promises;
|
|
17
|
+
|
|
18
|
+
mime.define({ 'text/markdown': ['md'] }, true); // ensure extension for markdown files is `.md` and not `.markdown`
|
|
19
|
+
|
|
20
|
+
export default class GitRepository extends RepositoryInterface {
|
|
21
|
+
constructor({ path, author, publish, snapshotIdentiferTemplate }) {
|
|
22
|
+
super();
|
|
23
|
+
this.path = path;
|
|
24
|
+
this.needsPublication = publish;
|
|
25
|
+
this.git = new Git({ path: this.path, author });
|
|
26
|
+
this.snapshotIdentiferTemplate = snapshotIdentiferTemplate;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async initialize() {
|
|
30
|
+
await this.git.initialize();
|
|
31
|
+
await this.git.cleanUp(); // Drop all uncommitted changes and remove all leftover files that may be present if the process was killed aggressively
|
|
32
|
+
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async save(record) {
|
|
37
|
+
const { serviceId, documentType, pageId, fetchDate } = record;
|
|
38
|
+
|
|
39
|
+
if (record.isFirstRecord === undefined || record.isFirstRecord === null) {
|
|
40
|
+
record.isFirstRecord = !await this.#isTracked(serviceId, documentType, pageId);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const { message, content, filePath: relativeFilePath } = await this.#toPersistence(record);
|
|
44
|
+
|
|
45
|
+
const filePath = path.join(this.path, relativeFilePath);
|
|
46
|
+
|
|
47
|
+
await GitRepository.#writeFile({ filePath, content });
|
|
48
|
+
const sha = await this.#commit({ filePath, message, date: fetchDate });
|
|
49
|
+
|
|
50
|
+
if (!sha) {
|
|
51
|
+
return Object(null);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
record.id = sha;
|
|
55
|
+
|
|
56
|
+
return record;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
finalize() {
|
|
60
|
+
if (!this.needsPublication) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return this.git.pushChanges();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async findLatest(serviceId, documentType, pageId) {
|
|
68
|
+
const filePath = DataMapper.generateFilePath(serviceId, documentType, pageId);
|
|
69
|
+
const commit = await this.git.getCommit([filePath]);
|
|
70
|
+
|
|
71
|
+
return this.#toDomain(commit);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async findById(recordId) {
|
|
75
|
+
const commit = await this.git.getCommit([recordId]);
|
|
76
|
+
|
|
77
|
+
return this.#toDomain(commit);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async findAll() {
|
|
81
|
+
return Promise.all((await this.#getCommits()).map(commit => this.#toDomain(commit, { deferContentLoading: true })));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async count() {
|
|
85
|
+
return (await this.git.log([
|
|
86
|
+
`--grep=${DataMapper.COMMIT_MESSAGE_PREFIX.startTracking}`,
|
|
87
|
+
`--grep=${DataMapper.COMMIT_MESSAGE_PREFIX.refilter}`,
|
|
88
|
+
`--grep=${DataMapper.COMMIT_MESSAGE_PREFIX.update}`,
|
|
89
|
+
])).length;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async* iterate() {
|
|
93
|
+
const commits = await this.#getCommits();
|
|
94
|
+
|
|
95
|
+
for (const commit of commits) {
|
|
96
|
+
yield this.#toDomain(commit);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async removeAll() {
|
|
101
|
+
return this.git.destroyHistory();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async loadRecordContent(record) {
|
|
105
|
+
const relativeFilePath = DataMapper.generateFilePath(record.serviceId, record.documentType, record.pageId, record.mimeType);
|
|
106
|
+
|
|
107
|
+
if (record.mimeType != mime.getType('pdf')) {
|
|
108
|
+
record.content = await this.git.show(`${record.id}:${relativeFilePath}`);
|
|
109
|
+
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// In case of PDF files, `git show` cannot be used as it converts PDF binary into strings that do not retain the original binary representation
|
|
114
|
+
// It is impossible to restore the original binary data from the resulting string
|
|
115
|
+
let pdfBuffer;
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
await this.git.restore(relativeFilePath, record.id); // Temporarily restore the PDF file to a specific commit
|
|
119
|
+
pdfBuffer = await fs.readFile(`${this.path}/${relativeFilePath}`); // …read the content
|
|
120
|
+
} finally {
|
|
121
|
+
await this.git.restore(relativeFilePath, 'HEAD'); // …and finally restore the file to its most recent state
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
record.content = pdfBuffer;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async #getCommits() {
|
|
128
|
+
return (await this.git.listCommits())
|
|
129
|
+
.filter(({ message }) => message.match(DataMapper.COMMIT_MESSAGE_PREFIXES_REGEXP)) // Skip commits which are not a document record (README, LICENSE…)
|
|
130
|
+
.sort((commitA, commitB) => new Date(commitA.date) - new Date(commitB.date)); // Make sure that the commits are sorted in ascending chronological order
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
static async #writeFile({ filePath, content }) {
|
|
134
|
+
const directory = path.dirname(filePath);
|
|
135
|
+
|
|
136
|
+
if (!fsApi.existsSync(directory)) {
|
|
137
|
+
await fs.mkdir(directory, { recursive: true });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
await fs.writeFile(filePath, content);
|
|
141
|
+
|
|
142
|
+
return filePath;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async #commit({ filePath, message, date }) {
|
|
146
|
+
try {
|
|
147
|
+
await this.git.add(filePath);
|
|
148
|
+
|
|
149
|
+
return await this.git.commit({ filePath, message, date });
|
|
150
|
+
} catch (error) {
|
|
151
|
+
throw new Error(`Could not commit ${filePath} with message "${message}" due to error: "${error}"`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#isTracked(serviceId, documentType, pageId) {
|
|
156
|
+
return this.git.isTracked(`${this.path}/${DataMapper.generateFilePath(serviceId, documentType, pageId)}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async #toDomain(commit, { deferContentLoading } = {}) {
|
|
160
|
+
if (!commit) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const record = DataMapper.toDomain(commit);
|
|
165
|
+
|
|
166
|
+
if (deferContentLoading) {
|
|
167
|
+
return record;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
await this.loadRecordContent(record);
|
|
171
|
+
|
|
172
|
+
return record;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async #toPersistence(record) {
|
|
176
|
+
if (record.content === undefined || record.content === null) {
|
|
177
|
+
await this.loadRecordContent(record);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return DataMapper.toPersistence(record, this.snapshotIdentiferTemplate);
|
|
181
|
+
}
|
|
182
|
+
}
|