Skip to content

Commit

Permalink
Added full support to trigger a create drive on request of an app;
Browse files Browse the repository at this point in the history
  • Loading branch information
stef-coenen committed Jan 8, 2024
1 parent 430d6f4 commit d983a38
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 91 deletions.
29 changes: 20 additions & 9 deletions packages/common-app/src/channels/ChannelsDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { DriveSearchResult, SecurityGroupType } from '@youfoundation/js-lib/core';
import {
DriveSearchResult,
NewDriveSearchResult,
SecurityGroupType,
} from '@youfoundation/js-lib/core';
import { useState } from 'react';
import { createPortal } from 'react-dom';
import {
Expand Down Expand Up @@ -92,14 +96,16 @@ export const ChannelItem = ({
chnl: chnlDsr,
onClose,
className,
isDefaultEdit,
}: {
chnl?: DriveSearchResult<ChannelDefinitionVm>;
chnl?: DriveSearchResult<ChannelDefinitionVm> | NewDriveSearchResult<ChannelDefinitionVm>;
onClose?: () => void;
className?: string;
isDefaultEdit?: boolean;
}) => {
const isNew = !chnlDsr;

const [isEdit, setIsEdit] = useState(false);
const [isEdit, setIsEdit] = useState(isDefaultEdit);
const [isAclEdit, setIsAclEdit] = useState(isNew);
const {
save: { mutateAsync: saveChannel, status: saveStatus },
Expand Down Expand Up @@ -164,7 +170,9 @@ export const ChannelItem = ({
e.preventDefault();
e.stopPropagation();

await saveChannel({
if (!e.currentTarget.reportValidity()) return;

const uploadResult = await saveChannel({
...chnlDsr,
fileMetadata: {
...chnlDsr?.fileMetadata,
Expand All @@ -185,9 +193,10 @@ export const ChannelItem = ({
accessControlList: newAcl,
},
});
setIsEdit(false);
onClose && onClose();

if (uploadResult) {
setIsEdit(false);
onClose && onClose();
}
return false;
}}
className="flex w-full flex-col"
Expand All @@ -197,6 +206,7 @@ export const ChannelItem = ({
<Input
id="name"
defaultValue={chnl?.name}
required={true}
onChange={(e) => {
setNewName(e.target.value);
setNewSlug(slugify(e.target.value));
Expand Down Expand Up @@ -231,7 +241,7 @@ export const ChannelItem = ({
</div>
<div className="-m-2 flex flex-row-reverse">
<ActionButton className="m-2" state={saveStatus}>
{t('Save')}
{isNew && !chnl ? t('Create Drive & Save') : t('Save')}
</ActionButton>
<ActionButton
type="secondary"
Expand All @@ -247,6 +257,7 @@ export const ChannelItem = ({
{t('Cancel')}
</ActionButton>
{chnlDsr &&
chnlDsr.fileId &&
!stringGuidsEqual(
chnlDsr.fileMetadata.appData.uniqueId,
BlogConfig.PublicChannelId
Expand All @@ -259,7 +270,7 @@ export const ChannelItem = ({
onClick={async (e) => {
e.stopPropagation();
e.preventDefault();
await removeChannel(chnlDsr);
await removeChannel(chnlDsr as DriveSearchResult<ChannelDefinitionVm>);
return false;
}}
confirmOptions={{
Expand Down
83 changes: 53 additions & 30 deletions packages/common-app/src/hooks/posts/channels/useChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
saveChannelDefinition,
} from '@youfoundation/js-lib/public';

import { useStaticFiles } from '@youfoundation/common-app';
import { useSecurityContext, useStaticFiles } from '@youfoundation/common-app';
import { ChannelDefinitionVm, parseChannelTemplate } from './useChannels';
import { useDotYouClient } from '../../../..';
import { stringGuidsEqual, stringifyToQueryParams, toGuidId } from '@youfoundation/js-lib/helpers';
Expand All @@ -17,6 +17,7 @@ import {
DrivePermissionType,
DriveSearchResult,
NewDriveSearchResult,
TargetDrive,
} from '@youfoundation/js-lib/core';
import { ROOT_PATH } from '@youfoundation/feed-app/src/app/App';
import { FEED_APP_ID } from '../../../../../owner-app/src/app/Constants';
Expand All @@ -26,11 +27,44 @@ type useChannelsProps = {
channelId?: string;
};

const getExtendAuthorizationUrl = (
identity: string,
name: string,
description: string,
targetDrive: TargetDrive,
returnUrl: string
) => {
const drives = [
{
a: targetDrive.alias,
t: targetDrive.type,
p:
DrivePermissionType.Read +
DrivePermissionType.Write +
DrivePermissionType.React +
DrivePermissionType.Comment, // Permission
n: name,
d: description,
},
];

const params = {
appId: FEED_APP_ID,
d: JSON.stringify(drives),
};

return `https://${identity}/owner/app-new-drive?${stringifyToQueryParams(
params
)}&return=${encodeURIComponent(returnUrl)}`;
};

export const useChannel = ({ channelSlug, channelId }: useChannelsProps) => {
const dotYouClient = useDotYouClient().getDotYouClient();
const queryClient = useQueryClient();
const { mutate: publishStaticFiles } = useStaticFiles().publish;

const { data: securityContext } = useSecurityContext().fetch;

const fetchChannelData = async ({ channelSlug, channelId }: useChannelsProps) => {
if (!channelSlug && !channelId) return null;

Expand Down Expand Up @@ -81,42 +115,31 @@ export const useChannel = ({ channelSlug, channelId }: useChannelsProps) => {
channelDef: NewDriveSearchResult<ChannelDefinition> | DriveSearchResult<ChannelDefinition>
) => {
if (!channelDef.fileId) {
if (!channelDef.fileMetadata.appData.uniqueId) {
if (!channelDef.fileMetadata.appData.uniqueId)
channelDef.fileMetadata.appData.uniqueId = toGuidId(
channelDef.fileMetadata.appData.content.name
);
}
}

const onMissingDrive = () => {
if (!channelDef.fileMetadata.appData.uniqueId)
throw new Error('Channel unique id is not set');

const identity = dotYouClient.getIdentity();
const returnUrl = `${ROOT_PATH}/channels?${JSON.stringify(channelDef)}`;
const returnUrl = `${ROOT_PATH}/channels?new=${JSON.stringify(channelDef)}`;

const targetDrive = GetTargetDriveFromChannelId(channelDef.fileMetadata.appData.uniqueId);

const drives = [
{
a: targetDrive.alias,
t: targetDrive.type,
p:
DrivePermissionType.Read +
DrivePermissionType.Write +
DrivePermissionType.React +
DrivePermissionType.Comment, // Permission
n: channelDef.fileMetadata.appData.content.name, // Name
d: '',
},
];

const params = {
appId: FEED_APP_ID,
d: JSON.stringify(drives),
};

const targetUrl = `https://${identity}/owner/app-new-drive?${stringifyToQueryParams(
params
)}&return=${encodeURIComponent(returnUrl)}`;
window.location.href = targetUrl;
} else {
return await saveChannelDefinition(dotYouClient, { ...channelDef });
}
window.location.href = getExtendAuthorizationUrl(
identity,
channelDef.fileMetadata.appData.content.name,
channelDef.fileMetadata.appData.content.description,
targetDrive,
returnUrl
);
};

return await saveChannelDefinition(dotYouClient, { ...channelDef }, onMissingDrive);
};

const removeChannel = async (channelDef: DriveSearchResult<ChannelDefinition>) => {
Expand Down
30 changes: 26 additions & 4 deletions packages/feed-app/src/templates/SocialFeed/ChannelsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
import { useState } from 'react';
import { ChannelItem, Plus } from '@youfoundation/common-app';
import { useMemo, useState } from 'react';
import { ChannelDefinitionVm, ChannelItem, Plus } from '@youfoundation/common-app';
import { Quote } from '@youfoundation/common-app';
import { t } from '@youfoundation/common-app';
import { useChannels } from '@youfoundation/common-app';
import { PageMeta } from '../../components/ui/PageMeta/PageMeta';
import { ROOT_PATH } from '../../app/App';
import { useSearchParams } from 'react-router-dom';
import { NewDriveSearchResult } from '@youfoundation/js-lib/core';
import { tryJsonParse } from '@youfoundation/js-lib/helpers';

export const ChannelsPage = () => {
const [params, setSearchParams] = useSearchParams();

const newChannelDefinition = useMemo(() => {
const newQueryParam = params.get('new');
if (!newQueryParam) return undefined;

const newChannel = tryJsonParse<NewDriveSearchResult<ChannelDefinitionVm>>(newQueryParam);
return newChannel;
}, [params]);

const { data: channels } = useChannels({ isAuthenticated: true, isOwner: true });
const [isAddNew, setIsAddNew] = useState(false);
const [isAddNew, setIsAddNew] = useState(!!newChannelDefinition);

return (
<>
Expand All @@ -27,7 +40,16 @@ export const ChannelsPage = () => {
))}
{isAddNew ? (
<div className="p-2" key={'new'}>
<ChannelItem onClose={() => setIsAddNew(false)} className="bg-background" />
<ChannelItem
chnl={newChannelDefinition}
isDefaultEdit={!!newChannelDefinition}
onClose={() => {
setIsAddNew(false);
params.delete('new');
setSearchParams(params);
}}
className="bg-background"
/>
</div>
) : (
<div className="p-2" key={'new'}>
Expand Down
30 changes: 24 additions & 6 deletions packages/js-lib/src/public/posts/PostDefinitionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
getContentFromHeaderOrPayload,
UploadResult,
SecurityGroupType,
ensureDrive,
UploadInstructionSet,
ScheduleOptions,
SendContents,
Expand All @@ -21,10 +20,12 @@ import {
queryBatch,
DriveSearchResult,
NewDriveSearchResult,
getSecurityContext,
} from '../../core/core';
import {
getRandom16ByteArray,
jsonStringify64,
stringGuidsEqual,
stringToUint8Array,
toGuidId,
} from '../../helpers/helpers';
Expand Down Expand Up @@ -94,9 +95,9 @@ export const getChannelDefinitionBySlug = async (dotYouClient: DotYouClient, slu

export const saveChannelDefinition = async (
dotYouClient: DotYouClient,
definition: NewDriveSearchResult<ChannelDefinition>
): Promise<UploadResult> => {
const channelMetadata = '';
definition: NewDriveSearchResult<ChannelDefinition>,
onMissingDrive?: () => void
): Promise<UploadResult | undefined> => {
const channelContent = definition.fileMetadata.appData.content;

if (!definition.fileMetadata.appData.uniqueId) {
Expand All @@ -116,15 +117,32 @@ export const saveChannelDefinition = async (
);

const targetDrive = GetTargetDriveFromChannelId(definition.fileMetadata.appData.uniqueId);
await ensureDrive(dotYouClient, targetDrive, channelContent.name, channelMetadata, true, true);

const existingChannelDef = await getChannelDefinitionInternal(
dotYouClient,
definition.fileMetadata.appData.uniqueId
);
const fileId = existingChannelDef?.fileId;
const versionTag = existingChannelDef?.fileMetadata.versionTag;

if (!fileId) {
// Channel doesn't exist yet, we need to check if the drive does exist and if there is access:
const securityContext = await getSecurityContext(dotYouClient);

if (
!securityContext?.permissionContext.permissionGroups.some((x) =>
x.driveGrants.some(
(driveGrant) =>
stringGuidsEqual(driveGrant.permissionedDrive.drive.alias, targetDrive.alias) &&
stringGuidsEqual(driveGrant.permissionedDrive.drive.type, targetDrive.type)
)
)
) {
console.warn(`[DotYouCore-js: PostDefinitionProvider] Save Channel: No access to drive`);
onMissingDrive && onMissingDrive();
return;
}
}

const instructionSet: UploadInstructionSet = {
transferIv: getRandom16ByteArray(),
storageOptions: {
Expand Down
Loading

0 comments on commit d983a38

Please sign in to comment.