-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
47 changed files
with
7,182 additions
and
387 deletions.
There are no files selected for viewing
128 changes: 128 additions & 0 deletions
128
apps/jonogon-core/src/api/http/handlers/jobabs/attachment.mts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import {TContextCreator} from '../../../trpc/context.mjs'; | ||
import sharp from 'sharp'; | ||
import {nanoid} from 'nanoid'; | ||
import {logger} from '../../../../logger.mjs'; | ||
import {Request, Response} from 'express'; | ||
import {z} from 'zod'; | ||
import { requireAuth, requireModeratorOrAdmin } from '../../../utility/auth-utils.js'; | ||
|
||
export function createJobabAttachmentHandler(createContext: TContextCreator) { | ||
return async (req: Request, res: Response) => { | ||
try { | ||
const ctx = await createContext({req, res}); | ||
|
||
const queryParams = z | ||
.object({ | ||
filename: z.string(), | ||
type: z.enum(['image', 'file']), | ||
}) | ||
.safeParse(req.query); | ||
|
||
if (!queryParams.success) { | ||
return res.status(400).json({ | ||
message: 'query params were not sent correctly', | ||
error: queryParams.error, | ||
}); | ||
} | ||
|
||
if (!req.body) { | ||
return res.status(400).json({ | ||
message: 'file where?', | ||
}); | ||
} | ||
|
||
// check if the user is logged in | ||
requireAuth(ctx, res, 'must be logged in to upload attachments'); | ||
|
||
// Check if user is moderator or admin | ||
requireModeratorOrAdmin(ctx, res, 'only moderators can add attachments to jobabs'); | ||
|
||
const jobab = await ctx.services.postgresQueryBuilder | ||
.selectFrom('jobabs') | ||
.selectAll() | ||
.where('id', '=', req.params.id) | ||
.where('deleted_at', 'is', null) | ||
.executeTakeFirst(); | ||
|
||
if (!jobab) { | ||
return res.status(404).json({ | ||
message: 'jobab not found', | ||
}); | ||
} | ||
|
||
const fileName = nanoid(); | ||
|
||
if (queryParams.data.type === 'image') { | ||
const imageKey = `jobab_attachment_${fileName}.jpg`; | ||
const thumbnailKey = `jobab_attachment_thumbnail_${fileName}.jpg`; | ||
|
||
const imageStream = await sharp(req.body) | ||
.rotate() | ||
.resize(1960) | ||
.jpeg() | ||
.toBuffer(); | ||
|
||
const thumbnailStream = await sharp(req.body) | ||
.rotate() | ||
.resize(512) | ||
.jpeg() | ||
.toBuffer(); | ||
|
||
await Promise.all([ | ||
ctx.services.fileStorage.storeFile(imageKey, imageStream), | ||
ctx.services.fileStorage.storeFile( | ||
thumbnailKey, | ||
thumbnailStream, | ||
), | ||
]); | ||
|
||
const result = await ctx.services.postgresQueryBuilder | ||
.insertInto('jobab_attachments') | ||
.values({ | ||
jobab_id: req.params.id, | ||
filename: queryParams.data.filename, | ||
attachment: imageKey, | ||
}) | ||
.returning(['id']) | ||
.executeTakeFirst(); | ||
|
||
return res.json({ | ||
message: 'image-added', | ||
data: { | ||
attachment_id: result?.id, | ||
url: await ctx.services.fileStorage.getFileURL(imageKey), | ||
}, | ||
}); | ||
} else { | ||
const ext = queryParams.data.filename.split('.').pop(); | ||
const fileKey = `jobab_attachment_${fileName}.${ext}`; | ||
|
||
await ctx.services.fileStorage.storeFile(fileKey, req.body); | ||
|
||
const result = await ctx.services.postgresQueryBuilder | ||
.insertInto('jobab_attachments') | ||
.values({ | ||
jobab_id: req.params.id, | ||
filename: queryParams.data.filename, | ||
attachment: fileKey, | ||
}) | ||
.returning(['id']) | ||
.executeTakeFirst(); | ||
|
||
return res.json({ | ||
message: 'attachment-added', | ||
data: { | ||
attachment_id: result?.id, | ||
url: await ctx.services.fileStorage.getFileURL(fileKey), | ||
}, | ||
}); | ||
} | ||
} catch (error) { | ||
logger.error('error uploading jobab attachment', {error}); | ||
|
||
res.status(500).json({ | ||
message: 'INTERNAL SERVER ERROR', | ||
}); | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
apps/jonogon-core/src/api/http/handlers/respondents/image.mts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import {TContextCreator} from '../../../trpc/context.mjs'; | ||
import sharp from 'sharp'; | ||
import {nanoid} from 'nanoid'; | ||
import {logger} from '../../../../logger.mjs'; | ||
import {Request, Response} from 'express'; | ||
import {z} from 'zod'; | ||
import {requireModeratorOrAdmin} from '../../../utility/auth-utils.js'; | ||
|
||
const querySchema = z.object({ | ||
type: z.enum(['organization', 'expert']), | ||
}); | ||
|
||
export function createRespondentImageHandler(createContext: TContextCreator) { | ||
return async (req: Request, res: Response) => { | ||
try { | ||
const ctx = await createContext({req, res}); | ||
|
||
const queryParams = querySchema.safeParse(req.query); | ||
|
||
if (!queryParams.success) { | ||
return res.status(400).json({ | ||
message: 'query params were not sent correctly', | ||
error: queryParams.error, | ||
}); | ||
} | ||
|
||
if (!req.body) { | ||
return res.status(400).json({ | ||
message: 'file where?', | ||
}); | ||
} | ||
|
||
// Check if user is moderator or admin | ||
requireModeratorOrAdmin( | ||
ctx, | ||
res, | ||
'only moderators can add images to respondents', | ||
); | ||
|
||
const fileName = nanoid(); | ||
const imageKey = `respondent_${queryParams.data.type}_${fileName}.jpg`; | ||
const thumbnailKey = `respondent_${queryParams.data.type}_thumbnail_${fileName}.jpg`; | ||
|
||
// Process image in two sizes like petition attachments | ||
const imageStream = await sharp(req.body) | ||
.rotate() | ||
.resize(1024, 1024, { | ||
fit: 'cover', | ||
position: 'attention', | ||
}) | ||
.jpeg() | ||
.toBuffer(); | ||
|
||
const thumbnailStream = await sharp(req.body) | ||
.rotate() | ||
.resize(512, 512, { | ||
fit: 'cover', | ||
position: 'attention', | ||
}) | ||
.jpeg() | ||
.toBuffer(); | ||
|
||
// Store both sizes | ||
await Promise.all([ | ||
ctx.services.fileStorage.storeFile(imageKey, imageStream), | ||
ctx.services.fileStorage.storeFile( | ||
thumbnailKey, | ||
thumbnailStream, | ||
), | ||
]); | ||
|
||
return res.json({ | ||
message: 'image-uploaded', | ||
data: { | ||
imageKey, | ||
thumbnail_url: | ||
await ctx.services.fileStorage.getFileURL(thumbnailKey), | ||
image_url: | ||
await ctx.services.fileStorage.getFileURL(imageKey), | ||
}, | ||
}); | ||
} catch (error) { | ||
logger.error('error uploading respondent image', error); | ||
res.status(500).json({ | ||
message: 'INTERNAL SERVER ERROR', | ||
}); | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.