diff --git a/.gitignore b/.gitignore
index 4c799eb..fd06b40 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+AutomaticClockOuts/.env
bin
obj
csx
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..a55e7a1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/AutomaticClockOuts/clock_out.js b/AutomaticClockOuts/clock_out.js
new file mode 100644
index 0000000..9807267
--- /dev/null
+++ b/AutomaticClockOuts/clock_out.js
@@ -0,0 +1,56 @@
+const CosmosClient = require("@azure/cosmos").CosmosClient;
+const config = require("./config");
+const TimeEntry = require('./time_entry');
+const axios = require('axios');
+const MsalClient = require('./msal_client')
+
+const doClockOut = async (context, timer) => {
+ context.log(`I am going to check how many entries were not clocked out ${new Date()}`);
+ const {endpoint, key, databaseId, containerId, slackWebHook} = config;
+ const client = new CosmosClient({endpoint, key});
+ const database = client.database(databaseId);
+ const container = database.container(containerId);
+ const response = await MsalClient.findUsersInMS();
+ const users = response.data.value;
+
+ const QUERY_WITHOUT_END_DATE =
+ "SELECT * FROM c WHERE (NOT IS_DEFINED(c.end_date) OR IS_NULL(c.end_date) = true) AND IS_DEFINED(c.start_date)"
+
+ const {resources: entries} = await container.items
+ .query({query: QUERY_WITHOUT_END_DATE})
+ .fetchAll();
+
+ context.log(`Checking for time-entries that need to be clocked out`);
+ let totalClockOutsExecuted = 0;
+ const usersWithClockOut = []
+ await Promise.all(entries.map(async (timeEntryAsJson) => {
+ const timeEntry = new TimeEntry(timeEntryAsJson)
+ if (timeEntry.needsToBeClockedOut()) {
+ usersWithClockOut.push(findUser(users, timeEntry.timeEntry.owner_id))
+ timeEntryAsJson.end_date = timeEntry.getTimeToClockOut()
+ await container.item(timeEntryAsJson.id, timeEntryAsJson.tenant_id).replace(timeEntryAsJson)
+ totalClockOutsExecuted++
+ }
+ }));
+ if(totalClockOutsExecuted > 0){
+ axios.post(slackWebHook,
+ {
+ "text": `Hey guys, I did a clock out for you. \nVisit https://timetracker.ioet.com/ and set the right end time for your entries :pls: \n- ${usersWithClockOut.join('\n- ')}`
+ }
+ )
+ .then(function (response) {
+ // console.log(response);
+ })
+ .catch(function (error) {
+ context.log(error);
+ });
+ }
+ context.log(`I just clocked out ${totalClockOutsExecuted} entries, thanks are not needed...`);
+}
+
+const findUser = (users, id) => {
+ const user = users.find( user => user.objectId === id)
+ return user.displayName
+}
+
+module.exports = { doClockOut };
diff --git a/AutomaticClockOuts/config.js b/AutomaticClockOuts/config.js
index 09eb6f8..2bdab84 100644
--- a/AutomaticClockOuts/config.js
+++ b/AutomaticClockOuts/config.js
@@ -1,9 +1,13 @@
const config = {
- endpoint: "https://time-tracker-db.documents.azure.com:443/",
- key: process.env["COSMOS_DB_KEY"],
+ endpoint: "xxx",
+ key: "xxx",
databaseId: "time-tracker-db",
containerId: "time_entry",
- partitionKey: { kind: "Hash", paths: ["/category"] }
+ partitionKey: { kind: "Hash", paths: ["/category"] },
+ clientId: "xxx",
+ authority: "xxx",
+ clientSecret: "xxx",
+ slackWebHook: "xxx"
};
module.exports = config;
diff --git a/AutomaticClockOuts/index.js b/AutomaticClockOuts/index.js
index 155c030..22de912 100644
--- a/AutomaticClockOuts/index.js
+++ b/AutomaticClockOuts/index.js
@@ -1,33 +1,10 @@
const CosmosClient = require("@azure/cosmos").CosmosClient;
-const moment = require("moment")
const config = require("./config");
const TimeEntry = require('./time_entry');
+const axios = require('axios');
+const MsalClient = require('./msal_client');
+const ClockOut = require('./clock_out');
module.exports = async function (context, myTimer) {
-
- context.log(`I am going to check how many entries were not clocked out ${new Date()}`);
- const {endpoint, key, databaseId, containerId} = config;
- const client = new CosmosClient({endpoint, key});
- const database = client.database(databaseId);
- const container = database.container(containerId);
-
- const QUERY_WITHOUT_END_DATE =
- "SELECT * FROM c WHERE (NOT IS_DEFINED(c.end_date) OR IS_NULL(c.end_date) = true) AND IS_DEFINED(c.start_date)"
-
- const {resources: entries} = await container.items
- .query({query: QUERY_WITHOUT_END_DATE})
- .fetchAll();
-
- context.log(`Checking for time-entries that need to be clocked out`);
- let totalClockOutsExecuted = 0;
- await Promise.all(entries.map(async (timeEntryAsJson) => {
- const timeEntry = new TimeEntry(timeEntryAsJson)
- if (timeEntry.needsToBeClockedOut()) {
- context.log(`I am going to clock out ${JSON.stringify(timeEntryAsJson.id)}`);
- timeEntryAsJson.end_date = timeEntry.getTimeToClockOut()
- await container.item(timeEntryAsJson.id, timeEntryAsJson.tenant_id).replace(timeEntryAsJson)
- totalClockOutsExecuted++
- }
- }));
- context.log(`I just clocked out ${totalClockOutsExecuted} entries, thanks are not needed...`);
+ await ClockOut.doClockOut(context, myTimer)
};
diff --git a/AutomaticClockOuts/msal_client.js b/AutomaticClockOuts/msal_client.js
new file mode 100644
index 0000000..332cb5b
--- /dev/null
+++ b/AutomaticClockOuts/msal_client.js
@@ -0,0 +1,27 @@
+const axios = require("axios")
+const msal = require('@azure/msal-node');
+const config = require("./config");
+
+const findUsersInMS = async() => {
+ const {clientId, authority, clientSecret} = config;
+ const endpoint = 'https://graph.windows.net/ioetec.onmicrosoft.com'
+ const configuration = {
+ auth: {
+ clientId: clientId,
+ authority: authority,
+ clientSecret: clientSecret
+ }
+ };
+
+ const cca = new msal.ConfidentialClientApplication(configuration);
+ const clientCredentialRequest = {
+ scopes: ['https://graph.windows.net/.default'],
+ };
+ const response = await cca.acquireTokenByClientCredential(clientCredentialRequest)
+ const token = response.accessToken
+ return axios.get(`${endpoint}/users?api-version=1.6&$select=displayName,otherMails,objectId`,
+ { 'headers': { 'Authorization': token } })
+}
+
+module.exports = { findUsersInMS };
+
diff --git a/AutomaticClockOuts/package-lock.json b/AutomaticClockOuts/package-lock.json
index 6deb375..0cc7627 100644
--- a/AutomaticClockOuts/package-lock.json
+++ b/AutomaticClockOuts/package-lock.json
@@ -21,6 +21,58 @@
"uuid": "^3.3.2"
}
},
+ "@azure/msal-common": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-1.2.0.tgz",
+ "integrity": "sha512-5MjxcUoalIxGo29MBHCdwMo6HCsCBt25HDkg+Du7x6nG7Y3NAUb9jM8oibLTth9iIRDaWYVvLfxnpXTKwBGs+A==",
+ "requires": {
+ "debug": "^4.1.1"
+ }
+ },
+ "@azure/msal-node": {
+ "version": "1.0.0-alpha.5",
+ "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.0.0-alpha.5.tgz",
+ "integrity": "sha512-DkoEmnGy+PF5UZbViuLrO8qJVKRBftIojEP3xf8ck6q/vjOY18NUGXxrcKkRXfhRmTe4P2mRGCFuiil8+12IbA==",
+ "requires": {
+ "@azure/msal-common": "^1.2.0",
+ "axios": "^0.19.2",
+ "debug": "^4.1.1",
+ "jsonwebtoken": "^8.5.1"
+ },
+ "dependencies": {
+ "axios": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
+ "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
+ "requires": {
+ "follow-redirects": "1.5.10"
+ }
+ },
+ "follow-redirects": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
+ "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
+ "requires": {
+ "debug": "=3.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ }
+ }
+ },
"@babel/code-frame": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
@@ -395,6 +447,14 @@
"yargs": "^15.4.1"
}
},
+ "axios": {
+ "version": "0.20.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz",
+ "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==",
+ "requires": {
+ "follow-redirects": "^1.10.0"
+ }
+ },
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@@ -466,6 +526,11 @@
"fill-range": "^7.0.1"
}
},
+ "buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
+ },
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
@@ -859,12 +924,25 @@
"is-obj": "^2.0.0"
}
},
+ "dotenv": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
+ "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
+ },
"duplexer3": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
"integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=",
"dev": true
},
+ "ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "requires": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
"emittery": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.1.tgz",
@@ -912,11 +990,6 @@
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
- "esm": {
- "version": "3.2.25",
- "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
- "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA=="
- },
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
@@ -1005,6 +1078,11 @@
"path-exists": "^4.0.0"
}
},
+ "follow-redirects": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz",
+ "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA=="
+ },
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -1360,6 +1438,42 @@
"integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
"dev": true
},
+ "jsonwebtoken": {
+ "version": "8.5.1",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
+ "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
+ "requires": {
+ "jws": "^3.2.2",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^5.6.0"
+ }
+ },
+ "jwa": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+ "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+ "requires": {
+ "buffer-equal-constant-time": "1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "jws": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "requires": {
+ "jwa": "^1.4.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
"keyv": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz",
@@ -1412,6 +1526,41 @@
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==",
"dev": true
},
+ "lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
+ },
+ "lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
+ },
+ "lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
+ },
+ "lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
+ },
+ "lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
+ },
+ "lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
+ },
+ "lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
+ },
"log-symbols": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz",
@@ -1607,6 +1756,14 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
+ "msal": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/msal/-/msal-1.4.0.tgz",
+ "integrity": "sha512-NTxMFQh6t5g2QWMlvZTWTxL1bmcqiCv0cs2lxTHhUbWEuxWCfvaVRZfjxN8i+T0VltVVGaVIdML8QEoBnlbaSw==",
+ "requires": {
+ "tslib": "^1.9.3"
+ }
+ },
"mute-stream": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
@@ -2093,8 +2250,7 @@
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "dev": true
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"semaphore": {
"version": "1.1.0",
diff --git a/AutomaticClockOuts/package.json b/AutomaticClockOuts/package.json
index fb21eb7..e04938c 100644
--- a/AutomaticClockOuts/package.json
+++ b/AutomaticClockOuts/package.json
@@ -8,7 +8,11 @@
},
"dependencies": {
"@azure/cosmos": "3.5.2",
- "moment": "^2.27.0"
+ "@azure/msal-node": "^1.0.0-alpha.5",
+ "axios": "^0.20.0",
+ "dotenv": "^8.2.0",
+ "moment": "^2.27.0",
+ "msal": "^1.4.0"
},
"author": "",
"license": "ISC",
diff --git a/AutomaticClockOuts/readme.md b/AutomaticClockOuts/readme.md
index f53fc63..ae88d19 100644
--- a/AutomaticClockOuts/readme.md
+++ b/AutomaticClockOuts/readme.md
@@ -29,5 +29,12 @@ func azure functionapp publish time-tracker-azure-functions
```
NOTE:
-Don't forget to set up the env variable `COSMOS_DB_KEY`, check the pinned
-message in our slack channel to get this value
+Don't forget to set the following environment variables to make this app work:
+
+```sh
+COSMOS_DB_KEY
+MS_CLIENT_ID
+MS_AUTHORITY
+MS_CLIENT_SECRET
+```
+Check the pinned message in our slack channel to get these values
diff --git a/AutomaticClockOuts/test/clock_out.spec.js b/AutomaticClockOuts/test/clock_out.spec.js
new file mode 100644
index 0000000..d3b954a
--- /dev/null
+++ b/AutomaticClockOuts/test/clock_out.spec.js
@@ -0,0 +1,12 @@
+const test = require('ava');
+const ClockOut = require('../clock_out')
+
+test('do clock out', async t => {
+ // const users = await MsalClient.findUsersInMS()
+ const log = (text) => console.log(text)
+ const context = {
+ log
+ }
+ // await ClockOut.doClockOut(context, null)
+ t.truthy(2>1)
+})
diff --git a/AutomaticClockOuts/test/index.spec.js b/AutomaticClockOuts/test/index.spec.js
deleted file mode 100644
index e69de29..0000000
diff --git a/AutomaticClockOuts/test/msal_client.spec.js b/AutomaticClockOuts/test/msal_client.spec.js
new file mode 100644
index 0000000..0717be1
--- /dev/null
+++ b/AutomaticClockOuts/test/msal_client.spec.js
@@ -0,0 +1,8 @@
+const test = require('ava');
+const MsalClient = require('../msal_client')
+
+test('Response contains ioet.com since users has it as part their emails', async t => {
+ const users = await MsalClient.findUsersInMS()
+
+ t.truthy(JSON.stringify(users.data.value).includes("ioet.com"))
+})