@ -16,7 +16,7 @@ const { SiteService, SiteAsync } = require('../../lib/site-lib');
class ImageService extends SiteService {
class ImageService extends SiteService {
constructor ( dtp ) {
constructor ( dtp ) {
super ( dtp , module . exports ) ;
super ( dtp , module . exports ) ;
this . populateImage = [
this . populateImage = [
{
{
@ -26,20 +26,20 @@ class ImageService extends SiteService {
] ;
] ;
}
}
async start ( ) {
async start ( ) {
await super . start ( ) ;
await super . start ( ) ;
await fs . promises . mkdir ( process . env . DTP_IMAGE_WORK_PATH , { recursive : true } ) ;
await fs . promises . mkdir ( process . env . DTP_IMAGE_WORK_PATH , { recursive : true } ) ;
}
}
async create ( owner , imageDefinition , file ) {
async create ( owner , imageDefinition , file ) {
const NOW = new Date ( ) ;
const NOW = new Date ( ) ;
const { minio : minioService } = this . dtp . services ;
const { minio : minioService } = this . dtp . services ;
try {
try {
this . log . debug ( 'processing uploaded image' , { imageDefinition , file } ) ;
this . log . debug ( 'processing uploaded image' , { imageDefinition , file } ) ;
const sharpImage = await sharp ( file . path ) ;
const sharpImage = sharp ( file . path ) ;
const metadata = await sharpImage . metadata ( ) ;
const metadata = await sharpImage . metadata ( ) ;
// create an Image model instance, but leave it here in application memory.
// create an Image model instance, but leave it here in application memory.
// we don't persist it to the db until MinIO accepts the binary data.
// we don't persist it to the db until MinIO accepts the binary data.
const image = new SiteImage ( ) ;
const image = new SiteImage ( ) ;
@ -49,12 +49,12 @@ class ImageService extends SiteService {
image . size = file . size ;
image . size = file . size ;
image . file . bucket = process . env . MINIO_IMAGE_BUCKET ;
image . file . bucket = process . env . MINIO_IMAGE_BUCKET ;
image . metadata = this . makeImageMetadata ( metadata ) ;
image . metadata = this . makeImageMetadata ( metadata ) ;
const imageId = image . _ id . toString ( ) ;
const imageId = image . _ id . toString ( ) ;
const ownerId = owner . _ id . toString ( ) ;
const ownerId = owner . _ id . toString ( ) ;
const fileKey = ` / ${ ownerId . slice ( 0 , 3 ) } / ${ ownerId } / ${ imageId . slice ( 0 , 3 ) } / ${ imageId } ` ;
const fileKey = ` / ${ ownerId . slice ( 0 , 3 ) } / ${ ownerId } / ${ imageId . slice ( 0 , 3 ) } / ${ imageId } ` ;
image . file . key = fileKey ;
image . file . key = fileKey ;
// upload the image file to MinIO
// upload the image file to MinIO
const response = await minioService . uploadFile ( {
const response = await minioService . uploadFile ( {
bucket : image . file . bucket ,
bucket : image . file . bucket ,
@ -65,13 +65,13 @@ class ImageService extends SiteService {
'Content-Length' : file . size ,
'Content-Length' : file . size ,
} ,
} ,
} ) ;
} ) ;
// store the eTag from MinIO in the Image model
// store the eTag from MinIO in the Image model
image . file . etag = response . etag ;
image . file . etag = response . etag ;
// save the Image model to the db
// save the Image model to the db
await image . save ( ) ;
await image . save ( ) ;
this . log . info ( 'processed uploaded image' , { ownerId , imageId , fileKey } ) ;
this . log . info ( 'processed uploaded image' , { ownerId , imageId , fileKey } ) ;
return image . toObject ( ) ;
return image . toObject ( ) ;
} catch ( error ) {
} catch ( error ) {
@ -83,14 +83,14 @@ class ImageService extends SiteService {
}
}
}
}
async getImageById ( imageId ) {
async getImageById ( imageId ) {
const image = await SiteImage
const image = await SiteImage
. findById ( imageId )
. findById ( imageId )
. populate ( this . populateImage ) ;
. populate ( this . populateImage ) ;
return image ;
return image ;
}
}
async getRecentImagesForOwner ( owner ) {
async getRecentImagesForOwner ( owner ) {
const images = await SiteImage
const images = await SiteImage
. find ( { owner : owner . _ id } )
. find ( { owner : owner . _ id } )
. sort ( { created : - 1 } )
. sort ( { created : - 1 } )
@ -100,7 +100,7 @@ class ImageService extends SiteService {
return images ;
return images ;
}
}
async deleteImage ( image ) {
async deleteImage ( image ) {
const { minio : minioService } = this . dtp . services ;
const { minio : minioService } = this . dtp . services ;
this . log . debug ( 'removing image from storage' , { bucket : image . file . bucket , key : image . file . key } ) ;
this . log . debug ( 'removing image from storage' , { bucket : image . file . bucket , key : image . file . key } ) ;
@ -110,13 +110,13 @@ class ImageService extends SiteService {
await SiteImage . deleteOne ( { _ id : image . _ id } ) ;
await SiteImage . deleteOne ( { _ id : image . _ id } ) ;
}
}
async processImageFile ( owner , file , outputs , options ) {
async processImageFile ( owner , file , outputs , options ) {
this . log . debug ( 'processing image file' , { owner , file , outputs } ) ;
this . log . debug ( 'processing image file' , { owner , file , outputs } ) ;
const sharpImage = sharp ( file . path ) ;
const sharpImage = sharp ( file . path ) ;
return this . processImage ( owner , sharpImage , outputs , options ) ;
return this . processImage ( owner , sharpImage , outputs , options ) ;
}
}
async processImage ( owner , sharpImage , outputs , options ) {
async processImage ( owner , sharpImage , outputs , options ) {
const NOW = new Date ( ) ;
const NOW = new Date ( ) ;
const service = this ;
const service = this ;
const { minio : minioService } = this . dtp . services ;
const { minio : minioService } = this . dtp . services ;
@ -128,7 +128,7 @@ class ImageService extends SiteService {
const imageWorkPath = process . env . DTP_IMAGE_WORK_PATH || '/tmp' ;
const imageWorkPath = process . env . DTP_IMAGE_WORK_PATH || '/tmp' ;
const metadata = await sharpImage . metadata ( ) ;
const metadata = await sharpImage . metadata ( ) ;
async function processOutputImage ( output ) {
async function processOutputImage ( output ) {
const outputMetadata = service . makeImageMetadata ( metadata ) ;
const outputMetadata = service . makeImageMetadata ( metadata ) ;
outputMetadata . width = output . width ;
outputMetadata . width = output . width ;
outputMetadata . height = output . height ;
outputMetadata . height = output . height ;
@ -149,7 +149,7 @@ class ImageService extends SiteService {
height : output . height ,
height : output . height ,
options : output . resizeOptions ,
options : output . resizeOptions ,
} )
} )
;
;
chain = chain [ output . format ] ( output . formatParameters ) ;
chain = chain [ output . format ] ( output . formatParameters ) ;
output . filePath = path . join ( imageWorkPath , ` ${ image . _ id } . ${ output . width } x ${ output . height } . ${ output . format } ` ) ;
output . filePath = path . join ( imageWorkPath , ` ${ image . _ id } . ${ output . width } x ${ output . height } . ${ output . format } ` ) ;
@ -165,11 +165,11 @@ class ImageService extends SiteService {
const imageId = image . _ id . toString ( ) ;
const imageId = image . _ id . toString ( ) ;
const ownerId = owner . _ id . toString ( ) ;
const ownerId = owner . _ id . toString ( ) ;
const fileKey = ` / ${ ownerId . slice ( 0 , 3 ) } / ${ ownerId } /images/ ${ imageId . slice ( 0 , 3 ) } / ${ imageId } . ${ output . format } ` ;
const fileKey = ` / ${ ownerId . slice ( 0 , 3 ) } / ${ ownerId } /images/ ${ imageId . slice ( 0 , 3 ) } / ${ imageId } . ${ output . format } ` ;
image . file . bucket = process . env . MINIO_IMAGE_BUCKET ;
image . file . bucket = process . env . MINIO_IMAGE_BUCKET ;
image . file . key = fileKey ;
image . file . key = fileKey ;
image . size = output . stat . size ;
image . size = output . stat . size ;
// upload the image file to MinIO
// upload the image file to MinIO
const response = await minioService . uploadFile ( {
const response = await minioService . uploadFile ( {
bucket : image . file . bucket ,
bucket : image . file . bucket ,
@ -180,13 +180,13 @@ class ImageService extends SiteService {
'Content-Length' : output . stat . size ,
'Content-Length' : output . stat . size ,
} ,
} ,
} ) ;
} ) ;
// store the eTag from MinIO in the Image model
// store the eTag from MinIO in the Image model
image . file . etag = response . etag ;
image . file . etag = response . etag ;
// save the Image model to the db
// save the Image model to the db
await image . save ( ) ;
await image . save ( ) ;
service . log . info ( 'processed uploaded image' , { ownerId , imageId , fileKey } ) ;
service . log . info ( 'processed uploaded image' , { ownerId , imageId , fileKey } ) ;
if ( options . removeWorkFiles ) {
if ( options . removeWorkFiles ) {
@ -216,7 +216,73 @@ class ImageService extends SiteService {
await SiteAsync . each ( outputs , processOutputImage , 4 ) ;
await SiteAsync . each ( outputs , processOutputImage , 4 ) ;
}
}
makeImageMetadata ( metadata ) {
async getSiteIconInfo ( ) {
const siteDomain = this . dtp . config . site . domainKey ;
const siteImagesDir = path . join ( this . dtp . config . root , 'client' , 'img' ) ;
const siteIconDir = path . join ( siteImagesDir , 'icon' , siteDomain ) ;
let icon ;
try {
await fs . promises . access ( siteIconDir ) ;
const iconMetadata = await sharp ( path . join ( siteIconDir , 'icon-512x512.png' ) ) . metadata ( ) ;
icon = {
metadata : iconMetadata ,
path : ` /img/icon/ ${ siteDomain } /icon-512x512.png ` ,
} ;
} catch ( error ) {
icon = null ;
}
return icon ;
}
async updateSiteIcon ( imageDefinition , file ) {
this . log . debug ( 'updating site icon' , { imageDefinition , file } ) ;
try {
const siteDomain = this . dtp . config . site . domainKey ;
const siteImagesDir = path . join ( this . dtp . config . root , 'client' , 'img' ) ;
const siteIconDir = path . join ( siteImagesDir , 'icon' , siteDomain ) ;
const sourceIconFilePath = file . path ;
const sizes = [ 16 , 32 , 36 , 48 , 57 , 60 , 70 , 72 , 76 , 96 , 114 , 120 , 144 , 150 , 152 , 180 , 192 , 256 , 310 , 384 , 512 ] ;
await fs . promises . mkdir ( siteIconDir , { force : true , recursive : true } ) ;
for ( var size of sizes ) {
await sharp ( sourceIconFilePath ) . resize ( {
fit : sharp . fit . contain ,
width : size ,
height : size ,
} ) . png ( )
. toFile ( path . join ( siteIconDir , ` icon- ${ size } x ${ size } .png ` ) ) ;
}
await fs . promises . cp ( sourceIconFilePath , path . join ( siteIconDir , ` ${ siteDomain } .png ` ) ) ;
await fs . promises . cp ( sourceIconFilePath , path . join ( siteImagesDir , 'social-cards' , ` ${ siteDomain } .png ` ) ) ;
return path . join ( siteIconDir , 'icon-512x512.png' ) ;
} catch ( error ) {
this . log . error ( 'failed to update site icon' , { error } ) ;
throw error ;
} finally {
this . log . info ( 'removing uploaded image from local file system' , { file : file . path } ) ;
await fs . promises . rm ( file . path ) ;
}
}
makeImageMetadata ( metadata ) {
return {
return {
format : metadata . format ,
format : metadata . format ,
size : metadata . size ,
size : metadata . size ,