2025-01-24 17:11:10 +01:00
|
|
|
import { FieldContext } from '@vinejs/vine/types';
|
|
|
|
import vine from '@vinejs/vine';
|
|
|
|
import { VineString } from '@vinejs/vine';
|
2025-01-29 11:26:21 +01:00
|
|
|
import axios from 'axios';
|
2025-01-24 17:11:10 +01:00
|
|
|
import { ReferenceIdentifierTypes } from '#contracts/enums';
|
|
|
|
|
|
|
|
type Options = {
|
|
|
|
typeField: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Function to check if DOI exists using the DOI API
|
|
|
|
async function checkDoiExists(doi: string): Promise<boolean> {
|
|
|
|
try {
|
2025-01-29 11:26:21 +01:00
|
|
|
const response = await axios.default.get(`${doi}`);
|
2025-01-24 17:11:10 +01:00
|
|
|
return response.status === 200; // If status is 200, DOI is valid
|
|
|
|
} catch (error) {
|
|
|
|
return false; // If request fails, DOI does not exist
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Function to check if ISBN exists using the Open Library API
|
|
|
|
async function checkIsbnExists(isbn: string): Promise<boolean> {
|
|
|
|
try {
|
2025-01-29 11:26:21 +01:00
|
|
|
const response = await axios.default.get(`https://isbnsearch.org/isbn/${isbn}`);
|
2025-01-24 17:11:10 +01:00
|
|
|
return response.data && response.status == 200; // If title is returned, ISBN is valid
|
|
|
|
} catch (error) {
|
|
|
|
return false; // If request fails, ISBN does not exist
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function validateReference(value: unknown, options: Options, field: FieldContext) {
|
|
|
|
if (typeof value !== 'string') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const type = field.parent[options.typeField];
|
|
|
|
|
|
|
|
if (type === ReferenceIdentifierTypes.URL) {
|
|
|
|
if (!/^https?:\/\/[^\s$.?#].[^\s]*$/.test(value)) {
|
|
|
|
field.report('The {{ field }} must be a valid URL', 'validateReference', field);
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
const exists = await checkDoiExists(value);
|
|
|
|
if (!exists) {
|
|
|
|
field.report('The {{ field }} must be an existing DOI', 'validateReference', field);
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
field.report('Error checking DOI existence: ' + error.message, 'validateReference', field);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Check if the value does not match the DOI pattern
|
|
|
|
// The regex pattern ^10.\d{4,9}\/[-._;()/:a-zA-Z0-9]+$ is designed to match valid DOI formats.
|
|
|
|
// - ^10. ensures that the string starts with "10."
|
|
|
|
// - \d{4,9} matches a sequence of 4 to 9 digits.
|
|
|
|
// - \/ matches the literal forward slash character.
|
|
|
|
// - [-._;()/:a-zA-Z0-9]+ matches one or more characters that can include uppercase and lowercase letters, digits, and a set of special characters.
|
|
|
|
// The i flag at the end of the regex makes the matching case-insensitive, meaning it will match both uppercase and lowercase letters.
|
|
|
|
// If the value does not match this pattern, the code inside the if block will execute.
|
|
|
|
else if (type === ReferenceIdentifierTypes.DOI) {
|
|
|
|
// Extract the DOI from the URL if it starts with 'https://doi.org/'
|
|
|
|
const doiPattern = /^https:\/\/doi\.org\/(10.\d{4,9}\/[-._;()/:a-zA-Z0-9]+)$/i;
|
|
|
|
const match = value.match(doiPattern);
|
|
|
|
const doi = match ? match[1] : value;
|
|
|
|
// Check if the extracted DOI or the value itself matches the DOI patter
|
|
|
|
if (!/^10.\d{4,9}\/[-._;()/:a-zA-Z0-9]+$/i.test(doi)) {
|
|
|
|
field.report('The {{ field }} must be a valid DOI', 'validateReference', field);
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
const exists = await checkDoiExists(value);
|
|
|
|
if (!exists) {
|
|
|
|
field.report('The {{ field }} must be an existing DOI', 'validateReference', field);
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
field.report('Error checking DOI existence: ' + error.message, 'validateReference', field);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (type === ReferenceIdentifierTypes.ISBN) {
|
|
|
|
if (!/^(?=(?:[^0-9]*[0-9]){10}(?:(?:[^0-9]*[0-9]){3})?$)[\d-]+$/.test(value)) {
|
|
|
|
field.report('The {{ field }} must be a valid ISBN', 'validateReference', field);
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
const exists = await checkIsbnExists(value);
|
|
|
|
if (!exists) {
|
|
|
|
field.report('The {{ field }} must be an existing ISBN', 'validateReference', field);
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
field.report('Error checking ISBN existence: ' + error.message, 'validateReference', field);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (type === ReferenceIdentifierTypes.Handle && !/^\d{2,}.\d{4,9}\/[-._;()/:a-zA-Z0-9]+$/.test(value)) {
|
|
|
|
/// Extract the Handle from the URL if it contains '/handle/'
|
|
|
|
field.report('The {{ field }} must be a valid Handle', 'validateReference', field);
|
|
|
|
} else if (
|
|
|
|
type === ReferenceIdentifierTypes.URN &&
|
|
|
|
!/^urn:[a-zA-Z0-9][a-zA-Z0-9-]{0,31}:[a-zA-Z0-9()+,\-.:=@;$_!*'%/?#]+$/.test(value)
|
|
|
|
) {
|
|
|
|
field.report('The {{ field }} must be a valid URN', 'validateReference', field);
|
|
|
|
} else if (type === ReferenceIdentifierTypes.ISSN && !/^\d{4}-\d{3}[\dxX]$/.test(value)) {
|
|
|
|
field.report('The {{ field }} must be a valid ISSN', 'validateReference', field);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export const validateReferenceRule = vine.createRule(validateReference);
|
|
|
|
|
|
|
|
declare module '@vinejs/vine' {
|
|
|
|
interface VineString {
|
|
|
|
validateReference(options: Options): this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
VineString.macro('validateReference', function (this: VineString, options: Options) {
|
|
|
|
return this.use(validateReferenceRule(options));
|
|
|
|
});
|