Feat: plugs
This commit is contained in:
parent
86d0d2490d
commit
74ad1410c7
|
|
@ -1,5 +1,5 @@
|
|||
import {
|
||||
Body, Controller, Delete, Get, Param, Post, Query, UseFilters
|
||||
Body, Controller, Delete, Get, Param, Post, Put, Query, UseFilters
|
||||
} from '@nestjs/common';
|
||||
import { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service';
|
||||
import { ConnectIntegrationDto } from '@gitroom/nestjs-libraries/dtos/integrations/connect.integration.dto';
|
||||
|
|
@ -23,6 +23,7 @@ import { IntegrationTimeDto } from '@gitroom/nestjs-libraries/dtos/integrations/
|
|||
import { AuthService } from '@gitroom/helpers/auth/auth.service';
|
||||
import { AuthTokenDetails } from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';
|
||||
import { NotEnoughScopes } from '@gitroom/nestjs-libraries/integrations/social.abstract';
|
||||
import { PlugDto } from '@gitroom/nestjs-libraries/dtos/plugs/plug.dto';
|
||||
|
||||
@ApiTags('Integrations')
|
||||
@Controller('/integrations')
|
||||
|
|
@ -352,7 +353,9 @@ export class IntegrationsController {
|
|||
}
|
||||
|
||||
if (refresh && id !== refresh) {
|
||||
throw new NotEnoughScopes('Please refresh the channel that needs to be refreshed');
|
||||
throw new NotEnoughScopes(
|
||||
'Please refresh the channel that needs to be refreshed'
|
||||
);
|
||||
}
|
||||
|
||||
return this._integrationService.createOrUpdateIntegration(
|
||||
|
|
@ -444,4 +447,35 @@ export class IntegrationsController {
|
|||
|
||||
return this._integrationService.deleteChannel(org.id, id);
|
||||
}
|
||||
|
||||
@Get('/plug/list')
|
||||
async getPlugList() {
|
||||
return { plugs: this._integrationManager.getAllPlugs() };
|
||||
}
|
||||
|
||||
@Get('/:id/plugs')
|
||||
async getPlugsByIntegrationId(
|
||||
@Param('id') id: string,
|
||||
@GetOrgFromRequest() org: Organization
|
||||
) {
|
||||
return this._integrationService.getPlugsByIntegrationId(org.id, id);
|
||||
}
|
||||
|
||||
@Post('/:id/plugs')
|
||||
async postPlugsByIntegrationId(
|
||||
@Param('id') id: string,
|
||||
@GetOrgFromRequest() org: Organization,
|
||||
@Body() body: PlugDto
|
||||
) {
|
||||
return this._integrationService.createOrUpdatePlug(org.id, id, body);
|
||||
}
|
||||
|
||||
@Put('/plugs/:id/activate')
|
||||
async changePlugActivation(
|
||||
@Param('id') id: string,
|
||||
@GetOrgFromRequest() org: Organization,
|
||||
@Body('status') status: boolean
|
||||
) {
|
||||
return this._integrationService.changePlugActivation(org.id, id, status);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
import { Plugs } from '@gitroom/frontend/components/plugs/plugs';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
import { Metadata } from 'next';
|
||||
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Plugs`,
|
||||
description: '',
|
||||
};
|
||||
|
||||
export default async function Index() {
|
||||
return (
|
||||
<>
|
||||
<Plugs />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ export const LaunchesComponent = () => {
|
|||
const load = useCallback(async (path: string) => {
|
||||
return (await (await fetch(path)).json()).integrations;
|
||||
}, []);
|
||||
|
||||
const user = useUser();
|
||||
|
||||
const {
|
||||
|
|
@ -132,7 +133,10 @@ export const LaunchesComponent = () => {
|
|||
'Channel disconnected, click to reconnect.',
|
||||
})}
|
||||
key={integration.id}
|
||||
className={clsx("flex gap-[8px] items-center", integration.refreshNeeded && 'cursor-pointer')}
|
||||
className={clsx(
|
||||
'flex gap-[8px] items-center',
|
||||
integration.refreshNeeded && 'cursor-pointer'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
|
|
|
|||
|
|
@ -8,16 +8,16 @@ import { useUser } from '@gitroom/frontend/components/layout/user.context';
|
|||
import { useVariables } from '@gitroom/react/helpers/variable.context';
|
||||
|
||||
export const useMenuItems = () => {
|
||||
const {isGeneral} = useVariables();
|
||||
const { isGeneral } = useVariables();
|
||||
return [
|
||||
...(!isGeneral
|
||||
? [
|
||||
{
|
||||
name: 'Analytics',
|
||||
icon: 'analytics',
|
||||
path: '/analytics',
|
||||
},
|
||||
]
|
||||
{
|
||||
name: 'Analytics',
|
||||
icon: 'analytics',
|
||||
path: '/analytics',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
name: isGeneral ? 'Calendar' : 'Launches',
|
||||
|
|
@ -26,32 +26,27 @@ export const useMenuItems = () => {
|
|||
},
|
||||
...(isGeneral
|
||||
? [
|
||||
{
|
||||
name: 'Analytics',
|
||||
icon: 'analytics',
|
||||
path: '/analytics',
|
||||
},
|
||||
]
|
||||
{
|
||||
name: 'Analytics',
|
||||
icon: 'analytics',
|
||||
path: '/analytics',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(!isGeneral
|
||||
? [
|
||||
{
|
||||
name: 'Settings',
|
||||
icon: 'settings',
|
||||
path: '/settings',
|
||||
role: ['ADMIN', 'SUPERADMIN'],
|
||||
},
|
||||
]
|
||||
{
|
||||
name: 'Settings',
|
||||
icon: 'settings',
|
||||
path: '/settings',
|
||||
role: ['ADMIN', 'SUPERADMIN'],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
name: 'Marketplace',
|
||||
icon: 'marketplace',
|
||||
path: '/marketplace',
|
||||
},
|
||||
{
|
||||
name: 'Messages',
|
||||
icon: 'messages',
|
||||
path: '/messages',
|
||||
name: 'Plugs',
|
||||
icon: 'plugs',
|
||||
path: '/plugs',
|
||||
},
|
||||
{
|
||||
name: 'Billing',
|
||||
|
|
@ -61,12 +56,12 @@ export const useMenuItems = () => {
|
|||
requireBilling: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
export const TopMenu: FC = () => {
|
||||
const path = usePathname();
|
||||
const user = useUser();
|
||||
const {billingEnabled} = useVariables();
|
||||
const { billingEnabled } = useVariables();
|
||||
const menuItems = useMenuItems();
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -0,0 +1,316 @@
|
|||
'use client';
|
||||
import {
|
||||
PlugSettings,
|
||||
PlugsInterface,
|
||||
usePlugs,
|
||||
} from '@gitroom/frontend/components/plugs/plugs.context';
|
||||
import { Button } from '@gitroom/react/form/button';
|
||||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
|
||||
import {
|
||||
FormProvider,
|
||||
SubmitHandler,
|
||||
useForm,
|
||||
useFormContext,
|
||||
} from 'react-hook-form';
|
||||
import { Input } from '@gitroom/react/form/input';
|
||||
import { CopilotTextarea } from '@copilotkit/react-textarea';
|
||||
import clsx from 'clsx';
|
||||
import { string, object } from 'yup';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { Slider } from '@gitroom/react/form/slider';
|
||||
import { useToaster } from '@gitroom/react/toaster/toaster';
|
||||
|
||||
export function convertBackRegex(s: string) {
|
||||
const matches = s.match(/\/(.*)\/([a-z]*)/);
|
||||
const pattern = matches?.[1] || '';
|
||||
const flags = matches?.[2] || '';
|
||||
|
||||
return new RegExp(pattern, flags);
|
||||
}
|
||||
|
||||
export const TextArea: FC<{ name: string; placeHolder: string }> = (props) => {
|
||||
const form = useFormContext();
|
||||
const { onChange, onBlur, ...all } = form.register(props.name);
|
||||
const value = form.watch(props.name);
|
||||
|
||||
return (
|
||||
<>
|
||||
<textarea className="hidden" {...all}></textarea>
|
||||
<CopilotTextarea
|
||||
disableBranding={true}
|
||||
placeholder={props.placeHolder}
|
||||
value={value}
|
||||
className={clsx(
|
||||
'!min-h-40 !max-h-80 p-[24px] overflow-hidden bg-customColor2 outline-none rounded-[4px] border-fifth border'
|
||||
)}
|
||||
onChange={(e) => {
|
||||
onChange({ target: { name: props.name, value: e.target.value } });
|
||||
}}
|
||||
autosuggestionsConfig={{
|
||||
textareaPurpose: `Assist me in writing social media posts.`,
|
||||
chatApiConfigs: {},
|
||||
}}
|
||||
/>
|
||||
<div className="text-red-400 text-[12px]">
|
||||
{form?.formState?.errors?.[props.name]?.message as string}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const PlugPop: FC<{
|
||||
plug: PlugsInterface;
|
||||
settings: PlugSettings;
|
||||
data?: {
|
||||
activated: boolean;
|
||||
data: string;
|
||||
id: string;
|
||||
integrationId: string;
|
||||
organizationId: string;
|
||||
plugFunction: string;
|
||||
};
|
||||
}> = (props) => {
|
||||
const { plug, settings, data } = props;
|
||||
const { closeAll } = useModals();
|
||||
const fetch = useFetch();
|
||||
const toaster = useToaster();
|
||||
|
||||
const values = useMemo(() => {
|
||||
if (!data?.data) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return JSON.parse(data.data).reduce((acc: any, current: any) => {
|
||||
return {
|
||||
...acc,
|
||||
[current.name]: current.value,
|
||||
};
|
||||
}, {} as any);
|
||||
}, []);
|
||||
|
||||
const yupSchema = useMemo(() => {
|
||||
return object(
|
||||
plug.fields.reduce((acc, field) => {
|
||||
return {
|
||||
...acc,
|
||||
[field.name]: field.validation
|
||||
? string().matches(convertBackRegex(field.validation), {
|
||||
message: 'Invalid value',
|
||||
})
|
||||
: null,
|
||||
};
|
||||
}, {})
|
||||
);
|
||||
}, []);
|
||||
|
||||
const form = useForm({
|
||||
resolver: yupResolver(yupSchema),
|
||||
values,
|
||||
mode: 'all',
|
||||
});
|
||||
|
||||
const submit: SubmitHandler<any> = useCallback(async (data) => {
|
||||
await fetch(`/integrations/${settings.providerId}/plugs`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
func: plug.methodName,
|
||||
fields: Object.keys(data).map((key) => ({
|
||||
name: key,
|
||||
value: data[key],
|
||||
})),
|
||||
}),
|
||||
});
|
||||
|
||||
toaster.show('Plug updated', 'success');
|
||||
closeAll();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={form.handleSubmit(submit)}>
|
||||
<div className="fixed left-0 top-0 bg-primary/80 z-[300] w-full min-h-full p-4 md:p-[60px] animate-fade">
|
||||
<div className="max-w-[1000px] w-full h-full bg-sixth border-tableBorder border-2 rounded-xl pb-[20px] px-[20px] relative mx-auto">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex-1">
|
||||
<TopTitle title={`Auto Plug: ${plug.title}`} />
|
||||
</div>
|
||||
<button
|
||||
onClick={closeAll}
|
||||
className="outline-none absolute right-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root bg-primary hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
>
|
||||
<path
|
||||
d="M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z"
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div className="my-[20px]">{plug.description}</div>
|
||||
<div>
|
||||
{plug.fields.map((field) => (
|
||||
<div key={field.name}>
|
||||
{field.type === 'richtext' ? (
|
||||
<TextArea
|
||||
name={field.name}
|
||||
placeHolder={field.placeholder}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
name={field.name}
|
||||
label={field.description}
|
||||
className="w-full mt-[8px] p-[8px] border border-tableBorder rounded-md text-black"
|
||||
placeholder={field.placeholder}
|
||||
type={field.type}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-[20px]">
|
||||
<Button type="submit">Activate</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const PlugItem: FC<{
|
||||
plug: PlugsInterface;
|
||||
addPlug: (data: any) => void;
|
||||
data?: {
|
||||
activated: boolean;
|
||||
data: string;
|
||||
id: string;
|
||||
integrationId: string;
|
||||
organizationId: string;
|
||||
plugFunction: string;
|
||||
};
|
||||
}> = (props) => {
|
||||
const { plug, addPlug, data } = props;
|
||||
const [activated, setActivated] = useState(!!data?.activated);
|
||||
useEffect(() => {
|
||||
setActivated(!!data?.activated);
|
||||
}, [data?.activated]);
|
||||
const fetch = useFetch();
|
||||
|
||||
const changeActivated = useCallback(
|
||||
async (status: 'on' | 'off') => {
|
||||
await fetch(`/integrations/plugs/${data?.id}/activate`, {
|
||||
body: JSON.stringify({
|
||||
status: status === 'on',
|
||||
}),
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
setActivated(status === 'on');
|
||||
},
|
||||
[activated]
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => addPlug(data)}
|
||||
key={plug.title}
|
||||
className="w-full h-[300px] bg-customColor48 hover:bg-customColor2 hover:border-customColor48 hover:border"
|
||||
>
|
||||
<div key={plug.title} className="p-[16px] h-full flex flex-col flex-1">
|
||||
<div className="flex">
|
||||
<div className="text-[20px] mb-[8px] flex-1">{plug.title}</div>
|
||||
{!!data && (
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<Slider
|
||||
value={activated ? 'on' : 'off'}
|
||||
onChange={changeActivated}
|
||||
fill={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1">{plug.description}</div>
|
||||
<Button>{!data ? 'Set Plug' : 'Edit Plug'}</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Plug = () => {
|
||||
const plug = usePlugs();
|
||||
const modals = useModals();
|
||||
const fetch = useFetch();
|
||||
const load = useCallback(async () => {
|
||||
return (await fetch(`/integrations/${plug.providerId}/plugs`)).json();
|
||||
}, [plug.providerId]);
|
||||
|
||||
const { data, isLoading, mutate } = useSWR(`plugs-${plug.providerId}`, load);
|
||||
|
||||
const addEditPlug = useCallback(
|
||||
(p: PlugsInterface) =>
|
||||
(data?: {
|
||||
activated: boolean;
|
||||
data: string;
|
||||
id: string;
|
||||
integrationId: string;
|
||||
organizationId: string;
|
||||
plugFunction: string;
|
||||
}) => {
|
||||
modals.openModal({
|
||||
classNames: {
|
||||
modal: 'bg-transparent text-textColor',
|
||||
},
|
||||
withCloseButton: false,
|
||||
onClose() {
|
||||
mutate();
|
||||
},
|
||||
size: '100%',
|
||||
children: (
|
||||
<PlugPop
|
||||
plug={p}
|
||||
data={data}
|
||||
settings={{
|
||||
identifier: plug.identifier,
|
||||
providerId: plug.providerId,
|
||||
name: plug.name,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
});
|
||||
},
|
||||
[data]
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-3 gap-[30px]">
|
||||
{plug.plugs.map((p) => (
|
||||
<PlugItem
|
||||
key={p.title + '-' + plug.providerId}
|
||||
addPlug={addEditPlug(p)}
|
||||
plug={p}
|
||||
data={data?.find((a: any) => a.plugFunction === p.methodName)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
'use client';
|
||||
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
export interface PlugSettings {
|
||||
providerId: string;
|
||||
name: string;
|
||||
identifier: string;
|
||||
}
|
||||
|
||||
export interface PlugInterface extends PlugSettings {
|
||||
plugs: PlugsInterface[];
|
||||
}
|
||||
|
||||
export interface FieldsInterface {
|
||||
name: string;
|
||||
type: string;
|
||||
validation: string;
|
||||
placeholder: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface PlugsInterface {
|
||||
title: string;
|
||||
description: string;
|
||||
runEveryMilliseconds: number;
|
||||
methodName: string;
|
||||
fields: FieldsInterface[];
|
||||
}
|
||||
|
||||
export const PlugsContext = createContext<PlugInterface>({
|
||||
providerId: '',
|
||||
name: '',
|
||||
identifier: '',
|
||||
plugs: [
|
||||
{
|
||||
title: '',
|
||||
description: '',
|
||||
runEveryMilliseconds: 0,
|
||||
methodName: '',
|
||||
fields: [{ name: '', type: '', placeholder: '', description: '', validation: '' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
export const usePlugs = () => useContext(PlugsContext);
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
'use client';
|
||||
|
||||
import useSWR from 'swr';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { capitalize, orderBy } from 'lodash';
|
||||
import clsx from 'clsx';
|
||||
import ImageWithFallback from '@gitroom/react/helpers/image.with.fallback';
|
||||
import Image from 'next/image';
|
||||
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
|
||||
import { Select } from '@gitroom/react/form/select';
|
||||
import { Button } from '@gitroom/react/form/button';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useToaster } from '@gitroom/react/toaster/toaster';
|
||||
import { PlugsContext } from '@gitroom/frontend/components/plugs/plugs.context';
|
||||
import { Plug } from '@gitroom/frontend/components/plugs/plug';
|
||||
|
||||
export const Plugs = () => {
|
||||
const fetch = useFetch();
|
||||
const router = useRouter();
|
||||
const [current, setCurrent] = useState(0);
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const toaster = useToaster();
|
||||
const load = useCallback(async () => {
|
||||
return (await (await fetch('/integrations/list')).json()).integrations;
|
||||
}, []);
|
||||
|
||||
const load2 = useCallback(async (path: string) => {
|
||||
return await (await fetch(path)).json();
|
||||
}, []);
|
||||
|
||||
const { data: plugList, isLoading: plugLoading } = useSWR(
|
||||
'/integrations/plug/list',
|
||||
load2,
|
||||
{
|
||||
fallbackData: [],
|
||||
}
|
||||
);
|
||||
|
||||
const { data, isLoading } = useSWR('analytics-list', load, {
|
||||
fallbackData: [],
|
||||
});
|
||||
|
||||
const sortedIntegrations = useMemo(() => {
|
||||
return orderBy(
|
||||
data.filter((integration: any) =>
|
||||
plugList?.plugs?.some(
|
||||
(f: any) => f.identifier === integration.identifier
|
||||
)
|
||||
),
|
||||
// data.filter((integration) => !integration.disabled),
|
||||
['type', 'disabled', 'identifier'],
|
||||
['desc', 'asc', 'asc']
|
||||
);
|
||||
}, [data, plugList]);
|
||||
|
||||
const currentIntegration = useMemo(() => {
|
||||
return sortedIntegrations[current];
|
||||
}, [current, sortedIntegrations]);
|
||||
|
||||
const currentIntegrationPlug = useMemo(() => {
|
||||
const plug = plugList?.plugs?.find(
|
||||
(f: any) => f?.identifier === currentIntegration?.identifier
|
||||
);
|
||||
|
||||
if (!plug) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
providerId: currentIntegration.id,
|
||||
...plug,
|
||||
};
|
||||
}, [currentIntegration, plugList]);
|
||||
|
||||
console.log(currentIntegrationPlug);
|
||||
if (isLoading || plugLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!sortedIntegrations.length && !isLoading) {
|
||||
return (
|
||||
<div className="flex flex-col items-center mt-[100px] gap-[27px] text-center">
|
||||
<div>
|
||||
<img src="/peoplemarketplace.svg" />
|
||||
</div>
|
||||
<div className="text-[48px]">
|
||||
Can{"'"}t show analytics yet
|
||||
<br />
|
||||
You have to add Social Media channels
|
||||
</div>
|
||||
<Button onClick={() => router.push('/launches')}>
|
||||
Go to the calendar to add channels
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-[30px] flex-1">
|
||||
<div className="p-[16px] bg-customColor48 overflow-hidden flex w-[220px]">
|
||||
<div className="flex gap-[16px] flex-col overflow-hidden">
|
||||
<div className="text-[20px] mb-[8px]">Channels</div>
|
||||
{sortedIntegrations.map((integration, index) => (
|
||||
<div
|
||||
key={integration.id}
|
||||
onClick={() => {
|
||||
if (integration.refreshNeeded) {
|
||||
toaster.show(
|
||||
'Please refresh the integration from the calendar',
|
||||
'warning'
|
||||
);
|
||||
return;
|
||||
}
|
||||
setRefresh(true);
|
||||
setTimeout(() => {
|
||||
setRefresh(false);
|
||||
}, 10);
|
||||
setCurrent(index);
|
||||
}}
|
||||
className={clsx(
|
||||
'flex gap-[8px] items-center',
|
||||
currentIntegration.id !== integration.id &&
|
||||
'opacity-20 hover:opacity-100 cursor-pointer'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'relative w-[34px] h-[34px] rounded-full flex justify-center items-center bg-fifth',
|
||||
integration.disabled && 'opacity-50'
|
||||
)}
|
||||
>
|
||||
{(integration.inBetweenSteps || integration.refreshNeeded) && (
|
||||
<div className="absolute left-0 top-0 w-[39px] h-[46px] cursor-pointer">
|
||||
<div className="bg-red-500 w-[15px] h-[15px] rounded-full left-0 -top-[5px] absolute z-[200] text-[10px] flex justify-center items-center">
|
||||
!
|
||||
</div>
|
||||
<div className="bg-primary/60 w-[39px] h-[46px] left-0 top-0 absolute rounded-full z-[199]" />
|
||||
</div>
|
||||
)}
|
||||
<ImageWithFallback
|
||||
fallbackSrc={`/icons/platforms/${integration.identifier}.png`}
|
||||
src={integration.picture}
|
||||
className="rounded-full"
|
||||
alt={integration.identifier}
|
||||
width={32}
|
||||
height={32}
|
||||
/>
|
||||
<Image
|
||||
src={`/icons/platforms/${integration.identifier}.png`}
|
||||
className="rounded-full absolute z-10 -bottom-[5px] -right-[5px] border border-fifth"
|
||||
alt={integration.identifier}
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
'flex-1 whitespace-nowrap text-ellipsis overflow-hidden',
|
||||
integration.disabled && 'opacity-50'
|
||||
)}
|
||||
>
|
||||
{integration.name}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col gap-[14px]">
|
||||
<PlugsContext.Provider value={currentIntegrationPlug}>
|
||||
<Plug />
|
||||
</PlugsContext.Provider>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,14 +1,19 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { StarsController } from './stars.controller';
|
||||
import {DatabaseModule} from "@gitroom/nestjs-libraries/database/prisma/database.module";
|
||||
import {TrendingService} from "@gitroom/nestjs-libraries/services/trending.service";
|
||||
import {PostsController} from "@gitroom/workers/app/posts.controller";
|
||||
import { DatabaseModule } from '@gitroom/nestjs-libraries/database/prisma/database.module';
|
||||
import { TrendingService } from '@gitroom/nestjs-libraries/services/trending.service';
|
||||
import { PostsController } from '@gitroom/workers/app/posts.controller';
|
||||
import { BullMqModule } from '@gitroom/nestjs-libraries/bull-mq-transport-new/bull.mq.module';
|
||||
import { PlugsController } from '@gitroom/workers/app/plugs.controller';
|
||||
|
||||
@Module({
|
||||
imports: [DatabaseModule, BullMqModule],
|
||||
controllers: [...!process.env.IS_GENERAL ? [StarsController] : [], PostsController],
|
||||
controllers: [
|
||||
...(!process.env.IS_GENERAL ? [StarsController] : []),
|
||||
PostsController,
|
||||
PlugsController,
|
||||
],
|
||||
providers: [TrendingService],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
import { Controller } from '@nestjs/common';
|
||||
import { EventPattern, Transport } from '@nestjs/microservices';
|
||||
import { BullMqClient } from '@gitroom/nestjs-libraries/bull-mq-transport-new/client';
|
||||
import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';
|
||||
|
||||
@Controller()
|
||||
export class PlugsController {
|
||||
constructor(
|
||||
private _workerServiceProducer: BullMqClient,
|
||||
private _integrationService: IntegrationService
|
||||
) {}
|
||||
|
||||
@EventPattern('plugs', Transport.REDIS)
|
||||
async plug(data: {
|
||||
orgId: string;
|
||||
integrationId: string;
|
||||
funcName: string;
|
||||
retry: number;
|
||||
delay: number;
|
||||
}) {
|
||||
try {
|
||||
await this._integrationService.startPlug(data);
|
||||
|
||||
if (process.env.NODE_ENV && process.env.NODE_ENV !== 'development') {
|
||||
return this._workerServiceProducer.emit('plugs', {
|
||||
id: data.integrationId + '-' + data.funcName,
|
||||
options: {
|
||||
delay: 6000, // delay,
|
||||
},
|
||||
payload: {
|
||||
...data,
|
||||
retry: data.retry,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
if (data.retry > 3) {
|
||||
return;
|
||||
}
|
||||
return this._workerServiceProducer.emit('plugs', {
|
||||
id: data.integrationId + '-' + data.funcName,
|
||||
options: {
|
||||
delay: data.delay, // delay,
|
||||
},
|
||||
payload: {
|
||||
...data,
|
||||
retry: data.retry + 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ export class PostsController {
|
|||
}
|
||||
|
||||
@EventPattern('submit', Transport.REDIS)
|
||||
async payout(data: { id: string, releaseURL: string }) {
|
||||
async payout(data: { id: string; releaseURL: string }) {
|
||||
return this._postsService.payout(data.id, data.releaseURL);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
import 'reflect-metadata';
|
||||
|
||||
export function Plug(params: {
|
||||
title: string;
|
||||
description: string;
|
||||
runEveryMilliseconds: number;
|
||||
fields: {
|
||||
name: string;
|
||||
description: string;
|
||||
type: string;
|
||||
placeholder: string;
|
||||
validation?: RegExp;
|
||||
}[];
|
||||
}) {
|
||||
return function (target: Object, propertyKey: string | symbol) {
|
||||
// Retrieve existing metadata or initialize an empty array
|
||||
const existingMetadata = Reflect.getMetadata('custom:plug', target) || [];
|
||||
|
||||
// Add the metadata information for this method
|
||||
existingMetadata.push({ methodName: propertyKey, ...params });
|
||||
|
||||
// Define metadata on the class prototype (so it can be retrieved from the class)
|
||||
Reflect.defineMetadata('custom:plug', existingMetadata, target);
|
||||
};
|
||||
}
|
||||
|
|
@ -40,8 +40,8 @@ export class BullMqClient extends ClientProxy {
|
|||
const job = await queue.add(packet.pattern, packet.data, {
|
||||
jobId: packet.data.id ?? v4(),
|
||||
...packet.data.options,
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
removeOnComplete: !packet.data.options.attempts,
|
||||
removeOnFail: !packet.data.options.attempts,
|
||||
});
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -5,13 +5,15 @@ import { Integration } from '@prisma/client';
|
|||
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
||||
import { IntegrationTimeDto } from '@gitroom/nestjs-libraries/dtos/integrations/integration.time.dto';
|
||||
import { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';
|
||||
import { PlugDto } from '@gitroom/nestjs-libraries/dtos/plugs/plug.dto';
|
||||
|
||||
@Injectable()
|
||||
export class IntegrationRepository {
|
||||
private storage = UploadFactory.createStorage();
|
||||
constructor(
|
||||
private _integration: PrismaRepository<'integration'>,
|
||||
private _posts: PrismaRepository<'post'>
|
||||
private _posts: PrismaRepository<'post'>,
|
||||
private _plugs: PrismaRepository<'plugs'>
|
||||
) {}
|
||||
|
||||
async setTimes(org: string, id: string, times: IntegrationTimeDto) {
|
||||
|
|
@ -310,4 +312,50 @@ export class IntegrationRepository {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
getPlugsByIntegrationId(org: string, id: string) {
|
||||
return this._plugs.model.plugs.findMany({
|
||||
where: {
|
||||
organizationId: org,
|
||||
integrationId: id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
createOrUpdatePlug(org: string, integrationId: string, body: PlugDto) {
|
||||
return this._plugs.model.plugs.upsert({
|
||||
where: {
|
||||
organizationId: org,
|
||||
plugFunction_integrationId: {
|
||||
integrationId,
|
||||
plugFunction: body.func,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
integrationId,
|
||||
organizationId: org,
|
||||
plugFunction: body.func,
|
||||
data: JSON.stringify(body.fields),
|
||||
activated: true,
|
||||
},
|
||||
update: {
|
||||
data: JSON.stringify(body.fields),
|
||||
},
|
||||
select: {
|
||||
activated: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
changePlugActivation(orgId: string, plugId: string, status: boolean) {
|
||||
return this._plugs.model.plugs.update({
|
||||
where: {
|
||||
organizationId: orgId,
|
||||
id: plugId,
|
||||
},
|
||||
data: {
|
||||
activated: !!status,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,23 @@
|
|||
import {
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Injectable,
|
||||
Param,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { IntegrationRepository } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.repository';
|
||||
import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integration.manager';
|
||||
import { InstagramProvider } from '@gitroom/nestjs-libraries/integrations/social/instagram.provider';
|
||||
import { FacebookProvider } from '@gitroom/nestjs-libraries/integrations/social/facebook.provider';
|
||||
import { AnalyticsData, SocialProvider } from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';
|
||||
import {
|
||||
AnalyticsData,
|
||||
SocialProvider,
|
||||
} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';
|
||||
import { Integration, Organization } from '@prisma/client';
|
||||
import { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';
|
||||
import { LinkedinPageProvider } from '@gitroom/nestjs-libraries/integrations/social/linkedin.page.provider';
|
||||
import { simpleUpload } from '@gitroom/nestjs-libraries/upload/r2.uploader';
|
||||
import axios from 'axios';
|
||||
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
||||
import dayjs from 'dayjs';
|
||||
import { timer } from '@gitroom/helpers/utils/timer';
|
||||
import { ioRedis } from '@gitroom/nestjs-libraries/redis/redis.service';
|
||||
import { RefreshToken } from '@gitroom/nestjs-libraries/integrations/social.abstract';
|
||||
import { IntegrationTimeDto } from '@gitroom/nestjs-libraries/dtos/integrations/integration.time.dto';
|
||||
import { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';
|
||||
import { PlugDto } from '@gitroom/nestjs-libraries/dtos/plugs/plug.dto';
|
||||
import { BullMqClient } from '@gitroom/nestjs-libraries/bull-mq-transport-new/client';
|
||||
|
||||
@Injectable()
|
||||
export class IntegrationService {
|
||||
|
|
@ -29,10 +25,15 @@ export class IntegrationService {
|
|||
constructor(
|
||||
private _integrationRepository: IntegrationRepository,
|
||||
private _integrationManager: IntegrationManager,
|
||||
private _notificationService: NotificationService
|
||||
private _notificationService: NotificationService,
|
||||
private _workerServiceProducer: BullMqClient
|
||||
) {}
|
||||
|
||||
async setTimes(orgId: string, integrationId: string, times: IntegrationTimeDto) {
|
||||
async setTimes(
|
||||
orgId: string,
|
||||
integrationId: string,
|
||||
times: IntegrationTimeDto
|
||||
) {
|
||||
return this._integrationRepository.setTimes(orgId, integrationId, times);
|
||||
}
|
||||
|
||||
|
|
@ -292,7 +293,12 @@ export class IntegrationService {
|
|||
return { success: true };
|
||||
}
|
||||
|
||||
async checkAnalytics(org: Organization, integration: string, date: string, forceRefresh = false): Promise<AnalyticsData[]> {
|
||||
async checkAnalytics(
|
||||
org: Organization,
|
||||
integration: string,
|
||||
date: string,
|
||||
forceRefresh = false
|
||||
): Promise<AnalyticsData[]> {
|
||||
const getIntegration = await this.getIntegrationById(org.id, integration);
|
||||
|
||||
if (!getIntegration) {
|
||||
|
|
@ -307,7 +313,10 @@ export class IntegrationService {
|
|||
getIntegration.providerIdentifier
|
||||
);
|
||||
|
||||
if (dayjs(getIntegration?.tokenExpiration).isBefore(dayjs()) || forceRefresh) {
|
||||
if (
|
||||
dayjs(getIntegration?.tokenExpiration).isBefore(dayjs()) ||
|
||||
forceRefresh
|
||||
) {
|
||||
const { accessToken, expiresIn, refreshToken } =
|
||||
await integrationProvider.refreshToken(getIntegration.refreshToken!);
|
||||
|
||||
|
|
@ -367,4 +376,133 @@ export class IntegrationService {
|
|||
|
||||
return [];
|
||||
}
|
||||
|
||||
getPlugsByIntegrationId(org: string, integrationId: string) {
|
||||
return this._integrationRepository.getPlugsByIntegrationId(
|
||||
org,
|
||||
integrationId
|
||||
);
|
||||
}
|
||||
|
||||
processPlugs(
|
||||
orgId: string,
|
||||
integrationId: string,
|
||||
delay: number,
|
||||
funcName: string
|
||||
) {
|
||||
return this._workerServiceProducer.emit('plugs', {
|
||||
id: integrationId + '-' + funcName,
|
||||
options: {
|
||||
delay: 0, // delay,
|
||||
},
|
||||
payload: {
|
||||
retry: 1,
|
||||
delay,
|
||||
orgId,
|
||||
integrationId: integrationId,
|
||||
funcName: funcName,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async activatedPlug(
|
||||
orgId: string,
|
||||
integrationId: string,
|
||||
funcName: string,
|
||||
type: 'add' | 'remove'
|
||||
) {
|
||||
const loadIntegration = await this.getIntegrationById(orgId, integrationId);
|
||||
const allPlugs = this._integrationManager.getAllPlugs();
|
||||
const findPlug = allPlugs.find(
|
||||
(p) => p.identifier === loadIntegration?.providerIdentifier!
|
||||
)!;
|
||||
const plug = findPlug.plugs.find((p: any) => p.methodName === funcName)!;
|
||||
|
||||
if (type === 'add') {
|
||||
return this.processPlugs(
|
||||
orgId,
|
||||
integrationId,
|
||||
plug.runEveryMilliseconds,
|
||||
funcName
|
||||
);
|
||||
} else {
|
||||
console.log(integrationId + '-' + funcName);
|
||||
return this._workerServiceProducer.delete(
|
||||
'plugs',
|
||||
integrationId + '-' + funcName
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async createOrUpdatePlug(
|
||||
orgId: string,
|
||||
integrationId: string,
|
||||
body: PlugDto
|
||||
) {
|
||||
const { activated } = await this._integrationRepository.createOrUpdatePlug(
|
||||
orgId,
|
||||
integrationId,
|
||||
body
|
||||
);
|
||||
|
||||
if (activated) {
|
||||
await this.activatedPlug(orgId, integrationId, body.func, 'add');
|
||||
}
|
||||
}
|
||||
|
||||
async changePlugActivation(orgId: string, plugId: string, status: boolean) {
|
||||
const { id, integrationId, plugFunction } =
|
||||
await this._integrationRepository.changePlugActivation(
|
||||
orgId,
|
||||
plugId,
|
||||
status
|
||||
);
|
||||
|
||||
if (status) {
|
||||
await this.activatedPlug(orgId, integrationId, plugFunction, 'add');
|
||||
} else {
|
||||
await this.activatedPlug(orgId, integrationId, plugFunction, 'remove');
|
||||
}
|
||||
|
||||
return { id };
|
||||
}
|
||||
|
||||
async startPlug(data: {
|
||||
orgId: string;
|
||||
integrationId: string;
|
||||
funcName: string;
|
||||
retry: number;
|
||||
delay: number;
|
||||
}) {
|
||||
const integration = await this.getIntegrationById(
|
||||
data.orgId,
|
||||
data.integrationId
|
||||
);
|
||||
|
||||
if (!integration) {
|
||||
return;
|
||||
}
|
||||
|
||||
const plugInformation = (
|
||||
await this._integrationRepository.getPlugsByIntegrationId(
|
||||
data.orgId,
|
||||
data.integrationId
|
||||
)
|
||||
).find((p) => p.plugFunction === data.funcName)!;
|
||||
|
||||
const plugData = JSON.parse(plugInformation.data).reduce(
|
||||
(all: any, current: any) => ({
|
||||
...all,
|
||||
[current.name]: current.value,
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
const integrationInstance = this._integrationManager.getSocialIntegration(
|
||||
integration.providerIdentifier
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
return integrationInstance[data.funcName](integration, plugData);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ model Organization {
|
|||
buyerOrganization MessagesGroup[]
|
||||
usedCodes UsedCodes[]
|
||||
credits Credits[]
|
||||
plugs Plugs[]
|
||||
}
|
||||
|
||||
model User {
|
||||
|
|
@ -264,6 +265,7 @@ model Integration {
|
|||
refreshNeeded Boolean @default(false)
|
||||
postingTimes String @default("[{\"time\":120}, {\"time\":400}, {\"time\":700}]")
|
||||
customInstanceDetails String?
|
||||
plugs Plugs[]
|
||||
|
||||
@@index([updatedAt])
|
||||
@@index([deletedAt])
|
||||
|
|
@ -439,6 +441,20 @@ model Messages {
|
|||
@@index([deletedAt])
|
||||
}
|
||||
|
||||
model Plugs {
|
||||
id String @id @default(uuid())
|
||||
organizationId String
|
||||
organization Organization @relation(fields: [organizationId], references: [id])
|
||||
plugFunction String
|
||||
data String
|
||||
integrationId String
|
||||
integration Integration @relation(fields: [integrationId], references: [id])
|
||||
activated Boolean @default(true)
|
||||
|
||||
@@unique([plugFunction, integrationId])
|
||||
@@index([organizationId])
|
||||
}
|
||||
|
||||
enum OrderStatus {
|
||||
PENDING
|
||||
ACCEPTED
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
import { IsDefined, IsString, ValidateNested } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class FieldsDto {
|
||||
@IsString()
|
||||
@IsDefined()
|
||||
name: string;
|
||||
|
||||
@IsString()
|
||||
@IsDefined()
|
||||
value: string;
|
||||
}
|
||||
|
||||
export class PlugDto {
|
||||
@IsString()
|
||||
@IsDefined()
|
||||
func: string;
|
||||
|
||||
@Type(() => FieldsDto)
|
||||
@ValidateNested({ each: true })
|
||||
@IsDefined()
|
||||
fields: FieldsDto[];
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import 'reflect-metadata';
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { XProvider } from '@gitroom/nestjs-libraries/integrations/social/x.provider';
|
||||
import { SocialProvider } from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';
|
||||
|
|
@ -64,6 +66,27 @@ export class IntegrationManager {
|
|||
})),
|
||||
};
|
||||
}
|
||||
|
||||
getAllPlugs() {
|
||||
return socialIntegrationList
|
||||
.map((p) => {
|
||||
return {
|
||||
name: p.name,
|
||||
identifier: p.identifier,
|
||||
plugs: (
|
||||
Reflect.getMetadata('custom:plug', p.constructor.prototype) || []
|
||||
).map((p: any) => ({
|
||||
...p,
|
||||
fields: p.fields.map((c: any) => ({
|
||||
...c,
|
||||
validation: c?.validation?.toString(),
|
||||
})),
|
||||
})),
|
||||
};
|
||||
})
|
||||
.filter((f) => f.plugs.length);
|
||||
}
|
||||
|
||||
getAllowedSocialsIntegrations() {
|
||||
return socialIntegrationList.map((p) => p.identifier);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
|||
import { LinkedinProvider } from '@gitroom/nestjs-libraries/integrations/social/linkedin.provider';
|
||||
import dayjs from 'dayjs';
|
||||
import { Integration } from '@prisma/client';
|
||||
import { Plug } from '@gitroom/helpers/decorators/plug.decorator';
|
||||
|
||||
export class LinkedinPageProvider
|
||||
extends LinkedinProvider
|
||||
|
|
@ -97,17 +98,20 @@ export class LinkedinPageProvider
|
|||
}
|
||||
|
||||
async companies(accessToken: string) {
|
||||
const { elements } = await (
|
||||
const { elements, ...all } = await (
|
||||
await fetch(
|
||||
'https://api.linkedin.com/v2/organizationalEntityAcls?q=roleAssignee&role=ADMINISTRATOR&projection=(elements*(organizationalTarget~(localizedName,vanityName,logoV2(original~:playableStreams))))',
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'X-Restli-Protocol-Version': '2.0.0',
|
||||
'LinkedIn-Version': '202402',
|
||||
},
|
||||
}
|
||||
)
|
||||
).json();
|
||||
|
||||
console.log(all);
|
||||
return (elements || []).map((e: any) => ({
|
||||
id: e.organizationalTarget.split(':').pop(),
|
||||
page: e.organizationalTarget.split(':').pop(),
|
||||
|
|
@ -124,7 +128,10 @@ export class LinkedinPageProvider
|
|||
requiredId: string,
|
||||
accessToken: string
|
||||
): Promise<AuthTokenDetails> {
|
||||
const information = await this.fetchPageInformation(accessToken, requiredId);
|
||||
const information = await this.fetchPageInformation(
|
||||
accessToken,
|
||||
requiredId
|
||||
);
|
||||
|
||||
return {
|
||||
id: information.id,
|
||||
|
|
@ -355,6 +362,41 @@ export class LinkedinPageProvider
|
|||
percentageChange: 5,
|
||||
}));
|
||||
}
|
||||
|
||||
@Plug({
|
||||
title: 'Auto Repost Posts',
|
||||
description:
|
||||
'When a post reached a certain number of likes, repost it to increase engagement',
|
||||
runEveryMilliseconds: 7200000,
|
||||
fields: [
|
||||
{
|
||||
name: 'likesAmount',
|
||||
type: 'number',
|
||||
placeholder: 'Amount of likes',
|
||||
description: 'The amount of likes to trigger the post',
|
||||
validation: /^\d+$/,
|
||||
},
|
||||
],
|
||||
})
|
||||
async autoAddPost(integration: Integration, fields: { likesAmount: number }) {
|
||||
const a = await fetch(
|
||||
`https://api.linkedin.com/rest/posts?author=${encodeURIComponent(
|
||||
`urn:li:organization:${integration.internalId}`
|
||||
)}&q=author&count=10&sortBy=LAST_MODIFIED`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'X-Restli-Protocol-Version': '2.0.0',
|
||||
'Content-Type': 'application/json',
|
||||
'LinkedIn-Version': '202402',
|
||||
Authorization: `Bearer ${integration.token}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
console.log(await a.json());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
export interface Root {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import {
|
|||
SocialAbstract,
|
||||
} from '@gitroom/nestjs-libraries/integrations/social.abstract';
|
||||
import { Integration } from '@prisma/client';
|
||||
import { Plug } from '@gitroom/helpers/decorators/plug.decorator';
|
||||
import { string } from 'yup';
|
||||
|
||||
export class LinkedinProvider extends SocialAbstract implements SocialProvider {
|
||||
identifier = 'linkedin';
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ import sharp from 'sharp';
|
|||
import { readOrFetch } from '@gitroom/helpers/utils/read.or.fetch';
|
||||
import removeMd from 'remove-markdown';
|
||||
import { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';
|
||||
import { Plug } from '@gitroom/helpers/decorators/plug.decorator';
|
||||
import { string } from 'yup';
|
||||
import { Integration } from '@prisma/client';
|
||||
|
||||
export class XProvider extends SocialAbstract implements SocialProvider {
|
||||
identifier = 'x';
|
||||
|
|
|
|||
Loading…
Reference in New Issue