Skip to content

Commit

Permalink
Add common shared classes
Browse files Browse the repository at this point in the history
  • Loading branch information
stevejkang committed Sep 6, 2024
1 parent 0c672ef commit 1e97692
Show file tree
Hide file tree
Showing 6 changed files with 357 additions and 0 deletions.
10 changes: 10 additions & 0 deletions src/shared/common/EmailHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export class EmailHandler {
/**
* Validate email
* Reference: https://emailregex.com
* @param email
*/
static validate(email: string): boolean {
return new RegExp(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/).test(email);
}
}
25 changes: 25 additions & 0 deletions src/shared/common/PhoneNumberHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import parsePhoneNumber from 'libphonenumber-js';

export class PhoneNumberHandler {
static getValidPhoneNumber(phoneNumber: string): string {
const parsedPhoneNumberInstance = parsePhoneNumber(phoneNumber, 'KR');
if (!parsedPhoneNumberInstance) {
return phoneNumber;
}

if (!parsedPhoneNumberInstance.isValid() || !parsedPhoneNumberInstance.isPossible()) {
return phoneNumber;
}

return parsedPhoneNumberInstance.number;
}

static getLocalPhoneNumber(phoneNumber: string, enableHyphen: boolean = true): string {
const parsedPhoneNumberInstance = parsePhoneNumber(phoneNumber, 'KR');
if (!parsedPhoneNumberInstance) {
return phoneNumber;
}

return enableHyphen ? parsedPhoneNumberInstance.formatNational() : parsedPhoneNumberInstance.formatNational().replaceAll('-', '');
}
}
70 changes: 70 additions & 0 deletions src/shared/common/Snowflake.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
export class Snowflake {
static EPOCH = Date.UTC(1970, 0, 1).valueOf();
static SHARD_ID = 1;
static SEQUENCE = 1;

static generate({ timestamp = Date.now(), shard_id = Snowflake.SHARD_ID }: { timestamp?: Date | number; shard_id?: number; } = {}): string {
if (timestamp instanceof Date) {
timestamp = timestamp.valueOf();
} else {
timestamp = new Date(timestamp).valueOf();
}

let result = (BigInt(timestamp) - BigInt(Snowflake.EPOCH)) << BigInt(22);
result = result | (BigInt(shard_id % 1024) << BigInt(12));
result = result | BigInt(Snowflake.SEQUENCE++ % 4096);

return result.toString();
}

static parse(snowflake: SnowflakeResolvable): DeconstructedSnowflake {
const binary = Snowflake.binary(snowflake);

return {
timestamp: Snowflake.extractBits(snowflake, 1, 41),
shard_id: Snowflake.extractBits(snowflake, 42, 10),
sequence: Snowflake.extractBits(snowflake, 52),
binary,
};
}

static isValid(snowflake: string): boolean {
if (!/^\d{19}$/.test(snowflake)) {
return false;
}

try {
Snowflake.parse(snowflake);
return true;
} catch (e) {
return false;
}
}

static extractBits(snowflake: SnowflakeResolvable, start: number, length?: number): number {
return parseInt(
length
? Snowflake.binary(snowflake).substring(start, start + length)
: Snowflake.binary(snowflake).substring(start),
2,
);
}

static binary(snowflake: SnowflakeResolvable): string {
const cached64BitZeros = '0000000000000000000000000000000000000000000000000000000000000000';
const binValue = BigInt(snowflake).toString(2);

return binValue.length < 64
? cached64BitZeros.substring(0, 64 - binValue.length) + binValue
: binValue;
}
}

type SnowflakeResolvable = string;

interface DeconstructedSnowflake {
timestamp: number;
shard_id: number;
sequence: number;
binary: string;
}
13 changes: 13 additions & 0 deletions src/shared/common/test/EmailHandler.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { EmailHandler } from '../EmailHandler';

