@socialgouv/matomo-postgres 1.5.0 → 1.5.2-beta.1

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.
@@ -1,194 +0,0 @@
1
- const mock_pgQuery = jest.fn();
2
- const mock_matomoApi = jest.fn();
3
- const formatISO = require("date-fns/formatISO");
4
- const addDays = require("date-fns/addDays");
5
-
6
- const { OFFSET } = require("../config");
7
-
8
- process.env.MATOMO_SITE = "42";
9
- process.env.PROJECT_NAME = "some-project";
10
-
11
- const matomoVisit = require("./visit.json");
12
-
13
- const run = require("../index");
14
-
15
- const NB_REQUEST_TO_INIT_DB = 3; // Number of query to init DB (createTable.js)
16
- const TEST_DATE = new Date();
17
-
18
- // @ts-ignore
19
- const isoDate = (date) => formatISO(date, { representation: "date" });
20
-
21
- jest.mock("pg", () => {
22
- class Client {
23
- escapeIdentifier(name) {
24
- return name;
25
- }
26
- end() {}
27
- connect() {
28
- return Promise.resolve();
29
- }
30
- query(...args) {
31
- return Promise.resolve(mock_pgQuery(...args));
32
- }
33
- }
34
- return {
35
- Client,
36
- };
37
- });
38
-
39
- jest.mock("piwik-client", () => {
40
- class MatomoClient {
41
- api(...args) {
42
- return mock_matomoApi(...args);
43
- }
44
- }
45
- return MatomoClient;
46
- });
47
-
48
- beforeEach(() => {
49
- jest.resetAllMocks();
50
- jest.resetModules();
51
- process.env.STARTDATE = "";
52
- //process.env.OFFSET = "";
53
- });
54
-
55
- test("run: should create table", async () => {
56
- mock_pgQuery.mockReturnValue({ rows: [] });
57
- mock_matomoApi.mockImplementation((options, cb) => {
58
- return cb(null, []);
59
- });
60
- await run();
61
- expect(mock_pgQuery.mock.calls[0]).toMatchSnapshot();
62
- });
63
-
64
- test("run: should fetch the latest event date if no date provided", async () => {
65
- jest.useFakeTimers("modern").setSystemTime(TEST_DATE.getTime());
66
- mock_pgQuery.mockReturnValue({ rows: [] });
67
-
68
- mock_matomoApi.mockImplementation((options, cb) => {
69
- return cb(null, [
70
- {
71
- ...matomoVisit,
72
- idVisit: 123,
73
- },
74
- {
75
- ...matomoVisit,
76
- idVisit: 124,
77
- },
78
- ]);
79
- });
80
-
81
- await run();
82
-
83
- // check matomo requests
84
- expect(mock_matomoApi.mock.calls[0][0].date).toEqual(isoDate(TEST_DATE));
85
- expect(mock_matomoApi.mock.calls[0][0].filter_offset).toEqual(0);
86
-
87
- // check db queries
88
- expect(mock_pgQuery.mock.calls[NB_REQUEST_TO_INIT_DB][0]).toEqual(
89
- "select action_timestamp from matomo order by action_timestamp desc limit 1"
90
- );
91
- });
92
-
93
- test("run: should resume using latest event date - offset if no date provided", async () => {
94
- jest.useFakeTimers("modern").setSystemTime(TEST_DATE.getTime());
95
-
96
- const LAST_EVENT_DATE_OFFSET = 2;
97
- // @ts-ignore
98
- const LAST_EVENT_DATE = addDays(TEST_DATE, -LAST_EVENT_DATE_OFFSET);
99
-
100
- mock_pgQuery.mockReturnValue({ rows: [{ action_timestamp: LAST_EVENT_DATE.getTime() }] });
101
-
102
- mock_matomoApi.mockImplementation((options, cb) => {
103
- return cb(null, [
104
- {
105
- ...matomoVisit,
106
- idVisit: 123,
107
- },
108
- {
109
- ...matomoVisit,
110
- idVisit: 124,
111
- },
112
- ]);
113
- });
114
-
115
- await run();
116
-
117
- // check matomo requests
118
- expect(mock_matomoApi.mock.calls[0][0].date).toEqual(
119
- // @ts-ignore
120
- isoDate(addDays(LAST_EVENT_DATE, -parseInt(OFFSET)))
121
- );
122
- expect(mock_matomoApi.mock.calls[0][0].filter_offset).toEqual(0);
123
-
124
- const daysCount = LAST_EVENT_DATE_OFFSET + parseInt(OFFSET) + 1;
125
- expect(mock_matomoApi.mock.calls.length).toEqual(daysCount);
126
-
127
- // check db queries
128
- expect(mock_pgQuery.mock.calls.length).toEqual(NB_REQUEST_TO_INIT_DB + 1 + daysCount * 7); // NB_REQUEST_TO_INIT_DB + select queries + days offset
129
- });
130
-
131
- test("run: should use today date if nothing in DB", async () => {
132
- jest.useFakeTimers("modern").setSystemTime(TEST_DATE.getTime());
133
- mock_pgQuery.mockReturnValue({ rows: [] });
134
-
135
- mock_matomoApi.mockImplementation((options, cb) => {
136
- return cb(null, [
137
- {
138
- ...matomoVisit,
139
- idVisit: 123,
140
- },
141
- ]);
142
- });
143
-
144
- await run();
145
-
146
- // check matomo requests
147
- expect(mock_matomoApi.mock.calls.length).toEqual(1);
148
- expect(mock_matomoApi.mock.calls[0][0].date).toEqual(isoDate(TEST_DATE));
149
-
150
- // check the 4 events inserted
151
- expect(mock_pgQuery.mock.calls.length).toEqual(NB_REQUEST_TO_INIT_DB + 5); // NB_REQUEST_TO_INIT_DB + check date + latest + 2 inserts
152
- });
153
-
154
- test("run: should use given date if any", async () => {
155
- jest.useFakeTimers("modern").setSystemTime(TEST_DATE.getTime());
156
- mock_pgQuery.mockReturnValue({ rows: [] });
157
-
158
- mock_matomoApi.mockImplementation((options, cb) => {
159
- return cb(null, [
160
- {
161
- ...matomoVisit,
162
- idVisit: 123,
163
- },
164
- ]);
165
- });
166
-
167
- // @ts-ignore
168
- await run(isoDate(addDays(TEST_DATE, -10)) + "T00:00:00.000Z");
169
-
170
- expect(mock_matomoApi.mock.calls.length).toEqual(11);
171
- expect(mock_pgQuery.mock.calls.length).toEqual(NB_REQUEST_TO_INIT_DB + 11 * 4); // NB_REQUEST_TO_INIT_DB + inserts. no initial select as date is provided
172
- });
173
-
174
- test("run: should use STARTDATE if any", async () => {
175
- // @ts-ignore
176
- process.env.STARTDATE = isoDate(addDays(TEST_DATE, -5)) + "T00:00:00.000Z";
177
- jest.useFakeTimers("modern").setSystemTime(TEST_DATE.getTime());
178
- mock_pgQuery.mockReturnValue({ rows: [] });
179
-
180
- mock_matomoApi.mockImplementation((options, cb) => {
181
- return cb(null, [
182
- {
183
- ...matomoVisit,
184
- idVisit: 123,
185
- },
186
- ]);
187
- });
188
-
189
- await run();
190
-
191
- expect(mock_matomoApi.mock.calls.length).toEqual(6);
192
-
193
- expect(mock_pgQuery.mock.calls.length).toEqual(NB_REQUEST_TO_INIT_DB + 1 + 6 * 4); // NB_REQUEST_TO_INIT_DB + initial select + inserts.
194
- });
@@ -1,103 +0,0 @@
1
- {
2
- "idVisit": "124",
3
- "idSite": "42",
4
- "country": "Argentine",
5
- "operatingSystemName": "Mac",
6
- "deviceBrand": "Inconnu",
7
- "deviceModel": "Générique Bureau",
8
- "visitDuration": "300",
9
- "daysSinceFirstVisit": "23",
10
- "actions": "2",
11
- "visitorType": "returningCustomer",
12
- "visitorId": "visitorId",
13
- "referrerType": "referrerType",
14
- "referrerName": "referrerName",
15
- "siteName": "tests",
16
- "userId": "24",
17
- "region": "Buenos Aires",
18
- "city": "Buenos Aires",
19
- "dimension1": "guest",
20
- "dimension3": "page",
21
- "dimension6": "shop",
22
- "dimension7": "v1.2.3",
23
- "dimension8": "fr",
24
- "dimension9": "light",
25
- "dimension10": "36",
26
- "firstActionTimestamp": 1629496512,
27
- "actionDetails": [
28
- {
29
- "type": "event",
30
- "url": "https://dive-shop.net/products/basic-wetsuit/",
31
- "pageIdAction": "304",
32
- "idpageview": "",
33
- "serverTimePretty": "20 août 2021 21:35:18",
34
- "pageId": "19696671",
35
- "eventCategory": "Ecommerce",
36
- "eventAction": "Cart change",
37
- "timeSpent": 48,
38
- "timeSpentPretty": "48s",
39
- "pageviewPosition": "8",
40
- "timestamp": 1629495318,
41
- "icon": "plugins/Morpheus/images/event.png",
42
- "iconSVG": "plugins/Morpheus/images/event.svg",
43
- "title": "Evènement",
44
- "subtitle": "Catégorie: \"Ecommerce', Action: \"Cart change\"",
45
- "eventName": "added - Basic Wetsuit",
46
- "eventValue": 1,
47
- "dimension2": "julien",
48
- "dimension4": "indonesia",
49
- "dimension5": "diving",
50
- "customVariables": {
51
- "1": {
52
- "customVariableName1": "page-author",
53
- "customVariableValue1": "Julien"
54
- },
55
- "2": {
56
- "customVariableName2": "post-age",
57
- "customVariableValue2": "-430 days"
58
- }
59
- }
60
- },
61
- {
62
- "type": "action",
63
- "url": "https://dive-shop.net/products/diving-boots/",
64
- "pageTitle": "Divezone Brand Diving Boots - Divezone Store",
65
- "pageIdAction": "60",
66
- "idpageview": "8CDIez",
67
- "serverTimePretty": "20 août 2021 21:30:25",
68
- "pageId": "19696664",
69
- "timeSpent": "2",
70
- "timeSpentPretty": "2s",
71
- "pageviewPosition": "5",
72
- "title": "Divezone Brand Diving Boots - Divezone Store",
73
- "subtitle": "https://dive-shop.net/products/diving-boots/",
74
- "icon": "",
75
- "iconSVG": "plugins/Morpheus/images/action.svg",
76
- "timestamp": 1629495025,
77
- "dimension2": "julien",
78
- "dimension4": "indonesia",
79
- "dimension5": "diving"
80
- },
81
- {
82
- "type": "search",
83
- "url": "https://dive-shop.net/products/diving-boots/",
84
- "pageTitle": "Divezone Brand Diving Boots - Divezone Store",
85
- "pageIdAction": "60",
86
- "idpageview": "8CDIez",
87
- "serverTimePretty": "20 août 2021 21:30:25",
88
- "pageId": "19696664",
89
- "timeSpent": "2",
90
- "timeSpentPretty": "2s",
91
- "pageviewPosition": "5",
92
- "title": "Divezone Brand Diving Boots - Divezone Store",
93
- "subtitle": "https://dive-shop.net/products/diving-boots/",
94
- "icon": "",
95
- "iconSVG": "plugins/Morpheus/images/action.svg",
96
- "timestamp": 1629495022,
97
- "dimension2": "julien",
98
- "dimension4": "indonesia",
99
- "dimension5": "diving",
100
- "siteSearchKeyword": "scuba"
101
- }
102
- ]
103
- }
package/src/config.js DELETED
@@ -1,9 +0,0 @@
1
- const MATOMO_KEY = process.env.MATOMO_KEY || "";
2
- const MATOMO_URL = process.env.MATOMO_URL || "https://matomo.fabrique.social.gouv.fr/";
3
- const MATOMO_SITE = process.env.MATOMO_SITE || 0;
4
- const PGDATABASE = process.env.PGDATABASE || "";
5
- const DESTINATION_TABLE = process.env.DESTINATION_TABLE || "matomo";
6
- const OFFSET = process.env.OFFSET || "3";
7
- const RESULTPERPAGE = process.env.RESULTPERPAGE || "500";
8
-
9
- module.exports = { MATOMO_KEY, MATOMO_URL, MATOMO_SITE, PGDATABASE, DESTINATION_TABLE, OFFSET, RESULTPERPAGE };
@@ -1,83 +0,0 @@
1
- const { Client } = require("pg");
2
-
3
- const { DESTINATION_TABLE } = require("./config");
4
-
5
- /**
6
- *
7
- * @param {Client} client
8
- */
9
- async function createTable(client) {
10
- const table = client.escapeIdentifier(DESTINATION_TABLE);
11
- const text = `
12
-
13
- CREATE SCHEMA IF NOT EXISTS partman;
14
- CREATE EXTENSION IF NOT EXISTS pg_partman SCHEMA partman;
15
- CREATE TABLE IF NOT EXISTS ${table}
16
- (
17
- idsite text,
18
- idvisit text,
19
- actions text,
20
- country text,
21
- region text,
22
- city text,
23
- operatingsystemname text,
24
- devicemodel text,
25
- devicebrand text,
26
- visitduration text,
27
- dayssincefirstvisit text,
28
- visitortype text,
29
- sitename text,
30
- userid text,
31
- serverdateprettyfirstaction date,
32
- action_id text,
33
- action_type text,
34
- action_eventcategory text,
35
- action_eventaction text,
36
- action_eventname text,
37
- action_eventvalue decimal,
38
- action_timespent text,
39
- action_timestamp timestamp with time zone DEFAULT now(),
40
- usercustomproperties json,
41
- usercustomdimensions json,
42
- dimension1 text,
43
- dimension2 text,
44
- dimension3 text,
45
- dimension4 text,
46
- dimension5 text,
47
- dimension6 text,
48
- dimension7 text,
49
- dimension8 text,
50
- dimension9 text,
51
- dimension10 text,
52
- action_url text,
53
- sitesearchkeyword text,
54
- action_title text,
55
- visitorid text,
56
- referrertype text,
57
- referrername text
58
- ) PARTITION BY RANGE (action_timestamp);
59
- `;
60
-
61
- await client.query(text, []);
62
-
63
- const migrations = [
64
- `CREATE INDEX IF NOT EXISTS idx_dimension1 ON matomo(dimension1);
65
- CREATE INDEX IF NOT EXISTS idx_dimension2 ON matomo(dimension2);
66
- CREATE INDEX IF NOT EXISTS idx_dimension3 ON matomo(dimension3);
67
- CREATE INDEX IF NOT EXISTS idx_dimension4 ON matomo(dimension4);
68
- CREATE INDEX IF NOT EXISTS idx_dimension5 ON matomo(dimension5);
69
- CREATE INDEX IF NOT EXISTS idx_userid ON matomo(userid);
70
- CREATE INDEX IF NOT EXISTS idx_actionurl ON matomo(action_url);
71
- CREATE INDEX IF NOT EXISTS idx_region ON matomo(region);`,
72
- `ALTER TABLE matomo ADD COLUMN IF NOT EXISTS visitorid text;
73
- ALTER TABLE matomo ADD COLUMN IF NOT EXISTS referrertype text;
74
- ALTER TABLE matomo ADD COLUMN IF NOT EXISTS referrername text;
75
- CREATE INDEX IF NOT EXISTS idx_visitorid ON matomo(visitorid);`,
76
- ];
77
-
78
- for (const query of migrations) {
79
- await client.query(query, []);
80
- }
81
- }
82
-
83
- module.exports = { createTable };
package/src/importDate.js DELETED
@@ -1,105 +0,0 @@
1
- const { Client } = require("pg");
2
- const pAll = require("p-all");
3
- const debug = require("debug")("importDate");
4
- const formatISO = require("date-fns/formatISO");
5
-
6
- const { importEvent, getEventsFromMatomoVisit } = require("./importEvent");
7
-
8
- const { RESULTPERPAGE, MATOMO_SITE, DESTINATION_TABLE } = require("./config");
9
-
10
- /**
11
- * return date as ISO yyyy-mm-dd
12
- *
13
- * @param {Date} date date
14
- *
15
- * @returns {string}
16
- */
17
- // @ts-ignore
18
- const isoDate = (date) => formatISO(date, { representation: "date" });
19
-
20
- /**
21
- * check count if imported rows for given date
22
- *
23
- * @param {Client} client PG client
24
- * @param {Date} date datetime as ISO string
25
- *
26
- * @returns {Promise<any>}
27
- */
28
- const getRecordsCount = async (client, date) => {
29
- const text = `SELECT COUNT(distinct idvisit) FROM ${client.escapeIdentifier(
30
- DESTINATION_TABLE
31
- )} WHERE action_timestamp::date='${isoDate(date)}';`;
32
- const result = await client.query(text);
33
- return parseInt((result && result.rows && result.rows.length && result.rows[0].count) || 0);
34
- };
35
-
36
- /**
37
- * import all matomo data for a given date
38
- *
39
- * @param {Client} client PG client
40
- * @param {any} piwikApi piwik.api instance
41
- * @param {Date} date datetime as ISO string
42
- *
43
- * @returns {Promise<any[]>}
44
- */
45
- const importDate = async (client, piwikApi, date, filterOffset = 0) => {
46
- const limit = parseInt(RESULTPERPAGE);
47
- const offset = filterOffset || (await getRecordsCount(client, date));
48
- if (!offset) {
49
- debug(`${isoDate(date)}: load ${limit} visits`);
50
- } else {
51
- debug(`${isoDate(date)}: load ${limit} more visits after ${offset}`);
52
- }
53
- // fetch visits details
54
- const visits = await new Promise((resolve) =>
55
- piwikApi(
56
- {
57
- method: "Live.getLastVisitsDetails",
58
- period: "day",
59
- date: isoDate(date),
60
- filter_limit: limit,
61
- filter_offset: offset,
62
- filter_sort_order: "asc",
63
- idSite: MATOMO_SITE,
64
- },
65
- (err, responseObject = []) => {
66
- if (err) {
67
- console.error("err", err);
68
- resolve([]);
69
- }
70
- return resolve(responseObject || []);
71
- }
72
- )
73
- );
74
-
75
- debug(`fetched ${visits.length} visits`);
76
-
77
- // flatten events
78
- const allEvents = visits.flatMap(getEventsFromMatomoVisit);
79
-
80
- if (!allEvents.length) {
81
- debug(`no more valid events after ${isoDate(date)}`);
82
- return [];
83
- }
84
-
85
- debug(`import ${allEvents.length} events`);
86
-
87
- // serial-import events into PG
88
- const importedEvents = await pAll(
89
- allEvents.map((event) => () => importEvent(client, event)),
90
- { concurrency: 10, stopOnError: true }
91
- );
92
-
93
- // continue to next page if necessary
94
- if (visits.length === limit) {
95
- const nextOffset = offset + limit;
96
- const nextEvents = await importDate(client, piwikApi, date, nextOffset);
97
- return [...importedEvents, ...(nextEvents || [])];
98
- }
99
-
100
- debug(`finished importing ${isoDate(date)}, offset ${offset}`);
101
-
102
- return importedEvents || [];
103
- };
104
-
105
- module.exports = { importDate };
@@ -1,108 +0,0 @@
1
- const { Client } = require("pg");
2
-
3
- const { DESTINATION_TABLE } = require("./config");
4
-
5
- /**
6
- *
7
- * @param {Client} client
8
- * @param {import("types").Event} event
9
- *
10
- * @return {Promise<Record<"rows", any[]>>}
11
- */
12
- const importEvent = (client, event) => {
13
- const eventKeys = Object.keys(event);
14
- const text = `insert into ${client.escapeIdentifier(DESTINATION_TABLE)}
15
- (${eventKeys.join(", ")})
16
- values (${eventKeys.map((k, i) => `\$${i + 1}`).join(", ")})
17
- ON CONFLICT DO NOTHING`;
18
- const values = [...eventKeys.map((k) => event[k])];
19
- return client.query(text, values).catch((e) => {
20
- console.log("QUERY error", e);
21
- return { rows: [] };
22
- });
23
- };
24
-
25
- const matomoProps = [
26
- "idSite",
27
- "idVisit",
28
- "actions",
29
- "country",
30
- "region",
31
- "city",
32
- "operatingSystemName",
33
- "deviceModel",
34
- "deviceBrand",
35
- "visitDuration",
36
- "daysSinceFirstVisit",
37
- "visitorType",
38
- "visitorId",
39
- "referrerType",
40
- "referrerName",
41
- "siteName",
42
- "userId",
43
- ];
44
-
45
- /** @type Record<string, (a: import("types/matomo").ActionDetail) => string | number> */
46
- const actionProps = {
47
- action_type: (action) => action.type,
48
- action_title: (action) => action.title,
49
- action_eventcategory: (action) => action.eventCategory,
50
- action_eventaction: (action) => action.eventAction,
51
- action_eventname: (action) => action.eventName,
52
- action_eventvalue: (action) => action.eventValue,
53
- action_timespent: (action) => action.timeSpent,
54
- action_timestamp: (action) => new Date(action.timestamp * 1000).toISOString(),
55
- action_url: (action) => action.url,
56
- siteSearchKeyword: (action) => action.siteSearchKeyword,
57
- };
58
-
59
- /**
60
- * Convert a single matomo visit {<import("types/matomo").Visit} to multiple {Event[]}
61
- *
62
- * @param {Partial<import("types/matomo").Visit>} matomoVisit
63
- *
64
- * @return {import("types").Event[]} list of events
65
- *
66
- */
67
- const getEventsFromMatomoVisit = (matomoVisit) => {
68
- return matomoVisit.actionDetails.map((actionDetail, actionIndex) => {
69
- /** @type {Record<string, string>} */
70
- const usercustomproperties = {};
71
- for (let k = 1; k < 10; k++) {
72
- const property = actionDetail.customVariables && actionDetail.customVariables[k];
73
- if (!property) continue; // max 10 custom variables
74
- usercustomproperties[property[`customVariableName${k}`]] = property[`customVariableValue${k}`];
75
- }
76
-
77
- /** @type {Record<string, string>} */
78
- const usercustomdimensions = {};
79
- for (let k = 1; k < 11; k++) {
80
- const dimension = `dimension${k}`;
81
- const value = actionDetail[dimension] || matomoVisit[dimension];
82
- if (!value) continue; // max 10 custom variables
83
- usercustomdimensions[dimension] = value;
84
- }
85
-
86
- /** @type {import("types").Event} */
87
- // @ts-ignore
88
- const event = {
89
- // default matomo visit properties
90
- ...matomoProps.reduce((a, prop) => ({ ...a, [prop.toLowerCase()]: matomoVisit[prop] }), {}),
91
- serverdateprettyfirstaction: new Date((matomoVisit.firstActionTimestamp || 0) * 1000).toISOString(),
92
- // action specific properties
93
- ...Object.keys(actionProps).reduce((a, prop) => ({ ...a, [prop]: actionProps[prop](actionDetail) }), {
94
- action_id: `${matomoVisit.idVisit}_${actionIndex}`,
95
- }),
96
- // custom variables
97
- usercustomproperties,
98
- // custom dimensions
99
- // We keep both for backwards compatibility.
100
- // Current implementation is flat with one column for each dimension.
101
- usercustomdimensions,
102
- ...usercustomdimensions,
103
- };
104
- return event;
105
- });
106
- };
107
-
108
- module.exports = { getEventsFromMatomoVisit, importEvent };
package/src/index.js DELETED
@@ -1,79 +0,0 @@
1
- const pAll = require("p-all");
2
- const debug = require("debug")("index");
3
- const eachDayOfInterval = require("date-fns/eachDayOfInterval");
4
- const PiwikClient = require("piwik-client");
5
- const { Client } = require("pg");
6
-
7
- const { MATOMO_KEY, MATOMO_URL, MATOMO_SITE, PGDATABASE, DESTINATION_TABLE, OFFSET } = require("./config");
8
-
9
- const { createTable } = require("./createTable");
10
- const { importDate } = require("./importDate");
11
-
12
- // run a sync with a 3-days range
13
- /**
14
- *
15
- * @param {string} [date]
16
- * @returns
17
- */
18
- async function run(date) {
19
- debug("run, date=" + date);
20
- const client = new Client({ connectionString: PGDATABASE });
21
- await client.connect();
22
-
23
- const piwik = new PiwikClient(MATOMO_URL, MATOMO_KEY);
24
-
25
- await createTable(client);
26
-
27
- // priority:
28
- // - optional parameter date
29
- // - last event in the table
30
- // - optional env.STARTDATE
31
- // - today
32
-
33
- let referenceDate;
34
- if (!referenceDate && date) referenceDate = new Date(date);
35
- if (!referenceDate) referenceDate = await findLastEventInMatomo(client);
36
- if (!referenceDate && process.env.STARTDATE) referenceDate = new Date(process.env.STARTDATE);
37
- if (!referenceDate) referenceDate = new Date();
38
-
39
- // @ts-ignore
40
- const dates = eachDayOfInterval({
41
- start: referenceDate,
42
- end: new Date(),
43
- });
44
-
45
- debug(`import : ${dates.join(", ")}`);
46
-
47
- // for each date, serial-import data
48
- const res = await pAll(
49
- dates.map((date) => () => importDate(client, piwik.api.bind(piwik), date)),
50
- { concurrency: 1, stopOnError: true }
51
- );
52
-
53
- await client.end();
54
- debug("close");
55
-
56
- return res;
57
- }
58
-
59
- module.exports = run;
60
-
61
- if (require.main === module) {
62
- (async () => {
63
- if (!MATOMO_SITE) return console.error("Missing env MATOMO_SITE");
64
- if (!MATOMO_KEY) return console.error("Missing env MATOMO_KEY");
65
- if (!PGDATABASE) return console.error("Missing env PGDATABASE");
66
- await run();
67
- debug("run finished");
68
- })();
69
- }
70
-
71
- async function findLastEventInMatomo(client) {
72
- const a = await client.query(
73
- `select action_timestamp from ${client.escapeIdentifier(DESTINATION_TABLE)} order by action_timestamp desc limit 1`
74
- );
75
- if (!a.rows.length || !a.rows[0].action_timestamp) return null;
76
- const d = new Date(a.rows[0].action_timestamp);
77
- d.setDate(d.getDate() - +OFFSET);
78
- return d;
79
- }