From 8240d1da93ba9d903a170a4a4b2b55bc44f403e4 Mon Sep 17 00:00:00 2001 From: Mikko Paderes Date: Sat, 14 Nov 2020 16:07:38 +0800 Subject: [PATCH 1/4] Create markdown docs --- .github/FUNDING.yml | 1 + README.md | 22 ++- docs/authentication.md | 92 +++++++++ docs/create-update-delete-records.md | 267 +++++++++++++++++++++++++++ docs/data-structure.md | 98 ++++++++++ docs/finding-records.md | 124 +++++++++++++ docs/getting-started.md | 68 +++++++ docs/relationships.md | 82 ++++++++ docs/testing.md | 26 +++ docs/transforms.md | 20 ++ 10 files changed, 796 insertions(+), 4 deletions(-) create mode 100644 .github/FUNDING.yml create mode 100644 docs/authentication.md create mode 100644 docs/create-update-delete-records.md create mode 100644 docs/data-structure.md create mode 100644 docs/finding-records.md create mode 100644 docs/getting-started.md create mode 100644 docs/relationships.md create mode 100644 docs/testing.md create mode 100644 docs/transforms.md diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..dc0ae97c --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: ['https://paypal.me/mikkopaderes'] diff --git a/README.md b/README.md index b963f166..16508ccd 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,23 @@ ember-cloud-firestore-adapter ============================================================================== -Unofficial Ember Data Adapter and Serializer for Cloud Firestore +This is an unofficial Ember Data Adapter and Serializer for Cloud Firestore. It's completely unrelated to [EmberFire](https://github.com/firebase/emberfire) but it's purpose is of the same. +Features +------------------------------------------------------------------------------ + +- **Customizable data structure** - There's an opinionated default on how your data will be structured but there's enough API to make it fit to your existing ones +- **Realtime bindings** - Listen to realtime updates easily +- **Authentication** - Integrate [Firebase Authentication](https://firebase.google.com/products/auth/) powered by [Ember Simple Auth](https://github.com/simplabs/ember-simple-auth) +- **FastBoot support** - Perform server-side rendering to speed up your boot time +- **Firebase Emulator** - Develop and test your app using the [Firebase Local Emulator Suite](https://firebase.google.com/docs/emulator-suite) + +Why Was This Built? +------------------------------------------------------------------------------ + +This was built becase EmberFire development is super slow or may even be abandoned by now. + +In order to continue development with Ember and Cloud Firestore, I had to build this addon and opted to make it generic enough to be used by other developers too. Compatibility ------------------------------------------------------------------------------ @@ -11,7 +26,6 @@ Compatibility * Ember CLI v2.13 or above * Node.js v10 or above - Installation ------------------------------------------------------------------------------ @@ -27,10 +41,10 @@ Once you've installed it, you can now install the addon itself: ember install ember-cloud-firestore-adapter ``` -Usage +Getting Started ------------------------------------------------------------------------------ -Checkout the docs [here](https://mikkopaderes.github.io/ember-cloud-firestore-adapter). +Checkout the docs [here](docs/getting-started.md). Contributing ------------------------------------------------------------------------------ diff --git a/docs/authentication.md b/docs/authentication.md new file mode 100644 index 00000000..8ced9710 --- /dev/null +++ b/docs/authentication.md @@ -0,0 +1,92 @@ +# Authentication + +The adapter supports Firebase authentication through the [ember-simple-auth](https://github.com/simplabs/ember-simple-auth) addon. + +## Signing in + +Using the `session` service provided by ember-simple-auth, a callback must be passed-in which will be responsible for authenticating the user. + +The callback will have the [`firebase.auth.Auth`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth) as a param. Use this to authenticate the user using any of the providers available. It **must** also return a promise that will resolve to an instance of [`firebase.auth.UserCredential`](https://firebase.google.com/docs/reference/js/firebase.auth#usercredential). + +```javascript +this.session.authenticate('authenticator:firebase', (auth) => { + return auth.signInWithEmailAndPassword('my_email@gmail.com', 'my_password'); +}); +``` + +## Signing out + +Also using the `session` service provided by ember-simple-auth, we just call `invalidate()`. + +```javascript +this.session.invalidate(); +``` + +## FastBoot + +Authentication in FastBoot is possible through service worker and a [custom authentication system](https://firebase.google.com/docs/auth/web/custom-auth). You will have to do some setup on both your Ember app and server. + +### Setup your Ember app + +1. Install the `ember-service-worker` addon +2. Create `app/session-stores/application.js` file + +The contents of the file should be something like this: + +```javascript +import Firebase from 'ember-cloud-firestore-adapter/session-stores/firebase'; + +export default Firebase.extend(); +``` + +The built-in service worker of this addon (powered by `ember-service-worker`) will intercept all `fetch` requests in order to add the result of [`getIdToken()`](https://firebase.google.com/docs/reference/js/firebase.User#getidtoken) in the request `Header`. This means that when a user visits your app, their ID token would be part of the request in which your server would process this for authentication. + +### Setup your server + +To continue with the server authentication process, you will need to: + +1. Verify the ID token via [`verifyIdToken()`](https://firebase.google.com/docs/reference/admin/node/admin.auth.Auth-1#verifyidtoken) +2. Create the custom token when the verification succeeds via [`createCustomToken()`](https://firebase.google.com/docs/reference/admin/node/admin.auth.Auth-1#createcustomtoken). +3. Set the request's `Authorization` header to `Bearer: `. + +That new header should now be picked up by your FastBoot app and `ember-cloud-firestore-adapter` would handle the rest through [`signInWithCustomToken()`](https://firebase.google.com/docs/reference/js/firebase.auth.Auth#signinwithcustomtoken). + +Here's an example Express server that does the above in Cloud Functions for Firebase: + +```javascript +const express = require('express'); + +const admin = require('firebase-admin'); +const fastbootMiddleware = require('fastboot-express-middleware'); +const functions = require('firebase-functions'); + +const app = express(); + +const distPath = 'app'; + +admin.initializeApp(); + +app.get('/*', async (req, res, next) => { + if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) { + try { + const idToken = req.headers.authorization.split('Bearer ')[1]; + const auth = admin.auth(); + const decodedIdToken = await auth.verifyIdToken(idToken); + const { uid } = decodedIdToken; + const customToken = await auth.createCustomToken(uid); + + req.headers.authorization = `Bearer ${customToken}`; + } catch (error) {} + } + + next(); +}, fastbootMiddleware(distPath)); + +app.use(express.static(distPath)); + +exports.app = functions.https.onRequest(app); +``` + +--- + +[Next: Testing »](testing) diff --git a/docs/create-update-delete-records.md b/docs/create-update-delete-records.md new file mode 100644 index 00000000..c926fc06 --- /dev/null +++ b/docs/create-update-delete-records.md @@ -0,0 +1,267 @@ +# Creating, Updating, and Deleting Records + +The adapter supports `store.createRecord`, `store.deleteRecord`, and, `store.destroyRecord`. However, there are some **optional** configs that you can make use of to support your needs. + +## `createRecord` + +The optional configs are available through the `adapterOptions` property in the `save` function. + +e.g. + +```javascript +const newPost = this.store.createRecord('post', { title: 'Post A' }); + +newPost.save({ + adapterOptions: { + isRealtime: true, + + include(batch, db) { + batch.set(db.collection('users').doc('user_b').collection('feeds').doc('feed_a'), { title: 'Post A' }); + } + } +}); +``` + +### `isRealtime` + +Indicates if the record will update in realtime after creating it + +**Type:** `boolean` + +### `buildReference` + +Hook for providing a custom collection reference on where you want to save the document to + +**Type:** `function` + +**Params:** + +| Name | Type | Description | +| ---- | ------------------------------------------------------------------------------------------------------------ | ----------- | +| db | [`firebase.firestore.Firestore`](https://firebase.google.com/docs/reference/js/firebase.firestore.Firestore) | | + +### `include` + +Hook for providing additional documents to batch write + +**Type:** `function` + +**Params:** + +| Name | Type | Description | +| ----- | -------------------------------------------------------------------------------------------------------------- | ----------- | +| batch | [`firebase.firestore.WriteBatch`](https://firebase.google.com/docs/reference/js/firebase.firestore.WriteBatch) | | +| db | [`firebase.firestore.Firestore`](https://firebase.google.com/docs/reference/js/firebase.firestore.Firestore) | | + +## `deleteRecord` + +The optional configs are available through the `adapterOptions` property in the `save` function. + +e.g. + +```javascript +user.deleteRecord(); +user.save({ + adapterOptions: { + include(batch, db) { + batch.delete(db.collection('usernames').doc(newUser.id)); + } + } +}); +``` + +### `buildReference` + +Hook for providing a custom collection reference on where the document to be deleted is located + +**Type:** `function` + +**Params:** + +| Name | Type | Description | +| ---- | ------------------------------------------------------------------------------------------------------------ | ----------- | +| db | [`firebase.firestore.Firestore`](https://firebase.google.com/docs/reference/js/firebase.firestore.Firestore) | | + +### `include` + +Hook for providing additional documents to batch write + +**Type:** `function` + +**Params:** + +| Name | Type | Description | +| ----- | -------------------------------------------------------------------------------------------------------------- | ----------- | +| batch | [`firebase.firestore.WriteBatch`](https://firebase.google.com/docs/reference/js/firebase.firestore.WriteBatch) | | +| db | [`firebase.firestore.Firestore`](https://firebase.google.com/docs/reference/js/firebase.firestore.Firestore) | | + +## `destroyRecord` + +The optional configs are available through the `adapterOptions` property. + +e.g. + +```javascript +user.destroyRecord({ + adapterOptions: { + include(batch, db) { + batch.delete(db.collection('usernames').doc(newUser.id)); + } + } +}); +``` + +### `buildReference` + +Hook for providing a custom collection reference on where the document to be deleted is located + +**Type:** `function` + +**Params:** + +| Name | Type | Description | +| ---- | ------------------------------------------------------------------------------------------------------------ | ----------- | +| db | [`firebase.firestore.Firestore`](https://firebase.google.com/docs/reference/js/firebase.firestore.Firestore) | | + +### `include` + +Hook for providing additional documents to batch write + +**Type:** `function` + +**Params:** + +| Name | Type | Description | +| ----- | -------------------------------------------------------------------------------------------------------------- | ----------- | +| batch | [`firebase.firestore.WriteBatch`](https://firebase.google.com/docs/reference/js/firebase.firestore.WriteBatch) | | +| db | [`firebase.firestore.Firestore`](https://firebase.google.com/docs/reference/js/firebase.firestore.Firestore) | | + +## Updating a record + +The optional configs are available through the `adapterOptions` property in the `save` function. + +e.g. + +```javascript +post.set('title', 'New Title'); +post.save({ + adapterOptions: { + isRealtime: true, + + include(batch, db) { + batch.update(db.collection('users').doc('user_b').collection('feeds').doc('feed_a'), { title: 'New Title' }); + } + } +}); +``` + +### `isRealtime` + +Indicates if the record will update in realtime after updating it + +**Type:** `boolean` + +### `buildReference` + +Hook for providing a custom collection reference on where the document to be updated is located + +**Type:** `function` + +**Params:** + +| Name | Type | Description | +| ---- | ------------------------------------------------------------------------------------------------------------ | ----------- | +| db | [`firebase.firestore.Firestore`](https://firebase.google.com/docs/reference/js/firebase.firestore.Firestore) | | + +### `include` + +Hook for providing additional documents to batch write + +**Type:** `function` + +**Params:** + +| Name | Type | Description | +| ----- | -------------------------------------------------------------------------------------------------------------- | ----------- | +| batch | [`firebase.firestore.WriteBatch`](https://firebase.google.com/docs/reference/js/firebase.firestore.WriteBatch) | | +| db | [`firebase.firestore.Firestore`](https://firebase.google.com/docs/reference/js/firebase.firestore.Firestore) | | + +## Saving relationships + +There are 3 types of relationships to take note of: one-to-many, many-to-many, and many-to-none. + +When saving a one-to-many relationship, the reference will be persisted on the belongs-to side automatically. For many-to-many and many-to-none however, you will need to manually save them through the `include` hook on the `adapterOptions`. This is because: + + - Ember Data currently doesn't provide a way to track relationship changes. This means that for the has-many side: + - If a related record exists, we wouldn't know if it was just added or if it was already existing in the first place. + - And, if we remove a related record, we wouldn't be able to know it. + - We want the flexibility of specifying which collection we want the documents to be saved to. + +### Saving many-to-many + +In this scenario, `User` model has a many-to-many relationship with `Group` model through the `groups` and `members` field name respectively. + +```javascript +// Assume that a someGroup variable already exists + +... + +const newUser = this.store.createRecord('user', { + name: 'Foo', + groups: [someGroup] +}); + +newUser.save({ + adapterOptions: { + include(batch, db) { + // Batch write to the users//groups sub-collection + batch.set(db.collection('users').doc(newUser.get('id')).collection('groups').doc(someGroup.get('id')), { + referenceTo: db.collection('groups').doc(someGroup.get('id')) + }); + + // Batch write to the groups//members sub-collection + batch.set(db.collection('groups').doc(someGroup.get('id')).collection('members').doc(newUser.get('id')), { + referenceTo: db.collection('users').doc(newUser.get('id')) + }); + } + } +}); +``` + +### Saving many-to-none + +Saving many-to-none is similar with many-to-many except that you will only batch write to one side of the relationship only. + +Another case for many-to-none relationship is to save the record itself in the sub-collection rather than using a reference field. To do so, just simply save the record to the appropriate sub-collection and then push it to the has-many array. + +e.g. + +In this scenario, the `User` model has a many-to-none relationship with `Reminder` model. + +```javascript +// Assume that a someUser variable already exists + +... + +const reminder = this.store.createRecord('reminder', { + title: 'Foo' +}); + +reminder.save({ + adapterOptions: { + buildReference(db) { + return db.collection('users').doc(someUser.get('id')).collection('reminders'); + } + } +}).then(() => { + // Update reminders hasMany without flagging someUser as "dirty" or unsaved + someUser.hasMany('reminders').push({ + type: 'reminder', + id: reminder.get('id') + }); +}); +``` + +--- + +[Next: Relationships »](relationships) diff --git a/docs/data-structure.md b/docs/data-structure.md new file mode 100644 index 00000000..e7143f71 --- /dev/null +++ b/docs/data-structure.md @@ -0,0 +1,98 @@ +# Data Structure + +There's an opinionated default on how this addon will structure your Cloud Firestore data. More of it will be explained below but basically: + + - Documents are stored under a collection derived from their camelized and pluralized model name (e.g. `user` = `users`, `city` = `cities`, and `blog-post` = `blogPosts`) + - Attributes are camelized + - Relationships are stored as `Reference` [data type](https://firebase.google.com/docs/firestore/manage-data/data-types#data_types) instead of their IDs + - One-to-many relationships are only persisted in the `belongsTo` side. + +There are however, APIs that would allow the adapter to consume whatever data structure that you already have. + +## Example Data + +Below demonstrates some simple Ember Data models and how they'd look like when persisted in Cloud Firestore. + + +```javascript +import Model, { attr, hasMany } from '@ember-data/model'; + +export default class GroupModel extends Model { + @attr title; + @hasMany('post') posts; + @hasMany('user') members; +} +``` + +```javascript +import Model, { attr, belongsTo } from '@ember-data/model'; + +export default class PostModel extends Model { + @attr title; + @attr body; + @attr('timestamp') createdOn; + @belongsTo('user') author; + @belongsTo('group') group; +} +``` + +```javascript +import Model, { attr, hasMany } from '@ember-data/model'; + +export default class UserModel extends Model { + @attr name; + @hasMany('group') groups; + @hasMany('post') posts; +} +``` + +### Persisted Structure + +```json +{ + "groups": { // Root-level collection + "group_a": { + "name": "Group A", + + "members": { // Subcollection + "user_a": { + "referenceTo": "" + } + } + } + }, + + "posts": { // Root-level collection + "post_a": { + "title": "Post A Title", + "body": "Post A Body", + "createdOn": "January 1, 2017 at 12:00:00 AM UTC+8", + "author": "", + "group": "" + } + }, + + "users": { // Root-level collection + "user_a": { + "name": "User A", + + "groups": { // Subcollection + "group_a": { + "referenceTo": "" + } + } + } + } +} +``` + +Notes: + - Notice that we don't have a `posts` subcollection in `groups/group_a` and `users/user_a`. This is because in one-to-many relationships, only the `belongsTo` side gets persisted. + - Relationships are saved as `Reference` [data type](https://firebase.google.com/docs/firestore/manage-data/data-types#data_types) instead of its ID of type string. + - `referenceTo` is a **reserved** attribute to indicate that a field is a reference to another document. You can configure this to be named as something else. This works as follows: + - When you fetch a `Group` document under `users/user_a/groups/group_a` and it has a `referenceTo` field to `groups/group_a`, it will return the document under that instead. + - If the `referenceTo` field doesn't exist, it would return the `users/user_a/groups/group_a` as the document for the `Group` model. + +--- + +[Next: Finding Records »](finding-records) diff --git a/docs/finding-records.md b/docs/finding-records.md new file mode 100644 index 00000000..a031d9d4 --- /dev/null +++ b/docs/finding-records.md @@ -0,0 +1,124 @@ +# Finding Records + +The adapter supports `store.findRecord`, `store.findAll`, and, `store.query`. However, there are some **optional** configs that you can make use of to support your needs. + +## `findRecord` + +The optional configs are available through the `adapterOptions` property. + +e.g. + +```javascript +this.store.findRecord('post', 'post_a', { + adapterOptions: { + isRealtime: true, + + buildReference(db) { + return db.collection('users').doc('user_a').collection('feeds'); + } + } +}); +``` + +### `isRealtime` + +Indicates if the record will update in realtime + +**Type:** `boolean` + +### `buildReference` + +Hook for providing a custom collection reference on where you want to fetch the document from + +**Type:** `function` + +**Params:** + +| Name | Type | Description | +| ---- | ------------------------------------------------------------------------------------------------------------ | ----------- | +| db | [`firebase.firestore.Firestore`](https://firebase.google.com/docs/reference/js/firebase.firestore.Firestore) | | + +## `findAll` + +The optional configs are available through the `adapterOptions` property. + +e.g. + +```javascript +this.store.findAll('post', { + adapterOptions: { + isRealtime: true + } +}); +``` + +### `isRealtime` + +Indicates if the record will update in realtime + +**Type:** `boolean` + +## `query` + +The optional configs are available through the query param. + +e.g. + +```javascript +this.store.query('post', { + isRealtime: true, + queryId: 'foobar', + + buildReference(db) { + return db.collection('users').doc('user_a').collection('feeds'); + }, + + filter(reference) { + return reference.where('likes', '>=', 100).limit(5); + } +}); +``` + +If the document contains a field that matches your [`referenceKeyName`](getting-started#adapter-settings), it'll fetch that one instead. + +### `isRealtime` + +Indicates if the record will update in realtime + +**Type:** `boolean` + +### `queryId` + +A unique ID that you will provide yourself. When there's an already existing `queryId`, `store.query` won't create another realtime listener to avoid duplication. + +This does nothing when `isRealtime` is false. + +**Type:** `string` + +### `buildReference` + +Hook for providing a custom collection reference on where you want to fetch the documents from + +**Type:** `function` + +**Params:** + +| Name | Type | Description | +| ---- | ------------------------------------------------------------------------------------------------------------ | ----------- | +| db | [`firebase.firestore.Firestore`](https://firebase.google.com/docs/reference/js/firebase.firestore.Firestore) | | + +### `filter` + +Hook for providing the query for the collection reference + +**Type:** `function` + +**Params:** + +| Name | Type | Description | +| --------- | -------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| reference | [`firebase.firestore.CollectionReference`](https://firebase.google.com/docs/reference/js/firebase.firestore.CollectionReference) | Will contain the return of `buildReference` when overriden. Otherwise, it'll be provided by the adapter itself. | + +--- + +[Next: Creating, Updating, and Deleting Records »](create-update-delete-records) diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 00000000..55b985d5 --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,68 @@ +# Getting Started + +## Configuration + +### 1. Setup Firebase and Addon Configuration + +Add a `firebase` property in your `config/environment.js`. Environment specific configurations for this addon can also be set up under the `ember-cloud-firestore-adapter` property in the same file. + +```javascript +let ENV = { + ... + + firebase: { + apiKey: '', + authDomain: '', + databaseURL: '', + projectId: '', + storageBucket: '', + messagingSenderId: '' + }, + + 'ember-cloud-firestore-adapter': { + emulator: { + hostname: 'localhost', + port: 8080, + }, + }, + + ... +} +``` + +#### `ember-cloud-firestore-adapter` Configurations + +These are the configurations currently available: + + - `emulator` - An object specifying the `hostname` and `port` to use when connecting to a Firebase Emulator. + +At the moment, there are no required configuration in this addon so you may opt to not add any `ember-cloud-firestore-adapter` property in the `config/environment.js` file. + +### 2. Create Your Application Adapter + +Create an application adapter by running: + +```bash +ember generate adapter application +``` + +Change it to look something like this: + +```javascript +import CloudFirestoreAdapter from 'ember-cloud-firestore-adapter/adapters/cloud-firestore'; + +export default class ApplicationAdapter extends CloudFirestoreAdapter { + referenceKeyName = 'foobar'; +} +``` + +#### Adapter Settings + +These are the settings currently available: + + - `firestoreSettings` - An object specifying the custom settings for your Cloud Firestore instance. See [here](https://firebase.google.com/docs/reference/js/firebase.firestore.Settings). (Defaults to null) + - `referenceKeyName` - Name of the field that will indicate whether a document is a reference to another one. (Defaults to `'referenceTo'`) + +--- + +[Next: Data Structure »](data-structure) diff --git a/docs/relationships.md b/docs/relationships.md new file mode 100644 index 00000000..1493b4f9 --- /dev/null +++ b/docs/relationships.md @@ -0,0 +1,82 @@ +# Relationships + +The adapter supports `hasMany` and `belongsTo`. However, there are some **optional** configs that you can make use of to support your needs. + +## `belongsTo` + +The optional configs are available by passing it as a param. + +```javascript +import Model, { attr, belongsTo } from '@ember-data/model'; + +export default class UserModel extends Model { + @attr name; + @belongsTo('country', { isRealtime: true }) country; +} +``` + +### `isRealtime` + +Indicates if the record will update in realtime + +**Type:** `boolean` + +## `hasMany` + +The optional configs are available by passing it as a param. + +```javascript +import Model, { attr, hasMany } from '@ember-data/model'; + +export default class GroupModel extends Model { + @attr name; + + @hasMany('post', { + isRealtime: true, + + filter(reference) { + return reference.where('status', '==', 'approved'); + } + }) + approvedPosts; +} +``` + +If the document contains a field that matches your [`referenceKeyName`](getting-started#adapter-settings), it'll fetch that one instead. + +### `isRealtime` + +Indicates if the record will update in realtime after creating it + +**Type:** `boolean` + +### `buildReference` + +Hook for providing a custom collection reference. + +This is ignored when the relationship is a many-to-one type. + +**Type:** `function` + +**Params:** + +| Name | Type | Description | +| -------| ------------------------------------------------------------------------------------------------------------ | ----------------- | +| db | [`firebase.firestore.Firestore`](https://firebase.google.com/docs/reference/js/firebase.firestore.Firestore) | | + +### `filter` + +Hook for providing the query for the collection reference + +**Type:** `function` + +**Params:** + +| Name | Type | Description | +| --------- | -------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | +| reference | [`firebase.firestore.CollectionReference`](https://firebase.google.com/docs/reference/js/firebase.firestore.CollectionReference) | Will contain the return of `buildReference` when overriden. Otherwise, it will be provided by the adapter itself. | +| record | Object | The record itself | + +--- + +[Next: Transforms »](transforms) diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 00000000..ab780cc0 --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,26 @@ +# Testing + +We use [Firebase Local Emulator Suite](https://firebase.google.com/docs/emulator-suite) for testing Cloud Firestore while [ember-simple-auth](https://github.com/simplabs/ember-simple-auth#testing) provides a set of test helpers for authentication. + +## Setup Addon to Use Emulator + +Add an `ember-cloud-firestore-adapter.emulator` property in your `config/environment.js` and make sure to disable it in production environment. + +```javascript +let ENV = { + ... + + 'ember-cloud-firestore-adapter': { + emulator: { + hostname: 'localhost', + port: 8080, + }, + }, + + ... +} + +if (environment === 'production') { + ENV['ember-cloud-firestore-adapter'] = null; +} +``` diff --git a/docs/transforms.md b/docs/transforms.md new file mode 100644 index 00000000..e85f2671 --- /dev/null +++ b/docs/transforms.md @@ -0,0 +1,20 @@ +# Transforms + +## Timestamp + +Timestamp transform is provided as a convenience to [`firebase.firestore.FieldValue.serverTimestamp()`](https://firebase.google.com/docs/reference/js/firebase.firestore.FieldValue#servertimestamp). + +```javascript +import Model, { attr } from '@ember-data/model'; + +export default class PostModel extends Model { + @attr title; + @attr('timestamp') createdOn; +} +``` + +In the example above, whenever you save a record who's `createdOn` is not of `Date` instance it will use the server timestamp. Otherwise, it will use that same `Date` instead. + +--- + +[Next: Authentication »](authentication) From 08bdc67dfcb5f3d958a1a92cf6778fe1ccdcf83e Mon Sep 17 00:00:00 2001 From: Mikko Paderes Date: Sat, 14 Nov 2020 16:08:30 +0800 Subject: [PATCH 2/4] Grammar fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 16508ccd..151a5e45 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ember-cloud-firestore-adapter ============================================================================== -This is an unofficial Ember Data Adapter and Serializer for Cloud Firestore. It's completely unrelated to [EmberFire](https://github.com/firebase/emberfire) but it's purpose is of the same. +This is an unofficial Ember Data Adapter and Serializer for Cloud Firestore. Its completely unrelated to [EmberFire](https://github.com/firebase/emberfire) but it's purpose is of the same. Features ------------------------------------------------------------------------------ From 46e7e99719dce2be43ce3f0d8f92280c25c8eb07 Mon Sep 17 00:00:00 2001 From: Mikko Paderes Date: Sat, 14 Nov 2020 16:08:55 +0800 Subject: [PATCH 3/4] Typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 151a5e45..4e44a3d4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ember-cloud-firestore-adapter ============================================================================== -This is an unofficial Ember Data Adapter and Serializer for Cloud Firestore. Its completely unrelated to [EmberFire](https://github.com/firebase/emberfire) but it's purpose is of the same. +This is an unofficial Ember Data Adapter and Serializer for Cloud Firestore. It's completely unrelated to [EmberFire](https://github.com/firebase/emberfire) but its purpose is of the same. Features ------------------------------------------------------------------------------ From 576e1ef6e6ee517a5afcadb0b202bb2a4428cb73 Mon Sep 17 00:00:00 2001 From: Mikko Paderes Date: Sat, 14 Nov 2020 16:12:24 +0800 Subject: [PATCH 4/4] Fix broken links --- docs/authentication.md | 2 +- docs/create-update-delete-records.md | 2 +- docs/data-structure.md | 2 +- docs/finding-records.md | 2 +- docs/getting-started.md | 2 +- docs/relationships.md | 2 +- docs/transforms.md | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/authentication.md b/docs/authentication.md index 8ced9710..84e947b4 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -89,4 +89,4 @@ exports.app = functions.https.onRequest(app); --- -[Next: Testing »](testing) +[Next: Testing »](testing.md) diff --git a/docs/create-update-delete-records.md b/docs/create-update-delete-records.md index c926fc06..8eeb6a60 100644 --- a/docs/create-update-delete-records.md +++ b/docs/create-update-delete-records.md @@ -264,4 +264,4 @@ reminder.save({ --- -[Next: Relationships »](relationships) +[Next: Relationships »](relationships.md) diff --git a/docs/data-structure.md b/docs/data-structure.md index e7143f71..fb65ee42 100644 --- a/docs/data-structure.md +++ b/docs/data-structure.md @@ -95,4 +95,4 @@ Notes: --- -[Next: Finding Records »](finding-records) +[Next: Finding Records »](finding-records.md) diff --git a/docs/finding-records.md b/docs/finding-records.md index a031d9d4..22fa4573 100644 --- a/docs/finding-records.md +++ b/docs/finding-records.md @@ -121,4 +121,4 @@ Hook for providing the query for the collection reference --- -[Next: Creating, Updating, and Deleting Records »](create-update-delete-records) +[Next: Creating, Updating, and Deleting Records »](create-update-delete-records.md) diff --git a/docs/getting-started.md b/docs/getting-started.md index 55b985d5..32951082 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -65,4 +65,4 @@ These are the settings currently available: --- -[Next: Data Structure »](data-structure) +[Next: Data Structure »](data-structure.md) diff --git a/docs/relationships.md b/docs/relationships.md index 1493b4f9..a2bf0a94 100644 --- a/docs/relationships.md +++ b/docs/relationships.md @@ -79,4 +79,4 @@ Hook for providing the query for the collection reference --- -[Next: Transforms »](transforms) +[Next: Transforms »](transforms.md) diff --git a/docs/transforms.md b/docs/transforms.md index e85f2671..1f36747e 100644 --- a/docs/transforms.md +++ b/docs/transforms.md @@ -17,4 +17,4 @@ In the example above, whenever you save a record who's `createdOn` is not of `Da --- -[Next: Authentication »](authentication) +[Next: Authentication »](authentication.md)