feat: media
This commit is contained in:
parent
3ef1bf8365
commit
a9c6fc6f29
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
|
|
@ -30,6 +30,7 @@ import { IntegrationContext } from '@gitroom/frontend/components/launches/helper
|
|||
import { Button } from '@gitroom/react/form/button';
|
||||
import { useT } from '@gitroom/react/translation/get.transation.service.client';
|
||||
import { PostComment } from '@gitroom/frontend/components/new-launch/providers/high.order.provider';
|
||||
import WordpressProvider from '@gitroom/frontend/components/new-launch/providers/wordpress/wordpress.provider';
|
||||
|
||||
export const Providers = [
|
||||
{
|
||||
|
|
@ -128,6 +129,10 @@ export const Providers = [
|
|||
identifier: 'vk',
|
||||
component: VkProvider,
|
||||
},
|
||||
{
|
||||
identifier: 'wordpress',
|
||||
component: WordpressProvider,
|
||||
},
|
||||
];
|
||||
export const ShowAllProviders = forwardRef((props, ref) => {
|
||||
const { date, current, global, selectedIntegrations, allIntegrations } =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
'use client';
|
||||
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { Select } from '@gitroom/react/form/select';
|
||||
import { useT } from '@gitroom/react/translation/get.transation.service.client';
|
||||
import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';
|
||||
import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';
|
||||
export const WordpressPostType: FC<{
|
||||
name: string;
|
||||
onChange: (event: {
|
||||
target: {
|
||||
value: string;
|
||||
name: string;
|
||||
};
|
||||
}) => void;
|
||||
}> = (props) => {
|
||||
const { onChange, name } = props;
|
||||
const t = useT();
|
||||
const customFunc = useCustomProviderFunction();
|
||||
const [orgs, setOrgs] = useState([]);
|
||||
const { getValues } = useSettings();
|
||||
const [currentMedia, setCurrentMedia] = useState<string | undefined>();
|
||||
const onChangeInner = (event: {
|
||||
target: {
|
||||
value: string;
|
||||
name: string;
|
||||
};
|
||||
}) => {
|
||||
setCurrentMedia(event.target.value);
|
||||
onChange(event);
|
||||
};
|
||||
useEffect(() => {
|
||||
customFunc.get('postTypes').then((data) => setOrgs(data));
|
||||
const settings = getValues()[props.name];
|
||||
if (settings) {
|
||||
setCurrentMedia(settings);
|
||||
}
|
||||
}, []);
|
||||
if (!orgs.length) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Select
|
||||
name={name}
|
||||
label="Select type"
|
||||
onChange={onChangeInner}
|
||||
value={currentMedia}
|
||||
>
|
||||
<option value="">{t('select_1', '--Select--')}</option>
|
||||
{orgs.map((org: any) => (
|
||||
<option key={org.id} value={org.id}>
|
||||
{org.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
'use client';
|
||||
|
||||
import { FC } from 'react';
|
||||
import {
|
||||
PostComment,
|
||||
withProvider,
|
||||
} from '@gitroom/frontend/components/new-launch/providers/high.order.provider';
|
||||
import { Input } from '@gitroom/react/form/input';
|
||||
import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';
|
||||
import { WordpressPostType } from '@gitroom/frontend/components/new-launch/providers/wordpress/wordpress.post.type';
|
||||
import { MediaComponent } from '@gitroom/frontend/components/media/media.component';
|
||||
|
||||
const WordpressSettings: FC = () => {
|
||||
const form = useSettings();
|
||||
return (
|
||||
<>
|
||||
<Input label="Title" {...form.register('title')} />
|
||||
<WordpressPostType {...form.register('type')} />
|
||||
<MediaComponent
|
||||
label="Cover picture"
|
||||
description="Add a cover picture"
|
||||
{...form.register('main_image')}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default withProvider({
|
||||
postComment: PostComment.COMMENT,
|
||||
minimumCharacters: [],
|
||||
SettingsComponent: WordpressSettings,
|
||||
CustomPreviewComponent: undefined, // WordpressPreview,
|
||||
dto: undefined,
|
||||
checkValidity: undefined,
|
||||
maximumCharacters: 100000,
|
||||
});
|
||||
|
|
@ -13,6 +13,7 @@ import { IsIn } from 'class-validator';
|
|||
import { MediumSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/medium.settings.dto';
|
||||
import { DevToSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/dev.to.settings.dto';
|
||||
import { HashnodeSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/hashnode.settings.dto';
|
||||
import { WordpressDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/wordpress.dto';
|
||||
|
||||
export type ProviderExtension<T extends string, M> = { __type: T } & M;
|
||||
export type AllProvidersSettings =
|
||||
|
|
@ -32,6 +33,7 @@ export type AllProvidersSettings =
|
|||
| ProviderExtension<'medium', MediumSettingsDto>
|
||||
| ProviderExtension<'devto', DevToSettingsDto>
|
||||
| ProviderExtension<'hashnode', HashnodeSettingsDto>
|
||||
| ProviderExtension<'wordpress', WordpressDto>
|
||||
| ProviderExtension<'facebook', None>
|
||||
| ProviderExtension<'threads', None>
|
||||
| ProviderExtension<'mastodon', None>
|
||||
|
|
@ -60,6 +62,7 @@ export const allProviders = (setEmpty?: any) => {
|
|||
{ value: InstagramDto, name: 'instagram-standalone' },
|
||||
{ value: MediumSettingsDto, name: 'medium' },
|
||||
{ value: DevToSettingsDto, name: 'devto' },
|
||||
{ value: WordpressDto, name: 'wordpress' },
|
||||
{ value: HashnodeSettingsDto, name: 'hashnode' },
|
||||
{ value: setEmpty, name: 'facebook' },
|
||||
{ value: setEmpty, name: 'threads' },
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
import {
|
||||
IsDefined,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MinLength,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { MediaDto } from '@gitroom/nestjs-libraries/dtos/media/media.dto';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
export class WordpressDto {
|
||||
@IsString()
|
||||
@MinLength(2)
|
||||
@IsDefined()
|
||||
title: string;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(() => MediaDto)
|
||||
main_image?: MediaDto;
|
||||
|
||||
@IsString()
|
||||
@IsDefined()
|
||||
type: string;
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ import { FarcasterProvider } from '@gitroom/nestjs-libraries/integrations/social
|
|||
import { TelegramProvider } from '@gitroom/nestjs-libraries/integrations/social/telegram.provider';
|
||||
import { NostrProvider } from '@gitroom/nestjs-libraries/integrations/social/nostr.provider';
|
||||
import { VkProvider } from '@gitroom/nestjs-libraries/integrations/social/vk.provider';
|
||||
import { WordpressProvider } from '@gitroom/nestjs-libraries/integrations/social/wordpress.provider';
|
||||
|
||||
export const socialIntegrationList: SocialProvider[] = [
|
||||
new XProvider(),
|
||||
|
|
@ -52,6 +53,7 @@ export const socialIntegrationList: SocialProvider[] = [
|
|||
new MediumProvider(),
|
||||
new DevToProvider(),
|
||||
new HashnodeProvider(),
|
||||
new WordpressProvider(),
|
||||
// new MastodonCustomProvider(),
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ export abstract class SocialAbstract {
|
|||
json = '{}';
|
||||
}
|
||||
|
||||
console.log(json);
|
||||
if (json.includes('rate_limit_exceeded') || json.includes('Rate limit')) {
|
||||
await timer(5000);
|
||||
console.log('rate limit trying again');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,237 @@
|
|||
import {
|
||||
AuthTokenDetails,
|
||||
PostDetails,
|
||||
PostResponse,
|
||||
SocialProvider,
|
||||
} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';
|
||||
import { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';
|
||||
import dayjs from 'dayjs';
|
||||
import { Integration } from '@prisma/client';
|
||||
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
|
||||
import { WordpressDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/wordpress.dto';
|
||||
import slugify from 'slugify';
|
||||
import FormData from 'form-data';
|
||||
import axios from 'axios';
|
||||
|
||||
export class WordpressProvider
|
||||
extends SocialAbstract
|
||||
implements SocialProvider
|
||||
{
|
||||
identifier = 'wordpress';
|
||||
name = 'WordPress';
|
||||
isBetweenSteps = false;
|
||||
editor = 'html' as const;
|
||||
scopes = [] as string[];
|
||||
|
||||
async generateAuthUrl() {
|
||||
const state = makeId(6);
|
||||
return {
|
||||
url: '',
|
||||
codeVerifier: makeId(10),
|
||||
state,
|
||||
};
|
||||
}
|
||||
|
||||
async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {
|
||||
return {
|
||||
refreshToken: '',
|
||||
expiresIn: 0,
|
||||
accessToken: '',
|
||||
id: '',
|
||||
name: '',
|
||||
picture: '',
|
||||
username: '',
|
||||
};
|
||||
}
|
||||
|
||||
async customFields() {
|
||||
return [
|
||||
{
|
||||
key: 'domain',
|
||||
label: 'Domain URL',
|
||||
validation: `/^https?:\\/\\/(?:www\\.)?[\\w\\-]+(\\.[\\w\\-]+)+([\\/?#][^\\s]*)?$/`,
|
||||
type: 'text' as const,
|
||||
},
|
||||
{
|
||||
key: 'username',
|
||||
label: 'Username',
|
||||
validation: `/.+/`,
|
||||
type: 'text' as const,
|
||||
},
|
||||
{
|
||||
key: 'password',
|
||||
label: 'Password',
|
||||
validation: `/.+/`,
|
||||
type: 'password' as const,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async authenticate(params: {
|
||||
code: string;
|
||||
codeVerifier: string;
|
||||
refresh?: string;
|
||||
}) {
|
||||
const body = JSON.parse(Buffer.from(params.code, 'base64').toString()) as {
|
||||
domain: string;
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
try {
|
||||
const auth = Buffer.from(`${body.username}:${body.password}`).toString(
|
||||
'base64'
|
||||
);
|
||||
const { id, name, avatar_urls } = await (
|
||||
await fetch(`${body.domain}/wp-json/wp/v2/users/me`, {
|
||||
headers: {
|
||||
Authorization: `Basic ${auth}`,
|
||||
},
|
||||
})
|
||||
).json();
|
||||
|
||||
const biggestImage = Object.entries(avatar_urls).reduce(
|
||||
(all, current) => {
|
||||
if (all > Number(current[0])) {
|
||||
return all;
|
||||
}
|
||||
return Number(current[0]);
|
||||
},
|
||||
0
|
||||
);
|
||||
|
||||
return {
|
||||
refreshToken: '',
|
||||
expiresIn: dayjs().add(100, 'years').unix() - dayjs().unix(),
|
||||
accessToken: params.code,
|
||||
id: body.domain + '_' + id,
|
||||
name,
|
||||
picture: avatar_urls?.[String(biggestImage)] || '',
|
||||
username: body.username,
|
||||
};
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return 'Invalid credentials';
|
||||
}
|
||||
}
|
||||
|
||||
async postTypes(token: string) {
|
||||
const body = JSON.parse(Buffer.from(token, 'base64').toString()) as {
|
||||
domain: string;
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
const auth = Buffer.from(`${body.username}:${body.password}`).toString(
|
||||
'base64'
|
||||
);
|
||||
|
||||
const postTypes = await (
|
||||
await this.fetch(`${body.domain}/wp-json/wp/v2/types`, {
|
||||
headers: {
|
||||
Authorization: `Basic ${auth}`,
|
||||
},
|
||||
})
|
||||
).json();
|
||||
|
||||
return Object.entries<any>(postTypes).reduce((all, [key, value]) => {
|
||||
if (
|
||||
key.indexOf('wp_') > -1 ||
|
||||
key.indexOf('nav_') > -1 ||
|
||||
key === 'attachment'
|
||||
) {
|
||||
return all;
|
||||
}
|
||||
|
||||
all.push({
|
||||
id: value.rest_base,
|
||||
name: value.name,
|
||||
});
|
||||
|
||||
return all;
|
||||
}, []);
|
||||
}
|
||||
|
||||
async post(
|
||||
id: string,
|
||||
accessToken: string,
|
||||
postDetails: PostDetails<WordpressDto>[],
|
||||
integration: Integration
|
||||
): Promise<PostResponse[]> {
|
||||
const body = JSON.parse(Buffer.from(accessToken, 'base64').toString()) as {
|
||||
domain: string;
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
const auth = Buffer.from(`${body.username}:${body.password}`).toString(
|
||||
'base64'
|
||||
);
|
||||
|
||||
let mediaId = '';
|
||||
if (postDetails?.[0]?.settings?.main_image?.path) {
|
||||
console.log('Uploading image to WordPress', postDetails[0].settings.main_image.path);
|
||||
const imageData = await axios.get(postDetails[0].settings.main_image.path, {
|
||||
responseType: 'stream',
|
||||
});
|
||||
|
||||
const form = new FormData();
|
||||
form.append('file', imageData.data, {
|
||||
filename: postDetails[0].settings.main_image.path.split('/').pop(), // You can customize the filename
|
||||
contentType: imageData.headers['content-type'],
|
||||
});
|
||||
if (postDetails[0].settings.main_image?.alt) {
|
||||
form.append('alt_text', postDetails[0].settings.main_image.alt);
|
||||
}
|
||||
|
||||
const mediaResponse = await axios.post(
|
||||
`${body.domain}/wp-json/wp/v2/media`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: form,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Basic ${auth}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
console.log('nevo', mediaResponse);
|
||||
mediaId = mediaResponse.data.id;
|
||||
}
|
||||
|
||||
const submit = await (
|
||||
await this.fetch(
|
||||
`https://cms.postiz.com/wp-json/wp/v2/${postDetails?.[0]?.settings?.type}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Basic ${auth}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
title: postDetails?.[0]?.settings?.title,
|
||||
content: postDetails?.[0]?.message,
|
||||
slug: slugify(postDetails?.[0]?.settings?.title, {
|
||||
lower: true,
|
||||
strict: true,
|
||||
trim: true,
|
||||
}),
|
||||
status: 'publish',
|
||||
...(mediaId ? { featured_media: mediaId } : {}),
|
||||
}),
|
||||
}
|
||||
)
|
||||
).json();
|
||||
|
||||
return [
|
||||
{
|
||||
id: postDetails?.[0].id,
|
||||
status: 'completed',
|
||||
postId: String(submit.id),
|
||||
releaseURL: submit.link,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -195,6 +195,7 @@
|
|||
"sha256": "^0.2.0",
|
||||
"sharp": "^0.33.4",
|
||||
"simple-statistics": "^7.8.3",
|
||||
"slugify": "^1.6.6",
|
||||
"stripe": "^15.5.0",
|
||||
"striptags": "^3.2.0",
|
||||
"subtitle": "4.2.2-alpha.0",
|
||||
|
|
|
|||
|
|
@ -462,6 +462,9 @@ importers:
|
|||
simple-statistics:
|
||||
specifier: ^7.8.3
|
||||
version: 7.8.8
|
||||
slugify:
|
||||
specifier: ^1.6.6
|
||||
version: 1.6.6
|
||||
stripe:
|
||||
specifier: ^15.5.0
|
||||
version: 15.12.0
|
||||
|
|
@ -13260,6 +13263,10 @@ packages:
|
|||
slate@0.94.1:
|
||||
resolution: {integrity: sha512-GH/yizXr1ceBoZ9P9uebIaHe3dC/g6Plpf9nlUwnvoyf6V1UOYrRwkabtOCd3ZfIGxomY4P7lfgLr7FPH8/BKA==}
|
||||
|
||||
slugify@1.6.6:
|
||||
resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
||||
smart-buffer@4.2.0:
|
||||
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
|
||||
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
|
||||
|
|
@ -31592,6 +31599,8 @@ snapshots:
|
|||
is-plain-object: 5.0.0
|
||||
tiny-warning: 1.0.3
|
||||
|
||||
slugify@1.6.6: {}
|
||||
|
||||
smart-buffer@4.2.0: {}
|
||||
|
||||
snake-case@3.0.4:
|
||||
|
|
|
|||
Loading…
Reference in New Issue