Skip to content

Commit a1b8290

Browse files
Merge branch 'main' into local-upload-tracking
2 parents 13c71cf + 99770b9 commit a1b8290

File tree

4 files changed

+51
-10
lines changed

4 files changed

+51
-10
lines changed

apps/web/app/(org)/dashboard/_components/Navbar/Items.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ const AdminNavItems = ({ toggleMobileNav }: Props) => {
364364
</div>
365365
<div className="pb-4 mt-auto w-full">
366366
<AnimatePresence>
367-
{!sidebarCollapsed && (
367+
{!sidebarCollapsed && !userIsSubscribed && (
368368
<motion.div
369369
initial={{ scale: 0 }}
370370
animate={{ scale: 1 }}

packages/web-backend/src/Spaces/SpacesPolicy.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,27 @@ export class SpacesPolicy extends Effect.Service<SpacesPolicy>()(
1111
effect: Effect.gen(function* () {
1212
const repo = yield* SpacesRepo;
1313

14-
const isMember = (spaceId: string) =>
14+
const hasMembership = (spaceId: string) =>
1515
Policy.policy(
1616
Effect.fn(function* (user) {
1717
return Option.isSome(yield* repo.membership(user.id, spaceId));
1818
}),
1919
);
2020

21-
return { isMember };
21+
const isOwner = (spaceId: string) =>
22+
Policy.policy(
23+
Effect.fn(function* (user) {
24+
const space = yield* repo.getById(spaceId);
25+
if (Option.isNone(space)) return false;
26+
27+
return space.value.createdById === user.id;
28+
}),
29+
);
30+
31+
const isMember = (spaceId: string) =>
32+
Policy.any(isOwner(spaceId), hasMembership(spaceId));
33+
34+
return { isMember, isOwner };
2235
}),
2336
dependencies: [
2437
OrganisationsRepo.Default,

packages/web-backend/src/Spaces/SpacesRepo.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ export class SpacesRepo extends Effect.Service<SpacesRepo>()("SpacesRepo", {
4545
),
4646
)
4747
.pipe(Effect.map(Array.get(0))),
48+
getById: (spaceId: string) =>
49+
db
50+
.execute((db) =>
51+
db.select().from(Db.spaces).where(Dz.eq(Db.spaces.id, spaceId)),
52+
)
53+
.pipe(Effect.map(Array.get(0))),
4854
};
4955
}),
5056
dependencies: [Database.Default],

packages/web-domain/src/Policy.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
// shoutout https://lucas-barake.github.io/building-a-composable-policy-system/
22

33
import { type Brand, Context, Data, Effect, type Option, Schema } from "effect";
4-
4+
import type { NonEmptyReadonlyArray } from "effect/Array";
55
import { CurrentUser } from "./Authentication.ts";
66

7-
export type Policy<E = never, R = never> = Brand.Branded<
8-
Effect.Effect<void, PolicyDeniedError | E, CurrentUser | R>,
9-
"Private"
7+
export type Policy<E = never, R = never> = Effect.Effect<
8+
void,
9+
PolicyDeniedError | E,
10+
CurrentUser | R
1011
>;
1112

12-
export type PublicPolicy<E = never, R = never> = Brand.Branded<
13-
Effect.Effect<void, PolicyDeniedError | E, R>,
14-
"Public"
13+
export type PublicPolicy<E = never, R = never> = Effect.Effect<
14+
void,
15+
PolicyDeniedError | E,
16+
R
1517
>;
1618

1719
export class PolicyDeniedError extends Schema.TaggedError<PolicyDeniedError>()(
@@ -73,3 +75,23 @@ export const withPublicPolicy =
7375
<E, R>(policy: PublicPolicy<E, R>) =>
7476
<A, E2, R2>(self: Effect.Effect<A, E2, R2>) =>
7577
Effect.zipRight(policy, self);
78+
79+
/**
80+
* Composes multiple policies with AND semantics - all policies must pass.
81+
* Returns a new policy that succeeds only if all the given policies succeed.
82+
*/
83+
export const all = <E, R>(
84+
...policies: NonEmptyReadonlyArray<Policy<E, R>>
85+
): Policy<E, R> =>
86+
Effect.all(policies, {
87+
concurrency: 1,
88+
discard: true,
89+
});
90+
91+
/**
92+
* Composes multiple policies with OR semantics - at least one policy must pass.
93+
* Returns a new policy that succeeds if any of the given policies succeed.
94+
*/
95+
export const any = <E, R>(
96+
...policies: NonEmptyReadonlyArray<Policy<E, R>>
97+
): Policy<E, R> => Effect.firstSuccessOf(policies);

0 commit comments

Comments
 (0)