import express from 'express'; import { version } from '../package.json'; import { AppDataSource } from './data-source' import { Link } from './entities/Link'; import inferUser from './middleware/inferUser'; import miscRouter from './routes/miscRoutes'; import userRouter from './routes/userRoutes'; import linkRouter from './routes/linkRoutes'; import { getCorsConfig } from './tools/cors'; import { LinkService } from './services/linkService'; import * as env from './tools/env'; import * as z from 'zod'; AppDataSource.initialize().then(async () => { await AppDataSource.runMigrations(); const app: express.Express = express(); const linkService = new LinkService(); const rs = env.getRewriteStrings(); const removedExpired = await linkService.removeAllExpired(); if (removedExpired !== 0) console.log(`[${Date.now() / 1_000}] (DB) Removed ${removedExpired} expired links.`); app.use(express.json()); app.use(getCorsConfig()); app.use(inferUser); app.use(miscRouter, userRouter, linkRouter); if (env.getBool('debug', true)) { const swaggerJsdocOpts = { failOnErrors: true, definition: { openapi: '3.0.4', info: { title: 'kittyurl API', description: 'A Typescript API for the kittyurl url shortener project.', version: version, contact: { name: 'Git repository for entire project', url: 'https://gitea.7o7.cx/kittyteam/kittyurl' }, license: { name: 'AGPLv3', url: 'https://www.gnu.org/licenses/agpl-3.0.en.html' } }, components: { securitySchemes: { BearerJWT: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT', description: 'JWT Authorization header using the Bearer scheme.
Enter your JWT from /api/v1/user/signIn to authorize.' } } }, security: [ { BearerJWT: [] } ], }, apis: ['./src/routes/*.ts', './src/schemas/*.ts'] }; const swaggerUi = require('swagger-ui-express'); const swaggerJsdoc = require('swagger-jsdoc'); const swaggerSpec = swaggerJsdoc(swaggerJsdocOpts); app.use('/kttydocs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); app.get('/kttydocs.json', (req: express.Request, res: express.Response) => { res.setHeader('Content-Type', 'application/json'); res.send(swaggerSpec); }); } // Handle 404s // https://stackoverflow.com/a/9802006 app.use(async function(req: express.Request, res: express.Response) { // Check if host header seems right try { z.string() .includes(rs.fqdn) .parse(req.headers.host); } catch { return res.status(400) .json({ status: 'error', error: 'Invalid host. Is your browser sending the host header?', code: 'no_host' }); } // Retrieve url, subdomain from request. let uri: string = req.url.slice(1); // discards / from /abc, /abc -> abc let subdomain: string | null = req.headers.host!.replace(rs.fqdn, '').slice(0, -1) || null; // slice() to remove trailing dot // Try to lookup the url in DB const reversedLink: Link | null = await linkService.lookupUriWithExpiryValidation(uri, subdomain); // Found something? if (reversedLink !== null) { // Count this as a visit reversedLink.visits += 1; linkService.save(reversedLink); // Redirect the user. return res.redirect(302, reversedLink.fullUrl); } // Nothing found? Return the standard 404. res.status(404); if (req.accepts('json')) { return res.json({ status: 'error', error: 'Not found', code: 'uri_not_found' }); } return res.type('txt') .send('Not found'); }); const errorHandler: express.ErrorRequestHandler = (err, req, res, next) => { console.error(`[${Date.now() / 1_000}] (ErrorHandler) Server error! Error stack:`); console.error(err?.stack); return res.status(500) .json({ status: 'error', error: 'Server error! Something broke', code: 'generic_server_error' }); }; app.use(errorHandler); app.listen(6567, () => console.log(`[${Date.now() / 1_000}] (HTTP Server) Listening on port 6567.`)); }).catch(error => console.log(error))