- fix: Update API routes to include DOI URL handling and improve route organization - chore: Add ORCID preload rule file and ensure proper registration - docs: Add MIT License to the project for open-source compliance - feat: Implement command to detect and fix missing dataset cross-references - feat: Create command for updating DataCite DOI records with detailed logging and error handling - docs: Add comprehensive documentation for dataset indexing command - docs: Create detailed documentation for DataCite update command with usage examples and error handling
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());
|
|
});
|