Skip to content

Commit

Permalink
Merge pull request #161 from autonomys/public-download-links
Browse files Browse the repository at this point in the history
feat: enable creation of public downloadable links
  • Loading branch information
clostao authored Jan 30, 2025
2 parents 275a648 + b867842 commit ae346fd
Show file tree
Hide file tree
Showing 22 changed files with 460 additions and 205 deletions.
37 changes: 19 additions & 18 deletions auth/src/models/jwt.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
type BaseTokenPayload = {
id: string
isRefreshToken: boolean
oauthProvider: string
oauthUserId: string
}
id: string;
isRefreshToken: boolean;
oauthProvider: string;
oauthUserId: string;
};

export type CustomAccessTokenPayload = BaseTokenPayload & {
isRefreshToken: false
refreshTokenId: string
'https://hasura.io/jwt/claims': {
'x-hasura-default-role': string
'x-hasura-allowed-roles': string[]
'x-hasura-oauth-provider': string
'x-hasura-oauth-user-id': string
'x-hasura-organization-id': string
}
}
isRefreshToken: false;
refreshTokenId: string;
"https://hasura.io/jwt/claims": {
"x-hasura-default-role": string;
"x-hasura-allowed-roles": string[];
"x-hasura-oauth-provider": string;
"x-hasura-oauth-user-id": string;
"x-hasura-organization-id": string;
"x-hasura-public-id": string;
};
};

export type CustomRefreshTokenPayload = BaseTokenPayload & {
isRefreshToken: true
}
isRefreshToken: true;
};

export type CustomTokenPayload =
| CustomAccessTokenPayload
| CustomRefreshTokenPayload
| CustomRefreshTokenPayload;
1 change: 1 addition & 0 deletions auth/src/services/authManager/providers/custom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ const createAccessToken = async (
"x-hasura-organization-id": userInfo?.onboarded
? userInfo.organizationId
: "none",
"x-hasura-public-id": userInfo?.publicId ?? "none",
},
};

Expand Down
20 changes: 13 additions & 7 deletions backend/__tests__/e2e/objects/object.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,21 +171,27 @@ describe('Object', () => {
])
})

