diff --git a/.vscode/settings.json b/.vscode/settings.json
index a6ef5626a..ad58dc51f 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -25,5 +25,6 @@
"[ignore]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
},
- "conventionalCommits.scopes": ["push-send-queued"]
+ "conventionalCommits.scopes": ["push-send-queued"],
+ "CodeGPT.apiKey": "OpenAI"
}
diff --git a/bundestag.io/admin/src/app/list/_components/pagination-navigation.tsx b/bundestag.io/admin/src/app/list/_components/pagination-navigation.tsx
new file mode 100644
index 000000000..e15f736bc
--- /dev/null
+++ b/bundestag.io/admin/src/app/list/_components/pagination-navigation.tsx
@@ -0,0 +1,13 @@
+import Link from 'next/link';
+
+export const PaginationNavigation = ({ currentPage, totalPages }) => (
+
+
+
+
+ {`Page ${currentPage} of ${totalPages}`}
+
+
+
+
+);
diff --git a/bundestag.io/admin/src/app/list/past/page.tsx b/bundestag.io/admin/src/app/list/past/page.tsx
index 61d7240cd..1f215468c 100644
--- a/bundestag.io/admin/src/app/list/past/page.tsx
+++ b/bundestag.io/admin/src/app/list/past/page.tsx
@@ -1,16 +1,47 @@
import Entry from '../_components/entry';
import { IProcedure } from '@democracy-deutschland/bundestagio-common';
import { unstable_noStore as noStore } from 'next/cache';
+import Link from 'next/link';
+import { PaginationNavigation } from '../_components/pagination-navigation';
+
+const ITEMS_PER_PAGE = 10;
export const dynamic = 'force-dynamic';
-export default async function Page() {
+async function getData(page: number = 1): Promise<{ procedures: IProcedure[]; count: number }> {
+ const limit = ITEMS_PER_PAGE;
+
+ console.log(
+ 'getData',
+ page,
+ `${process.env.PROCEDURES_SERVER_URL}/procedures/list/past?limit=${limit}&offset=${page}`,
+ );
+ const res = await fetch(`${process.env.PROCEDURES_SERVER_URL}/procedures/list/past?limit=${limit}&page=${page}`, {
+ headers: {
+ 'Cache-Control': 'no-cache',
+ cache: 'no-store',
+ },
+ });
+
+ if (!res.ok) {
+ throw new Error('Fehler beim Abrufen der Daten');
+ }
+
+ return res.json();
+}
+
+export default async function Page({ searchParams }: { searchParams: { page?: string } }) {
noStore();
- const data = await getData();
+ const currentPage = searchParams.page ? parseInt(searchParams.page, 10) : 1; // Standardwert ist Seite 1
+ const { procedures, count } = await getData(currentPage);
+ const totalPages = Math.ceil(count / ITEMS_PER_PAGE); // Berechne die Gesamtseitenzahl basierend auf der Anzahl der Elemente
+
return (
<>
Past procedures
- {data.map((procedure) => (
+
+
+ {procedures.map((procedure) => (
))}
+
+
>
);
}
-
-async function getData(): Promise {
- const res = await fetch(`${process.env.PROCEDURES_SERVER_URL}/procedures/list/past`, {
- headers: {
- 'Cache-Control': 'no-cache',
- cache: 'no-store',
- },
- });
-
- if (!res.ok) {
- throw new Error('Fehler beim Abrufen der Daten');
- }
-
- return res.json();
-}
diff --git a/bundestag.io/admin/src/app/list/upcoming/page.tsx b/bundestag.io/admin/src/app/list/upcoming/page.tsx
index f2869bc99..8ca1b3c74 100644
--- a/bundestag.io/admin/src/app/list/upcoming/page.tsx
+++ b/bundestag.io/admin/src/app/list/upcoming/page.tsx
@@ -1,16 +1,48 @@
import Entry from '../_components/entry';
import { IProcedure } from '@democracy-deutschland/bundestagio-common';
import { unstable_noStore as noStore } from 'next/cache';
+import Link from 'next/link';
+import { PaginationNavigation } from '../_components/pagination-navigation';
+
+const ITEMS_PER_PAGE = 10;
export const dynamic = 'force-dynamic';
-export default async function Page() {
+async function getData(page: number = 1): Promise<{ procedures: IProcedure[]; count: number }> {
+ const limit = ITEMS_PER_PAGE;
+
+ console.log(
+ 'getData',
+ page,
+ `${process.env.PROCEDURES_SERVER_URL}/procedures/list/upcoming?limit=${limit}&offset=${page}`,
+ );
+ const res = await fetch(`${process.env.PROCEDURES_SERVER_URL}/procedures/list/upcoming?limit=${limit}&page=${page}`, {
+ headers: {
+ 'Cache-Control': 'no-cache',
+ cache: 'no-store',
+ },
+ });
+
+ if (!res.ok) {
+ throw new Error('Fehler beim Abrufen der Daten');
+ }
+
+ return res.json();
+}
+
+export default async function Page({ searchParams }: { searchParams: { page?: string } }) {
noStore();
- const data = await getData();
+ const currentPage = searchParams.page ? parseInt(searchParams.page, 10) : 1; // Standardwert ist Seite 1
+ const { procedures, count } = await getData(currentPage);
+ const totalPages = Math.ceil(count / ITEMS_PER_PAGE); // Berechne die Gesamtseitenzahl basierend auf der Anzahl der Elemente
+
return (
<>
Upcoming procedures
- {data.map((procedure) => (
+
+
+
+ {procedures.map((procedure) => (
))}
+
+
>
);
}
-
-async function getData(): Promise {
- const res = await fetch(`${process.env.PROCEDURES_SERVER_URL}/procedures/list/upcoming`, {
- headers: {
- 'Cache-Control': 'no-cache',
- cache: 'no-store',
- },
- });
-
- if (!res.ok) {
- throw new Error('Fehler beim Abrufen der Daten');
- }
-
- return res.json();
-}
diff --git a/services/procedures/bruno/environments/localhost.bru b/services/procedures/bruno/environments/localhost.bru
index 5c0f373dc..694678daa 100644
--- a/services/procedures/bruno/environments/localhost.bru
+++ b/services/procedures/bruno/environments/localhost.bru
@@ -1,3 +1,3 @@
vars {
- url: http://localhost:3000
+ url: http://localhost:3006
}
diff --git a/services/procedures/bruno/findAll.bru b/services/procedures/bruno/findAll.bru
index 30a9f3e1d..a3920c85b 100644
--- a/services/procedures/bruno/findAll.bru
+++ b/services/procedures/bruno/findAll.bru
@@ -11,6 +11,6 @@ get {
}
assert {
- res.body.procedureId: isString
+ res.body.procedures[0].procedureId: isString
res.status: eq 200
}
diff --git a/services/procedures/bruno/pastProcedures.bru b/services/procedures/bruno/pastProcedures.bru
index a072d6aac..a19f38d61 100644
--- a/services/procedures/bruno/pastProcedures.bru
+++ b/services/procedures/bruno/pastProcedures.bru
@@ -5,12 +5,16 @@ meta {
}
get {
- url: {{url}}/procedures/list/past
+ url: {{url}}/procedures/list/past?limit=2
body: none
auth: none
}
+params:query {
+ limit: 2
+}
+
assert {
- res.body[0].procedureId: isString
+ res.body.count: gt 0
res.status: eq 200
}
diff --git a/services/procedures/bruno/upcomingProcedures.bru b/services/procedures/bruno/upcomingProcedures.bru
index bb9b5d4cf..011126374 100644
--- a/services/procedures/bruno/upcomingProcedures.bru
+++ b/services/procedures/bruno/upcomingProcedures.bru
@@ -5,12 +5,17 @@ meta {
}
get {
- url: {{url}}/procedures/list/upcoming
+ url: {{url}}/procedures/list/upcoming?limit=1&page=2
body: none
auth: none
}
+params:query {
+ limit: 1
+ page: 2
+}
+
assert {
- res.body[0].procedureId: isString
+ res.body.procedures[0].procedureId: isString
res.status: eq 200
}
diff --git a/services/procedures/garden.yml b/services/procedures/garden.yml
index 3743a6bc6..a9efc4470 100644
--- a/services/procedures/garden.yml
+++ b/services/procedures/garden.yml
@@ -32,7 +32,7 @@ spec:
sourcePath: src
mode: one-way
overrides:
- - command: [pnpm, --filter, procedures, run, garden:dev]
+ - command: [pnpm, --filter, procedures, run, dev]
patchResources:
- name: procedures
kind: Deployment
diff --git a/services/procedures/src/decorators/pagination.ts b/services/procedures/src/decorators/pagination.ts
new file mode 100644
index 000000000..c2582f702
--- /dev/null
+++ b/services/procedures/src/decorators/pagination.ts
@@ -0,0 +1,14 @@
+import { createParamDecorator, ExecutionContext } from '@nestjs/common';
+
+export const Pagination = createParamDecorator(
+ (data: unknown, ctx: ExecutionContext) => {
+ const request = ctx.switchToHttp().getRequest();
+ const { page = 1, limit = 10 } = request.query;
+
+ // Ensure valid pagination values
+ const validPage = Math.max(1, parseInt(page));
+ const validLimit = Math.min(Math.max(1, parseInt(limit)), 100); // Max limit of 100
+
+ return { page: validPage, limit: validLimit };
+ },
+);
diff --git a/services/procedures/src/procedures/procedures.controller.spec.ts b/services/procedures/src/procedures/procedures.controller.spec.ts
index e6faa0cbd..87ad66bce 100644
--- a/services/procedures/src/procedures/procedures.controller.spec.ts
+++ b/services/procedures/src/procedures/procedures.controller.spec.ts
@@ -1,7 +1,6 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ProceduresController } from './procedures.controller';
import { ProceduresService } from './procedures.service';
-import { IProcedure } from '@democracy-deutschland/bundestagio-common/dist/models/Procedure/schema';
describe('ProceduresController', () => {
let controller: ProceduresController;
@@ -10,36 +9,51 @@ describe('ProceduresController', () => {
jest.Mocked>
> = {
findAll: jest.fn(
- () =>
+ ({}: { page: number; limit: number }) =>
({
- title: 'test',
- id: 1,
- procedureId: '123456',
- type: '',
- period: '',
- importantDocuments: [],
- }) as unknown as Promise,
+ procedures: [
+ {
+ title: 'test',
+ id: 1,
+ procedureId: '123456',
+ type: '',
+ period: '',
+ importantDocuments: [],
+ },
+ ],
+ count: 1,
+ }) as any,
),
fetchUpcomingProcedures: jest.fn(
- () =>
+ ({}: { page: number; limit: number }) =>
({
- title: 'test',
- id: 1,
- procedureId: '123456',
- type: '',
- period: '',
- importantDocuments: [],
+ procedures: [
+ {
+ title: 'test',
+ id: 1,
+ procedureId: '123456',
+ type: '',
+ period: '',
+ importantDocuments: [],
+ },
+ ],
+ count: 1,
}) as any,
),
fetchPastProcedures: jest.fn(
- () =>
+ ({}: { page: number; limit: number }) =>
({
- title: 'test',
- id: 1,
- procedureId: '123456',
- type: '',
- period: '',
- importantDocuments: [],
+ procedures: [
+ {
+ title: 'test',
+ id: 1,
+ procedureId: '123456',
+ type: '',
+ period: '',
+ importantDocuments: [],
+ },
+ ],
+ count: 1,
}) as any,
),
};
@@ -60,35 +74,65 @@ describe('ProceduresController', () => {
});
it('should return an array of procedures', async () => {
- expect(controller.findAll()).toStrictEqual({
- title: 'test',
- id: 1,
- procedureId: '123456',
- type: '',
- period: '',
- importantDocuments: [],
+ expect(
+ controller.findAll({
+ page: 1,
+ limit: 1,
+ }),
+ ).toStrictEqual({
+ procedures: [
+ {
+ title: 'test',
+ id: 1,
+ procedureId: '123456',
+ type: '',
+ period: '',
+ importantDocuments: [],
+ },
+ ],
+ count: 1,
});
});
it('should return an array of upcoming procedures', async () => {
- expect(controller.upcomingProcedures()).toStrictEqual({
- title: 'test',
- id: 1,
- procedureId: '123456',
- type: '',
- period: '',
- importantDocuments: [],
+ expect(
+ controller.upcomingProcedures({
+ page: 1,
+ limit: 1,
+ }),
+ ).toStrictEqual({
+ procedures: [
+ {
+ title: 'test',
+ id: 1,
+ procedureId: '123456',
+ type: '',
+ period: '',
+ importantDocuments: [],
+ },
+ ],
+ count: 1,
});
});
it('should return an array of past procedures', async () => {
- expect(controller.pastProcedures()).toStrictEqual({
- title: 'test',
- id: 1,
- procedureId: '123456',
- type: '',
- period: '',
- importantDocuments: [],
+ expect(
+ controller.pastProcedures({
+ page: 1,
+ limit: 1,
+ }),
+ ).toStrictEqual({
+ procedures: [
+ {
+ title: 'test',
+ id: 1,
+ procedureId: '123456',
+ type: '',
+ period: '',
+ importantDocuments: [],
+ },
+ ],
+ count: 1,
});
});
});
diff --git a/services/procedures/src/procedures/procedures.controller.ts b/services/procedures/src/procedures/procedures.controller.ts
index 51669b80a..8d9a5721e 100644
--- a/services/procedures/src/procedures/procedures.controller.ts
+++ b/services/procedures/src/procedures/procedures.controller.ts
@@ -1,21 +1,33 @@
import { Controller, Get } from '@nestjs/common';
import { ProceduresService } from './procedures.service';
+import { Pagination } from '../decorators/pagination';
@Controller('procedures')
export class ProceduresController {
constructor(private readonly proceduresService: ProceduresService) {}
@Get()
- findAll() {
- return this.proceduresService.findAll();
+ findAll(@Pagination() pagination: { page: number; limit: number }) {
+ return this.proceduresService.findAll({
+ page: pagination.page,
+ limit: pagination.limit,
+ });
}
@Get('list/upcoming')
- upcomingProcedures() {
- return this.proceduresService.fetchUpcomingProcedures();
+ upcomingProcedures(
+ @Pagination() pagination: { page: number; limit: number },
+ ) {
+ return this.proceduresService.fetchUpcomingProcedures({
+ page: pagination.page,
+ limit: pagination.limit,
+ });
}
@Get('list/past')
- pastProcedures() {
- return this.proceduresService.fetchPastProcedures();
+ pastProcedures(@Pagination() pagination: { page: number; limit: number }) {
+ return this.proceduresService.fetchPastProcedures({
+ page: pagination.page,
+ limit: pagination.limit,
+ });
}
}
diff --git a/services/procedures/src/procedures/procedures.service.spec.ts b/services/procedures/src/procedures/procedures.service.spec.ts
index f0daba1ae..5e2f54326 100644
--- a/services/procedures/src/procedures/procedures.service.spec.ts
+++ b/services/procedures/src/procedures/procedures.service.spec.ts
@@ -7,18 +7,24 @@ describe('ProceduresService', () => {
let procedureModel: Partial;
beforeEach(async () => {
+ const mockResult = [
+ {
+ title: 'test',
+ id: 1,
+ type: '',
+ period: '',
+ importantDocuments: [],
+ },
+ ];
+
+ // Mock the chain of .find().sort().skip().limit()
+ const mockLimit = jest.fn().mockResolvedValue(mockResult);
+ const mockSkip = jest.fn().mockReturnValue({ limit: mockLimit });
+ const mockFind = jest.fn().mockReturnValue({ skip: mockSkip });
+
procedureModel = {
- find: jest.fn().mockReturnValue([
- [
- {
- title: 'test',
- id: 1,
- type: '',
- period: '',
- importantDocuments: [],
- },
- ],
- ]),
+ find: jest.fn().mockImplementation(mockFind),
+ countDocuments: jest.fn().mockResolvedValue(1),
};
const module: TestingModule = await Test.createTestingModule({
@@ -29,7 +35,7 @@ describe('ProceduresService', () => {
}).compile();
service = module.get(ProceduresService);
- // jest.clearAllMocks();
+ jest.clearAllMocks();
});
it('should be defined', () => {
@@ -37,33 +43,27 @@ describe('ProceduresService', () => {
});
it('should return an array of procedures', () => {
- expect(service.findAll()).resolves.toStrictEqual([
- {
- title: 'test',
- id: 1,
- type: '',
- period: '',
- importantDocuments: [],
- },
- ]);
- });
-
- it('should return an array of upcoming procedures', () => {
- (procedureModel.find as jest.Mock).mockReturnValueOnce({
- sort: jest.fn().mockReturnValueOnce({
- limit: jest.fn().mockResolvedValue([
- {
- title: 'test',
- id: 1,
- type: '',
- period: '',
- importantDocuments: [],
- },
- ]),
+ expect(
+ service.findAll({
+ page: 1,
+ limit: 1,
}),
+ ).resolves.toStrictEqual({
+ procedures: [
+ {
+ title: 'test',
+ id: 1,
+ type: '',
+ period: '',
+ importantDocuments: [],
+ },
+ ],
+ count: 1,
});
+ });
- expect(service.fetchUpcomingProcedures()).resolves.toStrictEqual([
+ it('should return an array of upcoming procedures', () => {
+ const mockResult = [
{
title: 'test',
id: 1,
@@ -71,25 +71,38 @@ describe('ProceduresService', () => {
period: '',
importantDocuments: [],
},
- ]);
- });
+ ];
+
+ // Mock the chain of .find().sort().skip().limit()
+ const mockLimit = jest.fn().mockResolvedValue(mockResult);
+ const mockSkip = jest.fn().mockReturnValue({ limit: mockLimit });
+ const mockSort = jest.fn().mockReturnValue({ skip: mockSkip });
+ const mockFind = jest.fn().mockReturnValue({ sort: mockSort });
+
+ // Assign the mock to procedureModel.find
+ (procedureModel.find as jest.Mock).mockImplementation(mockFind);
- it('should return an array of past procedures', () => {
- (procedureModel.find as jest.Mock).mockReturnValueOnce({
- sort: jest.fn().mockReturnValueOnce({
- limit: jest.fn().mockResolvedValue([
- {
- title: 'test',
- id: 1,
- type: '',
- period: '',
- importantDocuments: [],
- },
- ]),
+ expect(
+ service.fetchUpcomingProcedures({
+ page: 1,
+ limit: 1,
}),
+ ).resolves.toStrictEqual({
+ procedures: [
+ {
+ title: 'test',
+ id: 1,
+ type: '',
+ period: '',
+ importantDocuments: [],
+ },
+ ],
+ count: 1,
});
+ });
- expect(service.fetchPastProcedures()).resolves.toStrictEqual([
+ it('should return an array of past procedures', async () => {
+ const mockResult = [
{
title: 'test',
id: 1,
@@ -97,6 +110,33 @@ describe('ProceduresService', () => {
period: '',
importantDocuments: [],
},
- ]);
+ ];
+
+ // Mock the chain of .find().sort().skip().limit()
+ const mockLimit = jest.fn().mockResolvedValue(mockResult);
+ const mockSkip = jest.fn().mockReturnValue({ limit: mockLimit });
+ const mockSort = jest.fn().mockReturnValue({ skip: mockSkip });
+ const mockFind = jest.fn().mockReturnValue({ sort: mockSort });
+
+ // Assign the mock to procedureModel.find
+ (procedureModel.find as jest.Mock).mockImplementation(mockFind);
+
+ await expect(
+ service.fetchPastProcedures({
+ page: 1,
+ limit: 1,
+ }),
+ ).resolves.toStrictEqual({
+ procedures: [
+ {
+ title: 'test',
+ id: 1,
+ type: '',
+ period: '',
+ importantDocuments: [],
+ },
+ ],
+ count: 1,
+ });
});
});
diff --git a/services/procedures/src/procedures/procedures.service.ts b/services/procedures/src/procedures/procedures.service.ts
index 954c7b815..d50b1e85a 100644
--- a/services/procedures/src/procedures/procedures.service.ts
+++ b/services/procedures/src/procedures/procedures.service.ts
@@ -8,39 +8,63 @@ export class ProceduresService {
@InjectModel('Procedure') private procedureModel: typeof ProcedureModel,
) {}
- async findAll() {
- const procedures = await this.procedureModel.find();
- return procedures[0];
+ async findAll({ page, limit }: { page: number; limit: number }) {
+ const procedures = await this.procedureModel
+ .find()
+ .skip((page - 1) * limit)
+ .limit(limit);
+
+ const count = await this.procedureModel.countDocuments();
+
+ return { procedures, count };
}
- async fetchUpcomingProcedures() {
- return await this.procedureModel
- .find({
- $or: [
- {
- $and: [
- { voteDate: { $gte: new Date() } },
- {
- $or: [{ voteEnd: { $exists: false } }, { voteEnd: undefined }],
- },
- ],
- },
- { voteEnd: { $gte: new Date() } },
- ],
- })
+ async fetchUpcomingProcedures({
+ page,
+ limit,
+ }: {
+ page: number;
+ limit: number;
+ }) {
+ const filter = {
+ $or: [
+ {
+ $and: [
+ { voteDate: { $gte: new Date() } },
+ {
+ $or: [{ voteEnd: { $exists: false } }, { voteEnd: undefined }],
+ },
+ ],
+ },
+ { voteEnd: { $gte: new Date() } },
+ ],
+ };
+ const procedures = await this.procedureModel
+ .find(filter)
.sort({ voteDate: 1, voteEnd: 1, votes: -1 })
- .limit(100);
+ .skip((page - 1) * limit)
+ .limit(limit);
+
+ const count = await this.procedureModel.countDocuments(filter);
+
+ return { procedures, count };
}
- async fetchPastProcedures() {
- return await this.procedureModel
- .find({
- $or: [
- { voteDate: { $lt: new Date() } },
- { voteEnd: { $lt: new Date() } },
- ],
- })
+ async fetchPastProcedures({ page, limit }: { page: number; limit: number }) {
+ const filter = {
+ $or: [
+ { voteDate: { $lt: new Date() } },
+ { voteEnd: { $lt: new Date() } },
+ ],
+ };
+ const procedures = await this.procedureModel
+ .find(filter)
.sort({ voteDate: -1, voteEnd: -1, votes: -1 })
- .limit(100);
+ .skip((page - 1) * limit)
+ .limit(limit);
+
+ const count = await this.procedureModel.countDocuments(filter);
+
+ return { procedures, count };
}
}