Skip to content

Commit 803a822

Browse files
committed
Added Firestore implementation
1 parent d0a3cd9 commit 803a822

14 files changed

+159
-58
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ React Firebase Admin is our in-house admin dashboard boilerplate, used in many o
6464
- PWA ready thanks to CRA and Firebase
6565
- Multi-tenancy
6666
- Internationalization (English/Spanish)
67+
- Ability to choose between realtime database or firestore
6768

6869
## Tech Stack
6970

@@ -88,6 +89,7 @@ React Firebase Admin is our in-house admin dashboard boilerplate, used in many o
8889
- [Format.js](https://formatjs.io/) (★ 11.7k) libraries for internationalization (see [docs](https://formatjs.io/docs/basic-internationalization-principles)).
8990
- [date-fns](https://date-fns.org/) (★ 22.3k) date utility library (see [docs](https://date-fns.org/docs/Getting-Started)).
9091
- [cross-env](https://github.com/kentcdodds/cross-env) (★ 4.9k) run scripts that set and use environment variables across platforms (see [docs](https://www.npmjs.com/package/cross-env)).
92+
- [Inquirer](https://github.com/SBoudrias/Inquirer.js/) (★ 12.2k) A collection of common interactive command line user interfaces (see [docs](https://github.com/SBoudrias/Inquirer.js/#documentation)).
9193

9294
### Unit Testing
9395

config.json

Lines changed: 0 additions & 1 deletion
This file was deleted.

firebase.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@
2323
"predeploy": [
2424
"npm --prefix \"$RESOURCE_DIR\" run lint",
2525
"npm --prefix \"$RESOURCE_DIR\" run build"
26-
]
26+
],
27+
"source": "functions"
28+
},
29+
"firestore": {
30+
"rules": "firestore.rules",
31+
"indexes": "firestore.indexes.json"
2732
}
2833
}

firestore.indexes.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"indexes": [],
3+
"fieldOverrides": []
4+
}

firestore.rules

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
rules_version = '2';
2+
service cloud.firestore {
3+
match /databases/{database}/documents {
4+
5+
function isAuthenticated() {
6+
return request.auth != null && request.auth.token.email_verified == true;
7+
}
8+
9+
function isAdmin() {
10+
return request.auth.token.isAdmin == true;
11+
}
12+
13+
14+
match /users/{userId} {
15+
16+
function isIdentified() {
17+
return request.auth.uid == userId;
18+
}
19+
20+
allow read: if isAuthenticated() && (isAdmin() || isIdentified());
21+
22+
allow write: if isAuthenticated() && isAdmin();
23+
24+
allow update: if isAuthenticated() && (isAdmin() || isIdentified());
25+
26+
allow delete: if isAuthenticated() && isAdmin();
27+
}
28+
}
29+
}

functions/package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

functions/setupProject.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const admin = require('firebase-admin');
55
const inquirer = require('inquirer');
66
const fs = require('fs');
77

8-
const configPath = '../config.json';
8+
const configPath = '../src/state/api/index.js';
99

1010
const questions = [
1111
{
@@ -33,7 +33,7 @@ const questions = [
3333
type: 'list',
3434
name: 'database',
3535
message: 'Select the database of your choice:',
36-
choices: ['Real Time Database', 'Firestore'],
36+
choices: ['Realtime Database', 'Firestore'],
3737
},
3838
];
3939

@@ -57,16 +57,16 @@ const deleteDatabase = async (database) => {
5757
fs.rmdirSync(`./src/${dir}`, { recursive: true });
5858

5959
console.log(`${database} cloud functions are deleted!`);
60-
} catch (err) {
61-
console.error(`Error while deleting ${database}.`);
60+
} catch (error) {
61+
console.error(`Error while deleting ${database}. ${error}`);
6262
}
6363

6464
try {
6565
fs.rmdirSync(`./test/${dir}`, { recursive: true });
6666

6767
console.log(`${dir} tests are deleted!`);
68-
} catch (err) {
69-
console.error(`Error while deleting ${database}.`);
68+
} catch (error) {
69+
console.error(`Error while deleting ${database} tests. ${error}`);
7070
}
7171
};
7272

functions/test/db/users/onDelete.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import 'mocha';
66

77
chai.use(chaiAsPromised);
88

