### Major Features - Add comprehensive ORCID validation with checksum verification - Implement unsaved changes detection and auto-save functionality - Enhanced form component reactivity and state management ### ORCID Implementation - Create custom VineJS ORCID validation rule with MOD-11-2 algorithm - Add ORCID fields to Person model and TablePersons component - Update dataset validators to include ORCID validation - Add descriptive placeholder text for ORCID input fields ### UI/UX Improvements - Add UnsavedChangesWarning component with detailed change tracking - Improve FormCheckRadio and FormCheckRadioGroup reactivity - Enhanced BaseButton with proper disabled state handling - Better error handling and user feedback in file validation ### Data Management - Implement sophisticated change detection for all dataset fields - Add proper handling of array ordering for authors/contributors - Improve license selection with better state management - Enhanced subject/keyword processing with duplicate detection ### Technical Improvements - Optimize search indexing with conditional updates based on modification dates - Update person model column mapping for ORCID - Improve validation error messages and user guidance - Better handling of file uploads and deletion tracking ### Dependencies - Update various npm packages (AWS SDK, Babel, Vite, etc.) - Add baseline-browser-mapping for better browser compatibility ### Bug Fixes - Fix form reactivity issues with checkbox/radio groups - Improve error handling in file validation rules - Better handling of edge cases in change detection
175 lines
5.6 KiB
TypeScript
175 lines
5.6 KiB
TypeScript
/*
|
||
|--------------------------------------------------------------------------
|
||
| Preloaded File - node ace make:preload rules/orcid
|
||
| ❯ Do you want to register the preload file in .adonisrc.ts file? (y/N) · true
|
||
| DONE: create start/rules/orcid.ts
|
||
| DONE: update adonisrc.ts file
|
||
|--------------------------------------------------------------------------
|
||
*/
|
||
|
||
import vine, { VineString } from '@vinejs/vine';
|
||
import { FieldContext } from '@vinejs/vine/types';
|
||
|
||
/**
|
||
* ORCID Validator Implementation
|
||
*
|
||
* Validates ORCID identifiers using both format validation and checksum verification.
|
||
* ORCID (Open Researcher and Contributor ID) is a persistent digital identifier
|
||
* that distinguishes researchers and supports automated linkages between them
|
||
* and their professional activities.
|
||
*
|
||
* Format: 0000-0000-0000-0000 (where the last digit can be X for checksum 10)
|
||
* Algorithm: MOD-11-2 checksum validation as per ISO/IEC 7064:2003
|
||
*
|
||
* @param value - The ORCID value to validate
|
||
* @param _options - Unused options parameter (required by VineJS signature)
|
||
* @param field - VineJS field context for error reporting
|
||
*/
|
||
async function orcidValidator(value: unknown, _options: undefined, field: FieldContext) {
|
||
/**
|
||
* Type guard: We only validate string values
|
||
* The "string" rule should handle type validation before this rule runs
|
||
*/
|
||
if (typeof value !== 'string') {
|
||
return;
|
||
}
|
||
|
||
/**
|
||
* Handle optional fields: Skip validation for empty strings
|
||
* This allows the field to be truly optional when used with .optional()
|
||
*/
|
||
if (value.trim() === '') {
|
||
return;
|
||
}
|
||
|
||
/**
|
||
* Normalize the ORCID value:
|
||
* - Remove any whitespace characters
|
||
* - Convert to uppercase (for potential X check digit)
|
||
*/
|
||
const cleanOrcid = value.replace(/\s/g, '').toUpperCase();
|
||
|
||
/**
|
||
* Format Validation
|
||
*
|
||
* ORCID format regex breakdown:
|
||
* ^(\d{4}-){3} - Three groups of exactly 4 digits followed by hyphen
|
||
* \d{3} - Three more digits
|
||
* [\dX]$ - Final character: either digit or 'X' (for checksum 10)
|
||
*
|
||
* Valid examples: 0000-0002-1825-0097, 0000-0002-1825-009X
|
||
*/
|
||
const orcidRegex = /^(\d{4}-){3}\d{3}[\dX]$/;
|
||
|
||
if (!orcidRegex.test(cleanOrcid)) {
|
||
field.report('ORCID must be in format: 0000-0000-0000-0000 or 0000-0000-0000-000X', 'orcid', field);
|
||
return;
|
||
}
|
||
|
||
/**
|
||
* Checksum Validation - MOD-11-2 Algorithm
|
||
*
|
||
* This implements the official ORCID checksum algorithm based on ISO/IEC 7064:2003
|
||
* to verify mathematical validity and detect typos or invalid identifiers.
|
||
*/
|
||
|
||
// Step 1: Extract digits and separate check digit
|
||
const digits = cleanOrcid.replace(/-/g, ''); // Remove hyphens: "0000000218250097"
|
||
const baseDigits = digits.slice(0, -1); // First 15 digits: "000000021825009"
|
||
const checkDigit = digits.slice(-1); // Last character: "7"
|
||
|
||
/**
|
||
* Step 2: Calculate checksum using MOD-11-2 algorithm
|
||
*
|
||
* For each digit from left to right:
|
||
* 1. Add the digit to running total
|
||
* 2. Multiply result by 2
|
||
*
|
||
* Example for "000000021825009":
|
||
* - Start with total = 0
|
||
* - Process each digit: total = (total + digit) * 2
|
||
* - Continue until all 15 digits are processed
|
||
*/
|
||
let total = 0;
|
||
for (const digit of baseDigits) {
|
||
total = (total + parseInt(digit)) * 2;
|
||
}
|
||
|
||
/**
|
||
* Step 3: Calculate expected check digit
|
||
*
|
||
* Formula: (12 - (total % 11)) % 11
|
||
* - Get remainder when total is divided by 11
|
||
* - Subtract from 12 and take modulo 11 again
|
||
* - If result is 10, use 'X' (since we need single character)
|
||
*
|
||
* Example: total = 1314
|
||
* - remainder = 1314 % 11 = 5
|
||
* - result = (12 - 5) % 11 = 7
|
||
* - expectedCheckDigit = "7"
|
||
*/
|
||
const remainder = total % 11;
|
||
const result = (12 - remainder) % 11;
|
||
const expectedCheckDigit = result === 10 ? 'X' : result.toString();
|
||
|
||
/**
|
||
* Step 4: Verify checksum matches
|
||
*
|
||
* Compare the actual check digit with the calculated expected value.
|
||
* If they don't match, the ORCID is invalid (likely contains typos or is fabricated).
|
||
*/
|
||
if (checkDigit !== expectedCheckDigit) {
|
||
field.report('Invalid ORCID checksum', 'orcid', field);
|
||
return;
|
||
}
|
||
|
||
// If we reach this point, the ORCID is valid (both format and checksum)
|
||
}
|
||
|
||
/**
|
||
* Create the VineJS validation rule
|
||
*
|
||
* This creates a reusable rule that can be chained with other VineJS validators
|
||
*/
|
||
const orcidRule = vine.createRule(orcidValidator);
|
||
|
||
/**
|
||
* TypeScript module declaration
|
||
*
|
||
* Extends the VineString interface to include our custom orcid() method.
|
||
* This enables TypeScript autocompletion and type checking when using the rule.
|
||
*/
|
||
declare module '@vinejs/vine' {
|
||
interface VineString {
|
||
/**
|
||
* Validates that a string is a valid ORCID identifier
|
||
*
|
||
* Checks both format (0000-0000-0000-0000) and mathematical validity
|
||
* using the MOD-11-2 checksum algorithm.
|
||
*
|
||
* @example
|
||
* ```typescript
|
||
* // Usage in validation schema
|
||
* identifier_orcid: vine.string().trim().maxLength(255).orcid().optional()
|
||
* ```
|
||
*
|
||
* @returns {this} The VineString instance for method chaining
|
||
*/
|
||
orcid(): this;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Register the macro with VineJS
|
||
*
|
||
* This adds the .orcid() method to all VineString instances,
|
||
* allowing it to be used in validation schemas.
|
||
*
|
||
* Usage example:
|
||
* ```typescript
|
||
* vine.string().orcid().optional()
|
||
* ```
|
||
*/
|
||
VineString.macro('orcid', function (this: VineString) {
|
||
return this.use(orcidRule());
|
||
});
|