describe('EmailHandler', () => {
describe('validate', () => {
it('should return true when email is valid', () => {
expect(EmailHandler.validate('steve@example.com')).toBe(true);
});

it('should return false when email is invalid', () => {
expect(EmailHandler.validate('steve@example')).toBe(false);
});
});
});
157 changes: 157 additions & 0 deletions src/shared/common/test/PhoneNumberHandler.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { PhoneNumberHandler } from '../PhoneNumberHandler';

describe('PhoneNumberHandler', () => {
describe('getValidPhoneNumber', () => {
it('should return given parameter if phoneNumber is invalid', () => {
const undefinedPhoneNumberString = '';

const phoneNumber = PhoneNumberHandler.getValidPhoneNumber(undefinedPhoneNumberString);

expect(phoneNumber).toBe('');
});

it('should return given parameter if phoneNumber is unavailable', () => {
const impossiblePhoneNumberString = '000000000';

const phoneNumber = PhoneNumberHandler.getValidPhoneNumber(impossiblePhoneNumberString);

expect(phoneNumber).toBe('000000000')
});

describe('should return valid phoneNumber', () => {
it('if given phoneNumber is common mobile phone number', () => {
const phoneNumberString = '01012345678';
const phoneNumberStringWithHyphen = '010-1234-5678';
const phoneNumberStringWithSpace = '010 1234 5678';
const phoneNumberStringWithParenthesis = '(010)1234-5678';
const phoneNumberStringWithCountryCode = '+821012345678';
const phoneNumberStringWithCountryCodeAndHyphen = '+82-10-1234-5678';
const phoneNumberStringWithCountryCodeAndParenthesis = '+82(10)1234-5678';
const phoneNumberStringWithCountryCodeAndHyphenAndParenthesis = '+82-(10)1234-5678';

const phoneNumber = PhoneNumberHandler.getValidPhoneNumber(phoneNumberString);
const phoneNumberWithHyphen = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithHyphen);
const phoneNumberWithSpace = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithSpace);
const phoneNumberWithParenthesis = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithParenthesis);
const phoneNumberWithCountryCode = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithCountryCode);
const phoneNumberWithCountryCodeAndHyphen = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithCountryCodeAndHyphen);
const phoneNumberWithCountryCodeAndParenthesis = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithCountryCodeAndParenthesis);
const phoneNumberWithCountryCodeAndHyphenAndParenthesis = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithCountryCodeAndHyphenAndParenthesis);

expect(phoneNumber).toBe('+821012345678');
expect(phoneNumberWithHyphen).toBe('+821012345678');
expect(phoneNumberWithSpace).toBe('+821012345678');
expect(phoneNumberWithParenthesis).toBe('+821012345678');
expect(phoneNumberWithCountryCode).toBe('+821012345678');
expect(phoneNumberWithCountryCodeAndHyphen).toBe('+821012345678');
expect(phoneNumberWithCountryCodeAndParenthesis).toBe('+821012345678');
expect(phoneNumberWithCountryCodeAndHyphenAndParenthesis).toBe('+821012345678');
});

it('if given phoneNumber is common landline phone number', () => {
const phoneNumberString = '0311234567';
const phoneNumberStringWithHyphen = '031-123-4567';
const phoneNumberStringWithSpace = '031 123 4567';
const phoneNumberStringWithParenthesis = '(031)123-4567';
const phoneNumberStringWithCountryCode = '+82311234567';
const phoneNumberStringWithCountryCodeAndHyphen = '+82-31-123-4567';
const phoneNumberStringWithCountryCodeAndParenthesis = '+82(31)123-4567';
const phoneNumberStringWithCountryCodeAndHyphenAndParenthesis = '+82-(31)123-4567';

const phoneNumber = PhoneNumberHandler.getValidPhoneNumber(phoneNumberString);
const phoneNumberWithHyphen = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithHyphen);
const phoneNumberWithSpace = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithSpace);
const phoneNumberWithParenthesis = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithParenthesis);
const phoneNumberWithCountryCode = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithCountryCode);
const phoneNumberWithCountryCodeAndHyphen = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithCountryCodeAndHyphen);
const phoneNumberWithCountryCodeAndParenthesis = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithCountryCodeAndParenthesis);
const phoneNumberWithCountryCodeAndHyphenAndParenthesis = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithCountryCodeAndHyphenAndParenthesis);