9-
describe('onDelete Real Time Database', () => {
9+
describe('onDelete Realtime Database', () => {
1010
let userRecord: any;
1111

1212
it('should delete the user from the authentication section', async () => {

functions/test/db/users/onUpdate.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as chai from 'chai';
33
import onUpdate from '../../../src/db/users/onUpdate.function';
44
import 'mocha';
55

6-
describe('onUpdate Real Time Database', () => {
6+
describe('onUpdate Realtime Database', () => {
77
let userRecord: any;
88

99
before(async () => {

src/firebase.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'firebase/database';
33
import 'firebase/auth';
44
import 'firebase/storage';
55
import 'firebase/functions';
6+
import 'firebase/firestore';
67

78
const config = {
89
apiKey: process.env.REACT_APP_FIRE_BASE_KEY,
@@ -12,7 +13,7 @@ const config = {
1213
storageBucket: process.env.REACT_APP_FIRE_BASE_STORAGE_BUCKET,
1314
messagingSenderId: process.env.REACT_APP_FIRE_BASE_MESSAGING_SENDER_ID,
1415
appId: process.env.REACT_APP_FIRE_BASE_APP_ID,
15-
measurementId: process.env.REACT_APP_FIRE_BASE_MEASURMENT_ID
16+
measurementId: process.env.REACT_APP_FIRE_BASE_MEASURMENT_ID,
1617
};
1718

1819
firebase.initializeApp(config);

src/state/actions/users.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
fetchDocument,
1010
createDocument,
1111
deleteDocument,
12-
modifyDocument,
12+
updateDocument,
1313
} from '../api';
1414

1515
export const USERS_FETCH_DATA_INIT = createAction('USERS_FETCH_DATA_INIT');
@@ -256,7 +256,7 @@ export const modifyUser = ({
256256
isAdmin,
257257
logoUrl: logoUrl || newLogoUrl,
258258
};
259-
const updateUserDbTask = modifyDocument('users', id, userData);
259+
const updateUserDbTask = updateDocument('users', id, userData);
260260

261261
try {
262262
await Promise.all([deleteLogoTask, uploadLogoTask, updateUserDbTask]);

src/state/api/firestore.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import firebase from 'firebase.js';
2+
3+
const getFirestoreRef = (path) => firebase.firestore().collection(path);
4+
5+
export const fetchDocument = async (collection, id) => {
6+
const document = await getFirestoreRef(collection).doc(id).get();
7+
if (!document.exists) {
8+
return null;
9+
}
10+
11+
return { id: document.id, ...document.data() };
12+
};
13+
14+
export const fetchCollection = async (collection, options = {}) => {
15+
const data = [];
16+
let baseQuery = getFirestoreRef(collection);
17+
18+
if (options.queries) {
19+
const { queries } = options;
20+
queries.forEach(({ attribute, operator, value }) => {
21+
baseQuery = baseQuery.where(attribute, operator, value);
22+
});
23+
}
24+
25+
if (options.sort) {
26+
const { attribute, order } = options.sort;
27+
baseQuery = baseQuery.orderBy(attribute, order);
28+
}
29+
(await baseQuery.get()).forEach((doc) =>
30+
data.push({ id: doc.id, ...doc.data() })
31+
);
32+
33+
return data;
34+
};
35+
36+
export const deleteDocument = (collection, id) => {
37+
return getFirestoreRef(collection).doc(id).delete();
38+
};
39+
40+
export const createDocument = (collection, id, values) => {
41+
return getFirestoreRef(collection).doc(id).set(values);
42+
};
43+
44+
export const updateDocument = (collection, id, values) => {
45+
return getFirestoreRef(collection).doc(id).update(values);
46+
};

src/state/api/index.js

Lines changed: 14 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,15 @@
1-
import firebase from 'firebase.js';
2-
3-
const getRealTimeRef = (path) => firebase.database().ref(path);
4-
5-
export const fetchDocument = async (collection, id) => {
6-
const document = (
7-
await getRealTimeRef(`${collection}/${id}`).once(`value`)
8-
).val();
9-
10-
return document ? { id, ...document } : null;
11-
};
12-
13-
export const fetchCollection = async (collection, options = {}) => {
14-
let baseQuery = getRealTimeRef(collection);
15-
16-
if (options.filterBy) {
17-
const { filterBy, value } = options;
18-
baseQuery = baseQuery.orderByChild(filterBy).equalTo(value);
19-
}
20-
21-
const fetchedCollection = (await baseQuery.once('value')).val();
22-
23-
const data = fetchedCollection
24-
? Object.entries(fetchedCollection).map(([key, value]) => ({
25-
id: key,
26-
...value,
27-
}))
28-
: [];
29-
30-
return data;
31-
};
32-
33-
export const deleteDocument = (collection, id) => {
34-
return getRealTimeRef(`${collection}/${id}`).remove();
35-
};
36-
37-
export const createDocument = (collection, id, values) => {
38-
return getRealTimeRef(`${collection}/${id}`).set(values);
39-
};
40-
41-
export const modifyDocument = (collection, id, values) => {
42-
return getRealTimeRef(`${collection}/${id}`).update(values);
1+
import {
2+
createDocument,
3+
deleteDocument,
4+
fetchCollection,
5+
fetchDocument,
6+
updateDocument,
7+
} from './rtdb';
8+
9+
export {
10+
createDocument,
11+
deleteDocument,
12+
fetchCollection,
13+
fetchDocument,
14+
updateDocument,
4315
};

src/state/api/rtdb.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import firebase from 'firebase.js';
2+
3+
const getRealTimeRef = (path) => firebase.database().ref(path);
4+
5+
export const fetchDocument = async (collection, id) => {
6+
const document = (
7+
await getRealTimeRef(`${collection}/${id}`).once(`value`)
8+
).val();
9+
10+
return document ? { id, ...document } : null;
11+
};
12+
13+
export const fetchCollection = async (collection, options = {}) => {
14+
let baseQuery = getRealTimeRef(collection);
15+
16+
if (options.filterBy) {
17+
const { filterBy, value } = options;
18+
baseQuery = baseQuery.orderByChild(filterBy).equalTo(value);
19+
}
20+
21+
const fetchedCollection = (await baseQuery.once('value')).val();
22+
23+
const data = fetchedCollection
24+
? Object.entries(fetchedCollection).map(([key, value]) => ({
25+
id: key,
26+
...value,
27+
}))
28+
: [];
29+
30+
return data;
31+
};
32+
33+
export const deleteDocument = (collection, id) => {
34+
return getRealTimeRef(`${collection}/${id}`).remove();
35+
};
36+
37+
export const createDocument = (collection, id, values) => {
38+
return getRealTimeRef(`${collection}/${id}`).set(values);
39+
};
40+
41+
export const updateDocument = (collection, id, values) => {
42+
return getRealTimeRef(`${collection}/${id}`).update(values);
43+
};

0 commit comments

Comments
 (0)