5htp 0.3.6 → 0.3.8

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.
Files changed (101) hide show
  1. package/package.json +1 -1
  2. package/skeleton/docker-compose.yml +7 -15
  3. package/skeleton/identity.yaml +12 -8
  4. package/skeleton/package.json +25 -7
  5. package/skeleton/src/client/assets/identity/logo.svg +13 -63
  6. package/skeleton/src/client/assets/identity/logoAndText.svg +9 -104
  7. package/skeleton/src/client/assets/identity/logoAndTextBlack.svg +11 -0
  8. package/skeleton/src/client/assets/illustration/landing/banner.webp +0 -0
  9. package/skeleton/src/client/assets/illustration/landing/candidate/employers.webp +0 -0
  10. package/skeleton/src/client/assets/illustration/landing/candidate/hero.webp +0 -0
  11. package/skeleton/src/client/assets/illustration/landing/candidate/mentors.webp +0 -0
  12. package/skeleton/src/client/assets/illustration/landing/headhunter/hero.webp +0 -0
  13. package/skeleton/src/client/assets/illustration/landing/hero.jpeg +0 -0
  14. package/skeleton/src/client/assets/illustration/landing/hero.webp +0 -0
  15. package/skeleton/src/client/assets/illustration/landing/hero.xcf +0 -0
  16. package/skeleton/src/client/assets/illustration/landing/recruiter/onboarding.webp +0 -0
  17. package/skeleton/src/client/assets/illustration/landing/team/andre.png +0 -0
  18. package/skeleton/src/client/assets/illustration/landing/team/emma.png +0 -0
  19. package/skeleton/src/client/assets/illustration/landing/team/fei.png +0 -0
  20. package/skeleton/src/client/assets/illustration/landing/team/gaetan.png +0 -0
  21. package/skeleton/src/client/assets/illustration/landing/team/jordan.png +0 -0
  22. package/skeleton/src/client/assets/illustration/landing/team/lery.png +0 -0
  23. package/skeleton/src/client/assets/illustration/landing/team/mehdi.png +0 -0
  24. package/skeleton/src/client/assets/illustration/landing/team/omkar.png +0 -0
  25. package/skeleton/src/client/assets/illustration/landing/team/thibaut.png +0 -0
  26. package/skeleton/src/client/assets/img/background/header-blur.png +0 -0
  27. package/skeleton/src/client/assets/img/partners/citron.svg +987 -0
  28. package/skeleton/src/client/assets/patterns/dots.png +0 -0
  29. package/skeleton/src/client/assets/theme.less +179 -226
  30. package/skeleton/src/client/assets/vars.less +54 -0
  31. package/skeleton/src/client/context.ts +23 -0
  32. package/skeleton/src/client/index.ts +59 -0
  33. package/skeleton/src/client/pages/_messages/400.tsx +45 -0
  34. package/skeleton/src/client/pages/_messages/401.tsx +39 -0
  35. package/skeleton/src/client/pages/_messages/403.tsx +43 -0
  36. package/skeleton/src/client/pages/_messages/404.tsx +42 -0
  37. package/skeleton/src/client/pages/_messages/500.tsx +42 -0
  38. package/skeleton/src/client/pages/platform/Header.less +12 -0
  39. package/skeleton/src/client/pages/platform/Header.tsx +119 -0
  40. package/skeleton/src/client/pages/platform/_layout/index.less +118 -0
  41. package/skeleton/src/client/pages/platform/_layout/index.tsx +131 -0
  42. package/skeleton/src/client/pages/platform/_layout/mobile.less +114 -0
  43. package/skeleton/src/client/pages/platform/_page.tsx +54 -0
  44. package/skeleton/src/client/pages/platform/headhunters/index.tsx +88 -0
  45. package/skeleton/src/client/pages/platform/index.tsx +58 -0
  46. package/skeleton/src/client/pages/platform/missions/index.tsx +149 -0
  47. package/skeleton/src/client/services/metrics/index.ts +59 -0
  48. package/skeleton/src/client/tsconfig.json +4 -1
  49. package/skeleton/src/common/config/router.ts +16 -0
  50. package/skeleton/src/common/forms/company/bookCall.ts +25 -0
  51. package/skeleton/src/common/forms/company/importJob.ts +26 -0
  52. package/skeleton/src/common/forms/company/signup.ts +31 -0
  53. package/skeleton/src/common/forms/headhunter/feedback.ts +31 -0
  54. package/skeleton/src/common/forms/headhunter/mission/cancel.ts +26 -0
  55. package/skeleton/src/common/forms/headhunter/mission/candidate/availability.ts +31 -0
  56. package/skeleton/src/common/forms/headhunter/mission/candidate/education.ts +19 -0
  57. package/skeleton/src/common/forms/headhunter/mission/candidate/identity.ts +31 -0
  58. package/skeleton/src/common/forms/headhunter/mission/candidate/others.ts +19 -0
  59. package/skeleton/src/common/forms/headhunter/mission/candidate/skills.ts +21 -0
  60. package/skeleton/src/common/forms/headhunter/mission/reject.ts +23 -0
  61. package/skeleton/src/common/forms/headhunter/mission/search.ts +78 -0
  62. package/skeleton/src/common/forms/headhunter/signup.ts +34 -0
  63. package/skeleton/src/common/libs/headhunter/candidate/index.ts +155 -0
  64. package/skeleton/src/common/libs/headhunter/mission/index.ts +30 -0
  65. package/skeleton/src/common/libs/hub/index.ts +41 -0
  66. package/skeleton/src/server/config/communication.ts +48 -0
  67. package/skeleton/src/server/config/crosspath.ts +9 -0
  68. package/skeleton/src/server/config/data.ts +34 -0
  69. package/skeleton/src/server/config/database.ts +26 -0
  70. package/skeleton/src/server/config/internal.ts +21 -0
  71. package/skeleton/src/server/config/user.ts +90 -0
  72. package/skeleton/src/server/index.ts +111 -23
  73. package/skeleton/src/server/libs/utils/slug.ts +11 -0
  74. package/skeleton/src/server/routes/global.ts +33 -0
  75. package/skeleton/src/server/routes/headhunters.ts +24 -0
  76. package/skeleton/src/server/routes/missions.ts +50 -0
  77. package/skeleton/src/server/services/Headhunter/index.ts +127 -0
  78. package/skeleton/src/server/services/Headhunter/service.json +6 -0
  79. package/skeleton/src/server/services/Mission/index.ts +174 -0
  80. package/skeleton/src/server/services/Mission/service.json +6 -0
  81. package/skeleton/src/server/services/email/sendgrid/index.ts +97 -0
  82. package/skeleton/src/server/services/email/sendgrid/service.json +6 -0
  83. package/skeleton/src/server/services/slack/index.ts +105 -0
  84. package/skeleton/src/server/services/slack/service.json +6 -0
  85. package/skeleton/src/server/services/users/index.ts +133 -0
  86. package/skeleton/src/server/services/users/service.json +6 -0
  87. package/skeleton/src/server/tsconfig.json +6 -8
  88. package/skeleton/var/typings/routes.d.ts +541 -0
  89. package/src/index.ts +1 -1
  90. package/src/utils/keyboard.ts +1 -1
  91. package/skeleton/package-lock.json +0 -6139
  92. package/skeleton/src/client/components/LoginModal.tsx +0 -45
  93. package/skeleton/src/client/pages/app/_layout/index.less +0 -20
  94. package/skeleton/src/client/pages/app/_layout/index.tsx +0 -33
  95. package/skeleton/src/client/pages/app/index.tsx +0 -57
  96. package/skeleton/src/client/pages/landing/_layout/index.less +0 -145
  97. package/skeleton/src/client/pages/landing/_layout/index.tsx +0 -63
  98. package/skeleton/src/client/pages/landing/index.tsx +0 -73
  99. package/skeleton/src/server/models.ts +0 -117
  100. package/skeleton/src/server/routes/general.ts +0 -66
  101. package/skeleton/src/server/services/auth/index.ts +0 -80
