mirror of https://github.com/sylv/micro.git
build: smaller docker images
This commit is contained in:
parent
5b325e595c
commit
7e50fb043b
|
@ -1,24 +1,19 @@
|
|||
*
|
||||
.env
|
||||
*.env
|
||||
.microrc.yaml
|
||||
.secrets
|
||||
|
||||
!example
|
||||
!packages
|
||||
Dockerfile
|
||||
node_modules
|
||||
example
|
||||
data
|
||||
.vscode
|
||||
.github
|
||||
*.md
|
||||
|
||||
!LICENSE
|
||||
|
||||
!package.json
|
||||
!tsconfig.json
|
||||
!tsconfig.server.json
|
||||
!tailwind.config.js
|
||||
!next-env.d.ts
|
||||
!next.config.js
|
||||
!postcss.config.js
|
||||
!pnpm-lock.yaml
|
||||
!pnpm-workspace.yaml
|
||||
!tsup.config.ts
|
||||
!wrapper.sh
|
||||
!turbo.json
|
||||
|
||||
packages/api/data
|
||||
packages/api/dist
|
||||
packages/web/.next
|
||||
packages/*/.turbo
|
||||
packages/*/.turbo
|
||||
packages/*/data
|
||||
packages/*/node_modules
|
||||
packages/*/dist
|
||||
packages/*/.next
|
||||
packages/*/.swc
|
|
@ -1,12 +0,0 @@
|
|||
module.exports = {
|
||||
hooks: {
|
||||
readPackage: (pkg, context) => {
|
||||
if (pkg.name === "@mikro-orm/cli") {
|
||||
delete pkg.dependencies["@mikro-orm/core"];
|
||||
pkg.peerDependencies["@mikro-orm/core"] = pkg.peerDependencies["@mikro-orm/postgresql"];
|
||||
}
|
||||
|
||||
return pkg;
|
||||
},
|
||||
},
|
||||
};
|
66
Dockerfile
66
Dockerfile
|
@ -1,32 +1,56 @@
|
|||
FROM node:16-slim AS builder
|
||||
RUN npm i -g pnpm@7 tsup
|
||||
ENV NODE_ENV=development
|
||||
FROM node:16-alpine AS deps
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
RUN apk add --no-cache libc6-compat
|
||||
RUN npm i -g pnpm
|
||||
|
||||
# install development dependencies
|
||||
WORKDIR /usr/src/micro
|
||||
RUN apt update && apt install -y ffmpeg git
|
||||
|
||||
# copy package.jsons and install dependencies
|
||||
# doing this before copying everything helps with caching
|
||||
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json .
|
||||
COPY packages/web/package.json packages/web/package.json
|
||||
COPY packages/thumbnail-generator/package.json packages/thumbnail-generator/package.json
|
||||
COPY packages/api/package.json packages/api/package.json
|
||||
RUN pnpm install --frozen-lockfile
|
||||
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
||||
pnpm fetch
|
||||
|
||||
# copy sources and build app
|
||||
|
||||
|
||||
|
||||
FROM node:16-alpine AS builder
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
WORKDIR /usr/src/micro
|
||||
|
||||
RUN apk add --no-cache git
|
||||
RUN npm i -g pnpm
|
||||
|
||||
COPY --from=deps /usr/src/micro .
|
||||
COPY . .
|
||||
|
||||
RUN pnpm install --offline --frozen-lockfile
|
||||
RUN pnpm build
|
||||
|
||||
# prune unused packages
|
||||
# RUN pnpm prune --prod
|
||||
|
||||
|
||||
# run as the "node" user https://github.com/nodejs/docker-node/blob/master/docs/BestPractices.md#non-root-user
|
||||
|
||||
FROM node:16-alpine AS runner
|
||||
ENV NEXT_TELEMETRY_DISABLED 1
|
||||
ENV NODE_ENV production
|
||||
|
||||
WORKDIR /usr/src/micro
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
# copy file dependencies
|
||||
COPY --from=builder /usr/src/micro/packages/web/public ./packages/web/public
|
||||
COPY --from=builder /usr/src/micro/packages/web/next.config.js ./packages/web/next.config.js
|
||||
|
||||
# copy web server
|
||||
COPY --from=builder --chown=nextjs:nodejs /usr/src/micro/packages/web/.next/standalone/ ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /usr/src/micro/packages/web/.next/static ./packages/web/.next/static/
|
||||
|
||||
# copy api
|
||||
COPY --from=builder --chown=nextjs:nodejs /usr/src/micro/packages/api/dist ./packages/api/dist
|
||||
COPY --from=builder --chown=nextjs:nodejs /usr/src/micro/packages/api/dist ./packages/api/dist
|
||||
|
||||
COPY wrapper.sh .
|
||||
RUN chmod +x ./wrapper.sh
|
||||
RUN chown node:node packages/api/src/schema.gql
|
||||
USER node
|
||||
ENV NODE_ENV=production
|
||||
|
||||
# define how we want to start the app
|
||||
ENTRYPOINT ["./wrapper.sh"]
|
|
@ -17,6 +17,6 @@
|
|||
"clean": "rm -rf ./packages/*/{tsconfig.tsbuildinfo,lib,dist,yarn-error.log,.next}"
|
||||
},
|
||||
"devDependencies": {
|
||||
"turbo": "^1.3.0"
|
||||
"turbo": "1.3.2-canary.0"
|
||||
}
|
||||
}
|
|
@ -10,9 +10,8 @@
|
|||
},
|
||||
"scripts": {
|
||||
"watch": "tsup src/main.ts src/migrations/* --watch --onSuccess \"node dist/main.js\" --target node16",
|
||||
"build": "rm -rf ./dist && tsup src/main.ts src/migrations/* --dts --sourcemap --target node16",
|
||||
"build": "rm -rf ./dist && ncc build src/main.ts -o dist --minify --transpile-only --v8-cache --no-source-map-register",
|
||||
"lint": "eslint src --fix --cache",
|
||||
"start": "node ./dist/main.js",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -20,10 +19,10 @@
|
|||
"@fastify/helmet": "^8.0.0",
|
||||
"@fastify/multipart": "^6.0.0",
|
||||
"@jenyus-org/nestjs-graphql-utils": "^1.6.4",
|
||||
"@mikro-orm/core": "^5.2.0",
|
||||
"@mikro-orm/migrations": "^5.2.0",
|
||||
"@mikro-orm/core": "^5.2.2",
|
||||
"@mikro-orm/migrations": "^5.2.2",
|
||||
"@mikro-orm/nestjs": "^5.0.2",
|
||||
"@mikro-orm/postgresql": "^5.2.0",
|
||||
"@mikro-orm/postgresql": "^5.2.2",
|
||||
"@nestjs/common": "^8.4.4",
|
||||
"@nestjs/core": "^8.4.4",
|
||||
"@nestjs/graphql": "^10.0.16",
|
||||
|
@ -52,9 +51,9 @@
|
|||
"mime-types": "^2.1.35",
|
||||
"ms": "^3.0.0-canary.1",
|
||||
"nanoid": "^3.3.4",
|
||||
"nodemailer": "^6.7.5",
|
||||
"nodemailer": "^6.7.6",
|
||||
"normalize-url": "^6",
|
||||
"passport": "^0.5.2",
|
||||
"passport": "^0.6.0",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
|
@ -64,22 +63,23 @@
|
|||
"xbytes": "^1.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@mikro-orm/cli": "^5.2.0",
|
||||
"@swc/core": "^1.2.206",
|
||||
"@mikro-orm/cli": "^5.2.2",
|
||||
"@swc/core": "^1.2.208",
|
||||
"@sylo-digital/scripts": "^1.0.2",
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/dedent": "^0.7.0",
|
||||
"@types/jest": "^28.1.3",
|
||||
"@types/jest": "^28.1.4",
|
||||
"@types/luxon": "^2.3.2",
|
||||
"@types/mime-types": "^2.1.1",
|
||||
"@types/node": "16",
|
||||
"@types/passport-jwt": "^3.0.6",
|
||||
"@types/passport-local": "^1.0.34",
|
||||
"@types/sharp": "^0.30.2",
|
||||
"jest": "^28.1.0",
|
||||
"@vercel/ncc": "^0.34.0",
|
||||
"jest": "^28.1.2",
|
||||
"pretty-bytes": "^6.0.0",
|
||||
"ts-node": "^10.7.0",
|
||||
"tsup": "^6.1.2",
|
||||
"ts-node": "^10.8.2",
|
||||
"tsup": "^6.1.3",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"mikro-orm": {
|
||||
|
|
|
@ -18,13 +18,14 @@ export const migrate = async (
|
|||
return;
|
||||
}
|
||||
|
||||
const migrations = await migrator.getPendingMigrations();
|
||||
if (!migrations[0]) {
|
||||
ormLogger.debug(`No pending migrations`);
|
||||
const executedMigrations = await migrator.getExecutedMigrations();
|
||||
const pendingMigrations = await migrator.getPendingMigrations();
|
||||
if (!pendingMigrations[0]) {
|
||||
ormLogger.debug(`No pending migrations, ${executedMigrations.length} already executed`);
|
||||
return;
|
||||
}
|
||||
|
||||
ormLogger.log(`Migrating through ${migrations.length} migrations`);
|
||||
ormLogger.log(`Migrating through ${pendingMigrations.length} migrations`);
|
||||
await em.transactional(async (em) => {
|
||||
if (!skipLock) await em.execute(`LOCK TABLE ${migrationsTableName} IN EXCLUSIVE MODE`);
|
||||
await migrator.up({ transaction: em.getTransactionContext() });
|
||||
|
|
|
@ -15,7 +15,7 @@ import { checkThumbnailSupport } from '@ryanke/thumbnail-generator';
|
|||
import { Exclude } from 'class-transformer';
|
||||
import mimeType from 'mime-types';
|
||||
import { generateDeleteKey } from '../../helpers/generate-delete-key.helper';
|
||||
import { ResourceBase } from '../../types';
|
||||
import { Resource } from '../../helpers/resource.entity-base';
|
||||
import { Paginated } from '../../types/paginated.type';
|
||||
import { Thumbnail } from '../thumbnail/thumbnail.entity';
|
||||
import { User } from '../user/user.entity';
|
||||
|
@ -23,7 +23,7 @@ import { FileMetadata } from './file-metadata.embeddable';
|
|||
|
||||
@Entity({ tableName: 'files' })
|
||||
@ObjectType()
|
||||
export class File extends ResourceBase {
|
||||
export class File extends Resource {
|
||||
@PrimaryKey()
|
||||
@Field(() => ID)
|
||||
id: string;
|
||||
|
|
|
@ -2,12 +2,12 @@ import { Entity, IdentifiedReference, ManyToOne, OptionalProps, PrimaryKey, Prop
|
|||
import { Field, ID, ObjectType } from '@nestjs/graphql';
|
||||
import { Exclude } from 'class-transformer';
|
||||
import { generateContentId } from '../../helpers/generate-content-id.helper';
|
||||
import { ResourceBase } from '../../types';
|
||||
import { User } from '../user/user.entity';
|
||||
import { Resource } from '../../helpers/resource.entity-base';
|
||||
|
||||
@Entity({ tableName: 'links' })
|
||||
@ObjectType()
|
||||
export class Link extends ResourceBase {
|
||||
export class Link extends Resource {
|
||||
@PrimaryKey()
|
||||
@Field(() => ID)
|
||||
id: string = generateContentId();
|
||||
|
|
|
@ -5,13 +5,13 @@ import { IsBoolean, IsNumber, IsOptional, IsString, Length } from 'class-validat
|
|||
import mime from 'mime-types';
|
||||
import { config } from '../../config';
|
||||
import { generateContentId } from '../../helpers/generate-content-id.helper';
|
||||
import { ResourceBase } from '../../types';
|
||||
import { Resource } from '../../helpers/resource.entity-base';
|
||||
import { Paginated } from '../../types/paginated.type';
|
||||
import { User } from '../user/user.entity';
|
||||
|
||||
@Entity({ tableName: 'pastes' })
|
||||
@ObjectType({ isAbstract: true })
|
||||
export class Paste extends ResourceBase {
|
||||
export class Paste extends Resource {
|
||||
@PrimaryKey()
|
||||
@Field(() => ID)
|
||||
id: string = generateContentId();
|
||||
|
|
|
@ -6,12 +6,11 @@ import { Args, Mutation, Parent, Query, ResolveField, Resolver } from '@nestjs/g
|
|||
import ms from 'ms';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { paginate, parseCursor } from '../../helpers/pagination';
|
||||
import { File } from '../../types';
|
||||
import { UserId } from '../auth/auth.decorators';
|
||||
import { AuthService, TokenType } from '../auth/auth.service';
|
||||
import { JWTAuthGuard } from '../auth/guards/jwt.guard';
|
||||
import type { JWTPayloadUser } from '../auth/strategies/jwt.strategy';
|
||||
import { FilePage } from '../file/file.entity';
|
||||
import { File, FilePage } from '../file/file.entity';
|
||||
import { InviteService } from '../invite/invite.service';
|
||||
import { Paste, PastePage } from '../paste/paste.entity';
|
||||
import { CreateUserDto } from './dto/create-user.dto';
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
import { EntityRepository, QueryOrder, UniqueConstraintViolationException } from '@mikro-orm/core';
|
||||
import { InjectRepository } from '@mikro-orm/nestjs';
|
||||
import {
|
||||
BadRequestException,
|
||||
ConflictException,
|
||||
ForbiddenException,
|
||||
Injectable
|
||||
} from '@nestjs/common';
|
||||
import { BadRequestException, ConflictException, ForbiddenException, Injectable } from '@nestjs/common';
|
||||
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { readFileSync } from 'fs';
|
||||
import dedent from 'dedent';
|
||||
import { compile } from 'handlebars';
|
||||
import ms from 'ms';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
@ -25,12 +20,18 @@ import type { Pagination } from './dto/pagination.dto';
|
|||
import { UserVerification } from './user-verification.entity';
|
||||
import { User } from './user.entity';
|
||||
|
||||
const EMAIL_TEMPLATE_SOURCE = dedent`
|
||||
<body>
|
||||
<h1>Verify Your Email</h1>
|
||||
<p>Thanks for signing up to micro. Click the link below to verify your email and activate your account.</p>
|
||||
<a href="{{verifyUrl}}">{{verifyUrl}}</a>
|
||||
<p><i>If you did not sign up for micro, ignore this email.</i></p>
|
||||
`;
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
private static readonly VERIFICATION_EXPIRY = ms('6 hours');
|
||||
private static readonly EMAIL_TEMPLATE = compile<{ verifyUrl: string }>(
|
||||
readFileSync('templates/verify.handlebars', 'utf8')
|
||||
);
|
||||
private static readonly EMAIL_TEMPLATE = compile<{ verifyUrl: string }>(EMAIL_TEMPLATE_SOURCE);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(User) private readonly userRepo: EntityRepository<User>,
|
||||
|
@ -42,7 +43,7 @@ export class UserService {
|
|||
|
||||
async getUser(id: string, verified: boolean) {
|
||||
const user = await this.userRepo.findOneOrFail(id);
|
||||
if ( verified && config.email && !user.verifiedEmail) {
|
||||
if (verified && config.email && !user.verifiedEmail) {
|
||||
throw new ForbiddenException('You must verify your email first.');
|
||||
}
|
||||
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
import type { AppController } from './modules/app.controller';
|
||||
import type { File } from './modules/file/file.entity';
|
||||
import type { InviteController } from './modules/invite/invite.controller';
|
||||
import type { CreatePasteDto, Paste } from './modules/paste/paste.entity';
|
||||
import type { UserController } from './modules/user/user.controller';
|
||||
|
||||
export type Await<T> = T extends {
|
||||
then: (onfulfilled?: (value: infer U) => unknown) => unknown;
|
||||
}
|
||||
? U
|
||||
: T;
|
||||
|
||||
// invite
|
||||
export type GetInviteData = Await<ReturnType<InviteController['getInvite']>>;
|
||||
|
||||
// user
|
||||
export type GetUserData = Await<ReturnType<UserController['getUser']>>;
|
||||
export type GetUserFilesData = Await<ReturnType<UserController['getUserFiles']>>;
|
||||
export type GetUserPastesData = Await<ReturnType<UserController['getUserPastes']>>;
|
||||
export type GetUploadTokenData = Await<ReturnType<UserController['getUserToken']>>;
|
||||
export type PutUploadTokenData = Await<ReturnType<UserController['resetUserToken']>>;
|
||||
|
||||
// file
|
||||
export type GetFileData = File;
|
||||
|
||||
// app
|
||||
export type GetServerConfigData = Await<ReturnType<AppController['getConfig']>>;
|
||||
|
||||
export type GetPasteData = Paste;
|
||||
export type CreatePasteBody = CreatePasteDto;
|
||||
|
||||
export { Resource as ResourceBase } from './helpers/resource.entity-base';
|
||||
export type { ResourcePaths } from './helpers/resource.entity-base';
|
||||
export { File } from './modules/file/file.entity';
|
||||
export { User } from './modules/user/user.entity';
|
|
@ -1,5 +0,0 @@
|
|||
<body>
|
||||
<h1>Verify Your Email</h1>
|
||||
<p>Thanks for signing up to micro. Click the link below to verify your email and activate your account.</p>
|
||||
<a href="{{verifyUrl}}">{{verifyUrl}}</a>
|
||||
<p><i>If you did not sign up for micro, ignore this email.</i></p>
|
|
@ -21,13 +21,13 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/fluent-ffmpeg": "^2.1.20",
|
||||
"@types/jest": "^28.1.3",
|
||||
"@types/jest": "^28.1.4",
|
||||
"@types/mime-types": "^2.1.1",
|
||||
"@types/node": "^16.11.21",
|
||||
"@types/sharp": "^0.30.2",
|
||||
"eslint": "^8.16.0",
|
||||
"jest": "^28.1.0",
|
||||
"tsup": "^6.1.2",
|
||||
"eslint": "^8.19.0",
|
||||
"jest": "^28.1.2",
|
||||
"tsup": "^6.1.3",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"jest": {
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
output: 'standalone',
|
||||
experimental: {
|
||||
outputFileTracingRoot: path.join(__dirname, '../../'),
|
||||
},
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
},
|
||||
"scripts": {
|
||||
"watch": "NODE_ENV=development concurrently \"next dev\" \"pnpm generate --watch\"",
|
||||
"start": "NODE_ENV=production next start",
|
||||
"build": "NODE_ENV=production next build",
|
||||
"lint": "NODE_ENV=production next lint",
|
||||
"generate": "graphql-codegen --config codegen.yml"
|
||||
|
@ -28,9 +27,9 @@
|
|||
"graphql": "^16.5.0",
|
||||
"http-status-codes": "^2.2.0",
|
||||
"nanoid": "^3.3.4",
|
||||
"next": "12.1.6",
|
||||
"next": "12.2.0",
|
||||
"postcss": "^8.4.13",
|
||||
"prism-react-renderer": "^1.3.1",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"react-feather": "^2.0.9",
|
||||
|
@ -39,20 +38,20 @@
|
|||
"remark-gfm": "^3.0.1",
|
||||
"swr": "^1.3.0",
|
||||
"tailwindcss": "^3.0.24",
|
||||
"deepmerge": "^4.2.2",
|
||||
"concurrently": "^7.2.2",
|
||||
"lodash": "^4.17.21",
|
||||
"yup": "^0.32.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "^2.6.2",
|
||||
"@graphql-codegen/typescript": "2.5.1",
|
||||
"@graphql-codegen/typescript-operations": "2.4.2",
|
||||
"@graphql-codegen/typescript-react-apollo": "3.2.16",
|
||||
"@graphql-codegen/cli": "^2.7.0",
|
||||
"@graphql-codegen/typescript": "2.6.0",
|
||||
"@graphql-codegen/typescript-operations": "2.4.3",
|
||||
"@graphql-codegen/typescript-react-apollo": "3.2.17",
|
||||
"@sylo-digital/scripts": "^1.0.2",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/node": "16",
|
||||
"@types/react": "^18.0.8",
|
||||
"concurrently": "^7.2.2",
|
||||
"deepmerge": "^4.2.2",
|
||||
"lodash": "^4.17.21",
|
||||
"typescript": "^4.7.4"
|
||||
}
|
||||
}
|
2349
pnpm-lock.yaml
2349
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -7,7 +7,7 @@
|
|||
],
|
||||
"outputs": [
|
||||
"dist/**",
|
||||
".next/**"
|
||||
".next/{server,cache,src,static}"
|
||||
]
|
||||
},
|
||||
"lint": {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
|
||||
cd packages/api && npm run start &
|
||||
cd packages/web && npm run start &
|
||||
cd packages/api && node ./dist/index.js &
|
||||
cd packages/web && node ./server.js &
|
||||
|
||||
wait -n
|
||||
exit $?
|
Loading…
Reference in New Issue