Skip to content

Commit

Permalink
BO42
Browse files Browse the repository at this point in the history
  • Loading branch information
Camillo Bucciarelli committed May 6, 2024
1 parent 821792f commit a5eee4a
Show file tree
Hide file tree
Showing 3 changed files with 315 additions and 0 deletions.
73 changes: 73 additions & 0 deletions functions/src/utilities/firestore/get-paginated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {
DocumentData,
FirestoreDataConverter,
getFirestore,
Query,
} from "firebase-admin/firestore";
import {
OrderDirection,
PaginatedResponse,
PaginationParams,
} from "../../api/http/pagination/pagination.type";

const orderBy = <M, D extends DocumentData>(
query: Query<M, D>,
orderBy: string,
orderDirection: OrderDirection,
): Query<M, D> => {
if (!orderBy) return query;
return query.orderBy(orderBy, orderDirection);
};

const addPageFilters = <M, D extends DocumentData>(
query: Query<M, D>,
params: PaginationParams,
): Query<M, D> => {
if (params.offset > 0) {
query = query.offset(params.offset);
}

return query.limit(params.limit);
};

const getPaginated = async <M, D extends DocumentData>(
collection: string,
converter: FirestoreDataConverter<M, D>,
params: PaginationParams,
): Promise<PaginatedResponse<M>> => {
try {
let query: Query<M, D> = getFirestore()
.collection(collection)
.withConverter<M, D>(converter);
query = orderBy(query, params.orderBy, params.orderDirection);

const count = (await query.count().get()).data().count;
const totalPages = Math.ceil(count / params.limit);
if (count === 0) {
return {
data: [],
total: 0,
limit: params.limit,
offset: params.offset,
totalPages: 0,
};
}

query = addPageFilters(query, params);

const result = await query.get();
const items = result.docs.map((doc) => doc.data());
return {
data: items,
total: count,
limit: params.limit,
offset: params.offset,
totalPages,
};
} catch (error) {
console.error(`Error getting documents from ${collection}`, error);
throw error;
}
};

export default getPaginated;
11 changes: 11 additions & 0 deletions functions/src/utilities/firestore/model-document-mapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Timestamp } from "firebase-admin/firestore";

type MapDateToTimestamp<PropType> = PropType extends Date
? Timestamp
: PropType;

type FirestoreDocument<T> = {
[PropertyKey in keyof T]: MapDateToTimestamp<T[PropertyKey]>;
};

export default FirestoreDocument;
231 changes: 231 additions & 0 deletions functions/test/utilities/firestore/get-paginated.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import { afterEach, before, beforeEach, describe, test } from "mocha";
import { initializeFirebaseApp } from "../../../src/config";
import {
FirestoreDataConverter,
getFirestore,
Timestamp,
} from "firebase-admin/firestore";
// eslint-disable-next-line max-len
import { PaginationParams } from "../../../src/api/http/pagination/pagination.type";
import getPaginated from "../../../src/utilities/firestore/get-paginated";
// eslint-disable-next-line max-len
import FirestoreDocument from "../../../src/utilities/firestore/model-document-mapper";
import { expect } from "chai";

type TestModel = {
id?: string;
lastUpdate: Date;
name: string;
userUpdate: {
id: string;
username: string;
name: string;
role: string;
};
deleted: boolean;
};

type TestDoc = FirestoreDocument<TestModel>;

