Skip to content

Commit edc659f

Browse files
authored
fix: run webhooks (#82)
1 parent 2b5d0b4 commit edc659f

8 files changed

Lines changed: 110 additions & 19 deletions

File tree

packages/prime-core/src/entities/Webhook.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { User } from '@accounts/typeorm';
22
import GraphQLJSON from 'graphql-type-json';
33
import { Field, ID, ObjectType } from 'type-graphql';
44
import {
5-
AfterInsert,
65
Column,
76
CreateDateColumn,
87
Entity,
@@ -49,9 +48,4 @@ export class Webhook {
4948

5049
@OneToMany(type => WebhookCall, call => call.webhook)
5150
public calls: WebhookCall[];
52-
53-
@AfterInsert()
54-
public notify() {
55-
// pubSub.publish('WEBHOOK_ADDED', this);
56-
}
5751
}

packages/prime-core/src/entities/WebhookCall.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export class WebhookCall {
1818
@Field(type => Int)
1919
public status: number;
2020

21-
@Column('jsonb', { default: {} })
21+
@Column('jsonb', { default: {}, nullable: true })
2222
@Field(type => GraphQLJSON)
2323
public request: any;
2424

@@ -30,6 +30,9 @@ export class WebhookCall {
3030
@Field(type => Date)
3131
public executedAt: Date;
3232

33+
@Column({ nullable: true })
34+
public webhookId: string;
35+
3336
@ManyToOne(type => Webhook, webhook => webhook.calls, {
3437
onDelete: 'CASCADE',
3538
})

packages/prime-core/src/modules/internal/repositories/DocumentRepository.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,6 @@ export class DocumentRepository extends DataLoaderRepository<Document> {
6868
document.userId = userId;
6969
const res = await this.insert(document);
7070
const doc = res.identifiers.pop() || { id: null };
71-
return this.findOneOrFail(doc.id);
71+
return doc.id;
7272
}
7373
}

packages/prime-core/src/modules/internal/resolvers/DocumentResolver.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { Document } from '../../../entities/Document';
1818
import { Context } from '../../../interfaces/Context';
1919
import { DocumentTransformer } from '../../../utils/DocumentTransformer';
2020
import { getUniqueHashId } from '../../../utils/getUniqueHashId';
21+
import { processWebhooks } from '../../../utils/processWebhooks';
2122
import { DocumentRepository } from '../repositories/DocumentRepository';
2223
import { SchemaFieldRepository } from '../repositories/SchemaFieldRepository';
2324
import { SchemaRepository } from '../repositories/SchemaRepository';
@@ -186,18 +187,18 @@ export class DocumentResolver {
186187
@Arg('locale', { nullable: true }) locale?: string,
187188
@Arg('releaseId', type => ID, { nullable: true }) releaseId?: string
188189
): Promise<boolean> {
189-
const doc = await this.Document(id, locale, releaseId);
190+
const document = await this.Document(id, locale, releaseId);
190191
await this.documentRepository.update(
191192
{
192-
documentId: doc.documentId,
193+
documentId: document.documentId,
193194
...(locale && { locale }),
194195
...(releaseId && { releaseId }),
195196
},
196197
{
197198
deletedAt: new Date(),
198199
}
199200
);
200-
// @todo run webhook
201+
processWebhooks('document.removed', { document });
201202
// @todo update algolia
202203
return true;
203204
}
@@ -209,10 +210,11 @@ export class DocumentResolver {
209210
@Ctx() context: Context //
210211
) {
211212
const doc = await this.documentRepository.findOneOrFail({ id, deletedAt: IsNull() });
212-
const result = this.documentRepository.publish(doc, context.user.id);
213-
// @todo run webhook
213+
const publishedId = await this.documentRepository.publish(doc, context.user.id);
214+
const document = await this.Document(publishedId);
215+
processWebhooks('document.published', { document });
214216
// @todo update algolia
215-
return result;
217+
return document;
216218
}
217219

218220
@Mutation(returns => Document)
@@ -230,9 +232,10 @@ export class DocumentResolver {
230232
publishedAt: null as any,
231233
}
232234
);
233-
// @todo run webhook
234235
// @todo update algolia
235-
return this.Document(id);
236+
const document = await this.Document(id);
237+
processWebhooks('document.unpublished', { document });
238+
return document;
236239
}
237240

238241
@FieldResolver(returns => GraphQLJSON, { nullable: true })

packages/prime-core/src/modules/internal/resolvers/ReleaseResolver.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { getRepository, In } from 'typeorm';
44
import { InjectRepository } from 'typeorm-typedi-extensions';
55
import { Document } from '../../../entities/Document';
66
import { Release } from '../../../entities/Release';
7+
import { processWebhooks } from '../../../utils/processWebhooks';
78
import { DocumentRepository } from '../repositories/DocumentRepository';
89
import { ReleaseRepository } from '../repositories/ReleaseRepository';
910
import { ConnectionArgs, createConnectionType } from '../types/createConnectionType';
@@ -46,9 +47,10 @@ export class ReleaseResolver {
4647
@Arg('input') input: ReleaseInput,
4748
@Ctx() context: Context
4849
): Promise<Release> {
49-
const entity = this.releaseRepository.create(input);
50-
await this.releaseRepository.save(entity);
51-
return entity;
50+
const release = this.releaseRepository.create(input);
51+
await this.releaseRepository.save(release);
52+
processWebhooks('release.created', { release });
53+
return release;
5254
}
5355

5456
@Authorized()
@@ -60,6 +62,7 @@ export class ReleaseResolver {
6062
): Promise<Release> {
6163
const release = await this.releaseRepository.findOneOrFail(id);
6264
await this.releaseRepository.merge(release, input);
65+
processWebhooks('release.updated', { release });
6366
return await this.releaseRepository.save(release);
6467
}
6568

@@ -77,6 +80,8 @@ export class ReleaseResolver {
7780

7881
await this.releaseRepository.remove(release);
7982

83+
processWebhooks('release.removed', { release });
84+
8085
return true;
8186
}
8287

@@ -125,6 +130,8 @@ export class ReleaseResolver {
125130
release.publishedBy = context.user.id;
126131
await this.releaseRepository.save(release);
127132

133+
processWebhooks('release.published', { release });
134+
128135
return release;
129136
}
130137

packages/prime-core/src/modules/internal/resolvers/SchemaResolver.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { EntityConnection } from 'typeorm-cursor-connection';
55
import { InjectRepository } from 'typeorm-typedi-extensions';
66
import { Document } from '../../../entities/Document';
77
import { Schema, SchemaVariant } from '../../../entities/Schema';
8+
import { processWebhooks } from '../../../utils/processWebhooks';
89
import { pubSub } from '../../../utils/pubSub';
910
import { SchemaRepository } from '../repositories/SchemaRepository';
1011
import { ConnectionArgs, createConnectionType } from '../types/createConnectionType';
@@ -78,6 +79,7 @@ export class SchemaResolver {
7879
}
7980
pubSub.publish('REBUILD_EXTERNAL', schema);
8081
schema.variant = parseEnum(SchemaVariant, schema.variant);
82+
processWebhooks('schema.created', { schema });
8183
return schema;
8284
}
8385

