diff --git a/.config/docker_example.yml b/.config/docker_example.yml index 89e6925408..940b095fe2 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -114,6 +114,7 @@ redis: # Available methods: # aid ... Short, Millisecond accuracy +# aidx ... Millisecond accuracy # meid ... Similar to ObjectID, Millisecond accuracy # ulid ... Millisecond accuracy # objectid ... This is left for backward compatibility @@ -121,7 +122,7 @@ redis: # ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE # ID SETTINGS AFTER THAT! -id: 'aid' +id: 'aidx' # ┌─────────────────────┐ #───┘ Other configuration └───────────────────────────────────── diff --git a/.config/example.yml b/.config/example.yml index cca44ce88c..086a6ca8fc 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -125,6 +125,7 @@ redis: # Available methods: # aid ... Short, Millisecond accuracy +# aidx ... Millisecond accuracy # meid ... Similar to ObjectID, Millisecond accuracy # ulid ... Millisecond accuracy # objectid ... This is left for backward compatibility @@ -132,7 +133,7 @@ redis: # ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE # ID SETTINGS AFTER THAT! -id: 'aid' +id: 'aidx' # ┌─────────────────────┐ #───┘ Other configuration └───────────────────────────────────── diff --git a/.devcontainer/devcontainer.yml b/.devcontainer/devcontainer.yml index 5cfb6174ca..5dcd41599a 100644 --- a/.devcontainer/devcontainer.yml +++ b/.devcontainer/devcontainer.yml @@ -114,6 +114,7 @@ redis: # Available methods: # aid ... Short, Millisecond accuracy +# aidx ... Millisecond accuracy # meid ... Similar to ObjectID, Millisecond accuracy # ulid ... Millisecond accuracy # objectid ... This is left for backward compatibility @@ -121,7 +122,7 @@ redis: # ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE # ID SETTINGS AFTER THAT! -id: 'aid' +id: 'aidx' # ┌─────────────────────┐ #───┘ Other configuration └───────────────────────────────────── diff --git a/.github/misskey/test.yml b/.github/misskey/test.yml index f43f74be14..7a4aa4ae6c 100644 --- a/.github/misskey/test.yml +++ b/.github/misskey/test.yml @@ -12,4 +12,4 @@ db: redis: host: 127.0.0.1 port: 56312 -id: aid +id: aidx diff --git a/chart/files/default.yml b/chart/files/default.yml index e62032abfd..90b574b99f 100644 --- a/chart/files/default.yml +++ b/chart/files/default.yml @@ -135,6 +135,7 @@ redis: # Available methods: # aid ... Short, Millisecond accuracy +# aidx ... Millisecond accuracy # meid ... Similar to ObjectID, Millisecond accuracy # ulid ... Millisecond accuracy # objectid ... This is left for backward compatibility @@ -142,7 +143,7 @@ redis: # ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE # ID SETTINGS AFTER THAT! -id: "aid" +id: "aidx" # ┌─────────────────────┐ #───┘ Other configuration └───────────────────────────────────── diff --git a/packages/backend/package.json b/packages/backend/package.json index 4768ffd0ed..526d7d4678 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -120,6 +120,7 @@ "mime-types": "2.1.35", "misskey-js": "workspace:*", "ms": "3.0.0-canary.1", + "nanoid": "4.0.2", "nested-property": "4.0.0", "node-fetch": "3.3.2", "nodemailer": "6.9.4", diff --git a/packages/backend/src/core/IdService.ts b/packages/backend/src/core/IdService.ts index 186fd36b42..06c58ad8a1 100644 --- a/packages/backend/src/core/IdService.ts +++ b/packages/backend/src/core/IdService.ts @@ -8,6 +8,7 @@ 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 { genAidx, parseAidx } from '@/misc/id/aidx.js'; import { genMeid, parseMeid } from '@/misc/id/meid.js'; import { genMeidg, parseMeidg } from '@/misc/id/meidg.js'; import { genObjectId, parseObjectId } from '@/misc/id/object-id.js'; @@ -31,6 +32,7 @@ export class IdService { switch (this.method) { case 'aid': return genAid(date); + case 'aidx': return genAidx(date); case 'meid': return genMeid(date); case 'meidg': return genMeidg(date); case 'ulid': return ulid(date.getTime()); @@ -43,6 +45,7 @@ export class IdService { public parse(id: string): { date: Date; } { switch (this.method) { case 'aid': return parseAid(id); + case 'aidx': return parseAidx(id); case 'objectid': return parseObjectId(id); case 'meid': return parseMeid(id); case 'meidg': return parseMeidg(id); diff --git a/packages/backend/src/misc/id/aidx.ts b/packages/backend/src/misc/id/aidx.ts new file mode 100644 index 0000000000..5b031ea4c0 --- /dev/null +++ b/packages/backend/src/misc/id/aidx.ts @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: syuilo and other misskey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +// AIDX +// 長さ8の[2000年1月1日からの経過ミリ秒をbase36でエンコードしたもの] + 長さ4の[個体ID] + 長さ4の[カウンタ] +// (c) mei23 +// https://misskey.m544.net/notes/71899acdcc9859ec5708ac24 + +import { customAlphabet } from 'nanoid'; + +export const aidxRegExp = /^[0-9a-z]{16}$/; + +const TIME2000 = 946684800000; +const TIME_LENGTH = 8; +const NODE_LENGTH = 4; +const NOISE_LENGTH = 4; + +const nodeId = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', NODE_LENGTH)(); +let counter = 0; + +function getTime(time: number): string { + time = time - TIME2000; + if (time < 0) time = 0; + + return time.toString(36).padStart(TIME_LENGTH, '0').slice(-TIME_LENGTH); +} + +function getNoise(): string { + return counter.toString(36).padStart(NOISE_LENGTH, '0').slice(-NOISE_LENGTH); +} + +export function genAidx(date: Date): string { + const t = date.getTime(); + if (isNaN(t)) throw new Error('Failed to create AIDX: Invalid Date'); + counter++; + return getTime(t) + nodeId + getNoise(); +} + +export function parseAidx(id: string): { date: Date; } { + const time = parseInt(id.slice(0, TIME_LENGTH), 36) + TIME2000; + return { date: new Date(time) }; +} diff --git a/packages/backend/test/unit/AnnouncementService.ts b/packages/backend/test/unit/AnnouncementService.ts index f09ae3e2f4..d11808a4d8 100644 --- a/packages/backend/test/unit/AnnouncementService.ts +++ b/packages/backend/test/unit/AnnouncementService.ts @@ -12,7 +12,7 @@ import { GlobalModule } from '@/GlobalModule.js'; import { AnnouncementService } from '@/core/AnnouncementService.js'; import type { MiAnnouncement, AnnouncementsRepository, AnnouncementReadsRepository, UsersRepository, MiUser } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; -import { genAid } from '@/misc/id/aid.js'; +import { genAidx } from '@/misc/id/aidx.js'; import { CacheService } from '@/core/CacheService.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; @@ -33,7 +33,7 @@ describe('AnnouncementService', () => { function createUser(data: Partial = {}) { const un = secureRndstr(16); return usersRepository.insert({ - id: genAid(new Date()), + id: genAidx(new Date()), createdAt: new Date(), username: un, usernameLower: un, @@ -44,7 +44,7 @@ describe('AnnouncementService', () => { function createAnnouncement(data: Partial = {}) { return announcementsRepository.insert({ - id: genAid(new Date()), + id: genAidx(new Date()), createdAt: new Date(), updatedAt: null, title: 'Title', diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts index 32a686c83a..52224acabf 100644 --- a/packages/backend/test/unit/RoleService.ts +++ b/packages/backend/test/unit/RoleService.ts @@ -14,7 +14,7 @@ import { RoleService } from '@/core/RoleService.js'; import type { MiRole, RolesRepository, RoleAssignmentsRepository, UsersRepository, MiUser } from '@/models/index.js'; import { DI } from '@/di-symbols.js'; import { MetaService } from '@/core/MetaService.js'; -import { genAid } from '@/misc/id/aid.js'; +import { genAidx } from '@/misc/id/aidx.js'; import { CacheService } from '@/core/CacheService.js'; import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; @@ -37,7 +37,7 @@ describe('RoleService', () => { function createUser(data: Partial = {}) { const un = secureRndstr(16); return usersRepository.insert({ - id: genAid(new Date()), + id: genAidx(new Date()), createdAt: new Date(), username: un, usernameLower: un, @@ -48,7 +48,7 @@ describe('RoleService', () => { function createRole(data: Partial = {}) { return rolesRepository.insert({ - id: genAid(new Date()), + id: genAidx(new Date()), createdAt: new Date(), updatedAt: new Date(), lastUsedAt: new Date(), diff --git a/packages/backend/test/unit/misc/id.ts b/packages/backend/test/unit/misc/id.ts index 8cc2ea425f..57b4ea9947 100644 --- a/packages/backend/test/unit/misc/id.ts +++ b/packages/backend/test/unit/misc/id.ts @@ -6,6 +6,7 @@ import { ulid } from 'ulid'; import { describe, test, expect } from '@jest/globals'; import { aidRegExp, genAid, parseAid } from '@/misc/id/aid.js'; +import { aidxRegExp, genAidx, parseAidx } from '@/misc/id/aidx.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'; @@ -19,6 +20,13 @@ describe('misc:id', () => { expect(parseAid(gotAid).date.getTime()).toBe(date.getTime()); }); + test('aidx', () => { + const date = new Date(); + const gotAidx = genAidx(date); + expect(gotAidx).toMatch(aidxRegExp); + expect(parseAidx(gotAidx).date.getTime()).toBe(date.getTime()); + }); + test('meid', () => { const date = new Date(); const gotMeid = genMeid(date); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 950fa3e37b..32d9916a37 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -260,6 +260,9 @@ importers: ms: specifier: 3.0.0-canary.1 version: 3.0.0-canary.1 + nanoid: + specifier: ^4.0.2 + version: 4.0.2 nested-property: specifier: 4.0.0 version: 4.0.0 @@ -6998,7 +7001,7 @@ packages: ts-dedent: 2.2.0 type-fest: 2.19.0 vue: 3.3.4 - vue-component-type-helpers: 1.8.8 + vue-component-type-helpers: 1.8.10 transitivePeerDependencies: - encoding - supports-color @@ -16069,6 +16072,12 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + /nanoid@4.0.2: + resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==} + engines: {node: ^14 || ^16 || >=18} + hasBin: true + dev: false + /nanomatch@1.2.13: resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} engines: {node: '>=0.10.0'} @@ -21040,8 +21049,8 @@ packages: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} - /vue-component-type-helpers@1.8.8: - resolution: {integrity: sha512-Ohv9HQY92nSbpReC6WhY0X4YkOszHzwUHaaN/lev5tHQLM1AEw+LrLeB2bIGIyKGDU7ZVrncXcv/oBny4rjbYg==} + /vue-component-type-helpers@1.8.10: + resolution: {integrity: sha512-FJtmfw2Gn6eQ8kAVNEhw9nYIzWmVQJjdyQRtJXZ7tgXh/FoZhQnZ2KyxR+NuF9U4iZLBvSspeetIpnP9yxxyMw==} dev: true /vue-docgen-api@4.64.1(vue@3.3.4):