nextjs-cms 0.9.36 → 0.9.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +279 -279
  3. package/dist/api/actions/files.d.ts.map +1 -1
  4. package/dist/api/actions/pages.d.ts +4 -3
  5. package/dist/api/actions/pages.d.ts.map +1 -1
  6. package/dist/api/actions/pages.js +1 -0
  7. package/dist/api/index.d.ts +974 -1
  8. package/dist/api/index.d.ts.map +1 -1
  9. package/dist/api/index.js +13 -0
  10. package/dist/api/lib/serverActions.d.ts +49 -13
  11. package/dist/api/lib/serverActions.d.ts.map +1 -1
  12. package/dist/api/lib/serverActions.js +31 -103
  13. package/dist/api/root.d.ts +1916 -18
  14. package/dist/api/root.d.ts.map +1 -1
  15. package/dist/api/root.js +83 -18
  16. package/dist/api/routers/config.d.ts.map +1 -1
  17. package/dist/api/routers/gallery.d.ts +0 -1
  18. package/dist/api/routers/gallery.d.ts.map +1 -1
  19. package/dist/api/routers/gallery.js +8 -36
  20. package/dist/api/routers/hasItemsSection.d.ts +30 -5
  21. package/dist/api/routers/hasItemsSection.d.ts.map +1 -1
  22. package/dist/api/routers/navigation.d.ts +3 -3
  23. package/dist/api/routers/simpleSection.d.ts +15 -3
  24. package/dist/api/routers/simpleSection.d.ts.map +1 -1
  25. package/dist/api/trpc/root.d.ts +41 -7
  26. package/dist/api/trpc/root.d.ts.map +1 -1
  27. package/dist/api/trpc/routers/config.d.ts.map +1 -1
  28. package/dist/api/trpc/routers/hasItemsSection.d.ts.map +1 -1
  29. package/dist/api/trpc/routers/logs.d.ts +37 -4
  30. package/dist/api/trpc/routers/logs.d.ts.map +1 -1
  31. package/dist/api/trpc/routers/logs.js +92 -43
  32. package/dist/api/trpc/routers/navigation.d.ts +4 -3
  33. package/dist/api/trpc/routers/navigation.d.ts.map +1 -1
  34. package/dist/api/trpc/routers/simpleSection.d.ts.map +1 -1
  35. package/dist/api/trpc/server.d.ts +123 -21
  36. package/dist/api/trpc/server.d.ts.map +1 -1
  37. package/dist/auth/trpc.d.ts +1 -1
  38. package/dist/auth/trpc.d.ts.map +1 -1
  39. package/dist/auth/trpc.js +1 -0
  40. package/dist/cli/lib/db-config.js +10 -10
  41. package/dist/cli/lib/update-sections.d.ts.map +1 -1
  42. package/dist/cli/lib/update-sections.js +67 -34
  43. package/dist/core/config/config-loader.d.ts +2 -2
  44. package/dist/core/db/table-checker/MysqlTable.js +10 -10
  45. package/dist/core/factories/section-factory-with-esbuild.js +9 -9
  46. package/dist/core/factories/section-factory-with-jiti.js +9 -9
  47. package/dist/core/fields/date-range.d.ts +4 -4
  48. package/dist/core/fields/dateRange.d.ts +115 -0
  49. package/dist/core/fields/dateRange.d.ts.map +1 -0
  50. package/dist/core/fields/dateRange.js +149 -0
  51. package/dist/core/fields/slug.d.ts +55 -3
  52. package/dist/core/fields/slug.d.ts.map +1 -1
  53. package/dist/core/fields/slug.js +56 -2
  54. package/dist/core/sections/category.d.ts +4 -4
  55. package/dist/core/sections/hasItems.d.ts +4 -4
  56. package/dist/core/sections/section.d.ts +3 -3
  57. package/dist/core/sections/simple.d.ts +4 -4
  58. package/dist/logging/event-labels.d.ts +11 -0
  59. package/dist/logging/event-labels.d.ts.map +1 -0
  60. package/dist/logging/event-labels.js +14 -0
  61. package/dist/logging/log.d.ts +1 -1
  62. package/dist/logging/log.d.ts.map +1 -1
  63. package/dist/translations/base/en.d.ts +36 -0
  64. package/dist/translations/base/en.d.ts.map +1 -1
  65. package/dist/translations/base/en.js +36 -0
  66. package/dist/translations/client.d.ts +436 -4
  67. package/dist/translations/client.d.ts.map +1 -1
  68. package/dist/translations/locale-cookie.d.ts +24 -0
  69. package/dist/translations/locale-cookie.d.ts.map +1 -0
  70. package/dist/translations/locale-cookie.js +44 -0
  71. package/dist/translations/locale-utils.d.ts +8 -0
  72. package/dist/translations/locale-utils.d.ts.map +1 -0
  73. package/dist/translations/locale-utils.js +11 -0
  74. package/dist/translations/localization.d.ts +40 -0
  75. package/dist/translations/localization.d.ts.map +1 -0
  76. package/dist/translations/localization.js +48 -0
  77. package/dist/translations/server.d.ts +436 -4
  78. package/dist/translations/server.d.ts.map +1 -1
  79. package/dist/validators/dateRange.d.ts +11 -0
  80. package/dist/validators/dateRange.d.ts.map +1 -0
  81. package/dist/validators/dateRange.js +16 -0
  82. package/package.json +10 -3
  83. package/dist/api/client.d.ts +0 -30
  84. package/dist/api/client.d.ts.map +0 -1
  85. package/dist/api/client.js +0 -82
  86. package/dist/api/server.d.ts +0 -2748
  87. package/dist/api/server.d.ts.map +0 -1
  88. package/dist/api/server.js +0 -100
  89. package/dist/api/trpc/error-logging.d.ts +0 -14
  90. package/dist/api/trpc/error-logging.d.ts.map +0 -1
  91. package/dist/api/trpc/error-logging.js +0 -75
  92. package/dist/api/utils/async-caller-proxy.d.ts +0 -2
  93. package/dist/api/utils/async-caller-proxy.d.ts.map +0 -1
  94. package/dist/api/utils/async-caller-proxy.js +0 -36
  95. package/dist/api/utils/lazy-caller-proxy.d.ts +0 -2
  96. package/dist/api/utils/lazy-caller-proxy.d.ts.map +0 -1
  97. package/dist/api/utils/lazy-caller-proxy.js +0 -36
  98. package/dist/api/utils/router-types.d.ts +0 -7
  99. package/dist/api/utils/router-types.d.ts.map +0 -1
  100. package/dist/api/utils/router-types.js +0 -0
  101. package/dist/plugins/manifest.d.ts +0 -28
  102. package/dist/plugins/manifest.d.ts.map +0 -1
  103. package/dist/plugins/manifest.js +0 -83
  104. package/dist/plugins/registry.d.ts +0 -22
  105. package/dist/plugins/registry.d.ts.map +0 -1
  106. package/dist/plugins/registry.js +0 -25
  107. package/dist/utils/log.d.ts +0 -18
  108. package/dist/utils/log.d.ts.map +0 -1
  109. package/dist/utils/log.js +0 -28