@@ -97,6 +99,7 @@ export class SchemaResolver {
9799
await this.schemaRepository.save(schema);
98100
schema.variant = parseEnum(SchemaVariant, schema.variant);
99101
pubSub.publish('REBUILD_EXTERNAL', schema);
102+
processWebhooks('schema.updated', { schema });
100103
return schema;
101104
}
102105

@@ -106,6 +109,7 @@ export class SchemaResolver {
106109
const schema = await this.schemaRepository.findOneOrFail(id);
107110
await this.schemaRepository.remove(schema);
108111
pubSub.publish('REBUILD_EXTERNAL', schema);
112+
processWebhooks('schema.removed', { schema });
109113
return true;
110114
}
111115

packages/prime-core/src/modules/internal/resolvers/UserResolver.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Arg, Args, Ctx, FieldResolver, ID, Mutation, Query, Resolver, Root } fr
66
import { Repository } from 'typeorm';
77
import { InjectRepository } from 'typeorm-typedi-extensions';
88
import { Context } from '../../../interfaces/Context';
9+
import { processWebhooks } from '../../../utils/processWebhooks';
910
import { UserRepository } from '../repositories/UserRepository';
1011
import { ConnectionArgs, createConnectionType } from '../types/createConnectionType';
1112
import { UpdateUserInput } from '../types/UpdateUserInput';
@@ -80,6 +81,8 @@ export class UserResolver {
8081
await password.server.setProfile(userId, profile);
8182
}
8283

