mirror of
				https://github.com/strapi/strapi.git
				synced 2025-10-31 01:47:13 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			232 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			232 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| const _ = require('lodash/fp');
 | |
| const dateFns = require('date-fns');
 | |
| const { InvalidTimeError, InvalidDateError, InvalidDateTimeError } = require('./errors');
 | |
| 
 | |
| class Field {
 | |
|   constructor(config) {
 | |
|     this.config = config;
 | |
|   }
 | |
| 
 | |
|   toDB(value) {
 | |
|     return value;
 | |
|   }
 | |
| 
 | |
|   fromDB(value) {
 | |
|     return value;
 | |
|   }
 | |
| }
 | |
| 
 | |
| class StringField extends Field {
 | |
|   toDB(value) {
 | |
|     return _.toString(value);
 | |
|   }
 | |
| 
 | |
|   fromDB(value) {
 | |
|     return _.toString(value);
 | |
|   }
 | |
| }
 | |
| 
 | |
| class JSONField extends Field {
 | |
|   toDB(value) {
 | |
|     return JSON.stringify(value);
 | |
|   }
 | |
| 
 | |
|   fromDB(value) {
 | |
|     if (typeof value === 'string') return JSON.parse(value);
 | |
|     return value;
 | |
|   }
 | |
| }
 | |
| 
 | |
