diff --git a/packages/backend/src/core/IdService.ts b/packages/backend/src/core/IdService.ts index 94084ad84f..8aa6ccfc4e 100644 --- a/packages/backend/src/core/IdService.ts +++ b/packages/backend/src/core/IdService.ts @@ -3,10 +3,11 @@ import { ulid } from 'ulid'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { genAid, parseAid } from '@/misc/id/aid.js'; -import { genMeid } from '@/misc/id/meid.js'; -import { genMeidg } from '@/misc/id/meidg.js'; +import { genMeid, parseMeid } from '@/misc/id/meid.js'; +import { genMeidg, parseMeidg } from '@/misc/id/meidg.js'; import { genObjectId } from '@/misc/id/object-id.js'; import { bindThis } from '@/decorators.js'; +import { parseUlid } from '@/misc/id/ulid.js'; @Injectable() export class IdService { @@ -37,11 +38,10 @@ export class IdService { public parse(id: string): { date: Date; } { switch (this.method) { case 'aid': return parseAid(id); - // TODO - //case 'meid': - //case 'meidg': - //case 'ulid': - //case 'objectid': + case 'objectid': + case 'meid': return parseMeid(id); + case 'meidg': return parseMeidg(id); + case 'ulid': return parseUlid(id); default: throw new Error('unrecognized id generation method'); } } diff --git a/packages/backend/src/misc/id/aid.ts b/packages/backend/src/misc/id/aid.ts index 93a9929aa7..9e206ee98f 100644 --- a/packages/backend/src/misc/id/aid.ts +++ b/packages/backend/src/misc/id/aid.ts @@ -3,6 +3,8 @@ import * as crypto from 'node:crypto'; +export const aidRegExp = /^[0-9a-z]{10}$/; + const TIME2000 = 946684800000; let counter = crypto.randomBytes(2).readUInt16LE(0); diff --git a/packages/backend/src/misc/id/meid.ts b/packages/backend/src/misc/id/meid.ts index 30bbdf1698..337416b059 100644 --- a/packages/backend/src/misc/id/meid.ts +++ b/packages/backend/src/misc/id/meid.ts @@ -1,5 +1,8 @@ const CHARS = '0123456789abcdef'; +// same as object-id +export const meidRegExp = /^[0-9a-f]{24}$/; + function getTime(time: number) { if (time < 0) time = 0; if (time === 0) { @@ -24,3 +27,9 @@ function getRandom() { export function genMeid(date: Date): string { return getTime(date.getTime()) + getRandom(); } + +export function parseMeid(id: string): { date: Date; } { + return { + date: new Date(parseInt(id.slice(0, 12), 16) - 0x800000000000), + }; +} diff --git a/packages/backend/src/misc/id/meidg.ts b/packages/backend/src/misc/id/meidg.ts index d4aaaea1ba..19d0bc1fd2 100644 --- a/packages/backend/src/misc/id/meidg.ts +++ b/packages/backend/src/misc/id/meidg.ts @@ -3,6 +3,7 @@ const CHARS = '0123456789abcdef'; // 4bit Fixed hex value 'g' // 44bit UNIX Time ms in Hex // 48bit Random value in Hex +export const meidgRegExp = /^g[0-9a-f]{23}$/; function getTime(time: number) { if (time < 0) time = 0; @@ -26,3 +27,9 @@ function getRandom() { export function genMeidg(date: Date): string { return 'g' + getTime(date.getTime()) + getRandom(); } + +export function parseMeidg(id: string): { date: Date; } { + return { + date: new Date(parseInt(id.slice(1, 12), 16)), + }; +} diff --git a/packages/backend/src/misc/id/object-id.ts b/packages/backend/src/misc/id/object-id.ts index 392ea43301..aec3447bd7 100644 --- a/packages/backend/src/misc/id/object-id.ts +++ b/packages/backend/src/misc/id/object-id.ts @@ -1,5 +1,8 @@ const CHARS = '0123456789abcdef'; +// same as meid +export const objectIdRegExp = /^[0-9a-f]{24}$/; + function getTime(time: number) { if (time < 0) time = 0; if (time === 0) { @@ -24,3 +27,9 @@ function getRandom() { export function genObjectId(date: Date): string { return getTime(date.getTime()) + getRandom(); } + +export function parseObjectId(id: string): { date: Date; } { + return { + date: new Date(parseInt(id.slice(0, 8), 16) * 1000), + }; +} diff --git a/packages/backend/src/misc/id/ulid.ts b/packages/backend/src/misc/id/ulid.ts new file mode 100644 index 0000000000..e8aa752890 --- /dev/null +++ b/packages/backend/src/misc/id/ulid.ts @@ -0,0 +1,14 @@ +// Crockford's Base32 +// https://github.com/ulid/spec#encoding +const CHARS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; + +export const ulidRegExp = /^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$/; + +export function parseUlid(id: string): { date: Date; } { + const timestamp = id.slice(0, 10); + let time = 0; + for (let i = 0; i < 10; i++) { + time = time * 32 + CHARS.indexOf(timestamp[i]); + } + return { date: new Date(time) }; +} diff --git a/packages/backend/test/unit/misc/id.ts b/packages/backend/test/unit/misc/id.ts new file mode 100644 index 0000000000..ecd0e60a31 --- /dev/null +++ b/packages/backend/test/unit/misc/id.ts @@ -0,0 +1,44 @@ +import { aidRegExp, genAid, parseAid } from '@/misc/id/aid.js'; +import { genMeid, meidRegExp, parseMeid } from '@/misc/id/meid.js'; +import { genMeidg, meidgRegExp, parseMeidg } from '@/misc/id/meidg.js'; +import { genObjectId, objectIdRegExp, parseObjectId } from '@/misc/id/object-id.js'; +import { ulidRegExp, parseUlid } from '@/misc/id/ulid.js'; +import { ulid } from 'ulid'; +import { describe, test, expect } from '@jest/globals'; + +describe('misc:id', () => { + test('aid', () => { + const date = new Date(); + const gotAid = genAid(date); + expect(gotAid).toMatch(aidRegExp); + expect(parseAid(gotAid).date.getTime()).toBe(date.getTime()); + }); + + test('meid', () => { + const date = new Date(); + const gotMeid = genMeid(date); + expect(gotMeid).toMatch(meidRegExp); + expect(parseMeid(gotMeid).date.getTime()).toBe(date.getTime()); + }); + + test('meidg', () => { + const date = new Date(); + const gotMeidg = genMeidg(date); + expect(gotMeidg).toMatch(meidgRegExp); + expect(parseMeidg(gotMeidg).date.getTime()).toBe(date.getTime()); + }); + + test('objectid', () => { + const date = new Date(); + const gotObjectId = genObjectId(date); + expect(gotObjectId).toMatch(objectIdRegExp); + expect(Math.floor(parseObjectId(gotObjectId).date.getTime() / 1000)).toBe(Math.floor(date.getTime() / 1000)); + }); + + test('ulid', () => { + const date = new Date(); + const gotUlid = ulid(date.getTime()); + expect(gotUlid).toMatch(ulidRegExp); + expect(parseUlid(gotUlid).date.getTime()).toBe(date.getTime()); + }); +});