Skip to content

Commit 75bf4c0

Browse files
committed
fix(articles): list tenant articles for scoped editors
1 parent 92f2d0b commit 75bf4c0

2 files changed

Lines changed: 26 additions & 35 deletions

File tree

server/api/articles/search.ts

Lines changed: 19 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,63 +2,47 @@ import type { H3Event } from 'h3'
22

33
export default defineEventHandler(async (event: H3Event) => {
44
const { translate: t } = await useServerI18n(event)
5-
const user = (await getServerSession(event))?.user
6-
if (!user?.id) throw createError({ statusCode: 401, message: t('common.errors.unauthorized')! })
5+
const { user, membership } = await requireTenantScope(event, 'ARTICLE_WRITE')
76

87
const { skip, take } = await getPagination(event)
98
const query = getQuery(event).query as string | undefined
109

1110
const db = await getEnhancedPrisma(user)
1211

13-
const clientSite = await db.clientSite.findFirst({
14-
where: { users: { some: { id: user.id } } },
15-
select: { id: true },
16-
})
17-
18-
if (!clientSite?.id) {
19-
return { data: [], total: 0 }
20-
}
12+
const clientSiteId = membership.clientSiteId
2113

2214
const aiUserIds = await db.user
2315
.findMany({
24-
where: { clientSiteId: clientSite.id, role: 'ai' },
16+
where: { clientSiteId, role: 'ai' },
2517
select: { id: true },
2618
})
2719
.then((users) => users.map((u) => u.id))
2820

21+
const canEditOthers = hasTenantScope(membership, 'ARTICLE_WRITE_OTHERS')
22+
const authorFilter = canEditOthers ? {} : { userId: { in: [user.id, ...aiUserIds] } }
23+
const where = {
24+
clientSiteId,
25+
...authorFilter,
26+
...(query && {
27+
OR: [
28+
{ title: { contains: query, mode: 'insensitive' as const } },
29+
{ excerpt: { contains: query, mode: 'insensitive' as const } },
30+
{ content: { contains: query, mode: 'insensitive' as const } },
31+
],
32+
}),
33+
}
34+
2935
const [articles, total] = await Promise.all([
3036
db.article.findMany({
31-
where: {
32-
clientSiteId: clientSite.id,
33-
userId: { in: [user.id, ...aiUserIds] },
34-
...(query && {
35-
OR: [
36-
{ title: { contains: query, mode: 'insensitive' } },
37-
{ excerpt: { contains: query, mode: 'insensitive' } },
38-
{ content: { contains: query, mode: 'insensitive' } },
39-
],
40-
}),
41-
},
37+
where,
4238
orderBy: { createdAt: 'desc' },
4339
skip,
4440
take,
4541
// Feeds the admin table's language column. Drafts are visible here on purpose — this is
4642
// the owning admin's own list, and "awaiting review" is the whole point of the column.
4743
include: { translations: { select: { language: true, status: true, slug: true } } },
4844
}),
49-
db.article.count({
50-
where: {
51-
clientSiteId: clientSite.id,
52-
userId: { in: [user.id, ...aiUserIds] },
53-
...(query && {
54-
OR: [
55-
{ title: { contains: query, mode: 'insensitive' } },
56-
{ excerpt: { contains: query, mode: 'insensitive' } },
57-
{ content: { contains: query, mode: 'insensitive' } },
58-
],
59-
}),
60-
},
61-
}),
45+
db.article.count({ where }),
6246
])
6347

6448
return { data: articles, total }

tests/server/tenantMembership.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ describe('tenant boundary wiring', () => {
114114
]
115115
for (const [file, guard] of expectations) expect(source(file)).toContain(guard)
116116
})
117+
118+
it('uses ARTICLE_WRITE_OTHERS when building the admin article list', () => {
119+
const endpoint = source('server/api/articles/search.ts')
120+
expect(endpoint).toContain("requireTenantScope(event, 'ARTICLE_WRITE')")
121+
expect(endpoint).toContain("hasTenantScope(membership, 'ARTICLE_WRITE_OTHERS')")
122+
expect(endpoint).toContain('canEditOthers ? {}')
123+
})
117124
})
118125

119126
describe('invitation onboarding wiring', () => {

0 commit comments

Comments
 (0)