Image URLs that won't expire

Hi team, I’m integrating Algolia instant search into my app and have run into an issue with images.

I’m providing Algolia with the download URL for an avatar image to show in the results list item. It’s been working fine for the last 24 hours or so, but today the oldest images all stopped appearing. When opening them in their own browser tab I see the following message:

application AAdy0pa… policy error: the signature has expired

Is there a way to get an image URL that won’t expire?

1 Like

Hi, thank you for your report!
First of all, we don’t recommend to use persistent download URL. Here are some solutions:

  1. You could call createField mutation and set expiration time property in fieldTypeAttributes as you need. If you want to set expire time equals 2 days, you have to set “60 * 60 * 24 * 2”
mutation {
  system {
    fieldCreate (data:{
      tableId:"some_table_id",
      fieldType: FILE
      fieldTypeAttributes:{
        expiration: 60*60*24*2
      }
    })
  }
}
  1. Another way to download files is to use shareUrl property. This property links to 8base server, then the server will ask to login to 8base. This solution is used when you need to pass a link to email or whatever. It’s guaranteed to protect your file data. If you want to make this file public, just setup permission to allow all access to this file.
    If the user already has token and don’t have to login to 8base, you could add to shareUrl “download” query parameter.
    For example “shareUrl”?download=true. Otherwise, if the file has public access, the user will not have to have any token.
    In this case, the server will parse you token and proxy to temporary download url.
1 Like

@eugene.antonevich I’m having the same problem,

I need a persistent download URL for image files for an article system where I cannot use GraphQL (need to be a direct url to the image file location).

I’ve tried the shareUrl of an dummy image upload and set my Guest role to have full read access:
https://app.8base.com/file/ck1y5djt0000201ldey0t1bju/ck9h6tmdj00gu07js4u04foas?download=true

However if you go to that url you’ll just see the 8base loader?

The downloadUrl I presume will only be valid for 24 hours, and I need this url to be static (not change) and still be valid 5 years from now.

The only thing I need files for on this account is to show images in articles, so privacy is not a concern. I want everything to be public and accessible without a login / token.

How can I get a persistent url without querying 8base for it?

@sebastian.scholl would also be really nice to have this in the docs, I could only find some incomplete docs about upload / delete: https://docs.8base.com/docs/8base-console/handling-files

I agree, I’m having the same problem. I need to load images in public content from a static url.

I’m not sure what @eugene.antonevich means by “If you want to make this file public, just setup permission to allow all access to this file.”. I tried setting the public boolean on the image to true (by running a mutation on the database) and then using the shareUrl, but going to the shareUrl just shows the spinning 8base loader like @MarkLyck said. I also looked at roles and permission settings in the admin dashboard, but I don’t see how to set permissions for general public access, only for specific roles/API keys.

How do we get a static url to the images that we’ve uploaded to the 8base database?

cc @sebastian.scholl

@ilya.8base @sebastian.scholl @eugene.antonevich

Any chance we can get an elaboration of this? :cold_sweat:

We are investigating this problem and will let you know soon

1 Like

First of all, there is the issue with sharedUrl and we will research it.
Anyway, there are several opportunities to download file.
Did you try to add “download=true” to the end of sharedUrl? For example, the sharedUrl for some file is “https://app.8base.com/some_path”, then add “?download=true” to the end. The result url is https://app.8base.com/some_path?download=true. Then try to fetch this url. The server will redirect you to direct download link for the file if permissions are valid. In case of public file the user could download file through sharedUrl without any token. As the result you have static download url for public file.
Is it what you need ?

1 Like

@eugene.antonevich @sebastian.scholl Yes that’s what I need, but it’s not working.

It seems to be either time related or you have the edit the file to make it work.

Right after uploading a file with public=true, the shareUrl doesn’t work.
However an old file I uploaded with public=false, then edited to be public=true. That file’s shareUrl works.

Just uploaded another file with public=true and here’s it’s shareUrl:

https://app.8base.com/file/{workspaceId}/ck9wwd1ny017i07l788ur464g?download=true

Uploaded about 5 minutes ago and at the time of writing this the shareUrl still shows an 8base loading spinner.

The temporary downloadUrl in the response worked fine (the file is there and linked correctly).

Here’s my mutation:

export const FILE_CREATE_MUTATION = gql`
  mutation fileCreate($fileId: String!, $fileName: String) {
    fileCreate(data: { fileId: $fileId, filename: $fileName, public: true }) {
      id
      downloadUrl
      shareUrl
      public
    }
  }
`

public comes back as true
downloadUrl works
shareUrl is broken

I’m trying to upload an image (tried both .png. and jpg)

It was uploaded to fileStack correctly.

