Skip to content

Commit

Permalink
Merge branch 'main' into add-first-backend-e2e-test
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesread authored Oct 9, 2024
2 parents 6de7a3f + 76cc074 commit bcd7f7b
Show file tree
Hide file tree
Showing 17 changed files with 178 additions and 32 deletions.
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,11 @@ Thumbs.db
# Vim files
**/*.swp
**/*.swo

# Temporary files
*.orig
*.~*~
*.tsbuildinfo

# ignore Secrets folder
.secrets/
1 change: 1 addition & 0 deletions apps/backend/src/api/routes/integrations.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export class IntegrationsController {
identifier: p.providerIdentifier,
inBetweenSteps: p.inBetweenSteps,
refreshNeeded: p.refreshNeeded,
display: p.profile,
type: p.type,
time: JSON.parse(p.postingTimes),
changeProfilePicture: !!findIntegration?.changeProfilePicture,
Expand Down
5 changes: 2 additions & 3 deletions apps/backend/src/api/routes/media.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,8 @@ export class MediaController {
@GetOrgFromRequest() org: Organization,
@UploadedFile() file: Express.Multer.File
) {
const uploadedFile = await this.storage.uploadFile(file);
const filePath = uploadedFile.path.replace(process.env.UPLOAD_DIRECTORY, basename(process.env.UPLOAD_DIRECTORY));
return this._mediaService.saveFile(org.id, uploadedFile.originalname, filePath);
const uploadedFile = await this.storage.uploadFile(file);
return this._mediaService.saveFile(org.id, uploadedFile.originalname, uploadedFile.path);
}

@Post('/upload-simple')
Expand Down
1 change: 1 addition & 0 deletions apps/frontend/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default async function AppLayout({ children }: { children: ReactNode }) {
</head>
<body className={clsx(chakra.className, 'text-primary dark')}>
<VariableContextComponent
storageProvider={process.env.STORAGE_PROVIDER! as 'local' | 'cloudflare'}
backendUrl={process.env.NEXT_PUBLIC_BACKEND_URL!}
plontoKey={process.env.NEXT_PUBLIC_POLOTNO!}
billingEnabled={!!process.env.STRIPE_PUBLISHABLE_KEY}
Expand Down
1 change: 1 addition & 0 deletions apps/frontend/src/components/launches/calendar.context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export interface Integrations {
id: string;
disabled?: boolean;
inBetweenSteps: boolean;
display: string;
identifier: string;
type: string;
picture: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const GeneralPreviewComponent: FC<{maximumCharacters?: number}> = (props)
</svg>
</div>
<div className="text-[15px] font-[400] text-customColor27 ml-[4px]">
@username
{integration?.display || '@username'}
</div>
</div>
<pre className={clsx('text-wrap', chakra.className)} dangerouslySetInnerHTML={{__html: value.text}} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const EditorWrapper: FC<{ children: ReactNode }> = ({ children }) => {
};

export const withProvider = (
SettingsComponent: FC | null,
SettingsComponent: FC<{values?: any}> | null,
CustomPreviewComponent?: FC<{maximumCharacters?: number}>,
dto?: any,
checkValidity?: (
Expand Down Expand Up @@ -403,7 +403,7 @@ export const withProvider = (
)}
{(showTab === 0 || showTab === 2) && (
<div className={clsx('mt-[20px]', showTab !== 2 && 'hidden')}>
<Component />
<Component values={editInPlace ? InPlaceValue : props.value} />
</div>
)}
{showTab === 0 && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { FC } from 'react';
import {
FC,
ReactEventHandler,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider';
import { TikTokDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/tiktok.dto';
import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';
import { Select } from '@gitroom/react/form/select';
import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';

const privacyLevel = [
{
Expand Down Expand Up @@ -34,10 +42,72 @@ const yesNo = [
},
];

const TikTokSettings: FC = () => {
const CheckTikTokValidity: FC<{ picture: string }> = (props) => {
const { register } = useSettings();
const func = useCustomProviderFunction();
const [maxVideoLength, setMaxVideoLength] = useState(0);
const [isValidVideo, setIsValidVideo] = useState<undefined | boolean>(
undefined
);

const registerVideo = register('isValidVideo');
const video = useMemo(() => {
return props.picture;
}, [props.picture]);

useEffect(() => {
loadStats();
}, []);

const loadStats = useCallback(async () => {
const { maxDurationSeconds } = await func.get('maxVideoLength');
// setMaxVideoLength(5);
setMaxVideoLength(maxDurationSeconds);
}, []);

const loadVideo: ReactEventHandler<HTMLVideoElement> = useCallback(
(e) => {
// @ts-ignore
setIsValidVideo(e.target.duration <= maxVideoLength);
registerVideo.onChange({
target: {
name: 'isValidVideo',
// @ts-ignore
value: String(e.target.duration <= maxVideoLength),
},
});
},
[maxVideoLength, registerVideo]
);

if (!maxVideoLength || !video || video.indexOf('mp4') === -1) {
return null;
}

return (
<>
{isValidVideo === false && (
<div className="text-red-600 my-[20px]">
Video length is invalid, must be up to {maxVideoLength} seconds
</div>
)}
<video
controls
onLoadedMetadata={loadVideo}
className="w-0 h-0 overflow-hidden pointer-events-none"
>
<source src={video} type="video/mp4" />
</video>
</>
);
};

const TikTokSettings: FC<{ values?: any }> = (props) => {
const { register, control } = useSettings();

return (
<div className="flex flex-col">
<CheckTikTokValidity picture={props?.values?.[0]?.image?.[0]?.path} />
<Select
label="Privacy Level"
{...register('privacy_level', {
Expand Down
1 change: 1 addition & 0 deletions apps/frontend/src/components/layout/continue.provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export const ContinueProvider: FC = () => {
date: dayjs(),
value: [],
integration: {
display: '',
time: [{time: 0}],
id: continueId,
type: '',
Expand Down
5 changes: 3 additions & 2 deletions apps/frontend/src/components/media/new.uploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { FileInput, ProgressBar } from '@uppy/react';
// Uppy styles
import '@uppy/core/dist/style.min.css';
import '@uppy/dashboard/dist/style.min.css';
import { useVariables } from '@gitroom/react/helpers/variable.context';

export function MultipartFileUploader({
onUploadSuccess,
Expand Down Expand Up @@ -58,7 +59,7 @@ export function MultipartFileUploaderAfter({
onUploadSuccess: (result: UploadResult) => void;
allowedFileTypes: string;
}) {
const storageProvider = process.env.NEXT_PUBLIC_STORAGE_PROVIDER || "local";
const {storageProvider, backendUrl} = useVariables();
const fetch = useFetch();

const uppy = useMemo(() => {
Expand All @@ -71,7 +72,7 @@ export function MultipartFileUploaderAfter({
},
});

const { plugin, options } = getUppyUploadPlugin(storageProvider, fetch)
const { plugin, options } = getUppyUploadPlugin(storageProvider, fetch, backendUrl)
uppy2.use(plugin, options)
// Set additional metadata when a file is added
uppy2.on('file-added', (file) => {
Expand Down
33 changes: 26 additions & 7 deletions docker-compose.dev.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
services:
postiz-postgres:
image: postgres:14.5
# ref: https://hub.docker.com/_/postgres
image: postgres:17-alpine # 17.0
container_name: postiz-postgres
restart: always
environment:
Expand All @@ -13,8 +14,18 @@ services:
- 5432:5432
networks:
- postiz-network
postiz-redis:
# ref: https://hub.docker.com/_/redis
image: redis:7-alpine # 7.4.0
container_name: postiz-redis
restart: always
ports:
- 6379:6379
networks:
- postiz-network
postiz-pg-admin:
image: dpage/pgadmin4
# ref: https://hub.docker.com/r/dpage/pgadmin4/tags
image: dpage/pgadmin4:latest
container_name: postiz-pg-admin
restart: always
ports:
Expand All @@ -24,14 +35,22 @@ services:
PGADMIN_DEFAULT_PASSWORD: admin
networks:
- postiz-network
postiz-redis:
image: redis:7.2
container_name: postiz-redis
restart: always
postiz-redisinsight:
# ref: https://hub.docker.com/r/redis/redisinsight
image: redis/redisinsight:latest
container_name: postiz-redisinsight
links:
- postiz-redis
ports:
- 6379:6379
- "5540:5540"
volumes:
- redisinsight:/data
networks:
- postiz-network
restart: always

volumes:
redisinsight:
postgres-volume:
external: false

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { IsBoolean, IsIn, IsString } from 'class-validator';
import { IsBoolean, IsDefined, IsIn, IsString } from 'class-validator';

export class TikTokDto {
@IsIn(['PUBLIC_TO_EVERYONE', 'MUTUAL_FOLLOW_FRIENDS', 'FOLLOWER_OF_CREATOR', 'SELF_ONLY'])
@IsIn([
'PUBLIC_TO_EVERYONE',
'MUTUAL_FOLLOW_FRIENDS',
'FOLLOWER_OF_CREATOR',
'SELF_ONLY',
])
@IsString()
privacy_level: 'PUBLIC_TO_EVERYONE' | 'MUTUAL_FOLLOW_FRIENDS' | 'FOLLOWER_OF_CREATOR' | 'SELF_ONLY';
privacy_level:
| 'PUBLIC_TO_EVERYONE'
| 'MUTUAL_FOLLOW_FRIENDS'
| 'FOLLOWER_OF_CREATOR'
| 'SELF_ONLY';

@IsBoolean()
disable_duet: boolean;
Expand All @@ -19,4 +28,8 @@ export class TikTokDto {

@IsBoolean()
brand_organic_toggle: boolean;

@IsIn(['true'])
@IsDefined()
isValidVideo: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider {
identifier = 'tiktok';
name = 'Tiktok';
isBetweenSteps = false;
scopes = ['user.info.basic', 'video.publish', 'video.upload'];
scopes = [
'user.info.basic',
'video.publish',
'video.upload',
'user.info.profile',
];

async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {
const value = {
Expand All @@ -37,11 +42,11 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider {

const {
data: {
user: { avatar_url, display_name, open_id },
user: { avatar_url, display_name, open_id, username },
},
} = await (
await fetch(
'https://open.tiktokapis.com/v2/user/info/?fields=open_id,avatar_url,display_name',
'https://open.tiktokapis.com/v2/user/info/?fields=open_id,avatar_url,display_name,username',
{
method: 'GET',
headers: {
Expand All @@ -58,7 +63,7 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider {
id: open_id.replace(/-/g, ''),
name: display_name,
picture: avatar_url,
username: display_name.toLowerCase(),
username: username,
};
}

Expand Down Expand Up @@ -115,11 +120,11 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider {

const {
data: {
user: { avatar_url, display_name, open_id },
user: { avatar_url, display_name, open_id, username },
},
} = await (
await fetch(
'https://open.tiktokapis.com/v2/user/info/?fields=open_id,avatar_url,display_name',
'https://open.tiktokapis.com/v2/user/info/?fields=open_id,avatar_url,display_name,union_id,username',
{
method: 'GET',
headers: {
Expand All @@ -136,7 +141,28 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider {
refreshToken: refresh_token,
expiresIn: dayjs().add(23, 'hours').unix() - dayjs().unix(),
picture: avatar_url,
username: display_name.toLowerCase(),
username: username,
};
}

async maxVideoLength(accessToken: string) {
const {
data: { max_video_post_duration_sec },
} = await (
await this.fetch(
'https://open.tiktokapis.com/v2/post/publish/creator_info/query/',
{
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=UTF-8',
Authorization: `Bearer ${accessToken}`,
},
}
)
).json();

return {
maxDurationSeconds: max_video_post_duration_sec,
};
}

Expand Down Expand Up @@ -188,7 +214,7 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider {
} catch (err) {
throw new BadBody('titok-error', JSON.stringify(err), {
// @ts-ignore
postDetails
postDetails,
});
}
}
Expand Down
2 changes: 1 addition & 1 deletion libraries/nestjs-libraries/src/upload/local.storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class LocalStorage implements IUploadProvider {
const publicPath = `${innerPath}/${randomName}${extname(
file.originalname
)}`;
console.log(filePath);

// Logic to save the file to the filesystem goes here
writeFileSync(filePath, file.buffer);

Expand Down
Loading

0 comments on commit bcd7f7b

Please sign in to comment.