84+
processWebhooks('user.created', { userId });
85+
8386
return true;
8487
}
8588

@@ -100,6 +103,7 @@ export class UserResolver {
100103
if (user) {
101104
await accounts.addEmail(user.id, email, false);
102105
await accounts.sendVerificationEmail(email);
106+
processWebhooks('user.emailAdded', { userId: user.id, email });
103107
return true;
104108
}
105109
return false;
@@ -115,6 +119,7 @@ export class UserResolver {
115119
user.profile = input;
116120
context.ability.throwUnlessCan('update', user);
117121
await this.userRepository.save(user);
122+
processWebhooks('user.updated', { user });
118123
return user;
119124
}
120125

@@ -127,6 +132,7 @@ export class UserResolver {
127132
const user = await this.userRepository.findOneOrFail(id);
128133
context.ability.throwUnlessCan('delete', user);
129134
await this.userRepository.remove(user);
135+
processWebhooks('user.removed', { user });
130136
return true;
131137
}
132138

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { getRepository } from 'typeorm';
2+
import { Webhook } from '../entities/Webhook';
3+
import { WebhookCall } from '../entities/WebhookCall';
4+
5+
const successCodes = [200, 201, 202, 203, 204];
6+
7+
export const processWebhooks = async (action, body) => {
8+
const webhookRepository = getRepository(Webhook);
9+
const webhookCallRepository = getRepository(WebhookCall);
10+
11+
const webhooks = await webhookRepository.find();
12+
13+
await Promise.all(
14+
webhooks
15+
.filter(webhook => {
16+
if (webhook.options && Array.isArray(webhook.options.actions)) {
17+
return webhook.options.actions.includes(action);
18+
}
19+
return true;
20+
})
21+
.map(async webhook => {
22+
const request: any = {
23+
headers: {
24+
'x-prime-action': action,
25+
'x-prime-webhook-name': webhook.name,
26+
'content-type': 'application/json',
27+
'user-agent': 'prime',
28+
},
29+
method: webhook.method,
30+
url: webhook.url,
31+
body,
32+
};
33+
34+
let response;
35+
36+
try {
37+
response = await fetch(request.url, {
38+
headers: request.headers,
39+
method: request.method,
40+
body: JSON.stringify(request.body),
41+
});
42+
} catch (err) {
43+
response = {
44+
headers: [],
45+
status: -1,
46+
statusText: err.message,
47+
text: async () => '',
48+
};
49+
}
50+
51+
const headers = {};
52+
response.headers.forEach((value, key) => {
53+
headers[key] = value;
54+
});
55+
56+
await webhookCallRepository.create({
57+
webhookId: webhook.id,
58+
success: successCodes.indexOf(response.status) >= 0,
59+
status: response.status,
60+
executedAt: new Date(),
61+
request,
62+
response: {
63+
headers,
64+
body: await response.text(),
65+
redirected: response.redirected,
66+
url: response.url,
67+
type: response.type,
68+
status: response.status,
69+
statusText: response.statusText,
70+
},
71+
});
72+
})
73+
);
74+
};

0 commit comments

Comments
 (0)