expect(phoneNumber).toBe('+82311234567');
expect(phoneNumberWithHyphen).toBe('+82311234567');
expect(phoneNumberWithSpace).toBe('+82311234567');
expect(phoneNumberWithParenthesis).toBe('+82311234567');
expect(phoneNumberWithCountryCode).toBe('+82311234567');
expect(phoneNumberWithCountryCodeAndHyphen).toBe('+82311234567');
expect(phoneNumberWithCountryCodeAndParenthesis).toBe('+82311234567');
expect(phoneNumberWithCountryCodeAndHyphenAndParenthesis).toBe('+82311234567');
});

it('if given phoneNumber is 070 number', () => {
const phoneNumberString = '07012345678';
const phoneNumberStringWithHyphen = '070-1234-5678';
const phoneNumberStringWithSpace = '070 1234 5678';
const phoneNumberStringWithParenthesis = '(070)1234-5678';
const phoneNumberStringWithCountryCode = '+827012345678';
const phoneNumberStringWithCountryCodeAndHyphen = '+82-70-1234-5678';
const phoneNumberStringWithCountryCodeAndParenthesis = '+82(70)1234-5678';
const phoneNumberStringWithCountryCodeAndHyphenAndParenthesis = '+82-(70)1234-5678';

const phoneNumber = PhoneNumberHandler.getValidPhoneNumber(phoneNumberString);
const phoneNumberWithHyphen = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithHyphen);
const phoneNumberWithSpace = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithSpace);
const phoneNumberWithParenthesis = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithParenthesis);
const phoneNumberWithCountryCode = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithCountryCode);
const phoneNumberWithCountryCodeAndHyphen = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithCountryCodeAndHyphen);
const phoneNumberWithCountryCodeAndParenthesis = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithCountryCodeAndParenthesis);
const phoneNumberWithCountryCodeAndHyphenAndParenthesis = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithCountryCodeAndHyphenAndParenthesis);

expect(phoneNumber).toBe('+827012345678');
expect(phoneNumberWithHyphen).toBe('+827012345678');
expect(phoneNumberWithSpace).toBe('+827012345678');
expect(phoneNumberWithParenthesis).toBe('+827012345678');
expect(phoneNumberWithCountryCode).toBe('+827012345678');
expect(phoneNumberWithCountryCodeAndHyphen).toBe('+827012345678');
expect(phoneNumberWithCountryCodeAndParenthesis).toBe('+827012345678');
expect(phoneNumberWithCountryCodeAndHyphenAndParenthesis).toBe('+827012345678');
});

it('if given phoneNumber is XXXX-XXXX number', () => {
const phoneNumberString = '1234-5678';
const phoneNumberStringWithHyphen = '1234-5678';
const phoneNumberStringWithSpace = '1234 5678';
const phoneNumberStringWithParenthesis = '(1234)5678';
const phoneNumberStringWithCountryCode = '+8212345678';
const phoneNumberStringWithCountryCodeAndHyphen = '+82-1234-5678';
const phoneNumberStringWithCountryCodeAndParenthesis = '+82(1234)5678';
const phoneNumberStringWithCountryCodeAndHyphenAndParenthesis = '+82-(1234)5678';

const phoneNumber = PhoneNumberHandler.getValidPhoneNumber(phoneNumberString);
const phoneNumberWithHyphen = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithHyphen);
const phoneNumberWithSpace = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithSpace);
const phoneNumberWithParenthesis = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithParenthesis);
const phoneNumberWithCountryCode = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithCountryCode);
const phoneNumberWithCountryCodeAndHyphen = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithCountryCodeAndHyphen);
const phoneNumberWithCountryCodeAndParenthesis = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithCountryCodeAndParenthesis);
const phoneNumberWithCountryCodeAndHyphenAndParenthesis = PhoneNumberHandler.getValidPhoneNumber(phoneNumberStringWithCountryCodeAndHyphenAndParenthesis);

