6b14cfaa89
This PR fixes two critical bugs with the DATE type, following up the recent refactor around dates and time zones : https://github.com/twentyhq/twenty/pull/16544 There are a lot of related bugs in Sentry, this PR should fix all bugs that are of the form : `Cannot parse: 2026-02-04T00:00:00.000Z` # `node-postgres` date type The package `node-postgres`, used by TypeORM, returns by default a Date object for the date OID type. But we can change this behavior without patching anything. See the documentation for `pg-types` : https://github.com/brianc/node-pg-types Our fix is to call `setTypeParser` from `pg` to have the postgres "date" type returned as a string always. ```ts export const setPgDateTypeParser = () => { types.setTypeParser(PG_DATE_TYPE_OID, (val: string) => val); }; ``` Then in server's main.ts : ```ts const bootstrap = async () => { setPgDateTypeParser(); ``` ## Are we safe with this very low-level fix ? Since TypeORM bypasses a string value returned by `pg` we are safe with this modification at a very low-level. ```ts else if (columnMetadata.type === "date") { value = DateUtils.mixedDateToDateString(value) } ``` https://github.com/typeorm/typeorm/blob/0ec4079cd3760dc49247a02c54415f16a2a51766/src/driver/postgres/PostgresDriver.ts#L838 ```ts /** * Converts given value into date string in a "YYYY-MM-DD" format. */ static mixedDateToDateString(value: string | Date): string { if (value instanceof Date) { return ( this.formatZerolessValue(value.getFullYear(), 4) + "-" + this.formatZerolessValue(value.getMonth() + 1) + "-" + this.formatZerolessValue(value.getDate()) ) } return value } ``` https://github.com/typeorm/typeorm/blob/6f3788b83730463e3b787b2a98bb41695e13caf8/src/util/DateUtils.ts#L28 For reference the original type parser seems to be configured in node-pg-types, on the 1182 OID, which is an array of date / 1082 OID, this type parser calls another small library `postgres-date` which creates a JS Date. I couldn't find a type parser explicitly on 1082 in the stack TypeORM -> node-postgres -> node-pg-types -> postgres-date ```ts register(1182, parseStringArray) // date[] ``` https://github.com/brianc/node-pg-types/blob/26bfe645a8ddc0c73830b1b8c63f2c4f8265b24f/lib/textParsers.js#L168C19-L168C45 ```ts static parse (dateString) { return new PGDateParser(dateString).getJSDate() } ``` https://github.com/bendrucker/postgres-date/blob/b3060560ed62c250f7800d29e4b68a9d5a0622bd/index.js#L285 # Calendar view mixing DATE and DATE_TIME At the time of the refactor, DATE type wasn't properly tested with calendar view, thus the drag and drop of a card with a calendar view on a DATE field and not a DATE_TIME field, was not working, this is now fixed.
85 lines
2.7 KiB
TypeScript
85 lines
2.7 KiB
TypeScript
import { NestFactory } from '@nestjs/core';
|
|
import { type NestExpressApplication } from '@nestjs/platform-express';
|
|
|
|
import fs from 'fs';
|
|
|
|
import bytes from 'bytes';
|
|
import { useContainer } from 'class-validator';
|
|
import session from 'express-session';
|
|
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.mjs';
|
|
|
|
import { NodeEnvironment } from 'src/engine/core-modules/twenty-config/interfaces/node-environment.interface';
|
|
|
|
import { setPgDateTypeParser } from 'src/database/pg/set-pg-date-type-parser';
|
|
import { LoggerService } from 'src/engine/core-modules/logger/logger.service';
|
|
import { getSessionStorageOptions } from 'src/engine/core-modules/session-storage/session-storage.module-factory';
|
|
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
|
import { UnhandledExceptionFilter } from 'src/filters/unhandled-exception.filter';
|
|
|
|
import { AppModule } from './app.module';
|
|
import './instrument';
|
|
|
|
import { settings } from './engine/constants/settings';
|
|
import { generateFrontConfig } from './utils/generate-front-config';
|
|
|
|
const bootstrap = async () => {
|
|
setPgDateTypeParser();
|
|
|
|
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
|
|
cors: true,
|
|
bufferLogs: process.env.LOGGER_IS_BUFFER_ENABLED === 'true',
|
|
rawBody: true,
|
|
snapshot: process.env.NODE_ENV === NodeEnvironment.DEVELOPMENT,
|
|
...(process.env.SSL_KEY_PATH && process.env.SSL_CERT_PATH
|
|
? {
|
|
httpsOptions: {
|
|
key: fs.readFileSync(process.env.SSL_KEY_PATH),
|
|
cert: fs.readFileSync(process.env.SSL_CERT_PATH),
|
|
},
|
|
}
|
|
: {}),
|
|
});
|
|
const logger = app.get(LoggerService);
|
|
const twentyConfigService = app.get(TwentyConfigService);
|
|
|
|
app.use(session(getSessionStorageOptions(twentyConfigService)));
|
|
|
|
// Apply class-validator container so that we can use injection in validators
|
|
useContainer(app.select(AppModule), { fallbackOnErrors: true });
|
|
|
|
// Use our logger
|
|
app.useLogger(logger);
|
|
|
|
app.useGlobalFilters(new UnhandledExceptionFilter());
|
|
|
|
app.useBodyParser('json', { limit: settings.storage.maxFileSize });
|
|
app.useBodyParser('urlencoded', {
|
|
limit: settings.storage.maxFileSize,
|
|
extended: true,
|
|
});
|
|
|
|
// Graphql file upload
|
|
app.use(
|
|
'/graphql',
|
|
graphqlUploadExpress({
|
|
maxFieldSize: bytes(settings.storage.maxFileSize),
|
|
maxFiles: 10,
|
|
}),
|
|
);
|
|
|
|
app.use(
|
|
'/metadata',
|
|
graphqlUploadExpress({
|
|
maxFieldSize: bytes(settings.storage.maxFileSize),
|
|
maxFiles: 10,
|
|
}),
|
|
);
|
|
|
|
// Inject the server url in the frontend page
|
|
generateFrontConfig();
|
|
|
|
await app.listen(twentyConfigService.get('NODE_PORT'));
|
|
};
|
|
|
|
bootstrap();
|