@@ -1,23 +1,111 @@
1
- import app from '@server/app';
2
- import './config';
3
-
4
- // Load core services
5
- import '@/server/services/auth';
6
- import '@server/services/email';
7
- import '@server/services/socket';
8
- import '@server/services/cron';
9
- import '@server/services/database';
10
- import '@server/services/console';
11
- import '@server/services/http';
12
-
13
- // Load app-specific services
14
- // ...
15
-
16
- // Register client & server route
17
- // Use require to avoid circular references
18
- require("@/server/routes/**/*.ts");
19
- require("@server/routes/**/*.ts");
20
- require("@/client/pages/**/*.tsx");
21
- require("@client/pages/**/*.tsx");
22
-
23
- app.launch();
1
+ /*----------------------------------
2
+ - DEPS
3
+ ----------------------------------*/
4
+ import { Application } from '@server/app';
5
+ import type { ServerBug } from '@common/errors';
6
+
7
+ /*----------------------------------
8
+ - APPLICATION
9
+ ----------------------------------*/
10
+ export default class CrossPathCSM extends Application {
11
+
12
+ /*----------------------------------
13
+ - CORE ERVICES
14
+ ----------------------------------*/
15
+
16
+ public Users = this.use('CrossPath/Users');
17
+
18
+ public Disks = this.use('Core/Disks', {
19
+ s3: this.use('Core/Disks/S3'),
20
+ local: this.use('Core/Disks/Local')
21
+ });
22
+
23
+ public Router = this.use('Core/Router', {
24
+ // Router services
25
+ auth: this.use('Core/Users/Router', {
26
+ users: this.Users
27
+ }),
28
+ schema: this.use('Core/Schema/Router')
29
+ });
30
+
31
+ public SQL = this.use('Core/Database/SQL');
32
+
33
+ public Email = this.use('Core/Email', {
34
+ sendgrid: this.use('CrossPath/Email/Sendgrid')
35
+ });
36
+
37
+ public Fetch = this.use('Core/Fetch', {
38
+ disks: this.Disks,
39
+ router: this.Router
40
+ });
41
+
42
+ public Cron = this.use('Core/Cron');
43
+
44
+ public AES = this.use('Core/Encryption/AES');
45
+
46
+ public Slack = this.use('CrossPath/Slack');
47
+
48
+ /*----------------------------------
49
+ - BUSINESS SERVICES
50
+ ----------------------------------*/
51
+
52
+ public Mission = this.use('CrossPath/Mission');
53
+
54
+ public Headhunter = this.use('CrossPath/Headhunter');
55
+
56
+ /*----------------------------------
57
+ - LIFECYCLE
58
+ ----------------------------------*/
59
+
60
+ public async launch() {
61
+
62
+ console.log("Launching CrossPath CSM Server");
63
+
64
+ }
65
+
66
+ public async ready() {
67
+
68
+ console.log("CrossPath CSM Server is ready");
69
+
70
+ }
71
+
72
+ public async reportBug( report: ServerBug ) {
73
+
74
+ if (this.env.name === 'local')
75
+ return console.warn(`Cancel bug report since we're in local.`);
76
+
77
+ this.Email.started && this.Email.send({
78
+ from: this.Email.config.bugReport.from,
79
+ to: this.Email.config.bugReport.to,
80
+ subject: "Bug on CSM server: " + (report.error.message),
81
+ html: `
82
+ <a href="${this.Router.http.publicUrl}/admin/activity/requests/${report.channelId}">
83
+ View Request details & console
84
+ </a>
85
+ <br/>
86
+ Channel Type: ${report.channelType}
87
+ Channel ID: ${report.channelId}
88
+ User: ${report.user}
89
+ IP: ${report.ip}
90
+ <br/>
91
+ ${report.logs}
92
+ `
93
+ })
94
+
95
+ this.Slack.started && this.Slack.sendInChannel('engineering-alerts', `
96
+ *A bug occured on CSM server:* ${report.error.message}
97
+ Channel Type: ${report.channelType}
98
+ Channel ID: ${report.channelId}
99
+ User: ${report.user}
100
+ IP: ${report.ip}
101
+ Stacktrace:
102
+ ${report.error.stack}
103
+ `);
104
+ }
105
+
106
+ public async shutdown() {
107
+
108
+ console.log("CrossPath CSM Server will shutdown");
109
+
110
+ }
111
+ }
@@ -0,0 +1,11 @@
1
+
2
+ import slugify from 'slugify';
3
+
4
+ export const genSlug = (label: string) => slugify( label,{
5
+ replacement: '-', // replace spaces with replacement character, defaults to `-`
6
+ remove: undefined, // remove characters that match regex, defaults to `undefined`
7
+ lower: true, // convert to lower case, defaults to `false`
8
+ strict: false, // strip special characters except replacement, defaults to `false`
9
+ locale: 'vi', // language code of the locale to use
10
+ trim: true // trim leading and trailing replacement chars, defaults to `true`
11
+ });
@@ -0,0 +1,33 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
6
+
7
+ // Core
8
+ import { Router, Users } from '@app';
9
+
10
+ /*----------------------------------
11
+ - TYPES
12
+ ----------------------------------*/
13
+
14
+ /*----------------------------------
15
+ - SIGNUP
16
+ ----------------------------------*/
17
+
18
+ Router.get('/health', { priority: 11 }, async ({ auth, request, response}) => {
19
+ return "ok";
20
+ });
21
+
22
+ Router.get('/login', { priority: 11 }, async ({ schema, request, response }) => {
23
+
24
+ const { email, password } = schema.validate({
25
+ email: schema.email(),
26
+ password: schema.string(),
27
+ });
28
+
29
+ await Users.login(email, password, request);
30
+
31
+ return response.redirect('/');
32
+
33
+ });
@@ -0,0 +1,24 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
6
+
7
+ // Core
8
+ import { Router, Users, Headhunter } from '@app';
9
+
10
+ /*----------------------------------
11
+ - TYPES
12
+ ----------------------------------*/
13
+
14
+ /*----------------------------------
15
+ - SIGNUP
16
+ ----------------------------------*/
17
+
18
+ Router.get('/headhunters', async ({ schema, auth }) => {
19
+
20
+ const user = await auth.check('USER');
21
+
22
+ return await Headhunter.List();
23
+
24
+ });
@@ -0,0 +1,50 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
6
+
7
+ // Core
8
+ import { Router, Users, Mission } from '@app';
9
+
10
+ /*----------------------------------
11
+ - TYPES
12
+ ----------------------------------*/
13
+
14
+ /*----------------------------------
15
+ - SIGNUP
16
+ ----------------------------------*/
17
+
18
+ Router.get('/missions', async ({ schema, auth }) => {
19
+
20
+ const user = await auth.check('USER');
21
+
22
+ return await Mission.List(user);
23
+
24
+ });
25
+
26
+ Router.get('/missions/applications', async ({ schema, auth }) => {
27
+
28
+ const user = await auth.check('USER');
29
+
30
+ const { missionId } = schema.validate({
31
+ missionId: schema.string({ opt: true }),
32
+ });
33
+
34
+ return await Mission.Applications(user, missionId);
35
+
36
+ });
37
+
38
+ Router.post('/missions/applications/:response(approve|reject)', async ({ schema, auth, request }) => {
39
+
40
+ const user = await auth.check('USER');
41
+
42
+ const { missionId, headhunterId, response } = schema.validate({
43
+ missionId: schema.string(),
44
+ headhunterId: schema.string(),
45
+ response: schema.string()
46
+ });
47
+
48
+ return await Mission.RespondApplication( response, missionId, headhunterId, user );
49
+
50
+ });
@@ -0,0 +1,127 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
6
+ import got from 'got';
7
+
8
+ // Services
9
+ import { SQL, Router, Fetch } from '@app';
10
+
11
+ // Core
12
+ import Service from '@server/app/service';
13
+
14
+ // App
15
+ import type { Headhunter } from '@/server/models';
16
+
17
+ /*----------------------------------
18
+ - SERVICE TYPES
19
+ ----------------------------------*/
20
+
21
+ export type Config = {
22
+ debug?: boolean,
23
+ }
24
+
25
+ export type Hooks = {
26
+
27
+ }
28
+
29
+ export type Services = {
30
+
31
+ }
32
+
33
+ /*----------------------------------
34
+ - TYPES
35
+ ----------------------------------*/
36
+
37
+ export type HeadhunterWithKPIs = Headhunter & HeadhunterKPIs & {
38
+ url: string,
39
+ name: string,
40
+ onboarded: boolean,
41
+ }
42
+
43
+ export type HeadhunterKPIs = {
44
+
45
+ activity: number,
46
+ reactivity: number,
47
+ quality: number,
48
+ performance: number,
49
+
50
+ bonus: number,
51
+ levelProgress: number,
52
+ }
53
+
54
+ /*----------------------------------
55
+ - SERVICE
56
+ ----------------------------------*/
57
+ export default class HeadhuntersService extends Service<Config, Hooks, CrossPathCSM, Services> {
58
+
59
+ // Actors
60
+
61
+ /*----------------------------------
62
+ - LIFECYCLE
63
+ ----------------------------------*/
64
+
65
+ // Service start
66
+ protected async start() {
67
+
68
+
69
+ }
70
+
71
+ public async ready() {
72
+
73
+ }
74
+
75
+ public async shutdown() {
76
+
77
+ }
78
+
79
+ /*----------------------------------
80
+ - ACTIONS
81
+ ----------------------------------*/
82
+
83
+ public async List() {
84
+ return await SQL`
85
+ SELECT *,
86
+ CONCAT(firstName, ' ', lastName) as name
87
+ FROM Headhunter
88
+ ORDER BY created DESC
89
+ `.all();
90
+ }
91
+
92
+ /* public async List(): Promise<HeadhunterWithKPIs[]> {
93
+
94
+ const headhunterRepo = Headhunting.user.repository;
95
+
96
+ // https://www.notion.so/Headhunter-KPIs-b430f02765d941058b3a64e89a04d8a2
97
+ return await SQL<HeadhunterWithKPIs>`
98
+ SELECT
99
+ CONCAT(firstName, ' ', lastName) as name,
100
+ CONCAT('/admin/headhunters/', airtableId) as url,
101
+
102
+ (password IS NOT NULL) as onboarded,
103
+
104
+ (${headhunterRepo.kpis.activity}) as activity,
105
+ (${headhunterRepo.kpis.reactivity}) as reactivity,
106
+ (${headhunterRepo.kpis.quality}) as quality,
107
+ (${headhunterRepo.kpis.performance}) as performance,
108
+
109
+ (${Headhunting.user.gamification.queries.bonus}) as bonus,
110
+ (${Headhunting.user.gamification.queries.levelProgress}) as levelProgress
111
+
112
+ FROM Headhunter
113
+ ORDER BY performance DESC, quality DESC, activity DESC, reactivity ASC
114
+ LIMIT 100
115
+ `.all();
116
+ }*/
117
+
118
+ public async Suggested() {
119
+ return await SQL<HeadhunterWithKPIs>`
120
+ SELECT
121
+ CONCAT(firstName, ' ', lastName) as name,
122
+ CONCAT('/admin/headhunters/', airtableId) as url
123
+ FROM Headhunter
124
+ LIMIT 20
125
+ `.all();
126
+ }
127
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "CrossPath/Headhunter",
3
+ "name": "CrossPathHeadhunter",
4
+ "parent": "app",
5
+ "dependences": []
6
+ }
@@ -0,0 +1,174 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
6
+ import got from 'got';
7
+ import { RemoteProvider } from '5htp-airtable';
8
+
9
+ // Services
10
+ import { SQL, Router, Email } from '@app';
11
+
12
+ // Core
13
+ import Service from '@server/app/service';
14
+
15
+ // App
16
+
17
+ /*----------------------------------
18
+ - SERVICE TYPES
19
+ ----------------------------------*/
20
+
21
+ export const ApplicationResponses = ['approve', 'reject'] as const
22
+ export type TApplicationResponse = typeof ApplicationResponses[number]
23
+
24
+ export type Config = {
25
+ debug?: boolean,
26
+ }
27
+
28
+ export type Hooks = {
29
+
30
+ }
31
+
32
+ export type Services = {
33
+
34
+ }
35
+
36
+ /*----------------------------------
37
+ - SERVICE
38
+ ----------------------------------*/
39
+ export default class MissionsService extends Service<Config, Hooks, CrossPathCSM, Services> {
40
+
41
+ private provider = new RemoteProvider(this.app, "@recruiters/system/provider", "missions");
42
+
43
+ /*----------------------------------
44
+ - LIFECYCLE
45
+ ----------------------------------*/
46
+
47
+ // Service start
48
+ protected async start() {
49
+
50
+ await this.provider.start();
51
+
52
+
53
+ }
54
+
55
+ public async ready() {
56
+
57
+ }
58
+
59
+ public async shutdown() {
60
+
61
+ }
62
+
63
+ /*----------------------------------
64
+ - ACTIONS
65
+ ----------------------------------*/
66
+
67
+ public async List( user: User ) {
68
+ return await SQL`
69
+ SELECT *
70
+ FROM Mission
71
+ WHERE
72
+ customerSuccess = ${user.slug}
73
+ AND
74
+ Mission.status = 'Active'
75
+ `.all();
76
+ }
77
+
78
+ public async Applications( user: User, missionId?: string ) {
79
+ return await SQL`
80
+ SELECT
81
+
82
+ dateStart,
83
+ mission as missionId,
84
+ headhunter as headhunterId,
85
+
86
+ CONCAT(Headhunter.firstName, ' ', Headhunter.lastName) as headhunter,
87
+ Mission.title as mission
88
+ FROM AcceptedMission
89
+
90
+ INNER JOIN Headhunter ON Headhunter.email = AcceptedMission.headhunter
91
+ INNER JOIN Mission ON Mission.airtableId = AcceptedMission.mission
92
+
93
+ WHERE
94
+ :${missionId === undefined
95
+ ? `Mission.customerSuccess = "${user.slug}"`
96
+ : `Mission.airtableId = "${missionId}"`}
97
+ AND
98
+ AcceptedMission.status = 'approving'
99
+
100
+ `.all();
101
+ }
102
+
103
+ public async RespondApplication(
104
+ action: TApplicationResponse,
105
+ missionId: string,
106
+ headhunterEmail: string
107
+ ) {
108
+
109
+ // retrieve mission information
110
+ const mission = await SQL`
111
+ SELECT title, company
112
+ FROM Mission
113
+ WHERE airtableId = ${missionId};
114
+ `.firstOrFail("Unable to find mission info.");
115
+
116
+ // Update DB
117
+ await SQL.update('AcceptedMission', {
118
+ mission: missionId,
119
+ headhunter: headhunterEmail,
120
+ status: action === 'approve' ? 'accepted' : 'rejected'
121
+ });
122
+
123
+ if (action === 'reject') {
124
+ // Send notification
125
+ return true;
126
+ }
127
+
128
+ // Retrieve the current list of headhunters for this mission
129
+ const headhuntersList = await SQL<{ headhunter: string }>`
130
+ SELECT Headhunter.airtableId as headhunter
131
+ FROM AcceptedMission
132
+ INNER JOIN Headhunter ON Headhunter.email = AcceptedMission.headhunter
133
+ WHERE
134
+ AcceptedMission.mission = ${missionId}
135
+ AND
136
+ AcceptedMission.status = 'accepted'
137
+ `.all().then(list =>
138
+ list.map(accepted => accepted.headhunter)
139
+ );
140
+
141
+ // Add the currentheadhunter to the list of headhunterds for this mission
142
+ await this.provider.airtable.update([{
143
+ id: missionId,
144
+ 'Headhunters Matched': [...headhuntersList]
145
+ }]);
146
+
147
+ // Send notification
148
+ const missionUrl = Router.url('@recruiters/mission/accepted/' + mission.company + '/' + missionId);
149
+ await Email.send( headhunterEmail,
150
+ `🎉 You've been selected to work on a new mission: ${mission.title}`,
151
+ `
152
+ Hey, I'm pleased to announce your application to work on the mission "${mission.title}" has been approved by our team!
153
+
154
+ You can start working on it starting from now, [directly on our platform](${missionUrl}).
155
+
156
+ Kind regards,
157
+ Your Cross Path Team
158
+ `
159
+ );
160
+
161
+ return true;
162
+
163
+ }
164
+
165
+ public async RejectApplication( user: User, missionId: string, headhunterId: string ) {
166
+
167
+ // Update Db & Airtable
168
+
169
+ // Send notification
170
+
171
+ return true;
172
+
173
+ }
174
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "CrossPath/Mission",
3
+ "name": "CrossPathMission",
4
+ "parent": "app",
5
+ "dependences": []
6
+ }
@@ -0,0 +1,97 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
6
+ const sgMail = require('@sendgrid/mail');
7
+
8
+ // Core
9
+ import { Transporter, TCompleteEmail } from '@server/services/email';
10
+ import type { Application } from "@server/app/index";
11
+
12
+ /*----------------------------------
13
+ - CONFIG
14
+ ----------------------------------*/
15
+
16
+ const LogPrefix = "[email][sendgrid]";
17
+
18
+ /*----------------------------------
19
+ - TYPES
20
+ ----------------------------------*/
21
+
22
+ export type TConfig = {
23
+ debug: boolean,
24
+ api: string
25
+ }
26
+
27
+ /*----------------------------------
28
+ - TRABSPORTER
29
+ ----------------------------------*/
30
+
31
+ export default class SendGridTransporter extends Transporter<TConfig> {
32
+
33
+ public constructor(
34
+ parent: Application,
35
+ config: TConfig,
36
+ services: {},
37
+ app: Application | 'self',
38
+ ) {
39
+ super(parent, config, services, app);
40
+
41
+ sgMail.setApiKey( config.api );
42
+ }
43
+
44
+ public async send( emails: TCompleteEmail[] ) {
45
+
46
+ if (emails.length !== 1)
47
+ throw new Error(LogPrefix + ` Feature not supported by Mailchimp: can't send a different email for each person.`);
48
+
49
+ const email = emails[0];
50
+
51
+ const to = email.to.map( to => ({
52
+ email: to.email,
53
+ name: to.name,
54
+ type: 'to'
55
+ }));
56
+
57
+ if (email.cc)
58
+ for (const person of email.cc)
59
+ to.push({
60
+ email: person.email,
61
+ name: person.name,
62
+ type: 'cc'
63
+ });
64
+
65
+ const body = {
66
+ key: this.config.api,
67
+ message: {
68
+ from_email: email.from.email,
69
+ from_name: email.from.name,
70
+ subject: email.subject,
71
+ html: email.html,
72
+ to
73
+ }
74
+ }
75
+
76
+ this.config.debug && console.log(LogPrefix, 'Send request to api:', body);
77
+
78
+ for (const email of emails)
79
+ await sgMail.send({
80
+ to: email.to,
81
+ from: email.from,
82
+ subject: email.subject,
83
+ html: email.html
84
+ }).then((res) => {
85
+
86
+ this.config.debug && console.log(LogPrefix, 'API request reponse:', body, res.statusCode, res.body);
87
+
88
+ return true;
89
+
90
+ }).catch( e => {
91
+
92
+ console.log(LogPrefix, "failed", body, e.response?.statusCode, e, e.response?.body);
93
+ throw new Error(e.response?.body?.errors[0]?.message || e.message || "Send email failed with MailChimp")
94
+
95
+ })
96
+ }
97
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "CrossPath/Email/Sendgrid",
3
+ "name": "SendGrid",
4
+ "parent": "emails",
5
+ "dependences": []
6
+ }