Add a mediaController for s3 uploads
This commit is contained in:
parent
4b4bfd48c7
commit
3fc60c78d2
|
@ -1,4 +1,4 @@
|
|||
import { dotenv, getPublicKey, nip19, secp } from '@/deps.ts';
|
||||
import { dotenv, getPublicKey, nip19, secp, z } from '@/deps.ts';
|
||||
|
||||
/** Load environment config from `.env` */
|
||||
await dotenv.load({
|
||||
|
@ -7,6 +7,16 @@ await dotenv.load({
|
|||
examplePath: null,
|
||||
});
|
||||
|
||||
const optionalBooleanSchema = z
|
||||
.enum(['true', 'false'])
|
||||
.optional()
|
||||
.transform((value) => value !== undefined ? value === 'true' : undefined);
|
||||
|
||||
const optionalNumberSchema = z
|
||||
.string()
|
||||
.optional()
|
||||
.transform((value) => value !== undefined ? Number(value) : undefined);
|
||||
|
||||
/** Application-wide configuration. */
|
||||
const Conf = {
|
||||
/** Ditto admin secret key in nip19 format. This is the way it's configured by an admin. */
|
||||
|
@ -58,6 +68,36 @@ const Conf = {
|
|||
get adminEmail() {
|
||||
return Deno.env.get('ADMIN_EMAIL') || 'webmaster@localhost';
|
||||
},
|
||||
/** S3 media storage configuration. */
|
||||
s3: {
|
||||
get endPoint() {
|
||||
return Deno.env.get('S3_ENDPOINT')!;
|
||||
},
|
||||
get region() {
|
||||
return Deno.env.get('S3_REGION')!;
|
||||
},
|
||||
get accessKey() {
|
||||
return Deno.env.get('S3_ACCESS_KEY');
|
||||
},
|
||||
get secretKey() {
|
||||
return Deno.env.get('S3_SECRET_KEY');
|
||||
},
|
||||
get bucket() {
|
||||
return Deno.env.get('S3_BUCKET');
|
||||
},
|
||||
get pathStyle() {
|
||||
return optionalBooleanSchema.parse(Deno.env.get('S3_PATH_STYLE'));
|
||||
},
|
||||
get port() {
|
||||
return optionalNumberSchema.parse(Deno.env.get('S3_PORT'));
|
||||
},
|
||||
get sessionToken() {
|
||||
return Deno.env.get('S3_SESSION_TOKEN');
|
||||
},
|
||||
get useSSL() {
|
||||
return optionalBooleanSchema.parse(Deno.env.get('S3_USE_SSL'));
|
||||
},
|
||||
},
|
||||
/** Domain of the Ditto server as a `URL` object, for easily grabbing the `hostname`, etc. */
|
||||
get url() {
|
||||
return new URL(Conf.localDomain);
|
||||
|
|
|
@ -2,7 +2,7 @@ import { type AppController } from '@/app.ts';
|
|||
import { type Filter, findReplyTag, z } from '@/deps.ts';
|
||||
import * as mixer from '@/mixer.ts';
|
||||
import { getAuthor, getFollowedPubkeys, getFollows, syncUser } from '@/queries.ts';
|
||||
import { booleanParamSchema } from '@/schema.ts';
|
||||
import { booleanParamSchema, fileSchema } from '@/schema.ts';
|
||||
import { jsonMetaContentSchema } from '@/schemas/nostr.ts';
|
||||
import { toAccount, toRelationship, toStatus } from '@/transformers/nostr-to-mastoapi.ts';
|
||||
import { isFollowing, lookupAccount, Time } from '@/utils.ts';
|
||||
|
@ -113,8 +113,6 @@ const accountStatusesController: AppController = async (c) => {
|
|||
return paginated(c, events, statuses);
|
||||
};
|
||||
|
||||
const fileSchema = z.custom<File>((value) => value instanceof File);
|
||||
|
||||
const updateCredentialsSchema = z.object({
|
||||
display_name: z.string().optional(),
|
||||
note: z.string().optional(),
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import { AppController } from '@/app.ts';
|
||||
import { Conf } from '@/config.ts';
|
||||
import { S3Client, z } from '@/deps.ts';
|
||||
import { fileSchema } from '@/schema.ts';
|
||||
import { parseBody } from '@/utils/web.ts';
|
||||
|
||||
const s3 = new S3Client({ ...Conf.s3 });
|
||||
|
||||
const mediaBodySchema = z.object({
|
||||
file: fileSchema.refine((file) => !!file.type),
|
||||
thumbnail: fileSchema.optional(),
|
||||
description: z.string().optional(),
|
||||
focus: z.string().optional(),
|
||||
});
|
||||
|
||||
const mediaController: AppController = async (c) => {
|
||||
const result = mediaBodySchema.safeParse(await parseBody(c.req.raw));
|
||||
|
||||
if (!result.success) {
|
||||
return c.json({ error: 'Bad request.', schema: result.error }, 422);
|
||||
}
|
||||
|
||||
const { file } = result.data;
|
||||
|
||||
try {
|
||||
await s3.putObject('test', file.stream());
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return c.json({ error: 'Failed to upload file.' }, 500);
|
||||
}
|
||||
|
||||
return c.json({});
|
||||
};
|
||||
|
||||
export { mediaController };
|
|
@ -66,5 +66,6 @@ export {
|
|||
export { DenoSqliteDialect } from 'https://gitlab.com/soapbox-pub/kysely-deno-sqlite/-/raw/v1.0.1/mod.ts';
|
||||
export { default as tldts } from 'npm:tldts@^6.0.14';
|
||||
export * as cron from 'https://deno.land/x/deno_cron@v1.0.0/cron.ts';
|
||||
export { S3Client } from 'https://deno.land/x/s3_lite_client@0.6.1/mod.ts';
|
||||
|
||||
export type * as TypeFest from 'npm:type-fest@^4.3.0';
|
||||
|
|
|
@ -48,4 +48,16 @@ const safeUrlSchema = z.string().max(2048).url();
|
|||
/** https://github.com/colinhacks/zod/issues/1630#issuecomment-1365983831 */
|
||||
const booleanParamSchema = z.enum(['true', 'false']).transform((value) => value === 'true');
|
||||
|
||||
export { booleanParamSchema, decode64Schema, emojiTagSchema, filteredArray, hashtagSchema, jsonSchema, safeUrlSchema };
|
||||
/** Schema for `File` objects. */
|
||||
const fileSchema = z.custom<File>((value) => value instanceof File);
|
||||
|
||||
export {
|
||||
booleanParamSchema,
|
||||
decode64Schema,
|
||||
emojiTagSchema,
|
||||
fileSchema,
|
||||
filteredArray,
|
||||
hashtagSchema,
|
||||
jsonSchema,
|
||||
safeUrlSchema,
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue