chat-with-gpt/server/src/index.ts

181 lines
7.1 KiB
TypeScript
Raw Normal View History

2023-03-14 11:00:40 +00:00
require('dotenv').config()
import compression from 'compression';
2023-04-15 10:30:02 +00:00
import express from 'express';
import { execSync } from 'child_process';
2023-03-14 11:00:40 +00:00
import fs from 'fs';
2023-04-15 10:30:02 +00:00
import https from 'https';
2023-03-14 11:00:40 +00:00
import path from 'path';
2023-04-15 10:30:02 +00:00
import { configureAuth0 } from './auth0';
import { config } from './config';
2023-03-14 11:00:40 +00:00
import Database from './database/index';
2023-04-15 10:30:02 +00:00
import KnexDatabaseAdapter from './database/knex';
import GetShareRequestHandler from './endpoints/get-share';
2023-03-14 11:00:40 +00:00
import HealthRequestHandler from './endpoints/health';
2023-04-15 10:30:02 +00:00
import DeleteChatRequestHandler from './endpoints/delete-chat';
import ElevenLabsTTSProxyRequestHandler from './endpoints/service-proxies/elevenlabs/text-to-speech';
import ElevenLabsVoicesProxyRequestHandler from './endpoints/service-proxies/elevenlabs/voices';
import OpenAIProxyRequestHandler from './endpoints/service-proxies/openai';
2023-03-14 11:00:40 +00:00
import SessionRequestHandler from './endpoints/session';
2023-04-15 10:30:02 +00:00
import ShareRequestHandler from './endpoints/share';
import ObjectStore from './object-store/index';
import S3ObjectStore from './object-store/s3';
import SQLiteObjectStore from './object-store/sqlite';
2023-03-14 11:00:40 +00:00
import { configurePassport } from './passport';
2023-04-15 10:30:02 +00:00
import SyncRequestHandler, { getNumUpdatesProcessedIn5Minutes } from './endpoints/sync';
import LegacySyncRequestHandler from './endpoints/sync-legacy';
import { getActiveUsersInLast5Minutes } from './endpoints/base';
2023-03-14 11:00:40 +00:00
process.on('unhandledRejection', (reason, p) => {
console.error('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
2023-03-14 21:11:18 +00:00
if (process.env.CI) {
setTimeout(() => process.exit(), 10000);
}
2023-03-14 11:00:40 +00:00
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3001;
export default class ChatServer {
2023-04-15 10:30:02 +00:00
authProvider = 'local';
2023-03-14 11:00:40 +00:00
app: express.Application;
objectStore: ObjectStore = process.env.S3_BUCKET ? new S3ObjectStore() : new SQLiteObjectStore();
2023-04-15 10:30:02 +00:00
database: Database = new KnexDatabaseAdapter();
2023-03-14 11:00:40 +00:00
constructor() {
this.app = express();
2023-03-17 18:16:55 +00:00
}
async initialize() {
2023-03-18 23:11:22 +00:00
//const { default: helmet } = await import('helmet');
//this.app.use(helmet());
2023-03-17 19:03:50 +00:00
2023-03-14 11:00:40 +00:00
this.app.use(express.urlencoded({ extended: false }));
2023-04-15 10:30:02 +00:00
if (config.auth0?.clientID && config.auth0?.issuer && config.publicSiteURL) {
2023-03-14 11:00:40 +00:00
console.log('Configuring Auth0.');
2023-04-15 10:30:02 +00:00
this.authProvider = 'auth0';
2023-03-14 11:00:40 +00:00
configureAuth0(this);
} else {
console.log('Configuring Passport.');
2023-04-15 10:30:02 +00:00
this.authProvider = 'local';
2023-03-14 11:00:40 +00:00
configurePassport(this);
}
this.app.use(express.json({ limit: '1mb' }));
2023-04-15 10:30:02 +00:00
this.app.use(compression({
filter: (req, res) => !req.path.includes("proxies"),
}));
2023-03-17 18:16:55 +00:00
2023-04-05 09:04:59 +00:00
const { default: rateLimit } = await import('express-rate-limit'); // esm
2023-03-17 18:16:55 +00:00
2023-03-14 11:00:40 +00:00
this.app.get('/chatapi/health', (req, res) => new HealthRequestHandler(this, req, res));
2023-04-15 10:30:02 +00:00
this.app.get('/chatapi/session',
rateLimit({ windowMs: 60 * 1000, max: 100 }),
(req, res) => new SessionRequestHandler(this, req, res));
this.app.post('/chatapi/y-sync',
rateLimit({ windowMs: 60 * 1000, max: 100 }),
express.raw({ type: 'application/octet-stream', limit: '10mb' }),
(req, res) => new SyncRequestHandler(this, req, res));
this.app.get('/chatapi/legacy-sync',
rateLimit({ windowMs: 60 * 1000, max: 100 }),
(req, res) => new LegacySyncRequestHandler(this, req, res));
this.app.use(rateLimit({
windowMs: config.rateLimit.windowMs,
max: config.rateLimit.max,
}));
2023-03-17 20:45:27 +00:00
this.app.post('/chatapi/delete', (req, res) => new DeleteChatRequestHandler(this, req, res));
2023-03-14 11:00:40 +00:00
this.app.get('/chatapi/share/:id', (req, res) => new GetShareRequestHandler(this, req, res));
this.app.post('/chatapi/share', (req, res) => new ShareRequestHandler(this, req, res));
2023-04-15 10:30:02 +00:00
if (config.services?.openai?.apiKey) {
this.app.post('/chatapi/proxies/openai/v1/chat/completions', (req, res) => new OpenAIProxyRequestHandler(this, req, res));
}
if (config.services?.elevenlabs?.apiKey) {
this.app.post('/chatapi/proxies/elevenlabs/v1/text-to-speech/:voiceID', (req, res) => new ElevenLabsTTSProxyRequestHandler(this, req, res));
this.app.get('/chatapi/proxies/elevenlabs/v1/voices', (req, res) => new ElevenLabsVoicesProxyRequestHandler(this, req, res));
2023-03-14 11:00:40 +00:00
}
if (fs.existsSync('public')) {
2023-04-15 10:30:02 +00:00
const match = `<script> window.AUTH_PROVIDER = "local"; </script>`;
const replace = `<script> window.AUTH_PROVIDER = "${this.authProvider}"; </script>`;
const indexFilename = "public/index.html";
const indexSource = fs.readFileSync(indexFilename, 'utf8');
fs.writeFileSync(indexFilename, indexSource.replace(match, replace));
2023-03-14 11:00:40 +00:00
this.app.use(express.static('public'));
// serve index.html for all other routes
this.app.get('*', (req, res) => {
res.sendFile('public/index.html', { root: path.resolve(__dirname, '..') });
});
}
2023-03-15 20:13:44 +00:00
await this.objectStore.initialize();
await this.database.initialize();
2023-03-14 11:00:40 +00:00
try {
2023-04-15 10:30:02 +00:00
const callback = () => {
2023-03-14 11:00:40 +00:00
console.log(`Listening on port ${port}.`);
2023-04-15 10:30:02 +00:00
};
if (config.tls?.key && config.tls?.cert) {
console.log('Configuring TLS.');
const server = https.createServer({
key: fs.readFileSync(config.tls.key),
cert: fs.readFileSync(config.tls.cert),
}, this.app);
server.listen(port, callback);
} else if (config.tls?.selfSigned) {
console.log('Configuring self-signed TLS.');
if (!fs.existsSync('./data/key.pem') || !fs.existsSync('./data/cert.pem')) {
execSync('sh generate-self-signed-certificate.sh');
}
const server = https.createServer({
key: fs.readFileSync('./data/key.pem'),
cert: fs.readFileSync('./data/cert.pem'),
}, this.app);
server.listen(port, callback);
} else {
this.app.listen(port, callback);
}
2023-03-14 11:00:40 +00:00
} catch (e) {
console.log(e);
}
2023-04-15 10:30:02 +00:00
setInterval(() => {
const activeUsers = getActiveUsersInLast5Minutes();
const activeUsersToDisplay = activeUsers.slice(0, 10);
const extraActiveUsers = activeUsers.slice(10);
const numRecentUpdates = getNumUpdatesProcessedIn5Minutes();
console.log(`Statistics (last 5m):`);
if (extraActiveUsers.length) {
console.log(` - ${activeUsers.length} active users: ${activeUsersToDisplay.join(', ')} and ${extraActiveUsers.length} more`);
} else {
console.log(` - ${activeUsers.length} active users: ${activeUsersToDisplay.join(', ')}`);
}
console.log(` - ${numRecentUpdates} updates processed`);
}, 1000 * 60);
2023-03-14 11:00:40 +00:00
}
}
2023-03-18 23:11:22 +00:00
new ChatServer().initialize();