Skip to content

Commit

Permalink
Merge pull request #307 from mnfst/env-paths
Browse files Browse the repository at this point in the history
🔧 New environment variables: DB path / Public path / Show OPEN API
  • Loading branch information
SebConejo authored Feb 10, 2025
2 parents c838305 + e05bd4d commit 52a0094
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 27 deletions.
5 changes: 5 additions & 0 deletions .changeset/ninety-cats-float.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'manifest': patch
---

added dynamic path for db and public folder
3 changes: 2 additions & 1 deletion packages/core/manifest/src/config/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ export default (): { database: SqliteConnectionOptions } => {
return {
database: {
type: 'sqlite',
database: `${process.cwd()}/manifest/backend.db`,
database:
process.env.DATABASE_PATH || `${process.cwd()}/manifest/backend.db`,
dropSchema: process.env.DB_DROP_SCHEMA === 'true' || false,
synchronize: true
}
Expand Down
2 changes: 2 additions & 0 deletions packages/core/manifest/src/config/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import path from 'path'
export default (): {
paths: {
adminPanelFolder: string
publicFolder: string
manifestFile: string
handlersFolder: string
}
Expand All @@ -13,6 +14,7 @@ export default (): {
process.env.NODE_ENV === 'contribution'
? path.join(process.cwd(), '..', 'admin', 'dist')
: `${process.cwd()}/node_modules/manifest/dist/admin`,
publicFolder: process.env.PUBLIC_FOLDER || `${process.cwd()}/public`,
manifestFile:
process.env.MANIFEST_FILE_PATH ||
`${process.cwd()}/manifest/backend.yml`,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/manifest/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '@repo/types'

// Paths.
export const STORAGE_PATH: string = 'public/storage'
export const STORAGE_PATH: string = 'storage'
export const API_PATH: string = 'api'
export const COLLECTIONS_PATH: string = 'collections'
export const SINGLES_PATH: string = 'singles'
Expand Down
34 changes: 25 additions & 9 deletions packages/core/manifest/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import * as express from 'express'
import * as livereload from 'livereload'
import { join } from 'path'
import { AppModule } from './app.module'
import { API_PATH, DEFAULT_PORT, DEFAULT_TOKEN_SECRET_KEY } from './constants'
import {
API_PATH,
DEFAULT_PORT,
DEFAULT_TOKEN_SECRET_KEY,
STORAGE_PATH
} from './constants'
import { OpenApiService } from './open-api/services/open-api.service'

async function bootstrap() {
Expand Down Expand Up @@ -49,24 +54,34 @@ async function bootstrap() {
const adminPanelFolder: string = configService.get('paths').adminPanelFolder
app.use(express.static(adminPanelFolder))

app.use('/storage', express.static('public/storage'))
const publicFolder: string = configService.get('paths').publicFolder
const storagePath = join(publicFolder, STORAGE_PATH)

app.use(`/${STORAGE_PATH}`, express.static(storagePath))

// Redirect all requests to the client app index.
app.use((req, res, next) => {
if (req.url.startsWith(`/${API_PATH}`) || req.url.startsWith('/storage')) {
if (
req.url.startsWith(`/${API_PATH}`) ||
req.url.startsWith(`/${STORAGE_PATH}`)
) {
next()
} else {
res.sendFile(join(adminPanelFolder, 'index.html'))
}
})

const openApiService: OpenApiService = app.get(OpenApiService)
// Open API documentation.
const showOpenApi: boolean = process.env.OPEN_API_DOCS !== 'false'

if (showOpenApi) {
const openApiService: OpenApiService = app.get(OpenApiService)

SwaggerModule.setup(API_PATH, app, openApiService.generateOpenApiObject(), {
customfavIcon: 'assets/images/open-api/favicon.ico',
customSiteTitle: 'Manifest API Doc',
SwaggerModule.setup(API_PATH, app, openApiService.generateOpenApiObject(), {
customfavIcon: 'assets/images/open-api/favicon.ico',
customSiteTitle: 'Manifest API Doc',

customCss: `
customCss: `
.swagger-ui html {
box-sizing: border-box;
Expand Down Expand Up @@ -1782,7 +1797,8 @@ background: #ce107c;
fill: #535356;
}
`
})
})
}

await app.listen(configService.get('PORT') || DEFAULT_PORT)
}
Expand Down
22 changes: 15 additions & 7 deletions packages/core/manifest/src/storage/services/storage.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ export class StorageService {

const filePath: string = `${folder}/${uniqid()}-${slugify(file.originalname)}`

fs.writeFileSync(`${STORAGE_PATH}/${filePath}`, file.buffer)
fs.writeFileSync(
`${this.configService.get('paths').publicFolder}/${STORAGE_PATH}/${filePath}`,
file.buffer
)

return this.prependStorageUrl(filePath)
}
Expand Down Expand Up @@ -70,9 +73,12 @@ export class StorageService {
.resize(imageSizes[sizeName].width, imageSizes[sizeName].height, {
fit: imageSizes[sizeName].fit
})
.toFile(`${STORAGE_PATH}/${imagePath}`, () => {
return imagePath
})
.toFile(
`${this.configService.get('paths').publicFolder}/${STORAGE_PATH}/${imagePath}`,
() => {
return imagePath
}
)

imagePaths[sizeName] = this.prependStorageUrl(imagePath)
}
Expand All @@ -95,18 +101,20 @@ export class StorageService {

const folder: string = `${kebabize(entity)}/${kebabize(property)}/${dateString}`

mkdirp.sync(`${STORAGE_PATH}/${folder}`)
mkdirp.sync(
`${this.configService.get('paths').publicFolder}/${STORAGE_PATH}/${folder}`
)

return folder
}

/**
* Prepends the storage URL to the given value.
* Prepends the storage absolute URL to the given value.
*
* @param value The value to prepend the storage URL to.
* @returns The value with the storage URL prepended.
*/
prependStorageUrl(value: string): string {
return `${this.configService.get('baseUrl')}/storage/${value}`
return `${this.configService.get('baseUrl')}/${STORAGE_PATH}/${value}`
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ describe('StorageService', () => {
expect(filePaths).toBeDefined()
expect(Object.keys(filePaths).length).toBe(2)
expect(Object.keys(filePaths)).toMatchObject(Object.keys(imageSizes))
expect(sharp.prototype.resize).toHaveBeenCalledTimes(2)
expect(sharp.prototype.toFile).toHaveBeenCalledTimes(2)
})

it('should prepend the storage url before the path', () => {
Expand Down
44 changes: 37 additions & 7 deletions packages/core/manifest/src/upload/tests/upload.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,49 @@ describe('UploadController', () => {
})

describe('uploadFile', () => {
it('should return the path of the uploaded file', () => {})
it('should return the path of the uploaded file', () => {
const file = {
buffer: Buffer.from('file content'),
originalname: 'file.txt'
}

it('creates unique file names', () => {})
const entity = 'entity'
const property = 'property'
const path = 'path'

it('should return a 400 error if entity name or property is not provided', () => {})
jest.spyOn(uploadService, 'storeFile').mockReturnValue(path)

it('expect the file to be passed as "file" in a multipart/form-data', () => {})
expect(controller.uploadFile(file, entity, property)).toEqual({ path })

expect(uploadService.storeFile).toHaveBeenCalledWith({
file,
entity,
property
})
})
})
describe('uploadImage', () => {
it('should upload an image', () => {})
it('should upload an image', () => {
const image = {
buffer: Buffer.from('image content'),
originalname: 'image.jpg'
}

const entity = 'entity'
const property = 'property'
const imagePaths = { thumbnail: 'path', medium: 'path' }

jest.spyOn(uploadService, 'storeImage').mockReturnValue(imagePaths)

it('should return a 400 error if entity name or property is not provided', () => {})
expect(controller.uploadImage(image, entity, property)).toEqual(
imagePaths
)

it('expect the image to be passed as "image" in a multipart/form-data', () => {})
expect(uploadService.storeImage).toHaveBeenCalledWith({
image,
entity,
property
})
})
})
})
87 changes: 85 additions & 2 deletions packages/core/manifest/src/upload/tests/upload.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,47 @@ import { Test, TestingModule } from '@nestjs/testing'
import { UploadService } from '../services/upload.service'
import { EntityManifestService } from '../../manifest/services/entity-manifest.service'
import { StorageService } from '../../storage/services/storage.service'
import { PropType, PropertyManifest } from '../../../../types/src'
import { DEFAULT_IMAGE_SIZES } from '../../constants'

describe('UploadService', () => {
let service: UploadService
let storageService: StorageService
let entityManifestService: EntityManifestService

const dummyImageProp: PropertyManifest = {
name: 'avatar',
type: PropType.Image,
options: {
sizes: DEFAULT_IMAGE_SIZES
}
}
const imagePaths = { large: 'imagePaths' }

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UploadService,
{
provide: StorageService,
useValue: {
store: jest.fn()
store: jest.fn(),
storeImage: jest.fn(() => imagePaths)
}
},
{
provide: EntityManifestService,
useValue: {
getEntityManifest: jest.fn()
getEntityManifest: jest.fn(() => ({
properties: [
dummyImageProp,
{
name: 'not-an-image',
type: PropType.String,
options: { required: true }
}
]
}))
}
}
]
Expand All @@ -37,4 +58,66 @@ describe('UploadService', () => {
it('should be defined', () => {
expect(service).toBeDefined()
})

describe('storeFile', () => {
it('should store a file', () => {
const file = {
buffer: Buffer.from('file content'),
originalname: 'file.txt'
}

const entity = 'entity'
const property = 'property'
const path = 'path'

jest.spyOn(storageService, 'store').mockReturnValue(path)

expect(service.storeFile({ file, entity, property })).toEqual(path)

expect(storageService.store).toHaveBeenCalledWith(entity, property, file)
})
})

describe('store image', () => {
it('should store an image', () => {
const image = {
buffer: Buffer.from('image content'),
originalname: 'image.jpg'
}

const entity = 'entity'

const result = service.storeImage({
image,
entity,
property: dummyImageProp.name
})

expect(result).toEqual(imagePaths)

expect(storageService.storeImage).toHaveBeenCalledWith(
entity,
dummyImageProp.name,
image,
dummyImageProp.options.sizes
)
})

it('should fail if property is not an image', () => {
const image = {
buffer: Buffer.from('image content'),
originalname: 'image.jpg'
}

const entity = 'entity'

expect(() =>
service.storeImage({
image,
entity,
property: 'not-an-image'
})
).toThrow()
})
})
})

0 comments on commit 52a0094

Please sign in to comment.