describe("Get all documents", () => {
const testCollection = "test-collection";
const testConverter: FirestoreDataConverter<TestModel, TestDoc> = {
toFirestore: (model: TestModel): TestDoc => {
return {
...model,
lastUpdate: Timestamp.fromDate(model.lastUpdate),
};
},
fromFirestore: (
snapshot: FirebaseFirestore.QueryDocumentSnapshot,
): TestModel => {
const data = snapshot.data();
return {
id: snapshot.id,
...data,
lastUpdate: data.lastUpdate.toDate(),
} as TestModel;
},
};
const modelsToSave: TestModel[] = [
{
id: "test-model-1",
lastUpdate: new Date("2024-01-01"),
name: "Carrefour spa",
userUpdate: {
id: "test-id-1",
username: "test",
name: "Test User",
role: "admin",
},
deleted: false,
},
{
id: "test-model-2",
lastUpdate: new Date("2024-01-02"),
name: "Conad spa",
userUpdate: {
id: "test-id-1",
username: "test",
name: "Test User",
role: "admin",
},
deleted: false,
},
{
id: "test-model-3",
lastUpdate: new Date("2024-01-03"),
name: "Esselunga spa",
userUpdate: {
id: "test-id-2",
username: "test",
name: "Test User",
role: "admin",
},
deleted: false,
},
{
id: "test-model-4",
lastUpdate: new Date("2024-01-04"),
name: "Crai",
userUpdate: {
id: "test-id-2",
username: "test",
name: "Test User",
role: "admin",
},
deleted: false,
},
{
id: "test-model-5",
lastUpdate: new Date("2024-01-05"),
name: "Craig spa",
userUpdate: {
id: "test-id-3",
username: "test",
name: "Test User",
role: "admin",
},
deleted: false,
},
{
id: "test-model-6",
lastUpdate: new Date("2024-01-06"),
name: "Tigre",
userUpdate: {
id: "test-id-3",
username: "test",
name: "Test User",
role: "admin",
},
deleted: false,
},
{
id: "test-model-7",
lastUpdate: new Date("2024-01-07"),
name: "Coop",
userUpdate: {
id: "test-id-3",
username: "test",
name: "Test User",
role: "admin",
},
deleted: false,
},
{
id: "test-model-8",
lastUpdate: new Date("2024-01-08"),
name: "Coop adriatico",
userUpdate: {
id: "test-id-4",
username: "test",
name: "Test User",
role: "admin",
},
deleted: false,
},
];

before(() => {
initializeFirebaseApp();
});

beforeEach(async () => {
const batch = getFirestore().batch();
modelsToSave.forEach((model) => {
batch.create(
getFirestore().collection(testCollection).doc(model.id!),
model,
);
});
await batch.commit();
});

afterEach(async () => {
const docs = await getFirestore()
.collection(testCollection)
.listDocuments();
const batch = getFirestore().batch();
docs.forEach((doc) => {
batch.delete(doc);
});
await batch.commit();
});

test("Should get first page ordered", async () => {
const params: PaginationParams = {
offset: 0,
limit: 2,
orderBy: "lastUpdate",
orderDirection: "desc",
};

const result = await getPaginated(testCollection, testConverter, params);

expect(result).deep.equal({
data: [modelsToSave[7], modelsToSave[6]],
total: modelsToSave.length,
limit: 2,
offset: 0,
totalPages: Math.ceil(modelsToSave.length / params.limit),
});
});

test("should get second page ordered", async () => {
const params: PaginationParams = {
offset: 2,
limit: 2,
orderBy: "lastUpdate",
orderDirection: "desc",
};

const result = await getPaginated(testCollection, testConverter, params);

expect(result).deep.equal({
data: [modelsToSave[5], modelsToSave[4]],
total: modelsToSave.length,
limit: 2,
offset: 2,
totalPages: Math.ceil(modelsToSave.length / params.limit),
});
});

test("should get last page ordered", async () => {
const params: PaginationParams = {
offset: 6,
limit: 2,
orderBy: "lastUpdate",
orderDirection: "desc",
};

const result = await getPaginated(testCollection, testConverter, params);

expect(result).deep.equal({
data: [modelsToSave[1], modelsToSave[0]],
total: modelsToSave.length,
limit: 2,
offset: 6,
totalPages: Math.ceil(modelsToSave.length / params.limit),
});
});
});

0 comments on commit a5eee4a

Please sign in to comment.