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` */
|
/** Load environment config from `.env` */
|
||||||
await dotenv.load({
|
await dotenv.load({
|
||||||
|
@ -7,6 +7,16 @@ await dotenv.load({
|
||||||
examplePath: null,
|
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. */
|
/** Application-wide configuration. */
|
||||||
const Conf = {
|
const Conf = {
|
||||||
/** Ditto admin secret key in nip19 format. This is the way it's configured by an admin. */
|
/** 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() {
|
get adminEmail() {
|
||||||
return Deno.env.get('ADMIN_EMAIL') || 'webmaster@localhost';
|
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. */
|
/** Domain of the Ditto server as a `URL` object, for easily grabbing the `hostname`, etc. */
|
||||||
get url() {
|
get url() {
|
||||||
return new URL(Conf.localDomain);
|
return new URL(Conf.localDomain);
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { type AppController } from '@/app.ts';
|
||||||
import { type Filter, findReplyTag, z } from '@/deps.ts';
|
import { type Filter, findReplyTag, z } from '@/deps.ts';
|
||||||
import * as mixer from '@/mixer.ts';
|
import * as mixer from '@/mixer.ts';
|
||||||
import { getAuthor, getFollowedPubkeys, getFollows, syncUser } from '@/queries.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 { jsonMetaContentSchema } from '@/schemas/nostr.ts';
|
||||||
import { toAccount, toRelationship, toStatus } from '@/transformers/nostr-to-mastoapi.ts';
|
import { toAccount, toRelationship, toStatus } from '@/transformers/nostr-to-mastoapi.ts';
|
||||||
import { isFollowing, lookupAccount, Time } from '@/utils.ts';
|
import { isFollowing, lookupAccount, Time } from '@/utils.ts';
|
||||||
|
@ -113,8 +113,6 @@ const accountStatusesController: AppController = async (c) => {
|
||||||
return paginated(c, events, statuses);
|
return paginated(c, events, statuses);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fileSchema = z.custom<File>((value) => value instanceof File);
|
|
||||||
|
|
||||||
const updateCredentialsSchema = z.object({
|
const updateCredentialsSchema = z.object({
|
||||||
display_name: z.string().optional(),
|
display_name: z.string().optional(),
|
||||||
note: 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 { 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 { 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 * 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';
|
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 */
|
/** https://github.com/colinhacks/zod/issues/1630#issuecomment-1365983831 */
|
||||||
const booleanParamSchema = z.enum(['true', 'false']).transform((value) => value === 'true');
|
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