@@ -1 +1 @@
1
- {"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../../../src/api/trpc/root.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAsBvB,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAiBqxe,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAA+1nB,CAAC;;;;;2BAA0E,CAAC;;;;;;2BAAgH,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAAt4/B,CAAC;;;;;2BAAuE,CAAC;;;;;;2BAA6G,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAH1nH,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAA"}
1
+ {"version":3,"file":"root.d.ts","sourceRoot":"","sources":["../../../src/api/trpc/root.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAsBvB,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAiBw8d,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAA+0mB,CAAC;;;;;2BAAyE,CAAC;;;;;;2BAA8G,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2BAA7o+B,CAAC;;;;;2BAAsE,CAAC;;;;;;2BAA2G,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAHhhH,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,OAAO,WAAW,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../src/api/trpc/routers/config.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAaknhB,CAAC;;;;;;GAD1ohB,CAAA"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../src/api/trpc/routers/config.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBAagvgB,CAAC;;;;;;GADxwgB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"hasItemsSection.d.ts","sourceRoot":"","sources":["../../../../src/api/trpc/routers/hasItemsSection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAA;AAcxB,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAwGq1hC,CAAC;;;;;uBAA0E,CAAC;;;;;;uBAAgH,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GADljiC,CAAA"}
1
+ {"version":3,"file":"hasItemsSection.d.ts","sourceRoot":"","sources":["../../../../src/api/trpc/routers/hasItemsSection.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAA;AAcxB,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uBAwGm8/B,CAAC;;;;;uBAAyE,CAAC;;;;;;uBAA8G,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAD7pgC,CAAA"}
@@ -24,7 +24,10 @@ export declare const logsRouter: import("@trpc/server").TRPCBuiltRouter<{
24
24
  list: import("@trpc/server").TRPCQueryProcedure<{
25
25
  input: {
26
26
  limit?: number | undefined;
27
- offset?: number | undefined;
27
+ cursor?: {
28
+ createdAt: unknown;
29
+ id: number;
30
+ } | null | undefined;
28
31
  eventType?: string | undefined;
29
32
  actorId?: string | undefined;
30
33
  sectionName?: string | undefined;
@@ -35,6 +38,7 @@ export declare const logsRouter: import("@trpc/server").TRPCBuiltRouter<{
35
38
  } | undefined;
36
39
  output: {
37
40
  items: {
41
+ actorDeleted: boolean;
38
42
  id: number;
39
43
  eventType: string;
40
44
  actorId: string | null;
@@ -48,10 +52,39 @@ export declare const logsRouter: import("@trpc/server").TRPCBuiltRouter<{
48
52
  userAgent: string | null;
49
53
  source: string | null;
50
54
  createdAt: Date;
55
+ actorCurrentFullName: string | null;
56
+ }[];
57
+ nextCursor: {
58
+ createdAt: Date;
59
+ id: number;
60
+ } | null;
61
+ };
62
+ meta: object;
63
+ }>;
64
+ recent: import("@trpc/server").TRPCQueryProcedure<{
65
+ input: {
66
+ limit?: number | undefined;
67
+ } | undefined;
68
+ output: {
69
+ id: number;
70
+ eventType: string;
71
+ actorId: string | null;
72
+ actorUsername: string | null;
73
+ entityLabel: string | null;
74
+ sectionName: string | null;
75
+ createdAt: Date;
76
+ }[];
77
+ meta: object;
78
+ }>;
79
+ filterOptions: import("@trpc/server").TRPCQueryProcedure<{
80
+ input: void;
81
+ output: {
82
+ eventTypes: string[];
83
+ sections: string[];
84
+ actors: {
85
+ id: string;
86
+ username: string;
51
87
  }[];
52
- total: number;
53
- limit: number;
54
- offset: number;
55
88
  };
56
89
  meta: object;
57
90
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../../../src/api/trpc/routers/logs.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAA;AAqBxB,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiErB,CAAA"}
1
+ {"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../../../src/api/trpc/routers/logs.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAA;AAkCxB,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oBAqG8B,MAAM;0BAAY,MAAM;;;;;GAG3E,CAAA"}
@@ -1,15 +1,19 @@
1
1
  import { TRPCError } from '@trpc/server';
2
- import { and, desc, eq, gte, lte, sql } from 'drizzle-orm';
2
+ import { and, desc, eq, gte, lt, lte, or } from 'drizzle-orm';
3
3
  import * as z from 'zod';
4
4
  import { db } from '../../../db/client.js';
5
- import { LogsTable } from '../../../db/schema.js';
5
+ import { AdminsTable, LogsTable } from '../../../db/schema.js';
6
+ import getString from '../../../translations/index.js';
6
7
  import { isAccessAllowed } from '../../actions/privileges.js';
7
8
  import { privateProcedure, router } from '../trpc.js';
8
- import getString from '../../../translations/index.js';
9
+ const cursorSchema = z.object({
10
+ createdAt: z.coerce.date(),
11
+ id: z.number(),
12
+ });
9
13
  const listInputSchema = z
10
14
  .object({
11
15
  limit: z.number().min(1).max(200).optional(),
12
- offset: z.number().min(0).optional(),
16
+ cursor: cursorSchema.nullish(),
13
17
  eventType: z.string().optional(),
14
18
  actorId: z.string().optional(),
15
19
  sectionName: z.string().optional(),
@@ -19,61 +23,106 @@ const listInputSchema = z
19
23
  to: z.string().datetime().optional(),
20
24
  })
21
25
  .optional();
26
+ const ensureAccess = async (userId, language) => {
27
+ const accessAllowed = await isAccessAllowed({ sectionName: 'log', userId });
28
+ if (!accessAllowed) {
29
+ throw new TRPCError({ code: 'NOT_FOUND', message: getString('sectionNotFound', language) });
30
+ }
31
+ };
22
32
  export const logsRouter = router({
23
33
  list: privateProcedure.input(listInputSchema).query(async ({ ctx, input }) => {
24
- const accessAllowed = await isAccessAllowed({
25
- sectionName: 'log',
26
- userId: ctx.session.user.id,
27
- });
28
- if (!accessAllowed) {
29
- throw new TRPCError({
30
- code: 'NOT_FOUND',
31
- message: getString('sectionNotFound', ctx.session.user.language),
32
- });
33
- }
34
+ await ensureAccess(ctx.session.user.id, ctx.session.user.language);
34
35
  const limit = input?.limit ?? 50;
35
- const offset = input?.offset ?? 0;
36
36
  const conditions = [];
37
- if (input?.eventType) {
37
+ if (input?.eventType)
38
38
  conditions.push(eq(LogsTable.eventType, input.eventType));
39
- }
40
- if (input?.actorId) {
39
+ if (input?.actorId)
41
40
  conditions.push(eq(LogsTable.actorId, input.actorId));
42
- }
43
- if (input?.sectionName) {
41
+ if (input?.sectionName)
44
42
  conditions.push(eq(LogsTable.sectionName, input.sectionName));
45
- }
46
- if (input?.entityType) {
43
+ if (input?.entityType)
47
44
  conditions.push(eq(LogsTable.entityType, input.entityType));
48
- }
49
- if (input?.entityId) {
45
+ if (input?.entityId)
50
46
  conditions.push(eq(LogsTable.entityId, input.entityId));
51
- }
52
- if (input?.from) {
47
+ if (input?.from)
53
48
  conditions.push(gte(LogsTable.createdAt, new Date(input.from)));
54
- }
55
- if (input?.to) {
49
+ if (input?.to)
56
50
  conditions.push(lte(LogsTable.createdAt, new Date(input.to)));
51
+ // Keyset pagination on (createdAt, id) descending.
52
+ if (input?.cursor) {
53
+ const { createdAt, id } = input.cursor;
54
+ conditions.push(or(lt(LogsTable.createdAt, createdAt), and(eq(LogsTable.createdAt, createdAt), lt(LogsTable.id, id))));
57
55
  }
58
56
  const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
59
- let listQuery = db.select().from(LogsTable).$dynamic();
60
- let countQuery = db
61
- .select({ count: sql `count(*)` })
57
+ const rows = await db
58
+ .select({
59
+ id: LogsTable.id,
60
+ eventType: LogsTable.eventType,
61
+ actorId: LogsTable.actorId,
62
+ actorUsername: LogsTable.actorUsername,
63
+ entityType: LogsTable.entityType,
64
+ entityId: LogsTable.entityId,
65
+ entityLabel: LogsTable.entityLabel,
66
+ sectionName: LogsTable.sectionName,
67
+ metadata: LogsTable.metadata,
68
+ ipAddress: LogsTable.ipAddress,
69
+ userAgent: LogsTable.userAgent,
70
+ source: LogsTable.source,
71
+ createdAt: LogsTable.createdAt,
72
+ actorCurrentFullName: AdminsTable.fullName,
73
+ actorCurrentId: AdminsTable.id,
74
+ })
62
75
  .from(LogsTable)
63
- .$dynamic();
64
- if (whereClause) {
65
- listQuery = listQuery.where(whereClause);
66
- countQuery = countQuery.where(whereClause);
67
- }
68
- const [rows, totalRows] = await Promise.all([
69
- listQuery.orderBy(desc(LogsTable.createdAt)).limit(limit).offset(offset),
70
- countQuery,
76
+ .leftJoin(AdminsTable, eq(AdminsTable.id, LogsTable.actorId))
77
+ .where(whereClause)
78
+ .orderBy(desc(LogsTable.createdAt), desc(LogsTable.id))
79
+ .limit(limit + 1);
80
+ const hasMore = rows.length > limit;
81
+ const items = (hasMore ? rows.slice(0, limit) : rows).map((row) => {
82
+ const { actorCurrentId, ...rest } = row;
83
+ return {
84
+ ...rest,
85
+ // A log can have a null actorId (system events). Only call it "deleted"
86
+ // when it referenced an actor that no longer resolves to an admin.
87
+ actorDeleted: rest.actorId != null && actorCurrentId == null,
88
+ };
89
+ });
90
+ const last = items[items.length - 1];
91
+ const nextCursor = hasMore && last ? { createdAt: last.createdAt, id: last.id } : null;
92
+ return { items, nextCursor };
93
+ }),
94
+ // Minimal recent-activity feed for the navbar notifications popup.
95
+ // Intentionally NOT gated by the 'log' privilege: available to any authenticated
96
+ // admin. Returns only the few, low-sensitivity fields the popup renders.
97
+ recent: privateProcedure
98
+ .input(z.object({ limit: z.number().min(1).max(50).optional() }).optional())
99
+ .query(async ({ input }) => {
100
+ const limit = input?.limit ?? 20;
101
+ return db
102
+ .select({
103
+ id: LogsTable.id,
104
+ eventType: LogsTable.eventType,
105
+ actorId: LogsTable.actorId,
106
+ actorUsername: LogsTable.actorUsername,
107
+ entityLabel: LogsTable.entityLabel,
108
+ sectionName: LogsTable.sectionName,
109
+ createdAt: LogsTable.createdAt,
110
+ })
111
+ .from(LogsTable)
112
+ .orderBy(desc(LogsTable.createdAt), desc(LogsTable.id))
113
+ .limit(limit);
114
+ }),
115
+ filterOptions: privateProcedure.query(async ({ ctx }) => {
116
+ await ensureAccess(ctx.session.user.id, ctx.session.user.language);
117
+ const [eventTypeRows, sectionRows, actorRows] = await Promise.all([
118
+ db.selectDistinct({ value: LogsTable.eventType }).from(LogsTable),
119
+ db.selectDistinct({ value: LogsTable.sectionName }).from(LogsTable),
120
+ db.selectDistinct({ id: LogsTable.actorId, username: LogsTable.actorUsername }).from(LogsTable),
71
121
  ]);
72
122
  return {
73
- items: rows,
74
- total: totalRows[0]?.count ?? 0,
75
- limit,
76
- offset,
123
+ eventTypes: eventTypeRows.map((r) => r.value).filter((v) => v != null),
124
+ sections: sectionRows.map((r) => r.value).filter((v) => v != null),
125
+ actors: actorRows.filter((r) => r.id != null && r.username != null),
77
126
  };
78
127
  }),
79
128
  });