Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
cb8560e183
16 changed files with 613 additions and 333 deletions
|
|
@ -169,10 +169,20 @@ export class AuthController {
|
|||
}
|
||||
|
||||
response.header('reload', 'true');
|
||||
try {
|
||||
Sentry.metrics.count('auth.login.success', 1, {
|
||||
attributes: { provider: body.provider || 'LOCAL' },
|
||||
} as any);
|
||||
} catch (e) {}
|
||||
response.status(200).json({
|
||||
login: true,
|
||||
});
|
||||
} catch (e: any) {
|
||||
try {
|
||||
Sentry.metrics.count('auth.login.failure', 1, {
|
||||
attributes: { provider: body?.provider || 'LOCAL' },
|
||||
} as any);
|
||||
} catch (er) {}
|
||||
response.status(400).send(e.message);
|
||||
}
|
||||
}
|
||||
|
|
@ -260,6 +270,9 @@ export class AuthController {
|
|||
const { jwt, token } = await this._authService.checkExists(provider, code);
|
||||
|
||||
if (token) {
|
||||
try {
|
||||
Sentry.metrics.count('oauth.connects', 1, { attributes: { provider } } as any);
|
||||
} catch (e) {}
|
||||
return response.json({ token });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';
|
|||
import { SaveMediaInformationDto } from '@gitroom/nestjs-libraries/dtos/media/save.media.information.dto';
|
||||
import { VideoDto } from '@gitroom/nestjs-libraries/dtos/videos/video.dto';
|
||||
import { VideoFunctionDto } from '@gitroom/nestjs-libraries/dtos/videos/video.function.dto';
|
||||
import * as Sentry from '@sentry/nestjs';
|
||||
|
||||
@ApiTags('Media')
|
||||
@Controller('/media')
|
||||
|
|
@ -92,13 +93,26 @@ export class MediaController {
|
|||
@UploadedFile() file: Express.Multer.File
|
||||
) {
|
||||
const originalName = file?.originalname || '';
|
||||
const uploadedFile = await this.storage.uploadFile(file);
|
||||
return this._mediaService.saveFile(
|
||||
org.id,
|
||||
uploadedFile.originalname,
|
||||
uploadedFile.path,
|
||||
originalName
|
||||
);
|
||||
try {
|
||||
const uploadedFile = await this.storage.uploadFile(file);
|
||||
|
||||
try {
|
||||
Sentry.metrics.count('uploads.total', 1);
|
||||
Sentry.metrics.distribution('upload_size_bytes', file?.size || 0);
|
||||
} catch (e) {}
|
||||
|
||||
return this._mediaService.saveFile(
|
||||
org.id,
|
||||
uploadedFile.originalname,
|
||||
uploadedFile.path,
|
||||
originalName
|
||||
);
|
||||
} catch (err) {
|
||||
try {
|
||||
Sentry.metrics.count('uploads.failure', 1);
|
||||
} catch (e) {}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@Post('/save-media')
|
||||
|
|
@ -135,19 +149,31 @@ export class MediaController {
|
|||
@Body('preventSave') preventSave: string = 'false'
|
||||
) {
|
||||
const originalName = file.originalname;
|
||||
const getFile = await this.storage.uploadFile(file);
|
||||
try {
|
||||
const getFile = await this.storage.uploadFile(file);
|
||||
|
||||
if (preventSave === 'true') {
|
||||
const { path } = getFile;
|
||||
return { path };
|
||||
try {
|
||||
Sentry.metrics.count('uploads.total', 1);
|
||||
Sentry.metrics.distribution('upload_size_bytes', file?.size || 0);
|
||||
} catch (e) {}
|
||||
|
||||
if (preventSave === 'true') {
|
||||
const { path } = getFile;
|
||||
return { path };
|
||||
}
|
||||
|
||||
return this._mediaService.saveFile(
|
||||
org.id,
|
||||
getFile.originalname,
|
||||
getFile.path,
|
||||
originalName
|
||||
);
|
||||
} catch (err) {
|
||||
try {
|
||||
Sentry.metrics.count('uploads.failure', 1);
|
||||
} catch (e) {}
|
||||
throw err;
|
||||
}
|
||||
|
||||
return this._mediaService.saveFile(
|
||||
org.id,
|
||||
getFile.originalname,
|
||||
getFile.path,
|
||||
originalName
|
||||
);
|
||||
}
|
||||
|
||||
@Post('/:endpoint')
|
||||
|
|
@ -166,15 +192,27 @@ export class MediaController {
|
|||
const name = upload.Location.split('/').pop();
|
||||
const originalName = req.body?.file?.name;
|
||||
|
||||
const saveFile = await this._mediaService.saveFile(
|
||||
org.id,
|
||||
name,
|
||||
// @ts-ignore
|
||||
upload.Location,
|
||||
originalName || undefined
|
||||
);
|
||||
try {
|
||||
const saveFile = await this._mediaService.saveFile(
|
||||
org.id,
|
||||
name,
|
||||
// @ts-ignore
|
||||
upload.Location,
|
||||
originalName || undefined
|
||||
);
|
||||
|
||||
res.status(200).json({ ...upload, saved: saveFile });
|
||||
try {
|
||||
Sentry.metrics.count('uploads.total', 1);
|
||||
Sentry.metrics.distribution('upload_size_bytes', (req.headers['content-length'] ? Number(req.headers['content-length']) : 0) || 0);
|
||||
} catch (e) {}
|
||||
|
||||
res.status(200).json({ ...upload, saved: saveFile });
|
||||
} catch (err) {
|
||||
try {
|
||||
Sentry.metrics.count('uploads.failure', 1);
|
||||
} catch (e) {}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/')
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { GetPostsDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.dto'
|
|||
import { GetPostsListDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.list.dto';
|
||||
import { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import * as Sentry from '@sentry/nestjs';
|
||||
import { GeneratorDto } from '@gitroom/nestjs-libraries/dtos/generator/generator.dto';
|
||||
import { CreateGeneratedPostsDto } from '@gitroom/nestjs-libraries/dtos/generator/create.generated.posts.dto';
|
||||
import { AgentGraphService } from '@gitroom/nestjs-libraries/agent/agent.graph.service';
|
||||
|
|
@ -162,7 +163,18 @@ export class PostsController {
|
|||
) {
|
||||
console.log(JSON.stringify(rawBody, null, 2));
|
||||
const body = await this._postsService.mapTypeToPost(rawBody, org.id);
|
||||
return this._postsService.createPost(org.id, body);
|
||||
const created = await this._postsService.createPost(org.id, body);
|
||||
|
||||
try {
|
||||
for (const p of body.posts || []) {
|
||||
const providerRaw = (p?.settings && p.settings.__type) || (p?.integration && p.integration.id) || '';
|
||||
const provider = (typeof providerRaw === 'string' ? providerRaw.split('-')[0] : '')
|
||||
.toLowerCase();
|
||||
Sentry.metrics.count('posts.created', 1, { attributes: { provider } } as any);
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
@Post('/generator/draft')
|
||||
|
|
@ -204,7 +216,11 @@ export class PostsController {
|
|||
@Body('date') date: string,
|
||||
@Body('action') action: 'schedule' | 'update' = 'schedule'
|
||||
) {
|
||||
return this._postsService.changeDate(org.id, id, date, action);
|
||||
return (async () => {
|
||||
const res = await this._postsService.changeDate(org.id, id, date, action);
|
||||
|
||||
return res;
|
||||
})();
|
||||
}
|
||||
|
||||
@Post('/separate-posts')
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ import { useIntegration } from '@gitroom/frontend/components/launches/helpers/us
|
|||
import { useMediaDirectory } from '@gitroom/react/helpers/use.media.directory';
|
||||
import clsx from 'clsx';
|
||||
import { VideoOrImage } from '@gitroom/react/helpers/video.or.image';
|
||||
import { FC } from 'react';
|
||||
import { FC, useEffect } from 'react';
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
import { textSlicer } from '@gitroom/helpers/utils/count.length';
|
||||
import Image from 'next/image';
|
||||
import { useLaunchStore } from '@gitroom/frontend/components/new-launch/store';
|
||||
|
|
@ -11,6 +12,11 @@ import { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validatio
|
|||
export const GeneralPreviewComponent: FC<{
|
||||
maximumCharacters?: number;
|
||||
}> = (props) => {
|
||||
useEffect(() => {
|
||||
try {
|
||||
Sentry.metrics.count('preview.render', 1);
|
||||
} catch (e) {}
|
||||
}, []);
|
||||
const { value: topValue, integration } = useIntegration();
|
||||
const current = useLaunchStore((state) => state.current);
|
||||
const mediaDir = useMediaDirectory();
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import React, {
|
|||
forwardRef,
|
||||
useImperativeHandle,
|
||||
} from 'react';
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
import clsx from 'clsx';
|
||||
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
||||
import EmojiPicker from 'emoji-picker-react';
|
||||
|
|
@ -183,6 +184,12 @@ export const EditorWrapper: FC<{
|
|||
setLoaded(true);
|
||||
}, [loaded, loadedState]);
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
Sentry.metrics.count('editor.open', 1);
|
||||
} catch (e) {}
|
||||
}, []);
|
||||
|
||||
const canEdit = useMemo(() => {
|
||||
return current === 'global' || !!internal;
|
||||
}, [current, internal]);
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import { SelectCustomer } from '@gitroom/frontend/components/launches/select.cus
|
|||
import { CopilotPopup } from '@copilotkit/react-ui';
|
||||
import { DummyCodeComponent } from '@gitroom/frontend/components/new-launch/dummy.code.component';
|
||||
import { stripHtmlValidation } from '@gitroom/helpers/utils/strip.html.validation';
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
import {
|
||||
SettingsIcon,
|
||||
ChevronDownIcon,
|
||||
|
|
@ -413,6 +414,10 @@ export const ManageModal: FC<AddEditModalProps> = (props) => {
|
|||
}
|
||||
|
||||
if (!dummy) {
|
||||
try {
|
||||
Sentry.metrics.count('post.submit', 1, { attributes: { action: type } });
|
||||
} catch (e) {}
|
||||
|
||||
addEditSets
|
||||
? addEditSets(data)
|
||||
: await fetch('/posts', {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {
|
|||
postId as postIdSearchParam,
|
||||
} from '@gitroom/nestjs-libraries/temporal/temporal.search.attribute';
|
||||
import { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';
|
||||
import * as Sentry from '@sentry/nestjs';
|
||||
|
||||
@Injectable()
|
||||
@Activity()
|
||||
|
|
@ -80,7 +81,31 @@ export class PostActivity {
|
|||
|
||||
@ActivityMethod()
|
||||
async updatePost(id: string, postId: string, releaseURL: string) {
|
||||
return this._postService.updatePost(id, postId, releaseURL);
|
||||
const res = await this._postService.updatePost(id, postId, releaseURL);
|
||||
try {
|
||||
const posts = await this._postService.getPostByForWebhookId(postId);
|
||||
const post = Array.isArray(posts) && posts.length ? (posts[0] as any) : (posts as any);
|
||||
if (post && post.organizationId) {
|
||||
try {
|
||||
const running = this._temporalService.client
|
||||
.getRawClient()
|
||||
?.workflow.list({ query: `organizationId="${post.organizationId}" AND ExecutionStatus="Running"` });
|
||||
|
||||
let count = 0;
|
||||
if (running) {
|
||||
for await (const _ of running) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Sentry.metrics.gauge('posts.queued', count, { attributes: { taskQueue: post.integration?.providerIdentifier?.split('-')[0] || 'main' } } as any);
|
||||
} catch (e) {}
|
||||
} catch (e) {}
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@ActivityMethod()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { PostActivity } from '@gitroom/orchestrator/activities/post.activity';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import {
|
||||
ActivityFailure,
|
||||
ApplicationFailure,
|
||||
|
|
@ -171,6 +172,14 @@ export async function postWorkflowV101({
|
|||
postsResults[i].releaseURL
|
||||
);
|
||||
|
||||
if (i === 0) {
|
||||
try {
|
||||
const latency = Date.now() - startTime.getTime();
|
||||
Sentry.metrics.count('posts.published.success', 1, { attributes: { provider: post.integration?.providerIdentifier } } as any);
|
||||
Sentry.metrics.distribution('posts.publish_latency_ms', latency, { attributes: { provider: post.integration?.providerIdentifier } } as any);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
// send notification on a sucessful post
|
||||
await inAppNotification(
|
||||
|
|
@ -207,6 +216,16 @@ export async function postWorkflowV101({
|
|||
|
||||
// for other errors, change state and inform the user if needed
|
||||
await changeState(postsList[0].id, 'ERROR', err, postsList);
|
||||
try {
|
||||
const cause = (err as any)?.cause;
|
||||
const failure_reason = (cause && (cause as any).type) || (err as any)?.message || 'unknown';
|
||||
Sentry.metrics.count('posts.published.failure', 1, { attributes: { provider: post.integration?.providerIdentifier, failure_reason } } as any);
|
||||
} catch (e) {}
|
||||
try {
|
||||
const cause = (err as any)?.cause;
|
||||
const reason = (cause && (cause as any).type) || (err as any)?.message || 'unknown';
|
||||
Sentry.metrics.count('task_failures_by_reason', 1, { attributes: { reason } } as any);
|
||||
} catch (e) {}
|
||||
|
||||
// specific case for bad body errors
|
||||
if (
|
||||
|
|
@ -233,6 +252,13 @@ export async function postWorkflowV101({
|
|||
|
||||
if (postsResults.length === before) {
|
||||
// all retries exhausted without success
|
||||
try {
|
||||
Sentry.metrics.count('temporal.retry_exhausted', 1, { attributes: { workflow: 'postWorkflowV101' } } as any);
|
||||
} catch (e) {}
|
||||
try {
|
||||
Sentry.metrics.count('posts.published.failure', 1, { attributes: { provider: post.integration?.providerIdentifier, failure_reason: 'retry_exhausted' } } as any);
|
||||
} catch (e) {}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { CreatePostDto } from '@gitroom/nestjs-libraries/dtos/posts/create.post.dto';
|
||||
import { GetPostsDto } from '@gitroom/nestjs-libraries/dtos/posts/get.posts.dto';
|
||||
import fetch, { FormData } from 'node-fetch';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import crypto from 'crypto';
|
||||
|
||||
function toQueryString(obj: Record<string, any>): string {
|
||||
const params = new URLSearchParams();
|
||||
|
|
@ -19,6 +21,10 @@ export default class Postiz {
|
|||
) {}
|
||||
|
||||
async post(posts: CreatePostDto) {
|
||||
try {
|
||||
Sentry.metrics.count('sdk.requests', 1, { attributes: { method: 'post' } } as any);
|
||||
} catch (e) {}
|
||||
|
||||
return (
|
||||
await fetch(`${this._path}/public/v1/posts`, {
|
||||
method: 'POST',
|
||||
|
|
@ -32,6 +38,10 @@ export default class Postiz {
|
|||
}
|
||||
|
||||
async postList(filters: GetPostsDto) {
|
||||
try {
|
||||
Sentry.metrics.count('sdk.requests', 1, { attributes: { method: 'list' } } as any);
|
||||
} catch (e) {}
|
||||
|
||||
return (
|
||||
await fetch(`${this._path}/public/v1/posts?${toQueryString(filters)}`, {
|
||||
method: 'GET',
|
||||
|
|
@ -44,6 +54,10 @@ export default class Postiz {
|
|||
}
|
||||
|
||||
async upload(file: Buffer, extension: string) {
|
||||
try {
|
||||
Sentry.metrics.count('sdk.requests', 1, { attributes: { method: 'upload' } } as any);
|
||||
} catch (e) {}
|
||||
|
||||
const formData = new FormData();
|
||||
const type =
|
||||
extension === 'png'
|
||||
|
|
@ -72,6 +86,10 @@ export default class Postiz {
|
|||
}
|
||||
|
||||
async integrations() {
|
||||
try {
|
||||
Sentry.metrics.count('sdk.requests', 1, { attributes: { method: 'integrations' } } as any);
|
||||
} catch (e) {}
|
||||
|
||||
return (
|
||||
await fetch(`${this._path}/public/v1/integrations`, {
|
||||
method: 'GET',
|
||||
|
|
@ -84,6 +102,10 @@ export default class Postiz {
|
|||
}
|
||||
|
||||
deletePost(id: string) {
|
||||
try {
|
||||
Sentry.metrics.count('sdk.requests', 1, { attributes: { method: 'delete' } } as any);
|
||||
} catch (e) {}
|
||||
|
||||
return fetch(`${this._path}/public/v1/posts/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { SaveMediaInformationDto } from '@gitroom/nestjs-libraries/dtos/media/sa
|
|||
import { VideoManager } from '@gitroom/nestjs-libraries/videos/video.manager';
|
||||
import { VideoDto } from '@gitroom/nestjs-libraries/dtos/videos/video.dto';
|
||||
import { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';
|
||||
import * as Sentry from '@sentry/nestjs';
|
||||
import {
|
||||
AuthorizationActions,
|
||||
Sections,
|
||||
|
|
@ -41,11 +42,31 @@ export class MediaService {
|
|||
org,
|
||||
'ai_images',
|
||||
async () => {
|
||||
if (generatePromptFirst) {
|
||||
prompt = await this._openAi.generatePromptForPicture(prompt);
|
||||
console.log('Prompt:', prompt);
|
||||
const start = Date.now();
|
||||
try {
|
||||
try {
|
||||
Sentry.metrics.count('media.generate_attempt', 1, { attributes: { media_type: 'image' } } as any);
|
||||
} catch (e) {}
|
||||
|
||||
if (generatePromptFirst) {
|
||||
prompt = await this._openAi.generatePromptForPicture(prompt);
|
||||
console.log('Prompt:', prompt);
|
||||
}
|
||||
|
||||
const res = await this._openAi.generateImage(prompt, !!generatePromptFirst);
|
||||
|
||||
try {
|
||||
Sentry.metrics.count('media.generate_success', 1, { attributes: { media_type: 'image' } } as any);
|
||||
Sentry.metrics.distribution('media.generation_ms', Date.now() - start, { attributes: { media_type: 'image' } } as any);
|
||||
} catch (e) {}
|
||||
|
||||
return res;
|
||||
} catch (err) {
|
||||
try {
|
||||
Sentry.metrics.count('media.generate_failure', 1, { attributes: { media_type: 'image' } } as any);
|
||||
} catch (e) {}
|
||||
throw err;
|
||||
}
|
||||
return this._openAi.generateImage(prompt, !!generatePromptFirst);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -105,21 +126,39 @@ export class MediaService {
|
|||
|
||||
console.log(body.customParams);
|
||||
await video.instance.processAndValidate(body.customParams);
|
||||
console.log('no err');
|
||||
|
||||
return await this._subscriptionService.useCredit(
|
||||
org,
|
||||
'ai_videos',
|
||||
async () => {
|
||||
const loadedData = await video.instance.process(
|
||||
body.output,
|
||||
body.customParams
|
||||
);
|
||||
const start = Date.now();
|
||||
try {
|
||||
try {
|
||||
Sentry.metrics.count('media.generate_attempt', 1, { attributes: { media_type: 'video' } } as any);
|
||||
} catch (e) {}
|
||||
|
||||
const file = await this.storage.uploadSimple(loadedData);
|
||||
return this.saveFile(org.id, file.split('/').pop(), file);
|
||||
}
|
||||
);
|
||||
const result = await this._subscriptionService.useCredit(
|
||||
org,
|
||||
'ai_videos',
|
||||
async () => {
|
||||
const loadedData = await video.instance.process(
|
||||
body.output,
|
||||
body.customParams
|
||||
);
|
||||
|
||||
const file = await this.storage.uploadSimple(loadedData);
|
||||
return this.saveFile(org.id, file.split('/').pop(), file);
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
Sentry.metrics.count('media.generate_success', 1, { attributes: { media_type: 'video' } } as any);
|
||||
Sentry.metrics.distribution('media.generation_ms', Date.now() - start, { attributes: { media_type: 'video' } } as any);
|
||||
} catch (e) {}
|
||||
|
||||
return result;
|
||||
} catch (err) {
|
||||
try {
|
||||
Sentry.metrics.count('media.generate_failure', 1, { attributes: { media_type: 'video' } } as any);
|
||||
} catch (e) {}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async videoFunction(identifier: string, functionName: string, body: any) {
|
||||
|
|
|
|||
|
|
@ -681,6 +681,25 @@ export class PostsService {
|
|||
},
|
||||
]),
|
||||
});
|
||||
// update queued-per-org gauge (simple approach: count running workflows for org)
|
||||
try {
|
||||
const running = this._temporalService.client
|
||||
.getRawClient()
|
||||
?.workflow.list({
|
||||
query: `organizationId="${orgId}" AND ExecutionStatus="Running"`,
|
||||
});
|
||||
|
||||
let count = 0;
|
||||
if (running) {
|
||||
for await (const _ of running) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Sentry.metrics.gauge('posts.queued', count, { attributes: { taskQueue } } as any);
|
||||
} catch (err) {}
|
||||
} catch (err) {}
|
||||
} catch (err) {}
|
||||
}
|
||||
|
||||
|
|
@ -719,7 +738,7 @@ export class PostsService {
|
|||
).catch((err) => {});
|
||||
}
|
||||
|
||||
Sentry.metrics.count('post_created', 1);
|
||||
// metric moved: controller records `posts.created` with org/provider tags
|
||||
postList.push({
|
||||
postId: posts[0].id,
|
||||
integration: post.integration.id,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
SocialProvider,
|
||||
} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';
|
||||
import { TemporalService } from 'nestjs-temporal-core';
|
||||
import * as Sentry from '@sentry/nestjs';
|
||||
|
||||
@Injectable()
|
||||
export class RefreshIntegrationService {
|
||||
|
|
@ -71,45 +72,61 @@ export class RefreshIntegrationService {
|
|||
integration: Integration,
|
||||
socialProvider: SocialProvider
|
||||
): Promise<AuthTokenDetails | false> {
|
||||
const refresh: false | AuthTokenDetails = await socialProvider
|
||||
.refreshToken(integration.refreshToken)
|
||||
.catch((err) => false);
|
||||
try {
|
||||
try {
|
||||
Sentry.metrics.count('provider.refresh_attempt', 1, { attributes: { provider: socialProvider.identifier } } as any);
|
||||
} catch (e) {}
|
||||
|
||||
if (!refresh || !refresh.accessToken) {
|
||||
await this._integrationService.refreshNeeded(
|
||||
integration.organizationId,
|
||||
integration.id
|
||||
);
|
||||
|
||||
await this._integrationService.informAboutRefreshError(
|
||||
integration.organizationId,
|
||||
integration
|
||||
);
|
||||
|
||||
await this._integrationService.disconnectChannel(
|
||||
integration.organizationId,
|
||||
integration
|
||||
const refresh: false | AuthTokenDetails = await socialProvider
|
||||
.refreshToken(integration.refreshToken)
|
||||
.catch((err) => false);
|
||||
|
||||
if (!refresh || !refresh.accessToken) {
|
||||
try {
|
||||
Sentry.metrics.count('provider.refresh_fail', 1, { attributes: { provider: socialProvider.identifier } } as any);
|
||||
} catch (e) {}
|
||||
|
||||
await this._integrationService.refreshNeeded(
|
||||
integration.organizationId,
|
||||
integration.id
|
||||
);
|
||||
|
||||
await this._integrationService.informAboutRefreshError(
|
||||
integration.organizationId,
|
||||
integration
|
||||
);
|
||||
|
||||
await this._integrationService.disconnectChannel(
|
||||
integration.organizationId,
|
||||
integration
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// proceed with reconnect handling below
|
||||
if (
|
||||
!socialProvider.reConnect ||
|
||||
integration.rootInternalId === integration.internalId
|
||||
) {
|
||||
return refresh;
|
||||
}
|
||||
|
||||
const reConnect = await socialProvider.reConnect(
|
||||
integration.rootInternalId,
|
||||
integration.internalId,
|
||||
refresh.accessToken
|
||||
);
|
||||
|
||||
return {
|
||||
...refresh,
|
||||
...reConnect,
|
||||
};
|
||||
} catch (err) {
|
||||
try {
|
||||
Sentry.metrics.count('provider.refresh_fail', 1, { attributes: { provider: socialProvider.identifier } } as any);
|
||||
} catch (e) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
!socialProvider.reConnect ||
|
||||
integration.rootInternalId === integration.internalId
|
||||
) {
|
||||
return refresh;
|
||||
}
|
||||
|
||||
const reConnect = await socialProvider.reConnect(
|
||||
integration.rootInternalId,
|
||||
integration.internalId,
|
||||
refresh.accessToken
|
||||
);
|
||||
|
||||
return {
|
||||
...refresh,
|
||||
...reConnect,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { timer } from '@gitroom/helpers/utils/timer';
|
||||
import { Integration } from '@prisma/client';
|
||||
import { ApplicationFailure } from '@temporalio/activity';
|
||||
import * as Sentry from '@sentry/nestjs';
|
||||
|
||||
export class RefreshToken extends ApplicationFailure {
|
||||
constructor(identifier: string, json: string, body: BodyInit, message = '') {
|
||||
|
|
@ -104,10 +105,24 @@ export abstract class SocialAbstract {
|
|||
totalRetries = 0,
|
||||
ignoreConcurrency = false
|
||||
): Promise<Response> {
|
||||
const request = await fetch(url, options);
|
||||
const start = Date.now();
|
||||
let request: Response;
|
||||
try {
|
||||
request = await fetch(url, options);
|
||||
const latency = Date.now() - start;
|
||||
try {
|
||||
Sentry.metrics.distribution('provider.api_latency_ms', latency, { attributes: { provider: this.identifier, endpoint: url, status: 'success' } } as any);
|
||||
} catch (e) {}
|
||||
|
||||
if (request.status === 200 || request.status === 201) {
|
||||
return request;
|
||||
if (request.status === 200 || request.status === 201) {
|
||||
return request;
|
||||
}
|
||||
} catch (err) {
|
||||
const latency = Date.now() - start;
|
||||
try {
|
||||
Sentry.metrics.distribution('provider.api_latency_ms', latency, { attributes: { provider: this.identifier, endpoint: url, status: 'failure' } } as any);
|
||||
} catch (e) {}
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (totalRetries > 2) {
|
||||
|
|
@ -129,6 +144,9 @@ export abstract class SocialAbstract {
|
|||
json.includes('rate_limit_exceeded') ||
|
||||
json.includes('Rate limit')
|
||||
) {
|
||||
try {
|
||||
Sentry.metrics.count('provider.rate_limited', 1, { attributes: { provider: this.identifier } } as any);
|
||||
} catch (e) {}
|
||||
await timer(5000);
|
||||
return this.fetch(
|
||||
url,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,10 @@ export const initializeSentry = (appName: string, allowLogs = false) => {
|
|||
recordInputs: true,
|
||||
recordOutputs: true,
|
||||
}),
|
||||
Sentry.langChainIntegration({
|
||||
recordInputs: true,
|
||||
recordOutputs: true,
|
||||
}),
|
||||
],
|
||||
tracesSampleRate: 1.0,
|
||||
enableLogs: true,
|
||||
|
|
@ -42,5 +46,18 @@ export const initializeSentry = (appName: string, allowLogs = false) => {
|
|||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
try {
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
try {
|
||||
Sentry.metrics.count('app.unhandled_errors', 1, { attributes: { service: appName, route: 'unhandledRejection' } } as any);
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
process.on('uncaughtException', (err) => {
|
||||
try {
|
||||
Sentry.metrics.count('app.unhandled_errors', 1, { attributes: { service: appName, route: 'uncaughtException' } } as any);
|
||||
} catch (e) {}
|
||||
});
|
||||
} catch (e) {}
|
||||
return true;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -80,10 +80,10 @@
|
|||
"@pigment-css/react": "^0.0.30",
|
||||
"@postiz/wallets": "^0.0.1",
|
||||
"@prisma/client": "6.5.0",
|
||||
"@sentry/nestjs": "^10.26.0",
|
||||
"@sentry/nextjs": "^10.26.0",
|
||||
"@sentry/profiling-node": "^10.25.0",
|
||||
"@sentry/react": "^10.25.0",
|
||||
"@sentry/nestjs": "^10.45.0",
|
||||
"@sentry/nextjs": "^10.45.0",
|
||||
"@sentry/profiling-node": "^10.45.0",
|
||||
"@sentry/react": "^10.45.0",
|
||||
"@solana/wallet-adapter-react": "^0.15.35",
|
||||
"@solana/wallet-adapter-react-ui": "^0.9.35",
|
||||
"@stripe/react-stripe-js": "^5.4.1",
|
||||
|
|
|
|||
498
pnpm-lock.yaml
generated
498
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue