diff --git a/apps/backend/src/api/routes/integrations.controller.ts b/apps/backend/src/api/routes/integrations.controller.ts index 6cdf7ee6..791e0ac3 100644 --- a/apps/backend/src/api/routes/integrations.controller.ts +++ b/apps/backend/src/api/routes/integrations.controller.ts @@ -264,7 +264,8 @@ export class IntegrationsController { const load = await integrationProvider[body.name]( getIntegration.token, body.data, - getIntegration.internalId + getIntegration.internalId, + getIntegration ); return load; diff --git a/apps/frontend/public/icons/platforms/lemmy.png b/apps/frontend/public/icons/platforms/lemmy.png new file mode 100644 index 00000000..475d5c10 Binary files /dev/null and b/apps/frontend/public/icons/platforms/lemmy.png differ diff --git a/apps/frontend/src/components/launches/add.edit.model.tsx b/apps/frontend/src/components/launches/add.edit.model.tsx index 229be6e8..e0f7db99 100644 --- a/apps/frontend/src/components/launches/add.edit.model.tsx +++ b/apps/frontend/src/components/launches/add.edit.model.tsx @@ -1,7 +1,17 @@ 'use client'; import React, { - ClipboardEventHandler, FC, Fragment, MouseEventHandler, useCallback, useEffect, useMemo, useRef, ClipboardEvent, useState, memo + ClipboardEventHandler, + FC, + Fragment, + MouseEventHandler, + useCallback, + useEffect, + useMemo, + useRef, + ClipboardEvent, + useState, + memo, } from 'react'; import dayjs from 'dayjs'; import { Integrations } from '@gitroom/frontend/components/launches/calendar.context'; @@ -50,6 +60,7 @@ import { useClickOutside } from '@gitroom/frontend/components/layout/click.outsi import { useUppyUploader } from '@gitroom/frontend/components/media/new.uploader'; import { LoadingComponent } from '@gitroom/frontend/components/layout/loading'; import { DropFiles } from '@gitroom/frontend/components/layout/drop.files'; +import { SelectCustomer } from '@gitroom/frontend/components/launches/select.customer'; function countCharacters(text: string, type: string): number { if (type !== 'x') { @@ -94,10 +105,6 @@ export const AddEditModal: FC<{ return list; }, [customer, ints]); - const totalCustomers = useMemo(() => { - return uniqBy(ints, (i) => i?.customer?.id).length; - }, [ints]); - const [dateState, setDateState] = useState(date); // hook to open a new modal @@ -519,28 +526,13 @@ export const AddEditModal: FC<{ information={data} onChange={setPostFor} /> - {totalCustomers > 1 && ( - - )} + { + setCustomer(val); + setSelectedIntegrations([]); + }} + /> {!selectedIntegrations.length && ( , - integrations: [] as (Integrations & {refreshNeeded?: boolean})[], + integrations: [] as (Integrations & { refreshNeeded?: boolean })[], trendings: [] as string[], posts: [] as Array, reloadCalendarView: () => { @@ -42,6 +43,7 @@ export const CalendarContext = createContext({ currentDay: 0 | 1 | 2 | 3 | 4 | 5 | 6; currentMonth: number; display: 'week' | 'month' | 'day'; + customer: string | null; }) => { /** empty **/ }, @@ -66,7 +68,7 @@ export interface Integrations { customer?: { name?: string; id?: string; - } + }; } function getWeekNumber(date: Date) { @@ -94,6 +96,7 @@ export const CalendarWeekProvider: FC<{ const fetch = useFetch(); const [internalData, setInternalData] = useState([] as any[]); const [trendings] = useState([]); + const searchParams = useSearchParams(); const display = searchParams.get('display') || 'week'; @@ -110,6 +113,7 @@ export const CalendarWeekProvider: FC<{ currentWeek: +(searchParams.get('week') || getWeekNumber(new Date())), currentMonth: +(searchParams.get('month') || dayjs().month()), currentYear: +(searchParams.get('year') || dayjs().year()), + customer: (searchParams.get('customer') as string) || null, display, }); @@ -120,6 +124,7 @@ export const CalendarWeekProvider: FC<{ week: filters.currentWeek.toString(), month: (filters.currentMonth + 1).toString(), year: filters.currentYear.toString(), + customer: filters?.customer?.toString() || '', }).toString(); }, [filters, display]); @@ -142,6 +147,7 @@ export const CalendarWeekProvider: FC<{ currentYear: number; currentMonth: number; display: 'week' | 'month' | 'day'; + customer: string | null; }) => { setFilters(filters); setInternalData([]); @@ -152,6 +158,7 @@ export const CalendarWeekProvider: FC<{ `month=${filters.currentMonth}`, `year=${filters.currentYear}`, `display=${filters.display}`, + filters.customer ? `customer=${filters.customer}` : ``, ].filter((f) => f); window.history.replaceState(null, '', `/launches?${path.join('&')}`); }, diff --git a/apps/frontend/src/components/launches/filters.tsx b/apps/frontend/src/components/launches/filters.tsx index db21fa5b..4f883728 100644 --- a/apps/frontend/src/components/launches/filters.tsx +++ b/apps/frontend/src/components/launches/filters.tsx @@ -4,6 +4,7 @@ import clsx from 'clsx'; import dayjs from 'dayjs'; import { useCallback } from 'react'; import { isUSCitizen } from './helpers/isuscitizen.utils'; +import { SelectCustomer } from '@gitroom/frontend/components/launches/select.customer'; export const Filters = () => { const week = useCalendar(); @@ -13,30 +14,30 @@ export const Filters = () => { .year(week.currentYear) .isoWeek(week.currentWeek) .day(week.currentDay) - .format(isUSCitizen() ? 'MM/DD/YYYY' :'DD/MM/YYYY') + .format(isUSCitizen() ? 'MM/DD/YYYY' : 'DD/MM/YYYY') : week.display === 'week' ? dayjs() .year(week.currentYear) .isoWeek(week.currentWeek) .startOf('isoWeek') - .format(isUSCitizen() ? 'MM/DD/YYYY' :'DD/MM/YYYY') + + .format(isUSCitizen() ? 'MM/DD/YYYY' : 'DD/MM/YYYY') + ' - ' + dayjs() .year(week.currentYear) .isoWeek(week.currentWeek) .endOf('isoWeek') - .format(isUSCitizen() ? 'MM/DD/YYYY' :'DD/MM/YYYY') + .format(isUSCitizen() ? 'MM/DD/YYYY' : 'DD/MM/YYYY') : dayjs() .year(week.currentYear) .month(week.currentMonth) .startOf('month') - .format(isUSCitizen() ? 'MM/DD/YYYY' :'DD/MM/YYYY') + + .format(isUSCitizen() ? 'MM/DD/YYYY' : 'DD/MM/YYYY') + ' - ' + dayjs() .year(week.currentYear) .month(week.currentMonth) .endOf('month') - .format(isUSCitizen() ? 'MM/DD/YYYY' :'DD/MM/YYYY'); + .format(isUSCitizen() ? 'MM/DD/YYYY' : 'DD/MM/YYYY'); const setDay = useCallback(() => { week.setFilters({ @@ -45,6 +46,7 @@ export const Filters = () => { currentYear: dayjs().year(), currentMonth: dayjs().month(), display: 'day', + customer: week.customer, }); }, [week]); @@ -55,6 +57,7 @@ export const Filters = () => { currentYear: dayjs().year(), currentMonth: dayjs().month(), display: 'week', + customer: week.customer, }); }, [week]); @@ -65,9 +68,24 @@ export const Filters = () => { currentWeek: dayjs().isoWeek(), currentYear: dayjs().year(), display: 'month', + customer: week.customer, }); }, [week]); + const setCustomer = useCallback( + (customer: string) => { + week.setFilters({ + currentDay: week.currentDay, + currentMonth: week.currentMonth, + currentWeek: week.currentWeek, + currentYear: week.currentYear, + display: week.display as any, + customer: customer, + }); + }, + [week] + ); + const next = useCallback(() => { const increaseDay = week.display === 'day'; const increaseWeek = @@ -77,6 +95,7 @@ export const Filters = () => { week.display === 'month' || (increaseWeek && week.currentWeek === 52); week.setFilters({ + customer: week.customer, currentDay: (!increaseDay ? 0 : week.currentDay === 6 @@ -116,6 +135,7 @@ export const Filters = () => { week.display === 'month' || (decreaseWeek && week.currentWeek === 1); week.setFilters({ + customer: week.customer, currentDay: (!decreaseDay ? 0 : week.currentDay === 0 @@ -147,77 +167,82 @@ export const Filters = () => { ]); return (
-
-
- - - -
-
- {week.display === 'day' - ? `${dayjs() - .month(week.currentMonth) - .week(week.currentWeek) - .day(week.currentDay) - .format('dddd')}` - : week.display === 'week' - ? `Week ${week.currentWeek}` - : `${dayjs().month(week.currentMonth).format('MMMM')}`} -
-
- - - -
-
{betweenDates}
-
-
-
- Day -
-
- Week -
-
- Month -
-
+
+
+ + + +
+
+ {week.display === 'day' + ? `${dayjs() + .month(week.currentMonth) + .week(week.currentWeek) + .day(week.currentDay) + .format('dddd')}` + : week.display === 'week' + ? `Week ${week.currentWeek}` + : `${dayjs().month(week.currentMonth).format('MMMM')}`} +
+
+ + + +
+
{betweenDates}
+
+ setCustomer(customer)} + integrations={week.integrations} + /> +
+
+ Day +
+
+ Week +
+
+ Month +
+
); }; diff --git a/apps/frontend/src/components/launches/providers/high.order.provider.tsx b/apps/frontend/src/components/launches/providers/high.order.provider.tsx index c0ff02b0..57c11780 100644 --- a/apps/frontend/src/components/launches/providers/high.order.provider.tsx +++ b/apps/frontend/src/components/launches/providers/high.order.provider.tsx @@ -527,7 +527,7 @@ export const withProvider = function ( {(showTab === 0 || showTab === 2) && (
- {data?.internalPlugs?.length && ( + {!!data?.internalPlugs?.length && ( )}
diff --git a/apps/frontend/src/components/launches/providers/lemmy/lemmy.provider.tsx b/apps/frontend/src/components/launches/providers/lemmy/lemmy.provider.tsx new file mode 100644 index 00000000..f789a62b --- /dev/null +++ b/apps/frontend/src/components/launches/providers/lemmy/lemmy.provider.tsx @@ -0,0 +1,77 @@ +import { FC, useCallback } from 'react'; +import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; +import { useFieldArray } from 'react-hook-form'; +import { Button } from '@gitroom/react/form/button'; +import { deleteDialog } from '@gitroom/react/helpers/delete.dialog'; +import { Subreddit } from './subreddit'; +import { LemmySettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/lemmy.dto'; + +const LemmySettings: FC = () => { + const { register, control } = useSettings(); + const { fields, append, remove } = useFieldArray({ + control, // control props comes from useForm (optional: if you are using FormContext) + name: 'subreddit', // unique name for your Field Array + }); + + const addField = useCallback(() => { + append({}); + }, [fields, append]); + + const deleteField = useCallback( + (index: number) => async () => { + if ( + !(await deleteDialog('Are you sure you want to delete this Subreddit?')) + ) + return; + remove(index); + }, + [fields, remove] + ); + + return ( + <> +
+ {fields.map((field, index) => ( +
+
+ x +
+ +
+ ))} +
+ + {fields.length === 0 && ( +
+ Please add at least one Subreddit +
+ )} + + ); +}; + +export default withProvider( + LemmySettings, + undefined, + LemmySettingsDto, + async (items) => { + const [firstItems] = items; + + if ( + firstItems.length && + firstItems[0].path.indexOf('png') === -1 && + firstItems[0].path.indexOf('jpg') === -1 && + firstItems[0].path.indexOf('jpef') === -1 && + firstItems[0].path.indexOf('gif') === -1 + ) { + return 'You can set only one picture for a cover'; + } + + return true; + }, + 10000 +); diff --git a/apps/frontend/src/components/launches/providers/lemmy/subreddit.tsx b/apps/frontend/src/components/launches/providers/lemmy/subreddit.tsx new file mode 100644 index 00000000..014245a4 --- /dev/null +++ b/apps/frontend/src/components/launches/providers/lemmy/subreddit.tsx @@ -0,0 +1,168 @@ +import { FC, FormEvent, useCallback, useState } from 'react'; +import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function'; +import { Input } from '@gitroom/react/form/input'; +import { useDebouncedCallback } from 'use-debounce'; +import { useWatch } from 'react-hook-form'; +import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values'; + +export const Subreddit: FC<{ + onChange: (event: { + target: { + name: string; + value: { + id: string; + subreddit: string; + title: string; + name: string; + url: string; + body: string; + media: any[]; + }; + }; + }) => void; + name: string; +}> = (props) => { + const { onChange, name } = props; + + const state = useSettings(); + const split = name.split('.'); + const [loading, setLoading] = useState(false); + // @ts-ignore + const errors = state?.formState?.errors?.[split?.[0]]?.[split?.[1]]?.value; + + const [results, setResults] = useState([]); + const func = useCustomProviderFunction(); + const value = useWatch({ name }); + const [searchValue, setSearchValue] = useState(''); + + const setResult = (result: { id: string; name: string }) => async () => { + setLoading(true); + setSearchValue(''); + + onChange({ + target: { + name, + value: { + id: String(result.id), + subreddit: result.name, + title: '', + name: '', + url: '', + body: '', + media: [], + }, + }, + }); + + setLoading(false); + }; + + const setTitle = useCallback( + (e: any) => { + onChange({ + target: { + name, + value: { + ...value, + title: e.target.value, + }, + }, + }); + }, + [value] + ); + + const setURL = useCallback( + (e: any) => { + onChange({ + target: { + name, + value: { + ...value, + url: e.target.value, + }, + }, + }); + }, + [value] + ); + + const search = useDebouncedCallback( + useCallback(async (e: FormEvent) => { + // @ts-ignore + setResults([]); + // @ts-ignore + if (!e.target.value) { + return; + } + // @ts-ignore + const results = await func.get('subreddits', { word: e.target.value }); + // @ts-ignore + setResults(results); + }, []), + 500 + ); + + return ( +
+ {value?.subreddit ? ( + <> + + + + + ) : ( +
+ { + // @ts-ignore + setSearchValue(e.target.value); + await search(e); + }} + /> + {!!results.length && !loading && ( +
+ {results.map((r: { id: string; name: string }) => ( +
+ {r.name} +
+ ))} +
+ )} +
+ )} +
+ ); +}; diff --git a/apps/frontend/src/components/launches/providers/show.all.providers.tsx b/apps/frontend/src/components/launches/providers/show.all.providers.tsx index 3b26f62b..2549ca36 100644 --- a/apps/frontend/src/components/launches/providers/show.all.providers.tsx +++ b/apps/frontend/src/components/launches/providers/show.all.providers.tsx @@ -17,6 +17,7 @@ import DiscordProvider from '@gitroom/frontend/components/launches/providers/dis import SlackProvider from '@gitroom/frontend/components/launches/providers/slack/slack.provider'; import MastodonProvider from '@gitroom/frontend/components/launches/providers/mastodon/mastodon.provider'; import BlueskyProvider from '@gitroom/frontend/components/launches/providers/bluesky/bluesky.provider'; +import LemmyProvider from '@gitroom/frontend/components/launches/providers/lemmy/lemmy.provider'; export const Providers = [ {identifier: 'devto', component: DevtoProvider}, @@ -37,6 +38,7 @@ export const Providers = [ {identifier: 'slack', component: SlackProvider}, {identifier: 'mastodon', component: MastodonProvider}, {identifier: 'bluesky', component: BlueskyProvider}, + {identifier: 'lemmy', component: LemmyProvider}, ]; diff --git a/apps/frontend/src/components/launches/select.customer.tsx b/apps/frontend/src/components/launches/select.customer.tsx new file mode 100644 index 00000000..7fd4e5ea --- /dev/null +++ b/apps/frontend/src/components/launches/select.customer.tsx @@ -0,0 +1,44 @@ +import { Select } from '@gitroom/react/form/select'; +import { uniqBy } from 'lodash'; +import React, { FC, useMemo, useState } from 'react'; +import { Integrations } from '@gitroom/frontend/components/launches/calendar.context'; + +export const SelectCustomer: FC<{ + onChange: (value: string) => void; + integrations: Integrations[]; + customer?: string; +}> = (props) => { + const { onChange, integrations, customer: currentCustomer } = props; + const [customer, setCustomer] = useState(currentCustomer || ''); + + const totalCustomers = useMemo(() => { + return uniqBy(integrations, (i) => i?.customer?.id).length; + }, [integrations]); + + if (totalCustomers <= 1) { + return null; + } + + return ( + + ); +}; diff --git a/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts b/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts index 732d2724..9f4c8523 100644 --- a/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts +++ b/libraries/nestjs-libraries/src/database/prisma/posts/posts.repository.ts @@ -110,6 +110,11 @@ export class PostsRepository { }, deletedAt: null, parentPostId: null, + ...query.customer ? { + integration: { + customerId: query.customer, + } + }: {}, }, select: { id: true, diff --git a/libraries/nestjs-libraries/src/database/prisma/schema.prisma b/libraries/nestjs-libraries/src/database/prisma/schema.prisma index 97dec774..6d609616 100644 --- a/libraries/nestjs-libraries/src/database/prisma/schema.prisma +++ b/libraries/nestjs-libraries/src/database/prisma/schema.prisma @@ -293,6 +293,7 @@ model Integration { @@index([rootInternalId]) @@index([updatedAt]) @@index([deletedAt]) + @@index([customerId]) @@unique([organizationId, internalId]) } diff --git a/libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts b/libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts index f661a2a5..99941309 100644 --- a/libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts +++ b/libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts @@ -14,6 +14,7 @@ import { DribbbleDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-sett import { TikTokDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/tiktok.dto'; import { DiscordDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/discord.dto'; import { SlackDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/slack.dto'; +import { LemmySettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/lemmy.dto'; export class EmptySettings {} export class Integration { @@ -66,6 +67,7 @@ export class Post { { value: MediumSettingsDto, name: 'medium' }, { value: HashnodeSettingsDto, name: 'hashnode' }, { value: RedditSettingsDto, name: 'reddit' }, + { value: LemmySettingsDto, name: 'lemmy' }, { value: YoutubeSettingsDto, name: 'youtube' }, { value: PinterestSettingsDto, name: 'pinterest' }, { value: DribbbleDto, name: 'dribbble' }, diff --git a/libraries/nestjs-libraries/src/dtos/posts/get.posts.dto.ts b/libraries/nestjs-libraries/src/dtos/posts/get.posts.dto.ts index b228b821..b1b45273 100644 --- a/libraries/nestjs-libraries/src/dtos/posts/get.posts.dto.ts +++ b/libraries/nestjs-libraries/src/dtos/posts/get.posts.dto.ts @@ -1,10 +1,6 @@ import { Type } from 'class-transformer'; import { - IsDefined, - IsIn, - IsNumber, - Max, - Min, + IsDefined, IsIn, IsNumber, IsOptional, IsString, Max, Min } from 'class-validator'; import dayjs from 'dayjs'; @@ -36,4 +32,8 @@ export class GetPostsDto { @Max(dayjs().add(10, 'year').year()) @Min(2022) year: number; + + @IsOptional() + @IsString() + customer: string; } diff --git a/libraries/nestjs-libraries/src/dtos/posts/lemmy.dto.ts b/libraries/nestjs-libraries/src/dtos/posts/lemmy.dto.ts new file mode 100644 index 00000000..7fb064cf --- /dev/null +++ b/libraries/nestjs-libraries/src/dtos/posts/lemmy.dto.ts @@ -0,0 +1,46 @@ +import { + ArrayMinSize, + IsDefined, + IsOptional, + IsString, + IsUrl, + MinLength, + ValidateIf, + ValidateNested, +} from 'class-validator'; +import { Type } from 'class-transformer'; + +export class LemmySettingsDtoInner { + @IsString() + @MinLength(2) + @IsDefined() + subreddit: string; + + @IsString() + @IsDefined() + id: string; + + @IsString() + @MinLength(2) + @IsDefined() + title: string; + + @ValidateIf((o) => o.url) + @IsOptional() + @IsUrl() + url: string; +} + +export class LemmySettingsValueDto { + @Type(() => LemmySettingsDtoInner) + @IsDefined() + @ValidateNested() + value: LemmySettingsDtoInner; +} + +export class LemmySettingsDto { + @Type(() => LemmySettingsValueDto) + @ValidateNested({ each: true }) + @ArrayMinSize(1) + subreddit: LemmySettingsValueDto[]; +} diff --git a/libraries/nestjs-libraries/src/integrations/integration.manager.ts b/libraries/nestjs-libraries/src/integrations/integration.manager.ts index 8382946d..bf44d558 100644 --- a/libraries/nestjs-libraries/src/integrations/integration.manager.ts +++ b/libraries/nestjs-libraries/src/integrations/integration.manager.ts @@ -21,6 +21,7 @@ import { DiscordProvider } from '@gitroom/nestjs-libraries/integrations/social/d import { SlackProvider } from '@gitroom/nestjs-libraries/integrations/social/slack.provider'; import { MastodonProvider } from '@gitroom/nestjs-libraries/integrations/social/mastodon.provider'; import { BlueskyProvider } from '@gitroom/nestjs-libraries/integrations/social/bluesky.provider'; +import { LemmyProvider } from '@gitroom/nestjs-libraries/integrations/social/lemmy.provider'; // import { MastodonCustomProvider } from '@gitroom/nestjs-libraries/integrations/social/mastodon.custom.provider'; const socialIntegrationList: SocialProvider[] = [ @@ -39,6 +40,7 @@ const socialIntegrationList: SocialProvider[] = [ new SlackProvider(), new MastodonProvider(), new BlueskyProvider(), + new LemmyProvider(), // new MastodonCustomProvider(), ]; diff --git a/libraries/nestjs-libraries/src/integrations/social/lemmy.provider.ts b/libraries/nestjs-libraries/src/integrations/social/lemmy.provider.ts new file mode 100644 index 00000000..9e653528 --- /dev/null +++ b/libraries/nestjs-libraries/src/integrations/social/lemmy.provider.ts @@ -0,0 +1,245 @@ +import { + AuthTokenDetails, + PostDetails, + PostResponse, + SocialProvider, +} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface'; +import { makeId } from '@gitroom/nestjs-libraries/services/make.is'; +import { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract'; +import dayjs from 'dayjs'; +import { Integration } from '@prisma/client'; +import { AuthService } from '@gitroom/helpers/auth/auth.service'; +import { LemmySettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/lemmy.dto'; +import { groupBy } from 'lodash'; + +export class LemmyProvider extends SocialAbstract implements SocialProvider { + identifier = 'lemmy'; + name = 'Lemmy'; + isBetweenSteps = false; + scopes = []; + + async customFields() { + return [ + { + key: 'service', + label: 'Service', + defaultValue: 'https://lemmy.world', + validation: `/^https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)$/`, + type: 'text' as const, + }, + { + key: 'identifier', + label: 'Identifier', + validation: `/^.{3,}$/`, + type: 'text' as const, + }, + { + key: 'password', + label: 'Password', + validation: `/^.{3,}$/`, + type: 'password' as const, + }, + ]; + } + + async refreshToken(refreshToken: string): Promise { + return { + refreshToken: '', + expiresIn: 0, + accessToken: '', + id: '', + name: '', + picture: '', + username: '', + }; + } + + async generateAuthUrl() { + const state = makeId(6); + return { + url: '', + codeVerifier: makeId(10), + state, + }; + } + + async authenticate(params: { + code: string; + codeVerifier: string; + refresh?: string; + }) { + const body = JSON.parse(Buffer.from(params.code, 'base64').toString()); + + const load = await fetch(body.service + '/api/v3/user/login', { + body: JSON.stringify({ + username_or_email: body.identifier, + password: body.password, + }), + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (load.status === 401) { + return 'Invalid credentials'; + } + + const { jwt } = await load.json(); + + try { + const user = await ( + await fetch(body.service + `/api/v3/user?username=${body.identifier}`, { + headers: { + Authorization: `Bearer ${jwt}`, + }, + }) + ).json(); + + return { + refreshToken: jwt!, + expiresIn: dayjs().add(100, 'years').unix() - dayjs().unix(), + accessToken: jwt!, + id: String(user.person_view.person.id), + name: + user.person_view.person.display_name || + user.person_view.person.name || + '', + picture: user.person_view.person.avatar || '', + username: body.identifier || '', + }; + } catch (e) { + console.log(e); + return 'Invalid credentials'; + } + } + + async post( + id: string, + accessToken: string, + postDetails: PostDetails[], + integration: Integration + ): Promise { + const [firstPost, ...restPosts] = postDetails; + + const body = JSON.parse( + AuthService.fixedDecryption(integration.customInstanceDetails!) + ); + + const { jwt } = await ( + await fetch(body.service + '/api/v3/user/login', { + body: JSON.stringify({ + username_or_email: body.identifier, + password: body.password, + }), + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }) + ).json(); + + const valueArray: PostResponse[] = []; + + for (const lemmy of firstPost.settings.subreddit) { + const { post_view, ...all } = await ( + await fetch(body.service + '/api/v3/post', { + body: JSON.stringify({ + community_id: +lemmy.value.id, + name: lemmy.value.title, + body: firstPost.message, + ...(lemmy.value.url ? { url: lemmy.value.url } : {}), + ...(firstPost.media?.length + ? { custom_thumbnail: firstPost.media[0].url } + : {}), + nsfw: false, + }), + method: 'POST', + headers: { + Authorization: `Bearer ${jwt}`, + 'Content-Type': 'application/json', + }, + }) + ).json(); + + valueArray.push({ + postId: post_view.post.id, + releaseURL: body.service + '/post/' + post_view.post.id, + id: firstPost.id, + status: 'published', + }); + + for (const comment of restPosts) { + const { comment_view } = await ( + await fetch(body.service + '/api/v3/comment', { + body: JSON.stringify({ + post_id: post_view.post.id, + content: comment.message, + }), + method: 'POST', + headers: { + Authorization: `Bearer ${jwt}`, + 'Content-Type': 'application/json', + }, + }) + ).json(); + + valueArray.push({ + postId: comment_view.post.id, + releaseURL: body.service + '/comment/' + comment_view.comment.id, + id: comment.id, + status: 'published', + }); + } + } + + return Object.values(groupBy(valueArray, (p) => p.id)).map((p) => ({ + id: p[0].id, + postId: p.map((p) => String(p.postId)).join(','), + releaseURL: p.map((p) => p.releaseURL).join(','), + status: 'published', + })); + } + + async subreddits( + accessToken: string, + data: any, + id: string, + integration: Integration + ) { + const body = JSON.parse( + AuthService.fixedDecryption(integration.customInstanceDetails!) + ); + + const { jwt } = await ( + await fetch(body.service + '/api/v3/user/login', { + body: JSON.stringify({ + username_or_email: body.identifier, + password: body.password, + }), + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }) + ).json(); + + const { communities } = await ( + await fetch( + body.service + + `/api/v3/search?type_=Communities&sort=Active&q=${data.word}`, + { + headers: { + Authorization: `Bearer ${jwt}`, + }, + } + ) + ).json(); + + return communities.map((p: any) => ({ + title: p.community.title, + name: p.community.title, + id: p.community.id, + })); + } +}