| class BooleanField extends Field {
 | |
|   toDB(value) {
 | |
|     if (typeof value === 'boolean') return value;
 | |
| 
 | |
|     if (['true', 't', '1', 1].includes(value)) {
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     if (['false', 'f', '0', 0].includes(value)) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     return Boolean(value);
 | |
|   }
 | |
| 
 | |
|   fromDB(value) {
 | |
|     if (typeof value === 'boolean') {
 | |
|       return value;
 | |
|     }
 | |
| 
 | |
|     const strVal = _.toString(value);
 | |
| 
 | |
|     if (strVal === '1') {
 | |
|       return true;
 | |
|     } else if (strVal === '0') {
 | |
|       return false;
 | |
|     } else {
 | |
|       return null;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| class NumberField extends Field {
 | |
|   toDB(value) {
 | |
|     const numberValue = _.toNumber(value);
 | |
| 
 | |
|     if (Number.isNaN(numberValue)) {
 | |
|       throw new Error(`Expected a valid Number, got ${value}`);
 | |
|     }
 | |
| 
 | |
|     return numberValue;
 | |
|   }
 | |
| 
 | |
|   fromDB(value) {
 | |
|     return _.toNumber(value);
 | |
|   }
 | |
| }
 | |
| 
 | |
| class BigIntegerField extends NumberField {
 | |
|   toDB(value) {
 | |
|     return _.toString(value);
 | |
|   }
 | |
| 
 | |
|   fromDB(value) {
 | |
|     return _.toString(value);
 | |
|   }
 | |
| }
 | |
| 
 | |
| const timeRegex = new RegExp('^(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]{1,3})?$');
 | |
| const dateRegex = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;
 | |
| const partialDateRegex = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])/g;
 | |
| 
 | |
| const parseTime = value => {
 | |
|   if (dateFns.isDate(value)) return dateFns.format(value, 'HH:mm:ss.SSS');
 | |
| 
 | |
|   if (typeof value !== 'string') {
 | |
|     throw new InvalidTimeError(`Expected a string, got a ${typeof value}`);
 | |
|   }
 | |
|   const result = value.match(timeRegex);
 | |
| 
 | |
|   if (result === null) {
 | |
|     throw new InvalidTimeError('Invalid time format, expected HH:mm:ss.SSS');
 | |
|   }
 | |
| 
 | |
|   const [, hours, minutes, seconds, fraction = '.000'] = result;
 | |
|   const fractionPart = _.padCharsEnd('0', 3, fraction.slice(1));
 | |
| 
 | |
|   return `${hours}:${minutes}:${seconds}.${fractionPart}`;
 | |
| };
 | |
| 
 | |
| const parseDate = value => {
 | |
|   const found = _.isString(value) ? value.match(partialDateRegex) || [] : [];
 | |
|   const extractedValue = found[0];
 | |
| 
 | |
|   if (extractedValue && !dateRegex.test(value)) {
 | |
|     // TODO V5: throw an error when format yyyy-MM-dd is not respected
 | |
|     // throw new InvalidDateError(`Invalid format, expected yyyy-MM-dd`);
 | |
|     process.emitWarning(
 | |
|       `[deprecated] Using a date format other than YYYY-MM-DD will be removed in future versions. Date received: ${value}. Date stored: ${extractedValue}.`
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   let date = dateFns.parseISO(extractedValue);
 | |
|   if (!dateFns.isValid(date)) {
 | |
|     throw new InvalidDateError(`Invalid date`);
 | |
|   }
 | |
| 
 | |
|   return extractedValue;
 | |
| };
 | |
| 
 | |
| const parseDateTimeOrTimestamp = value => {
 | |
|   if (dateFns.isDate(value)) return value;
 | |
|   try {
 | |
|     const date = dateFns.parseISO(value);
 | |
|     if (dateFns.isValid(date)) return date;
 | |
| 
 | |
|     const milliUnixDate = dateFns.parse(value, 'T', new Date());
 | |
|     if (dateFns.isValid(milliUnixDate)) return milliUnixDate;
 | |
| 
 | |
|     throw new InvalidDateTimeError(`Invalid format, expected a timestamp or an ISO date`);
 | |
|   } catch (error) {
 | |
|     throw new InvalidDateTimeError(`Invalid format, expected a timestamp or an ISO date`);
 | |
|   }
 | |
| };
 | |
| 
 | |
| class DateField extends Field {
 | |
|   toDB(value) {
 | |
|     return parseDate(value);
 | |
|   }
 | |
| 
 | |
|   fromDB(value) {
 | |
|     return value;
 | |
|   }
 | |
| }
 | |
| class DatetimeField extends Field {
 | |
|   toDB(value) {
 | |
|     return parseDateTimeOrTimestamp(value);
 | |
|   }
 | |
| 
 | |
|   fromDB(value) {
 | |
|     const cast = new Date(value);
 | |
|     return dateFns.isValid(cast) ? cast.toISOString() : null;
 | |
|   }
 | |
| }
 | |
| 
 | |
| class TimeField extends Field {
 | |
|   toDB(value) {
 | |
|     return parseTime(value);
 | |
|   }
 | |
| 
 | |
|   fromDB(value) {
 | |
|     // make sure that's a string with valid format ?
 | |
|     return value;
 | |
|   }
 | |
| }
 | |
| class TimestampField extends Field {
 | |
|   toDB(value) {
 | |
|     return parseDateTimeOrTimestamp(value);
 | |
|   }
 | |
| 
 | |
|   fromDB(value) {
 | |
|     const cast = new Date(value);
 | |
|     return dateFns.isValid(cast) ? dateFns.format(cast, 'T') : null;
 | |
|   }
 | |
| }
 | |
| 
 | |
| const typeToFieldMap = {
 | |
|   increments: Field,
 | |
|   password: StringField,
 | |
|   email: StringField,
 | |
|   string: StringField,
 | |
|   uid: StringField,
 | |
|   richtext: StringField,
 | |
|   text: StringField,
 | |
|   enumeration: StringField,
 | |
|   json: JSONField,
 | |
|   biginteger: BigIntegerField,
 | |
|   integer: NumberField,
 | |
|   float: NumberField,
 | |
|   decimal: NumberField,
 | |
|   date: DateField,
 | |
|   time: TimeField,
 | |
|   datetime: DatetimeField,
 | |
|   timestamp: TimestampField,
 | |
|   boolean: BooleanField,
 | |
| };
 | |
| 
 | |
| const createField = attribute => {
 | |
|   const { type } = attribute;
 | |
| 
 | |
|   if (_.has(type, typeToFieldMap)) {
 | |
|     return new typeToFieldMap[type]({});
 | |
|   }
 | |
| 
 | |
|   throw new Error(`Undefined field for type ${type}`);
 | |
| };
 | |
| 
 | |
| module.exports = {
 | |
|   createField,
 | |
| };
 | 
