-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: identify org-wide features per user (#17010)
- Loading branch information
Showing
11 changed files
with
148 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import type { IFeaturesRepository } from "./features.repository.interface"; | ||
|
||
export class MockFeaturesRepository implements IFeaturesRepository { | ||
async checkIfUserHasFeature(userId: number, slug: string) { | ||
return slug === "mock-feature"; | ||
} | ||
async checkIfFeatureIsEnabledGlobally() { | ||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 23 additions & 0 deletions
23
packages/features/flags/operations/check-if-user-has-feature.controller.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import prismock from "../../../../tests/libs/__mocks__/prisma"; | ||
|
||
import { expect, it } from "vitest"; | ||
|
||
import { checkIfUserHasFeatureController } from "./check-if-user-has-feature.controller"; | ||
|
||
/** | ||
* Since our current controller doesn't run any authentication checks or input validation, | ||
* this test is identical to the test in the use case. | ||
*/ | ||
it("checks if user has access to feature", async () => { | ||
const userId = 1; | ||
await prismock.userFeatures.create({ | ||
data: { | ||
userId, | ||
featureId: "mock-feature", | ||
assignedBy: "1", | ||
updatedAt: new Date(), | ||
}, | ||
}); | ||
await expect(checkIfUserHasFeatureController(userId, "nonexistent-feature")).resolves.toBe(false); | ||
await expect(checkIfUserHasFeatureController(userId, "mock-feature")).resolves.toBe(true); | ||
}); |
32 changes: 32 additions & 0 deletions
32
packages/features/flags/operations/check-if-user-has-feature.controller.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { startSpan } from "@sentry/nextjs"; | ||
|
||
import { checkIfUserHasFeatureUseCase } from "./check-if-user-has-feature.use-case"; | ||
|
||
/** | ||
* Controllers use Presenters to convert the data to a UI-friendly format just before | ||
* returning it to the "consumer". This helps us ship less JavaScript to the client (logic | ||
* and libraries to convert the data), helps prevent leaking any sensitive properties, like | ||
* emails or hashed passwords, and also helps us slim down the amount of data we're sending | ||
* back to the client. | ||
*/ | ||
function presenter(userHasFeature: boolean) { | ||
return startSpan({ name: "checkIfUserHasFeature Presenter", op: "serialize" }, () => { | ||
return userHasFeature; | ||
}); | ||
} | ||
|
||
/** | ||
* Controllers perform authentication checks and input validation before passing the input | ||
* to the specific use cases. Controllers orchestrate Use Cases. They don't implement any | ||
* logic, but define the whole operations using use cases. | ||
*/ | ||
export async function checkIfUserHasFeatureController( | ||
userId: number | undefined, | ||
slug: string | ||
): Promise<ReturnType<typeof presenter>> { | ||
return await startSpan({ name: "checkIfUserHasFeature Controller" }, async () => { | ||
if (!userId) throw new Error("Missing userId in checkIfUserHasFeatureController"); | ||
const userHasFeature = await checkIfUserHasFeatureUseCase(userId, slug); | ||
return presenter(userHasFeature); | ||
}); | ||
} |
2 changes: 2 additions & 0 deletions
2
packages/features/flags/operations/check-if-user-has-feature.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/** We export the controller since it's the main entry point for this operation in the app */ | ||
export { checkIfUserHasFeatureController } from "./check-if-user-has-feature.controller"; |
21 changes: 21 additions & 0 deletions
21
packages/features/flags/operations/check-if-user-has-feature.use-case.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import prismock from "../../../../tests/libs/__mocks__/prisma"; | ||
|
||
import { expect, it } from "vitest"; | ||
|
||
import { checkIfUserHasFeatureUseCase } from "./check-if-user-has-feature.use-case"; | ||
|
||
// This is identical to the test in the controller since the controller currently | ||
// doesn't run any authentication checks or input validation. | ||
it("returns if user has access to feature", async () => { | ||
const userId = 1; | ||
await prismock.userFeatures.create({ | ||
data: { | ||
userId, | ||
featureId: "mock-feature", | ||
assignedBy: "1", | ||
updatedAt: new Date(), | ||
}, | ||
}); | ||
await expect(checkIfUserHasFeatureUseCase(userId, "nonexistent-feature")).resolves.toBe(false); | ||
await expect(checkIfUserHasFeatureUseCase(userId, "mock-feature")).resolves.toBe(true); | ||
}); |
18 changes: 18 additions & 0 deletions
18
packages/features/flags/operations/check-if-user-has-feature.use-case.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { startSpan } from "@sentry/nextjs"; | ||
|
||
import { FeaturesRepository } from "../features.repository"; | ||
|
||
/** | ||
* Use Cases represent individual operations, like "Create Feature" or "Sign In" or "Toggle Feature". | ||
* Accept pre-validated input (from controllers) and handle authorization checks. | ||
* Use Repositories and Services to access data sources and communicate with external systems. | ||
* Use cases should not use other use cases. That's a code smell. It means the use case | ||
* does multiple things and should be broken down into multiple use cases. | ||
*/ | ||
export function checkIfUserHasFeatureUseCase(userId: number, slug: string): Promise<boolean> { | ||
return startSpan({ name: "checkIfUserHasFeature UseCase", op: "function" }, async () => { | ||
const featuresRepository = new FeaturesRepository(); | ||
|
||
return await featuresRepository.checkIfUserHasFeature(userId, slug); | ||
}); | ||
} |
5 changes: 5 additions & 0 deletions
5
packages/prisma/migrations/20241119185538_add_index_for_user_and_team_features/migration.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
-- CreateIndex | ||
CREATE INDEX "TeamFeatures_teamId_featureId_idx" ON "TeamFeatures"("teamId", "featureId"); | ||
|
||
-- CreateIndex | ||
CREATE INDEX "UserFeatures_userId_featureId_idx" ON "UserFeatures"("userId", "featureId"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters