This commit is contained in:
2025-12-11 13:50:38 +01:00
parent e4c1be628d
commit c7e5de40d6
212 changed files with 2965 additions and 763 deletions

View File

@@ -1,64 +1,48 @@
import { z } from 'zod';
import type { IValueObject } from '@gridpilot/shared/domain';
import type { EmailValidationResult } from '../types/EmailAddress';
import { validateEmail, isDisposableEmail } from '../types/EmailAddress';
/**
* Core email validation schema
*/
export const emailSchema = z
.string()
.trim()
.toLowerCase()
.min(6, 'Email too short')
.max(254, 'Email too long')
.email('Invalid email format');
export type EmailValidationSuccess = {
success: true;
email: string;
error?: undefined;
};
export type EmailValidationFailure = {
success: false;
email?: undefined;
error: string;
};
export type EmailValidationResult = EmailValidationSuccess | EmailValidationFailure;
/**
* Validate and normalize an email address.
* Mirrors the previous apps/website/lib/email-validation.ts behavior.
*/
export function validateEmail(email: string): EmailValidationResult {
const result = emailSchema.safeParse(email);
if (result.success) {
return {
success: true,
email: result.data,
};
}
return {
success: false,
error: result.error.errors[0]?.message || 'Invalid email',
};
export interface EmailAddressProps {
value: string;
}
/**
* Basic disposable email detection.
* This list matches the previous website-local implementation and
* can be extended in the future without changing the public API.
* Value Object: EmailAddress
*
* Wraps a validated, normalized email string and provides equality semantics.
* Validation and helper utilities live in domain/types/EmailAddress.
*/
export const DISPOSABLE_DOMAINS = new Set<string>([
'tempmail.com',
'throwaway.email',
'guerrillamail.com',
'mailinator.com',
'10minutemail.com',
]);
export class EmailAddress implements IValueObject<EmailAddressProps> {
public readonly props: EmailAddressProps;
export function isDisposableEmail(email: string): boolean {
const domain = email.split('@')[1]?.toLowerCase();
return domain ? DISPOSABLE_DOMAINS.has(domain) : false;
}
private constructor(value: string) {
this.props = { value };
}
static create(raw: string): EmailAddress {
const result: EmailValidationResult = validateEmail(raw);
if (!result.success) {
throw new Error(result.error);
}
return new EmailAddress(result.email);
}
static fromValidated(value: string): EmailAddress {
return new EmailAddress(value);
}
get value(): string {
return this.props.value;
}
equals(other: IValueObject<EmailAddressProps>): boolean {
return this.props.value === other.props.value;
}
isDisposable(): boolean {
return isDisposableEmail(this.props.value);
}
}
export type { EmailValidationResult } from '../types/EmailAddress';
export { validateEmail, isDisposableEmail } from '../types/EmailAddress';