-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Camillo Bucciarelli
committed
May 6, 2024
1 parent
821792f
commit a5eee4a
Showing
3 changed files
with
315 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
11
functions/src/utilities/firestore/model-document-mapper.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
231
functions/test/utilities/firestore/get-paginated.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
}); | ||
}); | ||
}); |