Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ node_modules
*.log*

.DS_Store
dist
dist
.tap
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"dev": "ts-node-dev --respawn --transpile-only src/index.ts | pino-pretty",
"lint": "eslint . --quiet && prettier --write .",
"lint:fix": "eslint . --quiet --fix && prettier --write .",
"prepare": "husky || true"
"prepare": "husky || true",
"test": "tap --node-arg=--require=ts-node-dev"
},
"husky": {
"hooks": {
Expand All @@ -26,6 +27,7 @@
},
"dependencies": {
"@aws-sdk/client-s3": "^3.521.0",
"@faker-js/faker": "^8.4.1",
"@fastify/cookie": "^9.3.1",
"@fastify/cors": "^9.0.1",
"@fastify/jwt": "^8.0.0",
Expand All @@ -41,7 +43,9 @@
"pg": "^8.11.3",
"pino-pretty": "^10.3.1",
"reflect-metadata": "^0.2.1",
"sinon": "^17.0.1",
"tap": "^18.7.0",
"ts-mock-imports": "^1.3.8",
"typeorm": "^0.3.20"
},
"devDependencies": {
Expand All @@ -56,6 +60,7 @@
"lint-staged": "^15.2.2",
"prettier": "^3.2.5",
"ts-node-dev": "^2.0.0",
"typescript": "^5.3.3"
"typescript": "^5.3.3",
"vitest": "^1.4.0"
}
}
24 changes: 24 additions & 0 deletions src/__test__/server.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { FastifyInstance } from 'fastify'
import tap from 'tap'
import { app as server } from '..'
import { buildApp } from '../app';

let app: FastifyInstance;

tap.beforeEach(async () => {
app = await buildApp()
})

tap.afterEach(async () => {
await app.close()
})

tap.test('GET /health', async (t) => {
const response = await app.inject({
method: 'GET',
url: '/health'
})

t.equal(response.statusCode, 200, "/health returns 200")
t.same(response.json(), { status: "ok" }, "/health returns { status: 'ok' }")
})
149 changes: 149 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { S3Client } from "@aws-sdk/client-s3"
import { fastifyCookie, type FastifyCookieOptions } from "@fastify/cookie"
import fastifyCors from "@fastify/cors"
import fastifyJwt from "@fastify/jwt"
import multipart from "@fastify/multipart"
import swagger from "@fastify/swagger"
import swaggerUI from "@fastify/swagger-ui"
import fastify from "fastify"
import nodemailer, { type SentMessageInfo } from "nodemailer"
import type { DataSource } from "typeorm"
import authRoutes from "./routes/v1/Auth/routes"
import { characterRoutes } from "./routes/v1/Characters/routes"
import profileRoutes from "./routes/v1/Profile/routes"
import verifyToken from "./utils/auth"
import connectDatabase from "./utils/database"
import { checkModAbovePermissions } from "./utils/permission"
import artRoutes from "./routes/v1/Art/routes"
import * as dotenv from "dotenv"

declare module "fastify" {
interface FastifyInstance {
db: DataSource
auth: any
permissionAboveMod: any
mailer: nodemailer.Transporter<SentMessageInfo>
s3: S3Client
}

interface UserRequest extends FastifyRequest {
user: {
id: string
profileId: string
}
}
}

export const buildApp = async () => {

dotenv.config()


// Initalize Database and Fastify
const connection = await connectDatabase()
const server = fastify({ logger: {
level: 'info',
transport: {
target: 'pino-pretty'
}
} })

// S3
const s3 = new S3Client({
endpoint: process.env.S3_ENDPOINT as string,
region: process.env.AWS_DEFAULT_REGION as string,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID as string,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string
},
forcePathStyle: true
})

server.decorate("s3", s3)

// DB + Fastify
server.decorate("db", connection)
// server.decorateRequest('db', connection);

// Auth Decorator
server.decorate("auth", verifyToken)

// Permission Dectorator
server.decorate("permissionAboveMod", checkModAbovePermissions)

// Initialize Nodemailer
const mailer = nodemailer.createTransport({
host: process.env.SMTP_EMAIL_HOST,
port: Number(process.env.SMTP_EMAIL_PORT),
secure: process.env.NODE_ENV === "production" ? true : false
})

// Mailer Decorator
server.decorate("mailer", mailer).addHook("onClose", () => mailer.close())

// JWT
server.register(fastifyJwt, {
secret: String(process.env.MA_JWT_SECRET),
cookie: { cookieName: "accessToken", signed: false }
})

// Cookie
server.register(fastifyCookie, {
secret: process.env.MA_COOKIE_SECRET
} as FastifyCookieOptions)

// CORS
server.register(fastifyCors, {
origin:
`${process.env.MA_FRONTEND_HTTP}${process.env.MA_FRONTEND_DOMAIN}:${process.env.MA_FRONTEND_PORT}` ||
"http://localhost:3000",
credentials: true
})

// Multer
server.register(multipart, {
limits: {
fileSize: 10 * 1024 * 1024 // 10MB Limit
}
})

// Health Check
server.get("/health", async () => {
return { status: "ok" }
})

