Skip to content

Commit 6fc237f

Browse files
committed
feat: public api
2 parents 0cbf2fb + 87892ed commit 6fc237f

File tree

106 files changed

+10190
-6513
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+10190
-6513
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: "🙏🏻 Installation Problem"
2+
description: "Report an issue with installation"
3+
title: "Installation Problem"
4+
labels: ["type: installation"]
5+
body:
6+
- type: markdown
7+
attributes:
8+
value: For installation issues, please visit our [Discord Support](https://discord.postiz.com) for assistance.
9+
- type: textarea
10+
id: feature-description
11+
validations:
12+
required: true
13+
attributes:
14+
label: For installation issues, please visit our https://discord.postiz.com for assistance.
15+
description: For installation issues, please visit our [Discord Support](https://discord.postiz.com) for assistance.
16+
placeholder: |
17+
For installation issues, please visit our https://discord.postiz.com for assistance.
18+
Please do not save this issue - do not submit installation issues on GitHub.
19+
File renamed without changes.

README.md

+7-17
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1+
<p align="center">
2+
<a href="https://affiliate.postiz.com">
3+
<img src="https://github.com/user-attachments/assets/af9f47b3-e20c-402b-bd11-02f39248d738" />
4+
</a>
5+
</p>
6+
17
<p align="center">
28
<a href="https://postiz.com" target="_blank">
39
<picture>
410
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/user-attachments/assets/765e9d72-3ee7-4a56-9d59-a2c9befe2311">
5-
<img alt="Novu Logo" src="https://github.com/user-attachments/assets/f0d30d70-dddb-4142-8876-e9aa6ed1cb99" width="280"/>
11+
<img alt="Postiz Logo" src="https://github.com/user-attachments/assets/f0d30d70-dddb-4142-8876-e9aa6ed1cb99" width="280"/>
612
</picture>
713
</a>
814
</p>
@@ -58,22 +64,6 @@
5864

5965
<br />
6066

61-
62-
<p align="center">
63-
<br /><br /><br />
64-
<h1>We participate in Hacktoberfest 2024! 🎉🎊</h1>
65-
<p align="left">We are sending a t-shirt for every merged PR! (max 1 per person)</p>
66-
<p align="left"><strong>Rules:</strong></p>
67-
<ul align="left">
68-
<li>You must create an issue before making a pull request.</li>
69-
<li>You can also ask to be assigned to an issue. During Hacktoberfest, each issue can have multiple assignees.</li>
70-
<li>We have to approve the issue and add a "hacktoberfest" tag.</li>
71-
<li>We encourage everybody to contribute to all types of issues. We will only send swag for issues with features and bug fixes (no typos, sorry).</li>
72-
</ul>
73-
<p align="center"><img align="center" width="400" src="https://github.com/user-attachments/assets/3ceffccc-e4b3-4098-b9ba-44a94cf01294" /></p>
74-
<br /><br /><br />
75-
</p>
76-
7767
<p align="center">
7868
<video src="https://github.com/user-attachments/assets/05436a01-19c8-4827-b57f-05a5e7637a67" width="100%" />
7969
</p>

apps/backend/src/api/routes/copilot.controller.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Controller, Get, Post, Req, Res } from '@nestjs/common';
1+
import { Logger, Controller, Get, Post, Req, Res } from '@nestjs/common';
22
import {
33
CopilotRuntime,
44
OpenAIAdapter,
@@ -13,6 +13,11 @@ export class CopilotController {
1313
constructor(private _subscriptionService: SubscriptionService) {}
1414
@Post('/chat')
1515
chat(@Req() req: Request, @Res() res: Response) {
16+
if (process.env.OPENAI_API_KEY === undefined || process.env.OPENAI_API_KEY === '') {
17+
Logger.warn('OpenAI API key not set, chat functionality will not work');
18+
return
19+
}
20+
1621
const copilotRuntimeHandler = copilotRuntimeNestEndpoint({
1722
endpoint: '/copilot/chat',
1823
runtime: new CopilotRuntime(),

apps/backend/src/api/routes/integrations.controller.ts

+148-16
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
import {
2-
Body,
3-
Controller,
4-
Delete,
5-
Get,
6-
Param,
7-
Post,
8-
Query,
9-
UseFilters,
2+
Body, Controller, Delete, Get, Param, Post, Put, Query, UseFilters
103
} from '@nestjs/common';
114
import { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service';
125
import { ConnectIntegrationDto } from '@gitroom/nestjs-libraries/dtos/integrations/connect.integration.dto';
@@ -29,6 +22,12 @@ import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/po
2922
import { IntegrationTimeDto } from '@gitroom/nestjs-libraries/dtos/integrations/integration.time.dto';
3023
import { AuthService } from '@gitroom/helpers/auth/auth.service';
3124
import { AuthTokenDetails } from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';
25+
import { PlugDto } from '@gitroom/nestjs-libraries/dtos/plugs/plug.dto';
26+
import {
27+
NotEnoughScopes,
28+
RefreshToken,
29+
} from '@gitroom/nestjs-libraries/integrations/social.abstract';
30+
import { timer } from '@gitroom/helpers/utils/timer';
3231

3332
@ApiTags('Integrations')
3433
@Controller('/integrations')
@@ -43,6 +42,37 @@ export class IntegrationsController {
4342
return this._integrationManager.getAllIntegrations();
4443
}
4544

45+
@Get('/customers')
46+
getCustomers(@GetOrgFromRequest() org: Organization) {
47+
return this._integrationService.customers(org.id);
48+
}
49+
50+
@Put('/:id/group')
51+
async updateIntegrationGroup(
52+
@GetOrgFromRequest() org: Organization,
53+
@Param('id') id: string,
54+
@Body() body: { group: string }
55+
) {
56+
return this._integrationService.updateIntegrationGroup(
57+
org.id,
58+
id,
59+
body.group
60+
);
61+
}
62+
63+
@Put('/:id/customer-name')
64+
async updateOnCustomerName(
65+
@GetOrgFromRequest() org: Organization,
66+
@Param('id') id: string,
67+
@Body() body: { name: string }
68+
) {
69+
return this._integrationService.updateOnCustomerName(
70+
org.id,
71+
id,
72+
body.name
73+
);
74+
}
75+
4676
@Get('/list')
4777
async getIntegrationList(@GetOrgFromRequest() org: Organization) {
4878
return {
@@ -57,7 +87,7 @@ export class IntegrationsController {
5787
id: p.id,
5888
internalId: p.internalId,
5989
disabled: p.disabled,
60-
picture: p.picture,
90+
picture: p.picture || '/no-picture.jpg',
6191
identifier: p.providerIdentifier,
6292
inBetweenSteps: p.inBetweenSteps,
6393
refreshNeeded: p.refreshNeeded,
@@ -66,6 +96,7 @@ export class IntegrationsController {
6696
time: JSON.parse(p.postingTimes),
6797
changeProfilePicture: !!findIntegration?.changeProfilePicture,
6898
changeNickName: !!findIntegration?.changeNickname,
99+
customer: p.customer,
69100
};
70101
}),
71102
};
@@ -207,11 +238,51 @@ export class IntegrationsController {
207238
}
208239

209240
if (integrationProvider[body.name]) {
210-
return integrationProvider[body.name](
211-
getIntegration.token,
212-
body.data,
213-
getIntegration.internalId
214-
);
241+
try {
242+
const load = await integrationProvider[body.name](
243+
getIntegration.token,
244+
body.data,
245+
getIntegration.internalId
246+
);
247+
248+
return load;
249+
} catch (err) {
250+
if (err instanceof RefreshToken) {
251+
const { accessToken, refreshToken, expiresIn } =
252+
await integrationProvider.refreshToken(
253+
getIntegration.refreshToken
254+
);
255+
256+
if (accessToken) {
257+
await this._integrationService.createOrUpdateIntegration(
258+
getIntegration.organizationId,
259+
getIntegration.name,
260+
getIntegration.picture!,
261+
'social',
262+
getIntegration.internalId,
263+
getIntegration.providerIdentifier,
264+
accessToken,
265+
refreshToken,
266+
expiresIn
267+
);
268+
269+
getIntegration.token = accessToken;
270+
271+
if (integrationProvider.refreshWait) {
272+
await timer(10000);
273+
}
274+
return this.functionIntegration(org, body);
275+
} else {
276+
await this._integrationService.disconnectChannel(
277+
org.id,
278+
getIntegration
279+
);
280+
return false;
281+
}
282+
}
283+
284+
return false;
285+
}
215286
}
216287
throw new Error('Function not found');
217288
}
@@ -323,6 +394,7 @@ export class IntegrationsController {
323394
}
324395

325396
const {
397+
error,
326398
accessToken,
327399
expiresIn,
328400
refreshToken,
@@ -341,6 +413,17 @@ export class IntegrationsController {
341413
details ? JSON.parse(details) : undefined
342414
);
343415

416+
if (typeof auth === 'string') {
417+
return res({
418+
error: auth,
419+
accessToken: '',
420+
id: '',
421+
name: '',
422+
picture: '',
423+
username: '',
424+
});
425+
}
426+
344427
if (refresh && integrationProvider.reConnect) {
345428
const newAuth = await integrationProvider.reConnect(
346429
auth.id,
@@ -353,13 +436,31 @@ export class IntegrationsController {
353436
return res(auth);
354437
});
355438

439+
if (error) {
440+
throw new NotEnoughScopes(error);
441+
}
442+
356443
if (!id) {
357-
throw new Error('Invalid api key');
444+
throw new NotEnoughScopes('Invalid API key');
445+
}
446+
447+
if (refresh && id !== refresh) {
448+
throw new NotEnoughScopes(
449+
'Please refresh the channel that needs to be refreshed'
450+
);
358451
}
359452

453+
let validName = name;
454+
if (!validName) {
455+
if (username) {
456+
validName = username.split('.')[0] ?? username;
457+
} else {
458+
validName = `Channel_${String(id).slice(0, 8)}`;
459+
}
460+
}
360461
return this._integrationService.createOrUpdateIntegration(
361462
org.id,
362-
name,
463+
validName.trim(),
363464
picture,
364465
'social',
365466
String(id),
@@ -446,4 +547,35 @@ export class IntegrationsController {
446547

447548
return this._integrationService.deleteChannel(org.id, id);
448549
}
550+
551+
@Get('/plug/list')
552+
async getPlugList() {
553+
return { plugs: this._integrationManager.getAllPlugs() };
554+
}
555+
556+
@Get('/:id/plugs')
557+
async getPlugsByIntegrationId(
558+
@Param('id') id: string,
559+
@GetOrgFromRequest() org: Organization
560+
) {
561+
return this._integrationService.getPlugsByIntegrationId(org.id, id);
562+
}
563+
564+
@Post('/:id/plugs')
565+
async postPlugsByIntegrationId(
566+
@Param('id') id: string,
567+
@GetOrgFromRequest() org: Organization,
568+
@Body() body: PlugDto
569+
) {
570+
return this._integrationService.createOrUpdatePlug(org.id, id, body);
571+
}
572+
573+
@Put('/plugs/:id/activate')
574+
async changePlugActivation(
575+
@Param('id') id: string,
576+
@GetOrgFromRequest() org: Organization,
577+
@Body('status') status: boolean
578+
) {
579+
return this._integrationService.changePlugActivation(org.id, id, status);
580+
}
449581
}

apps/backend/src/api/routes/media.controller.ts

-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import handleR2Upload from '@gitroom/nestjs-libraries/upload/r2.uploader';
1010
import { FileInterceptor } from '@nestjs/platform-express';
1111
import { CustomFileValidationPipe } from '@gitroom/nestjs-libraries/upload/custom.upload.validation';
1212
import { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';
13-
import { basename } from 'path';
1413
import { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';
1514

1615
@ApiTags('Media')

apps/backend/src/api/routes/users.controller.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export class UsersController {
4747
if (!organization) {
4848
throw new HttpForbiddenException();
4949
}
50-
50+
// @ts-ignore
5151
return {
5252
...user,
5353
orgId: organization.id,
@@ -61,6 +61,8 @@ export class UsersController {
6161
isLifetime: !!organization?.subscription?.isLifetime,
6262
admin: !!user.isSuperAdmin,
6363
impersonate: !!req.cookies.impersonate,
64+
// @ts-ignore
65+
publicApi: (organization?.users[0]?.role === 'SUPERADMIN' || organization?.users[0]?.role === 'ADMIN') ? organization?.apiKey : '',
6466
};
6567
}
6668

apps/backend/src/app.module.ts

+20-1
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,31 @@ import { APP_GUARD } from '@nestjs/core';
66
import { PoliciesGuard } from '@gitroom/backend/services/auth/permissions/permissions.guard';
77
import { BullMqModule } from '@gitroom/nestjs-libraries/bull-mq-transport-new/bull.mq.module';
88
import { PluginModule } from '@gitroom/plugins/plugin.module';
9+
import { PublicApiModule } from '@gitroom/backend/public-api/public.api.module';
10+
import { ThrottlerBehindProxyGuard } from '@gitroom/nestjs-libraries/throttler/throttler.provider';
11+
import { ThrottlerModule } from '@nestjs/throttler';
912

1013
@Global()
1114
@Module({
12-
imports: [BullMqModule, DatabaseModule, ApiModule, PluginModule],
15+
imports: [
16+
BullMqModule,
17+
DatabaseModule,
18+
ApiModule,
19+
PluginModule,
20+
PublicApiModule,
21+
ThrottlerModule.forRoot([
22+
{
23+
ttl: 3600000,
24+
limit: 20,
25+
},
26+
]),
27+
],
1328
controllers: [],
1429
providers: [
30+
{
31+
provide: APP_GUARD,
32+
useClass: ThrottlerBehindProxyGuard,
33+
},
1534
{
1635
provide: APP_GUARD,
1736
useClass: PoliciesGuard,

0 commit comments

Comments
 (0)