Set up PostgreSQL database configuration and enhance application structure

- Added .env.example with database connection details and JWT configuration.
- Introduced docker-compose.yml for PostgreSQL service setup with health checks.
- Created drizzle.config.ts for database schema and migration management.
- Updated nest-cli.json to include Swagger plugin configuration for API documentation.
- Enhanced package.json with new database-related scripts and dependencies.
- Implemented initial database migrations for user and token management.
- Configured application bootstrap process to load environment variables and set up Swagger.
- Added shared application configuration in configure-app.ts for consistent setup.
- Included unit tests for application configuration and Swagger setup.
This commit is contained in:
shancheas
2026-08-21 15:00:19 +07:00
parent 7f37985d22
commit d01fd6e2ef
54 changed files with 4378 additions and 87 deletions
+78
View File
@@ -0,0 +1,78 @@
import { loadEnv } from './env';
describe('loadEnv', () => {
const valid = {
DATABASE_URL: 'postgresql://user:pass@localhost:5432/tracking',
JWT_ACCESS_SECRET: 'test-secret-at-least-32-chars-long!!',
};
it('loads required values with defaults', () => {
const env = loadEnv(valid);
expect(env.DATABASE_URL).toBe(valid.DATABASE_URL);
expect(env.JWT_ACCESS_SECRET).toBe(valid.JWT_ACCESS_SECRET);
expect(env.PORT).toBe(3000);
expect(env.JWT_ACCESS_EXPIRES_IN).toBe('15m');
expect(env.JWT_ACCESS_EXPIRES_IN_MS).toBe(15 * 60 * 1000);
expect(env.REFRESH_TOKEN_EXPIRES_IN_MS).toBe(7 * 24 * 60 * 60 * 1000);
expect(env.BCRYPT_SALT_ROUNDS).toBe(10);
expect(env.DEFAULT_TIMEZONE).toBe('GMT+7');
});
it('throws when DATABASE_URL is missing', () => {
expect(() =>
loadEnv({ JWT_ACCESS_SECRET: valid.JWT_ACCESS_SECRET }),
).toThrow('DATABASE_URL is not configured');
});
it('throws when JWT_ACCESS_SECRET is missing', () => {
expect(() => loadEnv({ DATABASE_URL: valid.DATABASE_URL })).toThrow(
'JWT_ACCESS_SECRET is not configured',
);
});
it('throws when JWT_ACCESS_SECRET is a placeholder', () => {
expect(() =>
loadEnv({
DATABASE_URL: valid.DATABASE_URL,
JWT_ACCESS_SECRET: 'change-me-to-a-long-random-secret',
}),
).toThrow('known placeholder');
});
it('throws when JWT_ACCESS_SECRET is too short', () => {
expect(() =>
loadEnv({
DATABASE_URL: valid.DATABASE_URL,
JWT_ACCESS_SECRET: 'short-secret',
}),
).toThrow('at least 32 characters');
});
it('parses optional numeric overrides', () => {
const env = loadEnv({
...valid,
PORT: '4000',
BCRYPT_SALT_ROUNDS: '12',
REFRESH_TOKEN_EXPIRES_IN_MS: '3600000',
JWT_ACCESS_EXPIRES_IN: '5m',
DEFAULT_TIMEZONE: 'GMT+0',
});
expect(env.PORT).toBe(4000);
expect(env.BCRYPT_SALT_ROUNDS).toBe(12);
expect(env.REFRESH_TOKEN_EXPIRES_IN_MS).toBe(3_600_000);
expect(env.JWT_ACCESS_EXPIRES_IN).toBe('5m');
expect(env.JWT_ACCESS_EXPIRES_IN_MS).toBe(5 * 60 * 1000);
expect(env.DEFAULT_TIMEZONE).toBe('GMT+0');
});
it('rejects invalid JWT_ACCESS_EXPIRES_IN format', () => {
expect(() =>
loadEnv({
...valid,
JWT_ACCESS_EXPIRES_IN: '900',
}),
).toThrow('must match');
});
});