// Swaggy Styff
await server.register(swagger)
await server.register(swaggerUI, {
routePrefix: "/documentation",
uiConfig: {
docExpansion: "full",
deepLinking: false
},
uiHooks: {
onRequest: function (_request, _reply, next) {
next()
},
preHandler: function (_request, _reply, next) {
next()
}
},
staticCSP: true,
transformStaticCSP: (header) => header,
transformSpecification: (swaggerObject) => {
return swaggerObject
},
transformSpecificationClone: true
})

// Registering Routes
server.get("/", () => { message: 'Hello' })
server.register(profileRoutes, { prefix: "/v1/user" })
server.register(authRoutes, { prefix: "/v1/auth" })
server.register(characterRoutes, { prefix: "/v1/character" })
server.register(profileRoutes, { prefix: "/v1/profile" })
server.register(artRoutes, { prefix: "/v1/art" })

return server;
}
159 changes: 9 additions & 150 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,158 +1,17 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { S3Client } from "@aws-sdk/client-s3"
import { fastifyCookie, type FastifyCookieOptions } from "@fastify/cookie"
import fastifyCors from "@fastify/cors"
import fastifyJwt from "@fastify/jwt"
import multipart from "@fastify/multipart"
import swagger from "@fastify/swagger"
import swaggerUI from "@fastify/swagger-ui"
import * as dotenv from "dotenv"
import fastify from "fastify"
import nodemailer, { type SentMessageInfo } from "nodemailer"
import type { DataSource } from "typeorm"
import authRoutes from "./routes/v1/Auth/routes"
import { characterRoutes } from "./routes/v1/Characters/routes"
import profileRoutes from "./routes/v1/Profile/routes"
import verifyToken from "./utils/auth"
import connectDatabase from "./utils/database"
import { checkModAbovePermissions } from "./utils/permission"
import artRoutes from "./routes/v1/Art/routes"
import { buildApp } from "./app"

declare module "fastify" {
interface FastifyInstance {
db: DataSource
auth: any
permissionAboveMod: any
mailer: nodemailer.Transporter<SentMessageInfo>
s3: S3Client
}
export const app = async () => {
const server = await buildApp()

interface UserRequest extends FastifyRequest {
user: {
id: string
profileId: string
}
}
}

const app = async () => {
dotenv.config()

// Initalize Database and Fastify
const connection = await connectDatabase()
const server = fastify({ logger: true })

// S3
const s3 = new S3Client({
endpoint: process.env.S3_ENDPOINT as string,
region: process.env.AWS_DEFAULT_REGION as string,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID as string,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string
},
forcePathStyle: true
})

server.decorate("s3", s3)

// DB + Fastify
server.decorate("db", connection)
// server.decorateRequest('db', connection);

// Auth Decorator
server.decorate("auth", verifyToken)

// Permission Dectorator
server.decorate("permissionAboveMod", checkModAbovePermissions)

// Initialize Nodemailer
const mailer = nodemailer.createTransport({
host: process.env.SMTP_EMAIL_HOST,
port: Number(process.env.SMTP_EMAIL_PORT),
secure: process.env.NODE_ENV === "production" ? true : false
})

// Mailer Decorator
server.decorate("mailer", mailer).addHook("onClose", () => mailer.close())

// JWT
server.register(fastifyJwt, {
secret: String(process.env.MA_JWT_SECRET),
cookie: { cookieName: "accessToken", signed: false }
})

// Cookie
server.register(fastifyCookie, {
secret: process.env.MA_COOKIE_SECRET
} as FastifyCookieOptions)

// CORS
server.register(fastifyCors, {
origin:
`${process.env.MA_FRONTEND_HTTP}${process.env.MA_FRONTEND_DOMAIN}:${process.env.MA_FRONTEND_PORT}` ||
"http://localhost:3000",
credentials: true
})

// Multer
server.register(multipart, {
limits: {
fileSize: 10 * 1024 * 1024 // 10MB Limit
// Starting server
server.listen({ port: 8080 }, (err) => {
if (err) {
server.log.error(err)
process.exit(1)
}
})

// Health Check
server.get("/health", async () => {
return { status: "ok" }
})

// Swaggy Styff
await server.register(swagger)
await server.register(swaggerUI, {
routePrefix: "/documentation",
uiConfig: {
docExpansion: "full",
deepLinking: false
},
uiHooks: {
onRequest: function (_request, _reply, next) {
next()
},
preHandler: function (_request, _reply, next) {
next()
}
},
staticCSP: true,
transformStaticCSP: (header) => header,
transformSpecification: (swaggerObject) => {
return swaggerObject
},
transformSpecificationClone: true
})

// Registering Routes
server.get("/", () => { message: 'Hello' })
server.register(profileRoutes, { prefix: "/v1/user" })
server.register(authRoutes, { prefix: "/v1/auth" })
server.register(characterRoutes, { prefix: "/v1/character" })
server.register(profileRoutes, { prefix: "/v1/profile" })
server.register(artRoutes, { prefix: "/v1/art" })

// Starting server
server.listen(
{
port: Number(process.env.MA_PORT) || 8080,
host: process.env.MA_HOST || "localhost"
},
(err, address) => {
if (err) {
server.log.error(err)
process.exit(1)
}

server.log.info(`server listening on ${address}`)
}
)
return server
}

app()
Loading