FirebaseGoodies / FireAdmin

:seedling: A mini headless CMS built with Angular & Firebase
https://firebasegoodies.github.io/FireAdmin/demo/login?email=guest@fireadmin.com&password=fireadmin
MIT License
80 stars 22 forks source link
admin admin-dashboard angular cms firebase firestore headless-cms

icon FireAdmin

NPM version Downloads License Donate

A minimalistic headless CMS built around Angular & Firebase.

screenshot

Demo

FireAdmin Demo

Features

Installation

npm install --save ng-fire-admin

Usage

It's recommended to use a multi-project workspace with basically 2 main applications (one for the frontend part & the other for the backend) to avoid any potential conflicts, then apply the following changes on your backend app:

Multi-project creation steps ```bash ng new my-workspace --createApplication="false" cd my-workspace ng generate application backend --defaults --routing=true // you can add the frontend app the same way: ng generate application frontend --defaults --routing=true npm install --save ng-fire-admin ```

1. Setup your firebase project:

2. Add your firebase configuration in environment.ts:

  export const environment = {
    production: false,
+   firebase: {
+     apiKey: "<API_KEY>",
+     authDomain: "<PROJECT_ID>.firebaseapp.com",
+     databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
+     projectId: "<PROJECT_ID>",
+     storageBucket: "<BUCKET>.appspot.com",
+     messagingSenderId: "<SENDER_ID>",
+     appId: "<APP_ID>"
+   }
  };

3. Register the FireAdminModule in a module, for example app module:

  import { BrowserModule } from '@angular/platform-browser';
  import { NgModule } from '@angular/core';

  import { AppRoutingModule } from './app-routing.module';
  import { AppComponent } from './app.component';
+ import { FireAdminModule } from 'ng-fire-admin';
+ import { environment } from '../environments/environment';

  @NgModule({
    declarations: [AppComponent],
    imports: [
      BrowserModule,
      AppRoutingModule,
+     FireAdminModule.initialize(environment.firebase)
    ],
    providers: [],
    bootstrap: [AppComponent]
  })
  export class AppModule {}

4. Setup a simple routing as below:

  import { NgModule } from '@angular/core';
  import { Routes, RouterModule } from '@angular/router';

  const routes: Routes = [
+   {
+     path: 'admin',
+     loadChildren: () => import('ng-fire-admin').then(m => m.FireAdminModule)
+   },
+   {
+     path: '**',
+     redirectTo: 'admin'
+   }
  ];

  @NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule]
  })
  export class AppRoutingModule { }

5. Edit your main component template (generally app.component.html) & keep only the <router-outlet></router-outlet> line:

+ <router-outlet></router-outlet>

6. Add the following styles & scripts entries to angular.json:

  "assets": [
    "projects/backend/src/favicon.ico",
    "projects/backend/src/assets"
  ],
  "styles": [
    "projects/backend/src/styles.css",
+   "node_modules/@fortawesome/fontawesome-free/css/all.min.css",
+   "node_modules/material-icons-font/material-icons-font.css",
+   "node_modules/bootstrap/dist/css/bootstrap.min.css",
+   "node_modules/datatables.net-responsive-dt/css/responsive.dataTables.min.css",
+   "node_modules/quill/dist/quill.snow.css"
  ],
  "scripts": [
+   "node_modules/jquery/dist/jquery.min.js",
+   "node_modules/popper.js/dist/umd/popper.min.js",
+   "node_modules/bootstrap/dist/js/bootstrap.min.js",
+   "node_modules/datatables.net/js/jquery.dataTables.min.js",
+   "node_modules/datatables.net-responsive-dt/js/responsive.dataTables.min.js",
+   "node_modules/chart.js/dist/Chart.min.js",
+   "node_modules/shards-ui/dist/js/shards.min.js",
+   "node_modules/quill/dist/quill.min.js"
  ]

7. You may also need to add the following lines to polyfills.ts:

  // Add global to window, assigning the value of window itself.
+ (window as any).global = window;

8. In order to protect your database & storage data, you must set the following rules in your firebase console:

Firestore Database rules:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{collection}/{document}/{path=**} {
      allow read: if isReadable(collection, document);
      allow write: if isWritable(collection, document);
    }

    // Checks if collection/document is readable
    function isReadable(collection, document) {
      return isAdmin() || !isReadProtected(collection) || isOwner(document);
    }

    // Checks if collection is protected against read
    function isReadProtected(collection) {
      return collection in ['users'];
    }

    // Checks if collection/document is writable
    function isWritable(collection, document) {
      return isAdmin() || (
        collection == 'users' && isRegistrationEnabled()
      ) || (
        isEditor() && (!isWriteProtected(collection) || isOwner(document))
      );
    }

    // Checks if collection is protected against write
    function isWriteProtected(collection) {
      return collection in ['users', 'config'];
    }

    // Checks if registration is enabled
    function isRegistrationEnabled() {
      return !exists(/databases/$(database)/documents/config/registration) || get(/databases/$(database)/documents/config/registration).data.enabled;
    }

    // User role check functions
    function isAdmin() {
      return hasRole('admin');
    }

    function isEditor() {
      return hasRole('editor');
    }

    function isSignedIn() {
      return request.auth != null;
    }

    function hasRole(role) {
      return isSignedIn() && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == role;
    }

    function isOwner(ownerId) {
      return isSignedIn() && request.auth.uid == ownerId;
    }
  }
}
More basic database rules? (not recommended) ```javascript rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /{collection}/{document=**} { allow read: if collection != 'users' || request.auth != null; allow write: if request.auth != null; } } } ```

Storage rules:

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read;
      allow write: if request.auth != null;
    }
  }
}

9. Launch your project using ng serve.

That's it :tada:, enjoy your ready to use backend app!

FAQ

Q: How to get & display my data on the frontend side?

A: FireAdmin is Headless, so it only stores/updates your data. This separation of concerns can avoid style/dependency conflicts & allows you to serve your backend & frontend apps on different servers.

You can retrieve the data on your frontend app using Firebase client API & display them in the way you want to.

Q: Why don't you use Firebase functions to manage users through a custom API for example?

A:

  1. I just wanted to prove the theory of a serverless CMS using both Angular & Firebase client side features.
  2. Firebase functions needs a blaze tier to be deployed, which will make the user management not usable/testable for free.

Q: Do you have any plans to continue this project?

A: I do, all i need is free time, ambition & some :coffee:.

Q: Cool! i liked your project, how can i help?

A:

ToDo

Build

Run ng build to build the project. The build artifacts will be stored in the dist/ directory.

Publishing

After building your library with ng build, go to the dist folder cd dist/fire-admin and run npm publish.

Credits

Firebase icon by Icons8.

License

This project is licensed under the MIT license.