@MarkLyck
There is the client web issue with the infinitive loader, and it will be fixed.
But, the example of sharedUrl “https://app.8base.com/file/ck1y5djt0000201ldey0t1bju/ck9wwd1ny017i07l788ur464g?download=true” that you show are used for preview file, not for download. It’s produced by the server to redirect to client web.

Original sharedUrl looks like “https://api.8base.com/file/download/<workspace_id>/<file_id>”, (https://api.8base.com/file/download vs https://app.8base.com/file) sorry, that was my mistake in the previous post. Anyway, try to add ?download=true to original sharedUrl and fetch. It would help you.

1 Like

@eugene.antonevich @sebastian.scholl that definitely explains it!

but why is the shareUrl returned from the FileCreate mutation not the “original” shareUrl?

So I just did a GET request for the same resource and the shareUrl returned at this point is “the original”:

before:
https://app.8base.com/file/{workspaceId}/ck9wwd1ny017i07l788ur464g

after:
https://api.8base.com/file/download/{workspaceId}_Master/ck9wwd1ny017i07l788ur464g

is this intentional or a bug?

If it’s a bug, I’m just gonna hold our until it’s sorted out. Otherwise, I have all the information to create the “real” link. But as a user, this is definitely confusing and I would expect shareUrl to be the same returned when Creatin it as it would be from fetching the row afterwards.

I could create it manually. like https://api.8base.com/file/download/${workspaceId}_Master/${fileId}?download=true but I find it a bit odd.

Please let me know if this is a bug and I should hold out, or if. this is intentional and I’ll use the above string-concatenation to create the real shareUrl.

Thanks for the quick response!

I tried to reproduce it from the api explorer and it works fine.

mutation create {
  fileCreate(data:{
    fileId:"TVKITR8uTs6eCX5ABz9W",
    filename:"test",   
  }) {
    shareUrl
  }
}

{
  "data": {
    "fileCreate": {
      "shareUrl": "https://api.8base.com/file/download/ck05befvn00c001mr9y2310pw_Master/ck9wyfcc000g007ib0q0407sv",
    }
  }
}

For example. To test it you could try to create file with client web picker. In the network tab you could find the request to the server with the file create mutation. The result of the request contain sharedUrl and it looks fine too.
It seems like you try to fetch the “original” sharedUrl after perform create file mutation.

1 Like

odd that’s not what I’m getting when I run my createMutation.

Here’s my code, added a shareUrl mutation bit to correct for it now.

It uploads the file to FILE_STACK
waits for a response
then uses the response data to createFile in 8base,

and the shareUrl returned from there, is the “wrong short one” like the one I shared above.

Now added a quick check to see if it’s the “original” and if not, manually build the right url and append ?download=true

upload_handler: async (file, imageBlock) => {
          fetch(`${FILE_STACK_URL}`, {
            method: 'POST',
            headers: new Headers({
              'Content-Type': file.type,
              'Content-Length': file.size,
            }),
            body: file,
          })
            .then((response) => response.json())
            .then(async (resp) => {
              const urlList = resp.url.split('/')
              const fileId = urlList[urlList.length - 1]

              const { data } = await fileCreateMutation({
                variables: {
                  fileId,
                  fileName: resp.filename,
                },
              })

              let { shareUrl } = data.fileCreate

              if (shareUrl.includes('/download')) {
                shareUrl = `${shareUrl}?download=true`
              } else {
                shareUrl = `https://api.8base.com/file/download/${WORK_SPACE_ID}_Master/${fileId}?download=true`
              }

              console.log('shareUrl: ', shareUrl)

              imageBlock.uploadCompleted(shareUrl)
            })
            .catch((err) => console.error('Storage upload error', err))
        },

This code now works for me, but a little hacky.

@MarkLyck there is another way you can extend the expiration time on a image link indefinitely by updating a meta value called expiration on the file relationship.

For example, let’s say I have a Users table with an Image field called favoritePhoto. I can manually set the expiration time on that specific relationship.

First, you’d need to get the ID of the Table Field:

query {
  system {
    table(name: "Users") {
      fields {
        id
        displayName
      }
    }
  }
}

Once you have the ID of the favoritePhoto field, then you can set the following fieldAttribute with a mutation.

mutation(
  $fieldId: ID!,
  $secondsUntilExpires: Int!) {
  system {
    fieldUpdate(data: {
      id: $fieldId
      fieldTypeAttributes: {
        expiration: $secondsUntilExpires
      }
    }) {
      id
    }
  }
}

So now, let’s say I update the expiration to one-billion seconds (1,000,000,000), whenever a downloadUrl is generated for my favoritePhoto it would expire after 31 years.

query {
  user {
    favoritePhoto {
      downloadUrl
    }
  }
}
1 Like