Skip to content

Instantly share code, notes, and snippets.

@barinbritva
Last active February 2, 2022 12:05
Show Gist options
  • Save barinbritva/be649a2d0e50ebeb1d7465e21d6c12ea to your computer and use it in GitHub Desktop.
Save barinbritva/be649a2d0e50ebeb1d7465e21d6c12ea to your computer and use it in GitHub Desktop.
Firebase example
import {container} from 'tsyringe';
import admin, {type ServiceAccount, type app} from 'firebase-admin';
import {type Firestore} from 'firebase-admin/firestore';
import serviceAccount from '../../serviceAccountKey.json';
export function buildDependencyContainer(): void {
const firebaseApp = admin.initializeApp({
credential: admin.credential.cert(serviceAccount as ServiceAccount)
});
container.register<app.App>('firebaseApp', {
useFactory: () => {
return firebaseApp;
}
});
container.register<Firestore>('firestore', {
useFactory: () => {
// admin instance of firebase
return admin.firestore();
}
});
}
import {container} from 'tsyringe';
import {initializeApp, type FirebaseApp} from 'firebase/app';
import {getFirestore, type Firestore} from 'firebase/firestore';
import {ServiceName} from '../types/ServiceName';
import {Configuration} from '../services/Configuration';
export function buildDependencyContainer(config: Configuration): void {
const firebaseApp = initializeApp({
apiKey: config.firebaseApiKey,
authDomain: config.firebaseAuthDomain,
projectId: '1234'
});
container.register<FirebaseApp>(ServiceName.FirebaseApp, {
useFactory: () => {
return firebaseApp;
}
});
container.register<Firestore>(ServiceName.Firestore, {
useFactory: () => {
// client instance of firebase
return getFirestore(firebaseApp);
}
});
}
import {
collection,
doc,
query,
getDocs,
where,
addDoc,
setDoc,
documentId,
type Firestore,
type CollectionReference,
type WhereFilterOp,
type QueryConstraint,
type FirestoreDataConverter
} from 'firebase/firestore';
import {
type FindManyOptions,
type FindOneOptions,
type WhereConditions
} from '../externals/typeorm';
import {BaseFirebaseModel} from '../models/BaseFirebaseModel';
import {UserRepository} from './UserRepository';
export type Conditions<T> = FindManyOptions<T> & FindOneOptions<T>;
export abstract class Operator<T> {
constructor(protected value: T) {}
abstract toQueryCondition(connection: Firestore): [WhereFilterOp, T];
}
export class Equal<T> extends Operator<T> {
toQueryCondition(): [WhereFilterOp, T] {
return ['==', this.value];
}
}
export class LessThanOrEqual<T> extends Operator<T> {
toQueryCondition(): [WhereFilterOp, T] {
return ['<=', this.value];
}
}
export class In extends Operator<any[]> {
toQueryCondition(connection: Firestore): [WhereFilterOp, any] {
let values = this.value;
if (isRef(this.value)) {
values = this.value.map((item) => {
return doc(connection, `${item.ref}/${item.value}`);
});
}
return ['in', values];
}
}
export class MoreThanOrEqual<T> extends Operator<T> {
toQueryCondition(): [WhereFilterOp, T] {
return ['>=', this.value];
}
}
export interface Ref {
ref: string;
value: string;
}
// todo #backlog 🟢 replace by function
export class EqualRef extends Operator<Ref> {
toQueryCondition(connection: Firestore): [WhereFilterOp, any] {
return ['==', doc(connection, `${this.value.ref}/${this.value.value}`)];
}
}
function isRef(value: any[]): value is Ref[] {
return typeof value === 'object' && 'ref' in value[0];
}
export abstract class Entity {
abstract id: string;
}
export interface Repository<Entity extends BaseFirebaseModel> {
save(entity: Entity): Promise<void>;
find(conditions?: Conditions<Entity>): Promise<Entity[]>;
findOne(conditions?: FindOneOptions<Entity>): Promise<Entity | undefined>;
findItemOfUser(userId: string): Promise<Entity | undefined>;
}
export abstract class AbstractRepository<Entity extends BaseFirebaseModel>
implements Repository<Entity>
{
public readonly collectionRef: CollectionReference<Entity>;
constructor(
public readonly collectionName: string,
private db: Firestore,
private convertor: FirestoreDataConverter<Entity>
) {
this.collectionRef = collection(this.db, this.collectionName).withConverter(this.convertor);
}
public async save(entity: Entity): Promise<void> {
if (entity.id == null) {
await addDoc(this.collectionRef, entity);
} else {
await setDoc(doc(this.collectionRef, entity.id), entity);
}
}
// update(entity: T): Promise<T> {}
public async find(conditions?: Conditions<Entity>): Promise<Entity[]> {
const queryConstrains: QueryConstraint[] = [];
if (conditions != null) {
if (conditions.where != null) {
queryConstrains.push(...this.composeWhere(conditions.where));
}
}
const q = query(this.collectionRef, ...queryConstrains).withConverter(this.convertor);
const querySnapshot = await getDocs(q);
const entities: Entity[] = [];
querySnapshot.forEach((item) => {
entities.push(item.data());
});
return entities;
}
public async findOne(conditions?: FindOneOptions<Entity>): Promise<Entity | undefined> {
const where =
conditions == null
? undefined
: Object.assign<Conditions<Entity>, Conditions<Entity>, Conditions<Entity>>(
{},
conditions,
{take: 1}
);
const data = await this.find(where);
return data.length > 0 ? data[0] : undefined;
}
public async findItemOfUser(userId: string): Promise<Entity | undefined> {
const profile = await this.findOne({
where: {
user: new EqualRef({ref: UserRepository.collectionName, value: userId})
}
});
return profile;
}
public createDocumentRef(documentId: string, collectionName: string) {
return doc(this.db, collectionName || this.collectionName, documentId);
}
private composeWhere(conditions: WhereConditions<Entity>): QueryConstraint[] {
const wheres: QueryConstraint[] = [];
// todo why constraints any?
const constraints = Object.entries(conditions);
constraints.forEach((constraint) => {
wheres.push(...this.processConstraint(constraint));
});
return wheres;
}
private processConstraint(constraint: [string, any]): QueryConstraint[] {
const wheres: QueryConstraint[] = [];
const [field, value] = constraint;
if (typeof value === 'string') {
// todo #backlog 🟢 not only by id
wheres.push(where(documentId(), '==', value));
} else if (value instanceof Operator) {
wheres.push(where(field, ...value.toQueryCondition(this.db)));
} else if (typeof value === 'string') {
wheres.push(where(field, '==', value));
} else if (Array.isArray(value)) {
value.forEach((item) => {
wheres.push(...this.processConstraint([field, item]));
});
} else {
throw new Error('Unsupported condition: ' + JSON.stringify(constraint));
}
return wheres;
}
}
import admin from 'firebase-admin';
import {collection} from 'firebase/firestore';
const serviceAccount = {
type: 'service_account',
project_id: '***',
private_key_id: '*****',
private_key: '******',
client_email: 'firebase-adminsdk-84lvo@speed-dating-47743.iam.gserviceaccount.com',
client_id: '*****',
auth_uri: 'https://accounts.google.com/o/oauth2/auth',
token_uri: 'https://oauth2.googleapis.com/token',
auth_provider_x509_cert_url: 'https://www.googleapis.com/oauth2/v1/certs',
client_x509_cert_url: '****'
};
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
collection(admin.firestore(), 'cities');
/*
file:///home/barinbritva/projects/speed-dating/node_modules/@firebase/firestore/dist/index.node.mjs:20043
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Expected first argument to collection() to be a CollectionReference, ' +
^
FirestoreError [FirebaseError]: Expected first argument to collection() to be a CollectionReference, a DocumentReference or FirebaseFirestore
at collection (file:///home/barinbritva/projects/speed-dating/node_modules/@firebase/firestore/dist/index.node.mjs:20043:19)
at file:///home/barinbritva/projects/speed-dating/workspaces/server/src/index.js:23:1
at ModuleJob.run (internal/modules/esm/module_job.js:183:25)
at async Loader.import (internal/modules/esm/loader.js:178:24)
at async Object.loadESM (internal/process/esm_loader.js:68:5) {
code: 'invalid-argument',
toString: [Function (anonymous)]
*/
import admin, {type ServiceAccount} from 'firebase-admin';
import {collection} from 'firebase/firestore';
import serviceAccount from '../serviceAccountKey.json';
admin.initializeApp({
credential: admin.credential.cert(serviceAccount as ServiceAccount)
});
// even typescript shows error here now
/*
No overload matches this call.
Overload 1 of 3, '(firestore: Firestore, path: string, ...pathSegments: string[]): CollectionReference<DocumentData>', gave the following error.
Argument of type 'FirebaseFirestore.Firestore' is not assignable to parameter of type 'import("/home/barinbritva/projects/speed-dating/node_modules/@firebase/firestore/dist/index").Firestore'.
Type 'Firestore' is missing the following properties from type 'Firestore': type, app, toJSON
Overload 2 of 3, '(reference: CollectionReference<unknown>, path: string, ...pathSegments: string[]): CollectionReference<DocumentData>', gave the following error.
Argument of type 'Firestore' is not assignable to parameter of type 'CollectionReference<unknown>'.
Type 'Firestore' is missing the following properties from type 'CollectionReference<unknown>': type, id, path, parent, and 3 more.
Overload 3 of 3, '(reference: DocumentReference<DocumentData>, path: string, ...pathSegments: string[]): CollectionReference<DocumentData>', gave the following error.
Argument of type 'Firestore' is not assignable to parameter of type 'DocumentReference<DocumentData>'.
Type 'Firestore' is missing the following properties from type 'DocumentReference<DocumentData>': converter, type, firestore, id, and 3 more.ts(2769)
*/
collection(admin.firestore(), 'cities');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment