Skip to content

Commit b73ca12

Browse files
authored
Restructure backend (#439)
* chore: update dependencies - Update uuid to ^11.1.0 and add @types/uuid - Update @autonomys/file-caching to ^1.5.1 - Update related dependencies across packages * feat: add HTTP range request utility functions - Add parseByteRange function for parsing Range headers - Add createPartialStream function for creating range-aware streams - Add utility functions for handling HTTP 206 partial content responses - Support for byte-range requests in readable streams * feat: implement range request support in download services - Add ByteRange interface for range request parameters - Implement partial download support in memory cache - Add range-aware download methods in download service - Support for caching partial content with byte ranges - Add sync download functionality with range support * feat: add HTTP range request support to download controllers - Update download controller to handle Range headers - Implement HTTP 206 Partial Content responses - Add proper Content-Range and Accept-Ranges headers - Support partial file downloads in object controllers - Update use cases to handle byte range parameters - Add range request support to object download endpoints * test: add comprehensive tests for partial retrieval functionality - Add tests for byte range parsing and validation - Test partial content download scenarios - Verify HTTP 206 response handling - Add test cases for range request edge cases - Test memory cache integration with range requests * refactor: move to separate files related logic * refactor: enhance download response handling with byte range support - Introduced isExpectedDocument function to streamline document expectation checks - Refactored handleDownloadResponseHeaders to utilize setFileResponseHeaders and setFolderResponseHeaders for improved clarity - Added byte range handling in setFileResponseHeaders for partial content responses - Updated content disposition and content type settings based on encryption and document expectations * refactor: reorganize file use cases * fix(testing): cache populated in async setting * feat: update auto files gateway version * refactor: reorganize backend structure and update package references - Updated package.json to reflect new paths for frontend and download server scripts. - Refactored import paths in various test files to align with the new directory structure. - Removed deprecated APIs and reorganized use cases and repositories for better clarity and maintainability. - Deleted unused files and directories to streamline the codebase. * refactor: re-structure core functionality placement * update: bump auto-sdk version * refactor: update FilesUseCases tests and imports - Refactored tests for FilesUseCases to use getObjectInformation instead of getMetadata. - Updated mock implementations to reflect changes in the download blocking logic. - Cleaned up imports in files.spec.ts and index.ts for better organization and clarity. * refactor: reorganize backend structure and update package references - Updated package.json to reflect new paths for frontend and download server scripts. - Refactored import paths in various test files to align with the new directory structure. - Removed deprecated APIs and reorganized use cases and repositories for better clarity and maintainability. - Deleted unused files and directories to streamline the codebase. * refactor: re-structure core functionality placement * refactor: clean up imports in files.spec.ts - Removed unused dbMigration import to streamline the test file and improve clarity. * fix: update byte range header handling in getByteRange function - Changed the header used to retrieve the byte range from 'content-range' to 'range' for improved accuracy in handling byte range requests. * fix: enhance byte range validation in getByteRange function - Added validation checks to ensure start and end byte values are non-negative and that the start value does not exceed the end value, improving the robustness of byte range handling. * chore: update package dependencies to version 1.5.11 * chore: update yarn.lock to reflect package versions 1.5.11 for auto-dag-data and file-caching * feat: improved error management w/ neverthrow lib * feat: enhance error logging in upload controller * fix: improve error handling in async download process * fix: enhance error handling in tagUpload function for folder uploads * refactor: update logging in subscription controller to use trace level and return subscription value * fix: improve error handling in async download tests and update tagUpload function for folder uploads * fix: remove unused mock for error handling in async download tests * fix: update error handling in dismissDownload function to return ForbiddenError for unauthorized users * fix: handle result in update progress
1 parent 1ae73d0 commit b73ca12

File tree

106 files changed

+2380
-1624
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+2380
-1624
lines changed

backend/__tests__/e2e/downloads/async.spec.ts

Lines changed: 81 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
import { User, AsyncDownloadStatus, AsyncDownload } from '@auto-drive/models'
22
import { createMockUser } from '../../utils/mocks.js'
3-
import { AsyncDownloadsUseCases } from '../../../src/useCases/asyncDownloads/index.js'
3+
import { AsyncDownloadsUseCases } from '../../../src/core/downloads/index.js'
44
import { uploadFile } from '../../utils/uploads.js'
55
import { dbMigration } from '../../utils/dbMigrate.js'
6-
import { asyncDownloadsRepository } from '../../../src/repositories/asyncDownloads/index.js'
7-
import { Rabbit } from '../../../src/drivers/rabbit.js'
6+
import { asyncDownloadsRepository } from '../../../src/infrastructure/repositories/asyncDownloads/index.js'
7+
import { Rabbit } from '../../../src/infrastructure/drivers/rabbit.js'
88
import { jest } from '@jest/globals'
9-
import { createTask } from '../../../src/services/eventRouter/tasks.js'
10-
import { ObjectUseCases } from '../../../src/useCases/index.js'
11-
import { downloadService } from '../../../src/services/download/index.js'
9+
import { createTask } from '../../../src/infrastructure/eventRouter/tasks.js'
10+
import { ObjectUseCases } from '../../../src/core/index.js'
11+
import { downloadService } from '../../../src/infrastructure/services/download/index.js'
1212
import { Readable } from 'stream'
13+
import {
14+
ForbiddenError,
15+
InternalError,
16+
ObjectNotFoundError,
17+
} from '../../../src/errors/index.js'
18+
import { err } from 'neverthrow'
1319

1420
describe('Async Downloads', () => {
1521
let user: User
@@ -31,9 +37,12 @@ describe('Async Downloads', () => {
3137
})
3238

3339
it('should fail if object is not found', async () => {
34-
await expect(
35-
AsyncDownloadsUseCases.createDownload(user, 'not-found'),
36-
).rejects.toThrow('Object not found')
40+
const result = await AsyncDownloadsUseCases.createDownload(
41+
user,
42+
'not-found',
43+
)
44+
expect(result.isErr()).toBe(true)
45+
expect(result._unsafeUnwrapErr()).toBeInstanceOf(ObjectNotFoundError)
3746
})
3847

3948
it('should create an async download', async () => {
@@ -45,7 +54,10 @@ describe('Async Downloads', () => {
4554
})
4655
const mockPublish = jest.spyOn(Rabbit, 'publish').mockResolvedValue()
4756

48-
const download = await AsyncDownloadsUseCases.createDownload(user, cid)
57+
const download = await AsyncDownloadsUseCases.createDownload(
58+
user,
59+
cid,
60+
).then((e) => e._unsafeUnwrap())
4961

5062
expect(download).toMatchObject({
5163
id: expect.any(String),
@@ -64,20 +76,23 @@ describe('Async Downloads', () => {
6476
})
6577

6678
it('should fail if download is not found', async () => {
67-
await expect(
68-
AsyncDownloadsUseCases.asyncDownload('not-found'),
69-
).rejects.toThrow('Download not found')
79+
const result = await AsyncDownloadsUseCases.asyncDownload('not-found')
80+
expect(result.isErr()).toBe(true)
81+
expect(result._unsafeUnwrapErr()).toBeInstanceOf(ObjectNotFoundError)
7082
})
7183

7284
it('should fail if object is not found', async () => {
73-
const download = await AsyncDownloadsUseCases.createDownload(user, cid)
85+
const download = await AsyncDownloadsUseCases.createDownload(
86+
user,
87+
cid,
88+
).then((e) => e._unsafeUnwrap())
7489
const mockGetMetadata = jest
7590
.spyOn(ObjectUseCases, 'getMetadata')
76-
.mockResolvedValue(undefined)
91+
.mockResolvedValue(err(new ObjectNotFoundError('Object not found')))
7792

78-
await expect(
79-
AsyncDownloadsUseCases.asyncDownload(download.id),
80-
).rejects.toThrow('Object not found')
93+
const result = await AsyncDownloadsUseCases.asyncDownload(download.id)
94+
expect(result.isErr()).toBe(true)
95+
expect(result._unsafeUnwrapErr()).toBeInstanceOf(ObjectNotFoundError)
8196

8297
mockGetMetadata.mockRestore()
8398
})
@@ -104,7 +119,10 @@ describe('Async Downloads', () => {
104119
}),
105120
)
106121

107-
const download = await AsyncDownloadsUseCases.createDownload(user, cid)
122+
const download = await AsyncDownloadsUseCases.createDownload(
123+
user,
124+
cid,
125+
).then((e) => e._unsafeUnwrap())
108126
const doneMock = jest.spyOn(AsyncDownloadsUseCases, 'updateStatus')
109127

110128
await AsyncDownloadsUseCases.asyncDownload(download.id)
@@ -138,18 +156,22 @@ describe('Async Downloads', () => {
138156
}),
139157
)
140158

141-
const download = await AsyncDownloadsUseCases.createDownload(user, cid)
142-
const doneMock = jest.spyOn(AsyncDownloadsUseCases, 'setError')
159+
const download = await AsyncDownloadsUseCases.createDownload(
160+
user,
161+
cid,
162+
).then((e) => e._unsafeUnwrap())
143163

144-
await expect(
145-
AsyncDownloadsUseCases.asyncDownload(download.id),
146-
).rejects.toThrow('Download failed')
164+
const result = await AsyncDownloadsUseCases.asyncDownload(download.id)
147165

148-
expect(doneMock).toHaveBeenCalledWith(download.id, expect.any(String))
166+
expect(result.isErr()).toBe(true)
167+
expect(result._unsafeUnwrapErr()).toBeInstanceOf(InternalError)
149168
})
150169

151170
it('should be dismissed if user dismiss it', async () => {
152-
const download = await AsyncDownloadsUseCases.createDownload(user, cid)
171+
const download = await AsyncDownloadsUseCases.createDownload(
172+
user,
173+
cid,
174+
).then((e) => e._unsafeUnwrap())
153175
await AsyncDownloadsUseCases.dismissDownload(user, download.id)
154176

155177
const dismissed = await asyncDownloadsRepository.getDownloadById(
@@ -164,8 +186,14 @@ describe('Async Downloads', () => {
164186

165187
it('should get all downloads for a user', async () => {
166188
// Create multiple downloads for the same user
167-
const download1 = await AsyncDownloadsUseCases.createDownload(user, cid)
168-
const download2 = await AsyncDownloadsUseCases.createDownload(user, cid)
189+
const download1 = await AsyncDownloadsUseCases.createDownload(
190+
user,
191+
cid,
192+
).then((e) => e._unsafeUnwrap())
193+
const download2 = await AsyncDownloadsUseCases.createDownload(
194+
user,
195+
cid,
196+
).then((e) => e._unsafeUnwrap())
169197

170198
// Get all downloads for the user
171199
const downloads = await AsyncDownloadsUseCases.getDownloadsByUser(user)
@@ -181,8 +209,14 @@ describe('Async Downloads', () => {
181209

182210
it('should get all downloads for a user', async () => {
183211
// Create multiple downloads for the same user
184-
const download1 = await AsyncDownloadsUseCases.createDownload(user, cid)
185-
const download2 = await AsyncDownloadsUseCases.createDownload(user, cid)
212+
const download1 = await AsyncDownloadsUseCases.createDownload(
213+
user,
214+
cid,
215+
).then((e) => e._unsafeUnwrap())
216+
const download2 = await AsyncDownloadsUseCases.createDownload(
217+
user,
218+
cid,
219+
).then((e) => e._unsafeUnwrap())
186220

187221
await AsyncDownloadsUseCases.dismissDownload(user, download1.id)
188222

@@ -197,16 +231,20 @@ describe('Async Downloads', () => {
197231

198232
it('should get a specific download by id', async () => {
199233
// Create a download
200-
const download = await AsyncDownloadsUseCases.createDownload(user, cid)
234+
const download = await AsyncDownloadsUseCases.createDownload(
235+
user,
236+
cid,
237+
).then((e) => e._unsafeUnwrap())
201238

202239
// Get the download by id
203-
const retrievedDownload = await AsyncDownloadsUseCases.getDownloadById(
240+
const retrieveResult = await AsyncDownloadsUseCases.getDownloadById(
204241
user,
205242
download.id,
206243
)
207244

208245
// Verify the retrieved download matches the created one
209-
expect(retrievedDownload).toMatchObject({
246+
expect(retrieveResult.isOk()).toBe(true)
247+
expect(retrieveResult._unsafeUnwrap()).toMatchObject({
210248
id: download.id,
211249
cid: download.cid,
212250
oauthProvider: user.oauthProvider,
@@ -215,22 +253,21 @@ describe('Async Downloads', () => {
215253
})
216254
})
217255

218-
it('should throw error when getting download with invalid id', async () => {
219-
// Attempt to get a download with an invalid id
220-
await expect(
221-
AsyncDownloadsUseCases.getDownloadById(user, 'invalid-id'),
222-
).rejects.toThrow('Download not found')
223-
})
224-
225256
it('should throw error when user tries to access another user download', async () => {
226257
// Create a download
227-
const download = await AsyncDownloadsUseCases.createDownload(user, cid)
258+
const download = await AsyncDownloadsUseCases.createDownload(
259+
user,
260+
cid,
261+
).then((e) => e._unsafeUnwrap())
228262

229263
// Create another user
230264
const anotherUser = createMockUser()
231265
// Attempt to get the download with another user
232-
await expect(
233-
AsyncDownloadsUseCases.getDownloadById(anotherUser, download.id),
234-
).rejects.toThrow('User unauthorized')
266+
const result = await AsyncDownloadsUseCases.getDownloadById(
267+
anotherUser,
268+
download.id,
269+
)
270+
expect(result.isErr()).toBe(true)
271+
expect(result._unsafeUnwrapErr()).toBeInstanceOf(ForbiddenError)
235272
})
236273
})

backend/__tests__/e2e/objects/nodes.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { v4 } from 'uuid'
2-
import { NodesUseCases, ObjectUseCases } from '../../../src/useCases/index.js'
2+
import { NodesUseCases, ObjectUseCases } from '../../../src/core/index.js'
33
import {
44
blake3HashFromCid,
55
cidOfNode,
@@ -16,17 +16,17 @@ import {
1616
metadataRepository,
1717
Node,
1818
nodesRepository,
19-
} from '../../../src/repositories/index.js'
19+
} from '../../../src/infrastructure/repositories/index.js'
2020
import {
2121
ObjectMapping,
2222
ObjectMappingListEntry,
2323
TransactionResult,
2424
} from '@auto-drive/models'
2525
import { mockRabbitPublish, unmockMethods } from '../../utils/mocks.js'
2626
import { jest } from '@jest/globals'
27-
import { EventRouter } from '../../../src/services/eventRouter/index.js'
28-
import { BlockstoreUseCases } from '../../../src/useCases/uploads/blockstore.js'
29-
import { MAX_RETRIES } from '../../../src/services/eventRouter/tasks.js'
27+
import { EventRouter } from '../../../src/infrastructure/eventRouter/index.js'
28+
import { BlockstoreUseCases } from '../../../src/core/uploads/blockstore.js'
29+
import { MAX_RETRIES } from '../../../src/infrastructure/eventRouter/tasks.js'
3030

3131
describe('Nodes', () => {
3232
const id = v4()

backend/__tests__/e2e/objects/object.spec.ts

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { UserWithOrganization } from '@auto-drive/models'
2-
import { AuthManager } from '../../../src/services/auth/index.js'
3-
import { ObjectUseCases } from '../../../src/useCases/index.js'
2+
import { AuthManager } from '../../../src/infrastructure/services/auth/index.js'
3+
import { ObjectUseCases } from '../../../src/core/index.js'
44
import { dbMigration } from '../../utils/dbMigrate.js'
5-
import { PreconditionError } from '../../utils/error.js'
65
import {
76
createMockUser,
87
mockRabbitPublish,
@@ -11,8 +10,9 @@ import {
1110
import { uploadFile } from '../../utils/uploads.js'
1211
import { jest } from '@jest/globals'
1312
import { v4 } from 'uuid'
14-
import { downloadService } from '../../../src/services/download/index.js'
13+
import { downloadService } from '../../../src/infrastructure/services/download/index.js'
1514
import { Readable } from 'stream'
15+
import { ForbiddenError } from '../../../src/errors/index.js'
1616

1717
describe('Object', () => {
1818
let user: UserWithOrganization
@@ -42,23 +42,27 @@ describe('Object', () => {
4242

4343
it('for not admin user should not be able to share object', async () => {
4444
const mockUser = createMockUser()
45-
await expect(
46-
ObjectUseCases.shareObject(mockUser, fileCid, user.publicId!),
47-
).rejects.toThrow(new Error('User is not an admin of this object'))
45+
const result = await ObjectUseCases.shareObject(
46+
mockUser,
47+
fileCid,
48+
user.publicId!,
49+
)
50+
expect(result.isErr()).toBe(true)
51+
expect(result._unsafeUnwrapErr()).toBeInstanceOf(ForbiddenError)
4852
})
4953

5054
it('User that is not owner should not be able to delete object', async () => {
5155
const mockUser = createMockUser()
52-
await expect(
53-
ObjectUseCases.markAsDeleted(mockUser, fileCid),
54-
).rejects.toThrow(new Error('User is not an owner of this object'))
56+
const result = await ObjectUseCases.markAsDeleted(mockUser, fileCid)
57+
expect(result.isErr()).toBe(true)
58+
expect(result._unsafeUnwrapErr()).toBeInstanceOf(ForbiddenError)
5559
})
5660

5761
it('User that is not owner should not be able to restore object', async () => {
5862
const mockUser = createMockUser()
59-
await expect(
60-
ObjectUseCases.restoreObject(mockUser, fileCid),
61-
).rejects.toThrow(new Error('User is not an owner of this object'))
63+
const result = await ObjectUseCases.restoreObject(mockUser, fileCid)
64+
expect(result.isErr()).toBe(true)
65+
expect(result._unsafeUnwrapErr()).toBeInstanceOf(ForbiddenError)
6266
})
6367

6468
it('isArchived should return false for not archived object', async () => {
@@ -164,8 +168,9 @@ describe('Object', () => {
164168
})
165169

166170
it('should be able to search object by name', async () => {
167-
const metadata = await ObjectUseCases.getMetadata(fileCid)
168-
if (!metadata) throw new PreconditionError('Metadata not found')
171+
const metadata = await ObjectUseCases.getMetadata(fileCid).then((e) =>
172+
e._unsafeUnwrap(),
173+
)
169174

170175
const search = await ObjectUseCases.searchMetadataByName(
171176
metadata.name!,
@@ -179,8 +184,10 @@ describe('Object', () => {
179184
})
180185

181186
it('should be able to see another user with user scope', async () => {
182-
const metadata = await ObjectUseCases.getMetadata(fileCid)
183-
if (!metadata) throw new PreconditionError('Metadata not found')
187+
const metadata = await ObjectUseCases.getMetadata(fileCid).then((e) =>
188+
e._unsafeUnwrap(),
189+
)
190+
184191
const mockUser = createMockUser()
185192
const search = await ObjectUseCases.searchMetadataByName(
186193
metadata.name!,
@@ -194,8 +201,10 @@ describe('Object', () => {
194201
})
195202

196203
it('should be able to see another user with global scope', async () => {
197-
const metadata = await ObjectUseCases.getMetadata(fileCid)
198-
if (!metadata) throw new PreconditionError('Metadata not found')
204+
const metadata = await ObjectUseCases.getMetadata(fileCid).then((e) =>
205+
e._unsafeUnwrap(),
206+
)
207+
199208
const search = await ObjectUseCases.searchMetadataByName(
200209
metadata.name!,
201210
5,
@@ -207,8 +216,9 @@ describe('Object', () => {
207216
})
208217

209218
it('should be able to search object by cid', async () => {
210-
const metadata = await ObjectUseCases.getMetadata(fileCid)
211-
if (!metadata) throw new PreconditionError('Metadata not found')
219+
const metadata = await ObjectUseCases.getMetadata(fileCid).then((e) =>
220+
e._unsafeUnwrap(),
221+
)
212222

213223
const search = await ObjectUseCases.searchMetadataByCID(fileCid, 5, {
214224
scope: 'user',
@@ -218,8 +228,9 @@ describe('Object', () => {
218228
})
219229

220230
it('should be able to search object by name (using common method)', async () => {
221-
const metadata = await ObjectUseCases.getMetadata(fileCid)
222-
if (!metadata) throw new PreconditionError('Metadata not found')
231+
const metadata = await ObjectUseCases.getMetadata(fileCid).then((e) =>
232+
e._unsafeUnwrap(),
233+
)
223234

224235
const search = await ObjectUseCases.searchByCIDOrName(metadata.name!, 5, {
225236
scope: 'user',
@@ -234,8 +245,9 @@ describe('Object', () => {
234245
})
235246

236247
it('should be able to search object by cid (using common method)', async () => {
237-
const metadata = await ObjectUseCases.getMetadata(fileCid)
238-
if (!metadata) throw new PreconditionError('Metadata not found')
248+
const metadata = await ObjectUseCases.getMetadata(fileCid).then((e) =>
249+
e._unsafeUnwrap(),
250+
)
239251

240252
const search = await ObjectUseCases.searchByCIDOrName(fileCid, 5, {
241253
scope: 'user',

0 commit comments

Comments
 (0)