expect(phoneNumber).toBe('+8212345678');
expect(phoneNumberWithHyphen).toBe('+8212345678');
expect(phoneNumberWithSpace).toBe('+8212345678');
expect(phoneNumberWithParenthesis).toBe('+8212345678');
expect(phoneNumberWithCountryCode).toBe('+8212345678');
expect(phoneNumberWithCountryCodeAndHyphen).toBe('+8212345678');
expect(phoneNumberWithCountryCodeAndParenthesis).toBe('+8212345678');
expect(phoneNumberWithCountryCodeAndHyphenAndParenthesis).toBe('+8212345678');
});
});
});

describe('getLocalPhoneNumber', () => {
it('should return local phone number', () => {
expect(PhoneNumberHandler.getLocalPhoneNumber('+821012345678')).toBe('010-1234-5678');
expect(PhoneNumberHandler.getLocalPhoneNumber('+82311234567')).toBe('031-123-4567');
expect(PhoneNumberHandler.getLocalPhoneNumber('+827012345678')).toBe('070-1234-5678');
expect(PhoneNumberHandler.getLocalPhoneNumber('+8212345678')).toBe('1234-5678');
expect(PhoneNumberHandler.getLocalPhoneNumber('+8221234567')).toBe('02-123-4567');
});

it('should return local phone number with hyphen', () => {
expect(PhoneNumberHandler.getLocalPhoneNumber('+821012345678', false)).toBe('01012345678');
expect(PhoneNumberHandler.getLocalPhoneNumber('+82311234567', false)).toBe('0311234567');
expect(PhoneNumberHandler.getLocalPhoneNumber('+827012345678', false)).toBe('07012345678');
expect(PhoneNumberHandler.getLocalPhoneNumber('+8212345678', false)).toBe('12345678');
expect(PhoneNumberHandler.getLocalPhoneNumber('+8221234567', false)).toBe('021234567');
});
});
});
82 changes: 82 additions & 0 deletions src/shared/common/test/Snowflake.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Snowflake } from '../Snowflake';

const tests = [
{
timestamp: 1653653263221,
value: '6935924496540897281',
shard_id: 1,
sequence: 1,
},
{
timestamp: 1653653289600,
value: '6935924607185511855',
shard_id: 750,
sequence: 1455,
},
{
timestamp: 1653653311149,
value: '6935924697568571351',
shard_id: 750,
sequence: 4055,
},
];

describe('Snowflake', () => {
beforeEach(() => {
Snowflake.SEQUENCE = 1;
});

describe('generate', () => {
it('should generate a snowflake', () => {
for (const test of tests) {
Snowflake.SEQUENCE = test.sequence;
expect(
Snowflake.generate({
timestamp: test.timestamp,
shard_id: test.shard_id,
}).toString()
).toEqual(test.value);
}
});

it('should generate a random snowflake', () => {
const generated: string[] = [];
for (const test of [...Array(5)]) {
generated.push(Snowflake.generate());
}
expect(generated.length).toEqual(new Set(generated).size);
});

it('should generate a unique snowflake', () => {
const generated: string[] = [];
for (const test of [...Array(1e6)]) {
generated.push(Snowflake.generate());
}
expect(generated.length).toEqual(new Set(generated).size);
});
});

describe('deconstruct', () => {
it('should deconstruct a snowflake', () => {
for (const test of tests) {
const parsed = Snowflake.parse(test.value);
expect(parsed.timestamp).toEqual(test.timestamp);
expect(parsed.sequence).toEqual(test.sequence);
expect(parsed.shard_id).toEqual(test.shard_id);
}
});
});

describe('isValid', () => {
it('should deconstruct a snowflake', () => {
const tests = [
{ value: '6917082698162902015', valid: true },
{ value: 'abv', valid: false },
];
for (const test of tests) {
const parsed = Snowflake.isValid(test.value);
expect(parsed).toEqual(test.valid);
}
});
});
});

0 comments on commit 1e97692

Please sign in to comment.