Skip to content

Commit

Permalink
add first version of code with unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ciminf committed Jan 10, 2022
1 parent 121a967 commit 2e4ab40
Show file tree
Hide file tree
Showing 24 changed files with 717 additions and 90 deletions.
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,21 @@

# @nhogs/nestjs-firebase

[Firebase](https://firebase.google.com/) module for [NestJS](https://github.com/nestjs/nest).
---------------------------
## [Firebase](https://firebase.google.com/) module for [NestJS](https://github.com/nestjs/nest).

[![npm peer dependency version Firebase](https://img.shields.io/npm/dependency-version/@nhogs/nestjs-firebase/peer/firebase?label=Firebase&logo=firebase)](https://firebase.google.com/)
[![npm peer dependency version NestJS)](https://img.shields.io/npm/dependency-version/@nhogs/nestjs-firebase/peer/@nestjs/core?label=Nestjs&logo=nestjs&logoColor=e0234e)](https://github.com/nestjs/nest)
---

[![CI](https://github.com/nhogs/nestjs-firebase/actions/workflows/ci.yml/badge.svg)](https://github.com/Nhogs/nestjs-firebase/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/Nhogs/nestjs-firebase/branch/main/graph/badge.svg?token=ZRPM5WFGO2)](https://codecov.io/gh/Nhogs/nestjs-firebase)
[![Maintainability](https://api.codeclimate.com/v1/badges/356bd937ca8b2e7b8d96/maintainability)](https://codeclimate.com/github/Nhogs/nestjs-firebase/maintainability)

## Installation

[![npm](https://img.shields.io/npm/v/@nhogs/nestjs-firebase?label=%40nhogs%2Fnestjs-firebase&logo=npm)](https://www.npmjs.com/package/@nhogs/nestjs-firebase)

```bash
$ npm i --save @nhogs/nestjs-firebase
```

## License

[MIT license](LICENSE).
[![MIT license](https://img.shields.io/github/license/nhogs/nestjs-firebase)](LICENSE)
2 changes: 1 addition & 1 deletion firebase.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"port": 9199
},
"ui": {
"enabled": false
"enabled": true
},
"auth": {
"port": 9099
Expand Down
5 changes: 3 additions & 2 deletions lib/firebase.constants.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export const FIREBASE_CONFIG: string = 'FirebaseConfig';
export const FIREBASE_APP: string = 'FirebaseApp';
export const FIREBASE_CONFIG: string = "FIREBASE_CONFIG";
export const FIREBASE_APP: string = "FIREBASE_APP";
export const DEFAULT_APP_NAME: string = "DEFAULT_APP_NAME";
25 changes: 13 additions & 12 deletions lib/firebase.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,29 @@ import {
Module,
OnApplicationShutdown,
Provider,
} from '@nestjs/common';
import { FirestoreService } from './service';
import { ConfigModule } from '@nestjs/config';
import { initializeApp } from 'firebase/app';
import { StorageService } from './service';
import { FIREBASE_APP, FIREBASE_CONFIG } from './firebase.constants';
import {FirebaseConfig} from "./interface";
} from "@nestjs/common";
import { FirestoreService } from "./service";
import { ConfigModule } from "@nestjs/config";
import { initializeApp } from "firebase/app";
import { StorageService } from "./service";
import { FIREBASE_APP, FIREBASE_CONFIG } from "./firebase.constants";
import { FirebaseConfig } from "./interface";
import { AuthService } from "./service/auth.service";

export const createApp = async (config: FirebaseConfig) => {
return initializeApp(config);
};

@Module({})
export class FirebaseModule implements OnApplicationShutdown {
async onApplicationShutdown() {

}
async onApplicationShutdown() {}

static forRoot(config: FirebaseConfig): DynamicModule {
return {
module: FirebaseModule,
global: true,
providers: [
AuthService,
FirestoreService,
StorageService,
{
Expand All @@ -38,7 +38,7 @@ export class FirebaseModule implements OnApplicationShutdown {
useFactory: async (config: FirebaseConfig) => createApp(config),
},
],
exports: [FirestoreService, StorageService],
exports: [AuthService, FirestoreService, StorageService],
};
}

Expand All @@ -49,6 +49,7 @@ export class FirebaseModule implements OnApplicationShutdown {
imports: [ConfigModule],

providers: [
AuthService,
FirestoreService,
StorageService,
{
Expand All @@ -62,7 +63,7 @@ export class FirebaseModule implements OnApplicationShutdown {
},
],

exports: [FirestoreService, StorageService],
exports: [AuthService, FirestoreService, StorageService],
};
}
}
5 changes: 3 additions & 2 deletions lib/interface/firebase-config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export interface FirebaseConfig {
apiKey: string;
authDomain: string;
projectId: string;
apiKey?: string;
authDomain?: string;
emulator?: boolean;
}
5 changes: 4 additions & 1 deletion lib/interface/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
export * from './firebase-config';
import { FirestoreDataConverter } from "@firebase/firestore";

export * from "./firebase-config";
export { FirestoreDataConverter };
40 changes: 40 additions & 0 deletions lib/service/auth.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Inject, Injectable, Logger } from "@nestjs/common";
import { FirebaseApp } from "firebase/app";
import { FIREBASE_APP, FIREBASE_CONFIG } from "../firebase.constants";
import {
connectAuthEmulator,
createUserWithEmailAndPassword,
getAuth,
signInWithEmailAndPassword,
UserCredential,
} from "firebase/auth";
import { FirebaseConfig } from "../interface";

@Injectable()
export class AuthService {
private readonly _logger = new Logger(AuthService.name);
private readonly _auth;
constructor(
@Inject(FIREBASE_APP) private readonly firebaseApp: FirebaseApp,
@Inject(FIREBASE_CONFIG) private readonly firebaseConfig: FirebaseConfig
) {
this._auth = getAuth(this.firebaseApp);
if (this.firebaseConfig.emulator) {
connectAuthEmulator(this._auth, "http://localhost:9099");
}
}

async createUserWithEmailAndPassword(
email: string,
password: string
): Promise<UserCredential> {
return createUserWithEmailAndPassword(this._auth, email, password);
}

async signInWithEmailAndPassword(
email: string,
password: string
): Promise<UserCredential> {
return signInWithEmailAndPassword(this._auth, email, password);
}
}
61 changes: 45 additions & 16 deletions lib/service/firestore.service.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,40 @@
import { Inject, Injectable, Logger } from "@nestjs/common";
import { FirebaseApp } from "@firebase/app";
import {
addDoc,
collection as firebaseCollection,
CollectionReference,
connectFirestoreEmulator,
doc,
FirestoreDataConverter,
getDoc,
getDocs,
getFirestore,
setDoc,
} from "@firebase/firestore";
import { FIREBASE_APP } from "../firebase.constants";
import { FIREBASE_APP, FIREBASE_CONFIG } from "../firebase.constants";
import { FirebaseConfig } from "../interface";

@Injectable()
export class FirestoreService {
constructor(
@Inject(FIREBASE_APP) private readonly firebaseApp: FirebaseApp
) {}
private readonly _logger = new Logger(FirestoreService.name);
private db = getFirestore(this.firebaseApp);
private readonly _db;
constructor(
@Inject(FIREBASE_APP) private readonly firebaseApp: FirebaseApp,
@Inject(FIREBASE_CONFIG) private readonly firebaseConfig: FirebaseConfig
) {
this._db = getFirestore(this.firebaseApp);
if (this.firebaseConfig.emulator) {
connectFirestoreEmulator(this._db, "localhost", 8080);
}
}

async getDocs<T>(
collection: string,
converter: FirestoreDataConverter<T>
): Promise<T[]> {
this._logger.debug("getDocs(" + collection + ")");

const collectionRef: CollectionReference<T> = firebaseCollection(
this.db,
this._db,
collection
).withConverter<T>(converter);

Expand All @@ -47,17 +55,38 @@ export class FirestoreService {
id: string,
converter: FirestoreDataConverter<T>
): Promise<T | null> {
this._logger.debug(
"getDoc(" + JSON.stringify({ collection, id, converter }) + ")"
);

const docRef = doc(this.db, collection, id).withConverter<T>(converter);
const documentSnapshot = await getDoc<T>(docRef);
const ref = doc(this._db, collection, id).withConverter<T>(converter);
const snapshot = await getDoc<T>(ref);

if (documentSnapshot.exists()) {
return documentSnapshot.data();
if (snapshot.exists()) {
return snapshot.data();
} else {
return null;
}
}

async setDoc<T>(
path,
id: string,
data: T,
converter?: FirestoreDataConverter<T>
): Promise<T> {
const ref = doc(this._db, path, id).withConverter<T>(converter);
await setDoc(ref, data);
const snapshot = await getDoc<T>(ref);

return snapshot.data();
}

async addDoc<T>(
path,
data: T,
converter?: FirestoreDataConverter<T>
): Promise<T> {
const dataDocRef = await addDoc(firebaseCollection(this._db, path), data);
const ref = dataDocRef.withConverter<T>(converter);
const snapshot = await getDoc<T>(ref);

return snapshot.data();
}
}
30 changes: 19 additions & 11 deletions lib/service/storage.service.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
import { Inject, Injectable, Logger } from '@nestjs/common';
import { FirebaseApp } from '@firebase/app';
import { getDownloadURL, getStorage, ref } from '@firebase/storage';
import { FIREBASE_APP } from '../firebase.constants';
import {Inject, Injectable, Logger} from "@nestjs/common";
import {FirebaseApp} from "@firebase/app";
import {connectStorageEmulator, getStorage,} from "@firebase/storage";
import {FIREBASE_APP, FIREBASE_CONFIG} from "../firebase.constants";
import {FirebaseConfig} from "../interface";

@Injectable()
export class StorageService {
constructor(
@Inject(FIREBASE_APP) private readonly firebaseApp: FirebaseApp,
) {}
private readonly _logger = new Logger(StorageService.name);
private storage = getStorage(this.firebaseApp);
private readonly _storage;

async getDownloadURL(url: string): Promise<string> {
this._logger.debug('getDownloadURL=>' + url);
return getDownloadURL(ref(this.storage, url));
constructor(
@Inject(FIREBASE_APP) private readonly firebaseApp: FirebaseApp,
@Inject(FIREBASE_CONFIG) private readonly firebaseConfig: FirebaseConfig
) {
this._storage = getStorage(this.firebaseApp);
if (this.firebaseConfig.emulator) {
connectStorageEmulator(this._storage, "localhost", 9199);
}
}
//
// async getDownloadURL(url: string): Promise<string> {
// this._logger.debug('getDownloadURL=>' + url);
// return getDownloadURL(ref(this.storage, url));
// }
}
55 changes: 55 additions & 0 deletions test/e2e/auth.async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { INestApplication } from "@nestjs/common";
import { Test, TestingModule } from "@nestjs/testing";
import axios from "axios";
import { AppAsyncModule } from "../src/app.async.module";
import { AuthService } from "../../lib/service/auth.service";

describe("Firebase", () => {
let module: TestingModule;
let app: INestApplication;
let authService: AuthService;

beforeAll(async () => {
module = await Test.createTestingModule({
imports: [AppAsyncModule],
}).compile();

app = module.createNestApplication();
await app.init();
authService = module.get<AuthService>(AuthService);
});

it(`should create and sign in user`, async () => {
const user = (
await authService.createUserWithEmailAndPassword(
"[email protected]",
"userpassword"
)
).user;

expect(user.email).toMatchInlineSnapshot(`"[email protected]"`);

const user2 = (
await authService.signInWithEmailAndPassword(
"[email protected]",
"userpassword"
)
).user;

expect(user2.email).toMatchInlineSnapshot(`"[email protected]"`);
});

afterEach(async () => {
return await axios
.delete(
"http://localhost:9099/emulator/v1/projects/demo-nhogs-nestjs-firebase/accounts"
)
.then(function (response) {
expect(response.status).toMatchInlineSnapshot(`200`);
});
});

afterAll(async () => {
return await app.close();
});
});
Loading

0 comments on commit 2e4ab40

Please sign in to comment.