1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

chore: add automatic ID generation algorithm (#7001)

This PR adds a function to automatically generate a project ID on
creation. Using this when the id is missing will be handled in following
PRs.

The function uses the existing `slug` package to create a slug, and then
takes the 12 characters of a uuidv4 string to generate an ID.

The included tests check that the 12 character hash is added and that
the resulting string is url friendly (by checking that
`encodeURIComponent` doesn't change it).

We could also test a lot of edge cases (such as dealing with double
spaces, trimming the string, etc), but I think that's better handled by
the library itself (but you can check out what I removed in
2d9bcb6390
for an idea).

The function doesn't really need to be in the service; it could be moved to a util. But for proximity, I'll create it here first.
This commit is contained in:
Thomas Heartman 2024-05-08 09:26:35 +02:00 committed by GitHub
parent 6477ccf34b
commit 861ae6aa93
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 51 additions and 29 deletions

View File

@ -184,6 +184,7 @@
"@types/owasp-password-strength-test": "1.3.2",
"@types/pg": "8.11.5",
"@types/semver": "7.5.8",
"@types/slug": "^5.0.8",
"@types/stoppable": "1.1.3",
"@types/supertest": "6.0.2",
"@types/type-is": "1.6.6",
@ -202,6 +203,7 @@
"nock": "13.5.4",
"openapi-enforcer": "1.23.0",
"proxyquire": "2.1.3",
"slug": "^9.0.0",
"source-map-support": "0.5.21",
"superagent": "9.0.2",
"supertest": "6.3.4",

View File

@ -1,3 +1,4 @@
import fc from 'fast-check';
import { createTestConfig } from '../../../test/config/test-config';
import { BadDataError } from '../../error';
import { type IBaseEvent, RoleName, TEST_AUDIT_USER } from '../../types';
@ -301,3 +302,29 @@ describe('enterprise extension: enable change requests', () => {
).resolves.toBeTruthy();
});
});
describe('project ID generation', () => {
const createService = () => {
const config = createTestConfig();
const service = createFakeProjectService(config);
return service;
};
test('the name that comes out is always url friendly', () => {
const service = createService();
fc.assert(
fc.property(fc.string(), (name) => {
const projectId = service.generateProjectId(name);
expect(projectId).toMatch(encodeURIComponent(projectId));
}),
);
});
test('it adds a 12 character hex to the end of the id', () => {
const service = createService();
const projectId = service.generateProjectId('one');
expect(projectId).toMatch(/^one-[a-f0-9]{12}$/);
});
});

View File

@ -1,5 +1,6 @@
import { subDays } from 'date-fns';
import { ValidationError } from 'joi';
import slug from 'slug';
import type { IAuditUser, IUser } from '../../types/user';
import type {
AccessService,
@ -60,7 +61,7 @@ import type {
import type FeatureToggleService from '../feature-toggle/feature-toggle-service';
import IncompatibleProjectError from '../../error/incompatible-project-error';
import ProjectWithoutOwnerError from '../../error/project-without-owner-error';
import { arraysHaveSameItems } from '../../util';
import { arraysHaveSameItems, randomId } from '../../util';
import type { GroupService } from '../../services/group-service';
import type { IGroupRole } from '../../types/group';
import type { FavoritesService } from '../../services/favorites-service';
@ -294,6 +295,13 @@ export default class ProjectService {
}
}
generateProjectId(name: string): string {
const urlFriendly = slug(name);
const tail = randomId().slice(-12);
const id = `${urlFriendly}-${tail}`;
return id;
}
async createProject(
newProject: CreateProject,
user: IUser,

View File

@ -1668,6 +1668,11 @@
"@types/mime" "*"
"@types/node" "*"
"@types/slug@^5.0.8":
version "5.0.8"
resolved "https://registry.yarnpkg.com/@types/slug/-/slug-5.0.8.tgz#76d40f033a8a99793f3dcbfb3f187ab9014f9d47"
integrity sha512-mblTWR1OST257k1gZ3QvqG+ERSr8Ea6dyM1FH6Jtm4jeXi0/r0/95VNctofuiywPxCVQuE8AuFoqmvJ4iVUlXQ==
"@types/ssri@*":
version "7.1.1"
resolved "https://registry.yarnpkg.com/@types/ssri/-/ssri-7.1.1.tgz#2a2c94abf0d3a8c3b07bb4ff08142dd571407bb5"
@ -6598,6 +6603,11 @@ slice-ansi@^7.0.0:
ansi-styles "^6.2.1"
is-fullwidth-code-point "^5.0.0"
slug@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/slug/-/slug-9.0.0.tgz#68f968a79ce5156c6606b7b2e233ed0ffab94bdf"
integrity sha512-ixytnHlpHPWM56heaGgYe/M8tDAcpJcsg/zBuyElbFDOORzMGOeP3Te6iJBRVYu3WQEiWLQPb70Gh9ig/sZgGQ==
slugify@~1.4.7:
version "1.4.7"
resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.4.7.tgz#e42359d505afd84a44513280868e31202a79a628"
@ -6793,16 +6803,7 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -6841,14 +6842,7 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@ -7475,7 +7469,7 @@ wordwrap@>=0.0.2:
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@ -7493,15 +7487,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"