const sharedWithUser = createMockUser()
describe('Share object', () => {
let randomFile: string
it('should be able to share object', async () => {
const mockUser = createMockUser()
randomFile = await uploadFile(mockUser, 'test.txt', 'test', 'text/plain')
randomFile = await uploadFile(
mockUser,
'test.txt',
Buffer.from(Math.random().toString()),
'text/plain',
)

jest
.spyOn(AuthManager, 'getUserFromPublicId')
.mockResolvedValueOnce(createMockUser())
.mockResolvedValueOnce(sharedWithUser)

await expect(
ObjectUseCases.shareObject(mockUser, randomFile, user.publicId!),
).resolves.not.toThrow()

const sharedRoots = await ObjectUseCases.getSharedRoots(user)
const sharedRoots = await ObjectUseCases.getSharedRoots(sharedWithUser)
expect(sharedRoots.rows).toMatchObject([
{
headCid: randomFile,
Expand All @@ -195,21 +201,21 @@ describe('Object', () => {

it('should be able to delete shared object', async () => {
await expect(
ObjectUseCases.markAsDeleted(user, randomFile),
ObjectUseCases.markAsDeleted(sharedWithUser, randomFile),
).resolves.not.toThrow()
})

it('should not be listed in shared objects', async () => {
const sharedRoots = await ObjectUseCases.getSharedRoots(user)
const sharedRoots = await ObjectUseCases.getSharedRoots(sharedWithUser)
expect(sharedRoots.rows).toMatchObject([])
})

it('should be able to restore shared object', async () => {
await expect(
ObjectUseCases.restoreObject(user, randomFile),
ObjectUseCases.restoreObject(sharedWithUser, randomFile),
).resolves.not.toThrow()

const sharedRoots = await ObjectUseCases.getSharedRoots(user)
const sharedRoots = await ObjectUseCases.getSharedRoots(sharedWithUser)
expect(sharedRoots.rows).toMatchObject([
{
headCid: randomFile,
Expand Down
70 changes: 70 additions & 0 deletions backend/__tests__/e2e/objects/publicUrl.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { InteractionType } from '../../../src/models/objects/interactions'
import { UserWithOrganization } from '../../../src/models/users/user'
import { PublishedObject } from '../../../src/repositories/objects/publishedObjects'
import { AuthManager } from '../../../src/services/auth'
import { ObjectUseCases, SubscriptionsUseCases } from '../../../src/useCases'
import { asyncIterableToPromiseOfArray } from '../../../src/utils/async'
import { dbMigration } from '../../utils/dbMigrate'
import { createMockUser } from '../../utils/mocks'
import { uploadFile } from '../../utils/uploads'
import { jest } from '@jest/globals'

describe('Public URL', () => {
let user: UserWithOrganization
let fileCid: string
let publishedObject: PublishedObject
const content = 'test'

beforeAll(async () => {
await dbMigration.up()
user = createMockUser()
fileCid = await uploadFile(user, 'test.txt', content, 'text/plain')
})

afterAll(async () => {
await dbMigration.down()
})

it('should be able to publish and retrieve', async () => {
publishedObject = await ObjectUseCases.publishObject(user, fileCid)

expect(publishedObject).toMatchObject({
publicId: user.publicId,
cid: fileCid,
id: expect.any(String),
})
})

it('should be downloadable by public id and credits should be deducted', async () => {
jest.spyOn(AuthManager, 'getUserFromPublicId').mockResolvedValue(user)

const { metadata, startDownload } =
await ObjectUseCases.downloadPublishedObject(publishedObject.id)

expect(metadata).toMatchObject({
type: 'file',
dataCid: publishedObject.cid,
})

const pendingCredits =
await SubscriptionsUseCases.getPendingCreditsByUserAndType(
user,
InteractionType.Download,
)

const downloadedContent = Buffer.concat(
await asyncIterableToPromiseOfArray(await startDownload()),
)
expect(downloadedContent).toEqual(Buffer.from(content))

const updatedPendingCredits =
await SubscriptionsUseCases.getPendingCreditsByUserAndType(
user,
InteractionType.Download,
)

expect(updatedPendingCredits).toBe(
pendingCredits - downloadedContent.length,
)
})
})
6 changes: 4 additions & 2 deletions backend/__tests__/e2e/uploads/files.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,10 @@ files.map((file, index) => {

describe('Downloading the file', () => {
it('should be able to retrieve the file', async () => {
const file = await FilesUseCases.downloadObject(user, cid)
const fileArray = await asyncIterableToPromiseOfArray(file)
const { startDownload } = await FilesUseCases.downloadObject(user, cid)
const fileArray = await asyncIterableToPromiseOfArray(
await startDownload(),
)
const fileBuffer = Buffer.concat(fileArray)
expect(fileBuffer).toEqual(rndBuffer)
})
Expand Down
3 changes: 2 additions & 1 deletion backend/__tests__/e2e/uploads/folder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,8 @@ describe('Folder Upload', () => {

it('should be able to download folder as zip', async () => {
const zip = await FilesUseCases.downloadObject(user, folderCID)
const zipArray = await asyncIterableToPromiseOfArray(zip)
const dataStream = await zip.startDownload()
const zipArray = await asyncIterableToPromiseOfArray(dataStream)
const zipBuffer = Buffer.concat(zipArray)
expect(zipBuffer).toBeDefined()
expect(() => {
Expand Down
18 changes: 4 additions & 14 deletions backend/__tests__/utils/mocks.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
import {
UnonboardedUser,
UserRole,
UserWithOrganization,
} from '../../src/models/users'
import { UserRole, UserWithOrganization } from '../../src/models/users'
import { v4 } from 'uuid'

export const MOCK_UNONBOARDED_USER: UnonboardedUser = {
oauthProvider: 'google',
oauthUserId: '123',
role: UserRole.User,
publicId: null,
onboarded: false,
}

export const createMockUser = (): UserWithOrganization => {
return {
...MOCK_UNONBOARDED_USER,
oauthProvider: 'google',
oauthUserId: v4(),
role: UserRole.User,
onboarded: true,
organizationId: v4(),
publicId: v4(),
Expand Down
57 changes: 57 additions & 0 deletions backend/migrations/20250130141940-add-public-urls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use strict'

var dbm
var type
var seed
var fs = require('fs')
var path = require('path')
var Promise

/**
* We receive the dbmigrate dependency from dbmigrate initially.
* This enables us to not have to rely on NODE_PATH.
*/
exports.setup = function (options, seedLink) {
dbm = options.dbmigrate
type = dbm.dataType
seed = seedLink
Promise = options.Promise
}

exports.up = function (db) {
var filePath = path.join(
__dirname,
'sqls',
'20250130141940-add-public-urls-up.sql',
)
return new Promise(function (resolve, reject) {
fs.readFile(filePath, { encoding: 'utf-8' }, function (err, data) {
if (err) return reject(err)

resolve(data)
})
}).then(function (data) {
return db.runSql(data)
})
}

exports.down = function (db) {
var filePath = path.join(
__dirname,
'sqls',
'20250130141940-add-public-urls-down.sql',
)
return new Promise(function (resolve, reject) {
fs.readFile(filePath, { encoding: 'utf-8' }, function (err, data) {
if (err) return reject(err)

resolve(data)
})
}).then(function (data) {
return db.runSql(data)
})
}

exports._meta = {
version: 1,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
DROP INDEX IF EXISTS idx_published_objects_public_id;
DROP INDEX IF EXISTS idx_published_objects_cid;
DROP TABLE IF EXISTS published_objects;
10 changes: 10 additions & 0 deletions backend/migrations/sqls/20250130141940-add-public-urls-up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CREATE TABLE published_objects (
id text PRIMARY KEY,
public_id text NOT NULL,
cid text NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_published_objects_public_id ON published_objects(public_id);
CREATE INDEX idx_published_objects_cid ON published_objects(cid);
Loading

0 comments on commit ae346fd

Please sign in to comment.