Bläddra i källkod

refactor(web): shared link key auth (#3855)

Jason Rasmussen 1 år sedan
förälder
incheckning
9bbef4a97b

+ 17 - 4
web/src/api/api.ts

@@ -39,6 +39,11 @@ export class ImmichApi {
   public userApi: UserApi;
 
   private config: Configuration;
+  private key?: string;
+
+  get isSharedLink() {
+    return !!this.key;
+  }
 
   constructor(params: ConfigurationParameters) {
     this.config = new Configuration(params);
@@ -73,6 +78,14 @@ export class ImmichApi {
     return (this.config.basePath || BASE_PATH) + toPathString(url);
   }
 
+  public setKey(key: string) {
+    this.key = key;
+  }
+
+  public getKey(): string | undefined {
+    return this.key;
+  }
+
   public setAccessToken(accessToken: string) {
     this.config.accessToken = accessToken;
   }
@@ -85,14 +98,14 @@ export class ImmichApi {
     this.config.basePath = baseUrl;
   }
 
-  public getAssetFileUrl(...[assetId, isThumb, isWeb, key]: ApiParams<typeof AssetApiFp, 'serveFile'>) {
+  public getAssetFileUrl(...[assetId, isThumb, isWeb]: ApiParams<typeof AssetApiFp, 'serveFile'>) {
     const path = `/asset/file/${assetId}`;
-    return this.createUrl(path, { isThumb, isWeb, key });
+    return this.createUrl(path, { isThumb, isWeb, key: this.getKey() });
   }
 
-  public getAssetThumbnailUrl(...[assetId, format, key]: ApiParams<typeof AssetApiFp, 'getAssetThumbnail'>) {
+  public getAssetThumbnailUrl(...[assetId, format]: ApiParams<typeof AssetApiFp, 'getAssetThumbnail'>) {
     const path = `/asset/thumbnail/${assetId}`;
-    return this.createUrl(path, { format, key });
+    return this.createUrl(path, { format, key: this.getKey() });
   }
 
   public getProfileImageUrl(...[userId]: ApiParams<typeof UserApiFp, 'getProfileImage'>) {

+ 6 - 6
web/src/lib/components/album-page/album-viewer.svelte

@@ -27,13 +27,13 @@
 
   let { isViewing: showAssetViewer } = assetViewingStore;
 
-  const assetStore = new AssetStore({ size: TimeBucketSize.Month, albumId: album.id, key: sharedLink.key });
+  const assetStore = new AssetStore({ size: TimeBucketSize.Month, albumId: album.id });
   const assetInteractionStore = createAssetInteractionStore();
   const { isMultiSelectState, selectedAssets } = assetInteractionStore;
 
   dragAndDropFilesStore.subscribe((value) => {
     if (value.isDragging && value.files.length > 0) {
-      fileUploadHandler(value.files, album.id, sharedLink.key);
+      fileUploadHandler(value.files, album.id);
       dragAndDropFilesStore.set({ isDragging: false, files: [] });
     }
   });
@@ -88,7 +88,7 @@
   };
 
   const downloadAlbum = async () => {
-    await downloadArchive(`${album.albumName}.zip`, { albumId: album.id }, sharedLink.key);
+    await downloadArchive(`${album.albumName}.zip`, { albumId: album.id });
   };
 </script>
 
@@ -97,7 +97,7 @@
     <AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
       <SelectAllAssets {assetStore} {assetInteractionStore} />
       {#if sharedLink.allowDownload}
-        <DownloadAction filename="{album.albumName}.zip" sharedLinkKey={sharedLink.key} />
+        <DownloadAction filename="{album.albumName}.zip" />
       {/if}
     </AssetSelectControlBar>
   {:else}
@@ -117,7 +117,7 @@
         {#if sharedLink.allowUpload}
           <CircleIconButton
             title="Add Photos"
-            on:click={() => openFileUploadDialog(album.id, sharedLink.key)}
+            on:click={() => openFileUploadDialog(album.id)}
             logo={FileImagePlusOutline}
           />
         {/if}
@@ -135,7 +135,7 @@
 <main
   class="relative h-screen overflow-hidden bg-immich-bg px-6 pt-[var(--navbar-height)] dark:bg-immich-dark-bg sm:px-12 md:px-24 lg:px-40"
 >
-  <AssetGrid {assetStore} {assetInteractionStore} publicSharedKey={sharedLink.key}>
+  <AssetGrid {assetStore} {assetInteractionStore}>
     <section class="pt-24">
       <!-- ALBUM TITLE -->
       <p

+ 14 - 9
web/src/lib/components/asset-viewer/asset-viewer.svelte

@@ -26,7 +26,6 @@
 
   export let assetStore: AssetStore | null = null;
   export let asset: AssetResponseDto;
-  export let publicSharedKey = '';
   export let showNavigation = true;
   export let sharedLink: SharedLinkResponseDto | undefined = undefined;
 
@@ -72,6 +71,10 @@
   $: asset.id && !sharedLink && getAllAlbums(); // Update the album information when the asset ID changes
 
   const getAllAlbums = async () => {
+    if (api.isSharedLink) {
+      return;
+    }
+
     try {
       const { data } = await api.albumApi.getAllAlbums({ assetId: asset.id });
       appearsInAlbums = data;
@@ -84,7 +87,9 @@
     switch (key) {
       case 'a':
       case 'A':
-        if (shiftKey) toggleArchive();
+        if (shiftKey) {
+          toggleArchive();
+        }
         return;
       case 'ArrowLeft':
         navigateAssetBackward();
@@ -94,7 +99,9 @@
         return;
       case 'd':
       case 'D':
-        if (shiftKey) downloadFile(asset, publicSharedKey);
+        if (shiftKey) {
+          downloadFile(asset);
+        }
         return;
       case 'Delete':
         isShowDeleteConfirmation = true;
@@ -272,7 +279,7 @@
       showDownloadButton={shouldShowDownloadButton}
       on:goBack={closeViewer}
       on:showDetail={showDetailInfoHandler}
-      on:download={() => downloadFile(asset, publicSharedKey)}
+      on:download={() => downloadFile(asset)}
       on:delete={() => (isShowDeleteConfirmation = true)}
       on:favorite={toggleFavorite}
       on:addToAlbum={() => openAlbumPicker(false)}
@@ -304,7 +311,6 @@
       {:else if asset.type === AssetTypeEnum.Image}
         {#if shouldPlayMotionPhoto && asset.livePhotoVideoId}
           <VideoViewer
-            {publicSharedKey}
             assetId={asset.livePhotoVideoId}
             on:close={closeViewer}
             on:onVideoEnded={() => (shouldPlayMotionPhoto = false)}
@@ -312,12 +318,12 @@
         {:else if asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR || asset.originalPath
             .toLowerCase()
             .endsWith('.insp')}
-          <PanoramaViewer {publicSharedKey} {asset} />
+          <PanoramaViewer {asset} />
         {:else}
-          <PhotoViewer {publicSharedKey} {asset} on:close={closeViewer} />
+          <PhotoViewer {asset} on:close={closeViewer} />
         {/if}
       {:else}
-        <VideoViewer {publicSharedKey} assetId={asset.id} on:close={closeViewer} />
+        <VideoViewer assetId={asset.id} on:close={closeViewer} />
       {/if}
     {/key}
   </div>
@@ -338,7 +344,6 @@
       <DetailPanel
         {asset}
         albums={appearsInAlbums}
-        {sharedLink}
         on:close={() => ($isShowDetail = false)}
         on:close-viewer={handleCloseViewer}
         on:description-focus-in={disableKeyDownEvent}

+ 7 - 6
web/src/lib/components/asset-viewer/detail-panel.svelte

@@ -9,21 +9,20 @@
   import ImageOutline from 'svelte-material-icons/ImageOutline.svelte';
   import MapMarkerOutline from 'svelte-material-icons/MapMarkerOutline.svelte';
   import { createEventDispatcher } from 'svelte';
-  import { AssetResponseDto, AlbumResponseDto, api, ThumbnailFormat, SharedLinkResponseDto } from '@api';
+  import { AssetResponseDto, AlbumResponseDto, api, ThumbnailFormat } from '@api';
   import { asByteUnitString } from '../../utils/byte-units';
   import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
   import { getAssetFilename } from '$lib/utils/asset-utils';
 
   export let asset: AssetResponseDto;
   export let albums: AlbumResponseDto[] = [];
-  export let sharedLink: SharedLinkResponseDto | undefined = undefined;
 
   let textarea: HTMLTextAreaElement;
   let description: string;
 
   $: {
     // Get latest description from server
-    if (asset.id && !sharedLink) {
+    if (asset.id && !api.isSharedLink) {
       api.assetApi.getAssetById({ id: asset.id }).then((res) => {
         people = res.data?.people || [];
         textarea.value = res.data?.exifInfo?.description || '';
@@ -91,13 +90,15 @@
     <p class="text-lg text-immich-fg dark:text-immich-dark-fg">Info</p>
   </div>
 
-  <section class="mx-4 mt-10">
+  <section
+    class="mx-4 mt-10"
+    style:display={$page?.data?.user?.id !== asset.ownerId && textarea?.value == '' ? 'none' : 'block'}
+  >
     <textarea
       bind:this={textarea}
       class="max-h-[500px]
       w-full resize-none overflow-hidden border-b border-gray-500 bg-transparent text-base text-black outline-none transition-all focus:border-b-2 focus:border-immich-primary disabled:border-none dark:text-white dark:focus:border-immich-dark-primary"
       placeholder={$page?.data?.user?.id !== asset.ownerId ? '' : 'Add a description'}
-      style:display={$page?.data?.user?.id !== asset.ownerId && textarea?.value == '' ? 'none' : 'block'}
       on:focusin={handleFocusIn}
       on:focusout={handleFocusOut}
       on:input={autoGrowHeight}
@@ -106,7 +107,7 @@
     />
   </section>
 
-  {#if people.length > 0}
+  {#if !api.isSharedLink && people.length > 0}
     <section class="px-4 py-4 text-sm">
       <h2>PEOPLE</h2>
 

+ 4 - 2
web/src/lib/components/asset-viewer/panorama-viewer.svelte

@@ -4,14 +4,16 @@
   import { api, AssetResponseDto } from '@api';
   import View360, { EquirectProjection } from '@egjs/svelte-view360';
   import './panorama-viewer.css';
+
   export let asset: AssetResponseDto;
-  export let publicSharedKey = '';
+
   let dataUrl = '';
   let errorMessage = '';
+
   const loadAssetData = async () => {
     try {
       const { data } = await api.assetApi.serveFile(
-        { id: asset.id, isThumb: false, isWeb: false, key: publicSharedKey },
+        { id: asset.id, isThumb: false, isWeb: false, key: api.getKey() },
         { responseType: 'blob' },
       );
       if (data instanceof Blob) {

+ 2 - 4
web/src/lib/components/asset-viewer/photo-viewer.svelte

@@ -8,12 +8,10 @@
   import { photoZoomState } from '$lib/stores/zoom-image.store';
 
   export let asset: AssetResponseDto;
-  export let publicSharedKey = '';
   export let element: HTMLDivElement | undefined = undefined;
-  let imgElement: HTMLDivElement;
 
+  let imgElement: HTMLDivElement;
   let assetData: string;
-
   let copyImageToClipboard: (src: string) => Promise<Blob>;
   let canCopyImagesToClipboard: () => boolean;
 
@@ -28,7 +26,7 @@
   const loadAssetData = async () => {
     try {
       const { data } = await api.assetApi.serveFile(
-        { id: asset.id, isThumb: false, isWeb: true, key: publicSharedKey },
+        { id: asset.id, isThumb: false, isWeb: true, key: api.getKey() },
         {
           responseType: 'blob',
         },

+ 1 - 2
web/src/lib/components/asset-viewer/video-viewer.svelte

@@ -7,7 +7,6 @@
   import { handleError } from '../../utils/handle-error';
 
   export let assetId: string;
-  export let publicSharedKey: string | undefined = undefined;
 
   let isVideoLoading = true;
   const dispatch = createEventDispatcher<{ onVideoEnded: void }>();
@@ -37,7 +36,7 @@
     bind:volume={$videoViewerVolume}
     poster={api.getAssetThumbnailUrl(assetId, ThumbnailFormat.Jpeg)}
   >
-    <source src={api.getAssetFileUrl(assetId, false, true, publicSharedKey)} type="video/mp4" />
+    <source src={api.getAssetFileUrl(assetId, false, true)} type="video/mp4" />
     <track kind="captions" />
   </video>
 

+ 5 - 6
web/src/lib/components/assets/thumbnail/thumbnail.svelte

@@ -27,7 +27,6 @@
   export let selectionCandidate = false;
   export let disabled = false;
   export let readonly = false;
-  export let publicSharedKey: string | undefined = undefined;
   export let showArchiveIcon = false;
 
   let mouseOver = false;
@@ -118,13 +117,13 @@
         />
 
         <!-- Favorite asset star -->
-        {#if asset.isFavorite && !publicSharedKey}
+        {#if !api.isSharedLink && asset.isFavorite}
           <div class="absolute bottom-2 left-2 z-10">
             <Heart size="24" class="text-white" />
           </div>
         {/if}
 
-        {#if showArchiveIcon && asset.isArchived}
+        {#if !api.isSharedLink && showArchiveIcon && asset.isArchived}
           <div class="absolute {asset.isFavorite ? 'bottom-10' : 'bottom-2'} left-2 z-10">
             <ArchiveArrowDownOutline size="24" class="text-white" />
           </div>
@@ -140,7 +139,7 @@
 
         {#if asset.resized}
           <ImageThumbnail
-            url={api.getAssetThumbnailUrl(asset.id, format, publicSharedKey)}
+            url={api.getAssetThumbnailUrl(asset.id, format)}
             altText={asset.originalFileName}
             widthStyle="{width}px"
             heightStyle="{height}px"
@@ -156,7 +155,7 @@
         {#if asset.type === AssetTypeEnum.Video}
           <div class="absolute top-0 h-full w-full">
             <VideoThumbnail
-              url={api.getAssetFileUrl(asset.id, false, true, publicSharedKey)}
+              url={api.getAssetFileUrl(asset.id, false, true)}
               enablePlayback={mouseOver}
               curve={selected}
               durationInSeconds={timeToSeconds(asset.duration)}
@@ -167,7 +166,7 @@
         {#if asset.type === AssetTypeEnum.Image && asset.livePhotoVideoId}
           <div class="absolute top-0 h-full w-full">
             <VideoThumbnail
-              url={api.getAssetFileUrl(asset.livePhotoVideoId, false, true, publicSharedKey)}
+              url={api.getAssetFileUrl(asset.livePhotoVideoId, false, true)}
               pauseIcon={MotionPauseOutline}
               playIcon={MotionPlayOutline}
               showTime={false}

+ 2 - 3
web/src/lib/components/photos-page/actions/download-action.svelte

@@ -6,7 +6,6 @@
   import { getAssetControlContext } from '../asset-select-control-bar.svelte';
 
   export let filename = 'immich.zip';
-  export let sharedLinkKey: string | undefined = undefined;
   export let menuItem = false;
 
   const { getAssets, clearSelect } = getAssetControlContext();
@@ -15,12 +14,12 @@
     const assets = Array.from(getAssets());
     if (assets.length === 1) {
       clearSelect();
-      await downloadFile(assets[0], sharedLinkKey);
+      await downloadFile(assets[0]);
       return;
     }
 
     clearSelect();
-    await downloadArchive(filename, { assetIds: assets.map((asset) => asset.id) }, sharedLinkKey);
+    await downloadArchive(filename, { assetIds: assets.map((asset) => asset.id) });
   };
 </script>
 

+ 1 - 1
web/src/lib/components/photos-page/actions/remove-from-shared-link.svelte

@@ -20,7 +20,7 @@
         assetIdsDto: {
           assetIds: Array.from(getAssets()).map((asset) => asset.id),
         },
-        key: sharedLink.key,
+        key: api.getKey(),
       });
 
       for (const result of results) {

+ 1 - 3
web/src/lib/components/photos-page/asset-date-group.svelte

@@ -21,7 +21,6 @@
   export let isSelectionMode = false;
   export let viewport: Viewport;
   export let singleSelect = false;
-  export let publicSharedKey: string | undefined = undefined;
 
   export let assetStore: AssetStore;
   export let assetInteractionStore: AssetInteractionStore;
@@ -96,7 +95,7 @@
       return;
     }
 
-    assetViewingStore.setAssetId(asset.id, publicSharedKey);
+    assetViewingStore.setAssetId(asset.id);
   };
 
   const handleSelectGroup = (title: string, assets: AssetResponseDto[]) => dispatch('select', { title, assets });
@@ -189,7 +188,6 @@
               disabled={$assetStore.albumAssets.has(asset.id)}
               thumbnailWidth={box.width}
               thumbnailHeight={box.height}
-              {publicSharedKey}
             />
           </div>
         {/each}

+ 3 - 5
web/src/lib/components/photos-page/asset-grid.svelte

@@ -23,7 +23,7 @@
   export let assetStore: AssetStore;
   export let assetInteractionStore: AssetInteractionStore;
   export let removeAction: AssetAction | null = null;
-  export let publicSharedKey: string | undefined = undefined;
+
   const { assetSelectionCandidates, assetSelectionStart, selectedGroup, selectedAssets, isMultiSelectState } =
     assetInteractionStore;
   const viewport: Viewport = { width: 0, height: 0 };
@@ -97,7 +97,7 @@
   const handlePrevious = async () => {
     const previousAsset = await assetStore.getPreviousAssetId($viewingAsset.id);
     if (previousAsset) {
-      assetViewingStore.setAssetId(previousAsset, publicSharedKey);
+      assetViewingStore.setAssetId(previousAsset);
     }
 
     return !!previousAsset;
@@ -106,7 +106,7 @@
   const handleNext = async () => {
     const nextAsset = await assetStore.getNextAssetId($viewingAsset.id);
     if (nextAsset) {
-      assetViewingStore.setAssetId(nextAsset, publicSharedKey);
+      assetViewingStore.setAssetId(nextAsset);
     }
 
     return !!nextAsset;
@@ -349,7 +349,6 @@
                 bucketDate={bucket.bucketDate}
                 bucketHeight={bucket.bucketHeight}
                 {viewport}
-                {publicSharedKey}
               />
             {/if}
           </div>
@@ -371,7 +370,6 @@
       on:unarchived={({ detail: asset }) => handleAction(asset, AssetAction.UNARCHIVE)}
       on:favorite={({ detail: asset }) => handleAction(asset, AssetAction.FAVORITE)}
       on:unfavorite={({ detail: asset }) => handleAction(asset, AssetAction.UNFAVORITE)}
-      {publicSharedKey}
     />
   {/if}
 </Portal>

+ 6 - 7
web/src/lib/components/share-page/individual-shared-viewer.svelte

@@ -15,7 +15,6 @@
   import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
   import SelectAll from 'svelte-material-icons/SelectAll.svelte';
   import ImmichLogo from '../shared-components/immich-logo.svelte';
-
   import { notificationController, NotificationType } from '../shared-components/notification/notification';
   import { handleError } from '../../utils/handle-error';
 
@@ -35,23 +34,23 @@
   });
 
   const downloadAssets = async () => {
-    await downloadArchive(`immich-shared.zip`, { assetIds: assets.map((asset) => asset.id) }, sharedLink.key);
+    await downloadArchive(`immich-shared.zip`, { assetIds: assets.map((asset) => asset.id) });
   };
 
   const handleUploadAssets = async (files: File[] = []) => {
     try {
       let results: (string | undefined)[] = [];
       if (!files || files.length === 0 || !Array.isArray(files)) {
-        results = await openFileUploadDialog(undefined, sharedLink.key);
+        results = await openFileUploadDialog(undefined);
       } else {
-        results = await fileUploadHandler(files, undefined, sharedLink.key);
+        results = await fileUploadHandler(files, undefined);
       }
       const { data } = await api.sharedLinkApi.addSharedLinkAssets({
         id: sharedLink.id,
         assetIdsDto: {
           assetIds: results.filter((id) => !!id) as string[],
         },
-        key: sharedLink.key,
+        key: api.getKey(),
       });
 
       const added = data.filter((item) => item.success).length;
@@ -75,7 +74,7 @@
     <AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}>
       <CircleIconButton title="Select all" logo={SelectAll} on:click={handleSelectAll} />
       {#if sharedLink?.allowDownload}
-        <DownloadAction filename="immich-shared.zip" sharedLinkKey={sharedLink.key} />
+        <DownloadAction filename="immich-shared.zip" />
       {/if}
       {#if isOwned}
         <RemoveFromSharedLink bind:sharedLink />
@@ -106,6 +105,6 @@
     </ControlAppBar>
   {/if}
   <section class="my-[160px] flex flex-col px-6 sm:px-12 md:px-24 lg:px-40">
-    <GalleryViewer {assets} {sharedLink} bind:selectedAssets />
+    <GalleryViewer {assets} bind:selectedAssets />
   </section>
 </section>

+ 1 - 5
web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte

@@ -2,14 +2,13 @@
   import { page } from '$app/stores';
   import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
   import { handleError } from '$lib/utils/handle-error';
-  import { AssetResponseDto, SharedLinkResponseDto, ThumbnailFormat } from '@api';
+  import { AssetResponseDto, ThumbnailFormat } from '@api';
   import AssetViewer from '../../asset-viewer/asset-viewer.svelte';
   import { flip } from 'svelte/animate';
   import { getThumbnailSize } from '$lib/utils/thumbnail-util';
   import { assetViewingStore } from '$lib/stores/asset-viewing.store';
 
   export let assets: AssetResponseDto[];
-  export let sharedLink: SharedLinkResponseDto | undefined = undefined;
   export let selectedAssets: Set<AssetResponseDto> = new Set();
   export let disableAssetSelect = false;
   export let showArchiveIcon = false;
@@ -90,7 +89,6 @@
           {asset}
           {thumbnailSize}
           readonly={disableAssetSelect}
-          publicSharedKey={sharedLink?.key}
           format={assets.length < 7 ? ThumbnailFormat.Jpeg : ThumbnailFormat.Webp}
           on:click={(e) => (isMultiSelectionMode ? selectAssetHandler(e) : viewAssetHandler(e))}
           on:select={selectAssetHandler}
@@ -106,8 +104,6 @@
 {#if $showAssetViewer}
   <AssetViewer
     asset={selectedAsset}
-    publicSharedKey={sharedLink?.key}
-    {sharedLink}
     on:previous={navigateAssetBackward}
     on:next={navigateAssetForward}
     on:close={closeViewer}

+ 2 - 2
web/src/lib/stores/asset-viewing.store.ts

@@ -5,8 +5,8 @@ function createAssetViewingStore() {
   const viewingAssetStoreState = writable<AssetResponseDto>();
   const viewState = writable<boolean>(false);
 
-  const setAssetId = async (id: string, key?: string) => {
-    const { data } = await api.assetApi.getAssetById({ id, key });
+  const setAssetId = async (id: string) => {
+    const { data } = await api.assetApi.getAssetById({ id, key: api.getKey() });
     viewingAssetStoreState.set(data);
     viewState.set(true);
   };

+ 10 - 3
web/src/lib/stores/assets.store.ts

@@ -58,7 +58,10 @@ export class AssetStore {
     this.assetToBucket = {};
     this.albumAssets = new Set();
 
-    const { data: buckets } = await api.assetApi.getTimeBuckets(this.options);
+    const { data: buckets } = await api.assetApi.getTimeBuckets({
+      ...this.options,
+      key: api.getKey(),
+    });
 
     this.buckets = buckets.map((bucket) => {
       const unwrappedWidth = (3 / 2) * bucket.count * THUMBNAIL_HEIGHT * (7 / 10);
@@ -107,7 +110,11 @@ export class AssetStore {
       bucket.cancelToken = new AbortController();
 
       const { data: assets } = await api.assetApi.getByTimeBucket(
-        { ...this.options, timeBucket: bucketDate },
+        {
+          ...this.options,
+          timeBucket: bucketDate,
+          key: api.getKey(),
+        },
         { signal: bucket.cancelToken.signal },
       );
 
@@ -117,7 +124,7 @@ export class AssetStore {
             albumId: this.albumId,
             timeBucket: bucketDate,
             size: this.options.size,
-            key: this.options.key,
+            key: api.getKey(),
           },
           { signal: bucket.cancelToken.signal },
         );

+ 20 - 18
web/src/lib/utils/asset-utils.ts

@@ -3,20 +3,22 @@ import { downloadManager } from '$lib/stores/download';
 import { api, BulkIdResponseDto, AssetResponseDto, DownloadResponseDto, DownloadInfoDto } from '@api';
 import { handleError } from './handle-error';
 
-export const addAssetsToAlbum = async (
-  albumId: string,
-  assetIds: Array<string>,
-  key: string | undefined = undefined,
-): Promise<BulkIdResponseDto[]> =>
-  api.albumApi.addAssetsToAlbum({ id: albumId, bulkIdsDto: { ids: assetIds }, key }).then(({ data: results }) => {
-    const count = results.filter(({ success }) => success).length;
-    notificationController.show({
-      type: NotificationType.Info,
-      message: `Added ${count} asset${count === 1 ? '' : 's'}`,
-    });
+export const addAssetsToAlbum = async (albumId: string, assetIds: Array<string>): Promise<BulkIdResponseDto[]> =>
+  api.albumApi
+    .addAssetsToAlbum({
+      id: albumId,
+      bulkIdsDto: { ids: assetIds },
+      key: api.getKey(),
+    })
+    .then(({ data: results }) => {
+      const count = results.filter(({ success }) => success).length;
+      notificationController.show({
+        type: NotificationType.Info,
+        message: `Added ${count} asset${count === 1 ? '' : 's'}`,
+      });
 
-    return results;
-  });
+      return results;
+    });
 
 const downloadBlob = (data: Blob, filename: string) => {
   const url = URL.createObjectURL(data);
@@ -32,11 +34,11 @@ const downloadBlob = (data: Blob, filename: string) => {
   URL.revokeObjectURL(url);
 };
 
-export const downloadArchive = async (fileName: string, options: DownloadInfoDto, key?: string) => {
+export const downloadArchive = async (fileName: string, options: DownloadInfoDto) => {
   let downloadInfo: DownloadResponseDto | null = null;
 
   try {
-    const { data } = await api.assetApi.getDownloadInfo({ downloadInfoDto: options, key });
+    const { data } = await api.assetApi.getDownloadInfo({ downloadInfoDto: options, key: api.getKey() });
     downloadInfo = data;
   } catch (error) {
     handleError(error, 'Unable to download files');
@@ -61,7 +63,7 @@ export const downloadArchive = async (fileName: string, options: DownloadInfoDto
 
     try {
       const { data } = await api.assetApi.downloadArchive(
-        { assetIdsDto: { assetIds: archive.assetIds }, key },
+        { assetIdsDto: { assetIds: archive.assetIds }, key: api.getKey() },
         {
           responseType: 'blob',
           signal: abort.signal,
@@ -80,7 +82,7 @@ export const downloadArchive = async (fileName: string, options: DownloadInfoDto
   }
 };
 
-export const downloadFile = async (asset: AssetResponseDto, key?: string) => {
+export const downloadFile = async (asset: AssetResponseDto) => {
   const assets = [
     {
       filename: `${asset.originalFileName}.${getFilenameExtension(asset.originalPath)}`,
@@ -104,7 +106,7 @@ export const downloadFile = async (asset: AssetResponseDto, key?: string) => {
       downloadManager.add(downloadKey, size, abort);
 
       const { data } = await api.assetApi.downloadFile(
-        { id, key },
+        { id, key: api.getKey() },
         {
           responseType: 'blob',
           onDownloadProgress: (event: ProgressEvent) => {

+ 7 - 20
web/src/lib/utils/file-uploader.ts

@@ -14,10 +14,7 @@ const getExtensions = async () => {
   return _extensions;
 };
 
-export const openFileUploadDialog = async (
-  albumId: string | undefined = undefined,
-  sharedKey: string | undefined = undefined,
-) => {
+export const openFileUploadDialog = async (albumId: string | undefined = undefined) => {
   const extensions = await getExtensions();
 
   return new Promise<(string | undefined)[]>((resolve, reject) => {
@@ -34,7 +31,7 @@ export const openFileUploadDialog = async (
         }
         const files = Array.from(target.files);
 
-        resolve(fileUploadHandler(files, albumId, sharedKey));
+        resolve(fileUploadHandler(files, albumId));
       };
 
       fileSelector.click();
@@ -45,18 +42,14 @@ export const openFileUploadDialog = async (
   });
 };
 
-export const fileUploadHandler = async (
-  files: File[],
-  albumId: string | undefined = undefined,
-  sharedKey: string | undefined = undefined,
-) => {
+export const fileUploadHandler = async (files: File[], albumId: string | undefined = undefined) => {
   const extensions = await getExtensions();
   const iterable = {
     files: files.filter((file) => extensions.some((ext) => file.name.toLowerCase().endsWith(ext)))[Symbol.iterator](),
 
     async *[Symbol.asyncIterator]() {
       for (const file of this.files) {
-        yield fileUploader(file, albumId, sharedKey);
+        yield fileUploader(file, albumId);
       }
     },
   };
@@ -78,11 +71,7 @@ const fromAsync = async function <T>(iterable: AsyncIterable<T>) {
 };
 
 // TODO: should probably use the @api SDK
-async function fileUploader(
-  asset: File,
-  albumId: string | undefined = undefined,
-  sharedKey: string | undefined = undefined,
-): Promise<string | undefined> {
+async function fileUploader(asset: File, albumId: string | undefined = undefined): Promise<string | undefined> {
   const formData = new FormData();
   const fileCreatedAt = new Date(asset.lastModified).toISOString();
   const deviceAssetId = 'web' + '-' + asset.name + '-' + asset.lastModified;
@@ -103,9 +92,7 @@ async function fileUploader(
     });
 
     const response = await axios.post('/api/asset/upload', formData, {
-      params: {
-        key: sharedKey,
-      },
+      params: { key: api.getKey() },
       onUploadProgress: (event) => {
         const percentComplete = Math.floor((event.loaded / event.total) * 100);
         uploadAssetsStore.updateProgress(deviceAssetId, percentComplete);
@@ -120,7 +107,7 @@ async function fileUploader(
       }
 
       if (albumId && res.id) {
-        await addAssetsToAlbum(albumId, [res.id], sharedKey);
+        await addAssetsToAlbum(albumId, [res.id]);
       }
 
       setTimeout(() => {

+ 1 - 1
web/src/routes/(user)/albums/[albumId]/+page.svelte

@@ -174,7 +174,7 @@
   };
 
   const handleSelectFromComputer = async () => {
-    await openFileUploadDialog(album.id, '');
+    await openFileUploadDialog(album.id);
     timelineInteractionStore.clearMultiselect();
     viewMode = ViewMode.VIEW;
   };

+ 0 - 1
web/src/routes/(user)/share/[key]/photos/[assetId]/+page.svelte

@@ -8,7 +8,6 @@
 {#if data.asset && data.key}
   <AssetViewer
     asset={data.asset}
-    publicSharedKey={data.key}
     showNavigation={false}
     on:previous={() => null}
     on:next={() => null}

+ 5 - 0
web/src/routes/+layout.svelte

@@ -17,11 +17,16 @@
   import { loadFeatureFlags } from '$lib/stores/feature-flags.store';
   import { handleError } from '$lib/utils/handle-error';
   import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
+  import { api } from '@api';
 
   let showNavigationLoadingBar = false;
   export let data: LayoutData;
   let albumId: string | undefined;
 
+  if ($page.route.id?.startsWith('/(user)/share/[key]')) {
+    api.setKey($page.params.key);
+  }
+
   beforeNavigate(() => {
     showNavigationLoadingBar = true;
   });