Last active
February 2, 2022 12:05
-
-
Save barinbritva/be649a2d0e50ebeb1d7465e21d6c12ea to your computer and use it in GitHub Desktop.
Firebase example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
}); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)] | |
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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