Skip to content

Commit

Permalink
big
Browse files Browse the repository at this point in the history
  • Loading branch information
Moe03 committed Apr 15, 2024
1 parent 19c0cc1 commit 1559d7d
Show file tree
Hide file tree
Showing 24 changed files with 655 additions and 3 deletions.
37 changes: 37 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: CI

on:
push:
branches:
- main

pull_request:
branches:
- main

jobs:
test:
runs-on: ${{ matrix.os }}

strategy:
matrix:
os: [ubuntu-latest, macos-latest]
fail-fast: false

steps:
- id: checkout
name: Checkout
uses: actions/checkout@v3
- id: setup-bun
name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- id: install-deps
name: Install dependencies
run: |
bun install
- id: test
name: Run test
run: |
bun test
22 changes: 22 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Release

on:
push:
tags:
- 'v*'

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0

- uses: actions/setup-node@v3
with:
node-version: 16.x

- run: npx changelogithub
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
dist
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2024 Moe Ayman
Copyright (c) 2022 Robert Soriano <https://github.com/wobsoriano>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,26 @@
# firebase-admin-rest
Firebase admin wrapper on REST apis works on Vercel edge functions, cloudflare workers, bun, deno or any js runtime.
# bun starter

## Getting Started

Click the [Use this template](https://github.com/wobsoriano/bun-lib-starter/generate) button to create a new repository with the contents starter.

OR

Run `bun create wobsoriano/bun-lib-starter ./my-lib`.

## Setup

```bash
# install dependencies
bun install

# test the app
bun test

# build the app, available under dist
bun run build
```

## License

MIT
8 changes: 8 additions & 0 deletions build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import dts from 'bun-plugin-dts'

await Bun.build({
entrypoints: ['./src/index.ts'],
outdir: './dist',
minify: true,
plugins: [dts()]
})
Binary file added bun.lockb
Binary file not shown.
35 changes: 35 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "firebase-edge",
"version": "0.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"description": "",
"scripts": {
"build": "bun run build.mjs",
"prepublishOnly": "bun run build",
"dev-test": "bun run src/tests/index.ts"
},
"files": [
"dist"
],
"keywords": [
"bun"
],
"license": "MIT",
"homepage": "https://github.com/wobsoriano/pkg-name#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/wobsoriano/pkg-name.git"
},
"bugs": "https://github.com/wobsoriano/pkg-name/issues",
"author": "Robert Soriano <[email protected]>",
"devDependencies": {
"bun-plugin-dts": "^0.2.1",
"@types/bun": "^1.0.0",
"typescript": "^5.2.2"
},
"dependencies": {
"@tsndr/cloudflare-worker-jwt": "^2.5.3",
"dotenv": "^16.4.5"
}
}
Empty file.
1 change: 1 addition & 0 deletions src/firebase-auth-utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {}
58 changes: 58 additions & 0 deletions src/firebase-auth-utils/initFirebase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@


import * as jwt from '@tsndr/cloudflare-worker-jwt'
import { FirebaseAdminConfig, FirestoreDatabase } from '../types';

export async function generateJWT(input?: {
service?: string,
serviceAccount?: {
client_email: string;
private_key: string;
private_key_id: string;
}
}) {
const serviceAccount = input?.serviceAccount || JSON.parse(process.env.GCLOUD_SERVICE_ACCOUNT || '{}');
// const serviceAccount: {
// client_email: string;
// private_key: string;
// private_key_id: string;
// } | undefined = JSON.parse(process.env.SERVICE_ACCOUNT || '{}');
if (!serviceAccount) throw new Error('SERVICE_ACCOUNT not found in environment variables');
const privateKey = serviceAccount.private_key;
const signedJwt = await jwt.sign(
{
iss: serviceAccount.client_email,
sub: serviceAccount.client_email,
// scope: 'https://www.googleapis.com/auth/datastore',
aud: `https://${input?.service || 'firestore'}.googleapis.com/`,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600, // Expires in 1 hour
},
privateKey,
{
algorithm: 'RS256',
header: {
'kid': serviceAccount.private_key_id,
'typ': 'JWT',
'alg': 'RS256',
},
});

return signedJwt;
}

export async function initEdgeFirebase(options: {
serviceAccount: FirebaseAdminConfig
databaseId?: string;
}): Promise<FirestoreDatabase> {
if (options.serviceAccount.project_id === undefined) throw new Error('project_id is required in serviceAccount.');
const accessToken = await generateJWT({ serviceAccount: options.serviceAccount });
process.env.FIREBASE_REST_ACCESS_TOKEN = accessToken;
process.env.FIREBASE_REST_PROJECT_ID = options.serviceAccount.project_id;
process.env.FIREBASE_REST_DATABASE_ID = options.databaseId || '(default)';
return {
name: options.databaseId || '(default)',
projectId: options.serviceAccount?.project_id,
accessToken: accessToken
};
}
47 changes: 47 additions & 0 deletions src/firestore/deleteDoc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { typedEnv } from "..";
import { generateFirebaseReqHeaders } from "./utils";

/**
* Fetches documents from Firestore.
* @param {string} collectionPath - The path to the Firestore collection.
* @param {Object} options - Additional options for fetching documents.
* @param {number | undefined} options.limit - Optional. The maximum number of documents to fetch defaults to 100.
* @param {string | undefined} options.nextPageToken - The token for fetching the next page of documents.
* @template T - The type of the documents being fetched. Defaults to 'any'.
* @returns {Promise<RestDocuments<T>>} A Promise that resolves to a response object containing fetched Firestore documents.
*/
export async function deleteDocEdge(
docPath: string,
options?: {
limit?: number,
nextPageToken?: string
}): Promise<{
response?: Response,
error?: any,
}> {

try {
let qs = new URLSearchParams({
fields: 'documents(fields,name),nextPageToken',
});

if (options?.nextPageToken) {
qs.append('pageToken', options.nextPageToken);
}

const deleteResponse = await fetch(`https://firestore.googleapis.com/v1beta1/projects/speakwiz-app/databases/${typedEnv.FIREBASE_REST_DATABASE_ID}/documents/${docPath}`, {
method: 'DELETE',
headers: {
...generateFirebaseReqHeaders()
},
})
return {
response: deleteResponse
}
} catch (error) {
return {
error: error
}
}

}
48 changes: 48 additions & 0 deletions src/firestore/getDoc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { typedEnv } from "..";
import { CompatibleDocument } from "../types";
import { formatValuesWithType, generateFirebaseReqHeaders } from "./utils";

/**
* Fetches documents from Firestore.
* @param {string} docPath - The path to the Firestore document.
* @param {Object | undefined} options - Additional options for fetching documents.
* @param {number | undefined} options.db - Optional. database-id instead of the default one.
* @template T - The type of the document being fetched. Defaults to 'any'.
* @returns {Promise<CompatibleDocument<T>>} A Promise that resolves to a response object containing fetched Firestore document.
*/
export async function getDocEdge(
docPath: string,
options?: {
db?: string
}): Promise<CompatibleDocument> {
try {
const response: any = await fetch(`https://firestore.googleapis.com/v1beta1/projects/speakwiz-app/databases/${typedEnv.FIREBASE_REST_PROJECT_ID}/documents/${docPath}`, {
method: 'GET',
headers: generateFirebaseReqHeaders(options?.db || typedEnv.FIREBASE_REST_DATABASE_ID)
}
).then((res) => res.json());
// console.log(response)

if (response?.fields) {
return {
id: docPath.includes(`/`) ? (docPath.split('/').pop() || docPath) : docPath,
exists: () => true,
data: () => formatValuesWithType(response)
}
} else {
return {
id: docPath.includes(`/`) ? (docPath.split('/').pop() || docPath) : docPath,
exists: () => false,
data: () => undefined,
}
}
} catch (error) {
console.error(error)
return {
id: docPath.includes(`/`) ? (docPath.split('/').pop() || docPath) : docPath,
exists: () => false,
data: () => undefined,
error: error,
}
}
}
73 changes: 73 additions & 0 deletions src/firestore/getDocs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@

import { typedEnv } from "..";
import { FirestoreDocument, RestDocuments } from "../types";
import { formatValuesWithType, generateFirebaseReqHeaders } from "./utils";

/**
* Fetches documents from Firestore.
* @param {string} collectionPath - The path to the Firestore collection.
* @param {Object| undefined} options - Additional options for fetching documents.
* @param {number | undefined} options.limit - Optional. The maximum number of documents to fetch defaults to 100.
* @param {string | undefined} options.nextPageToken - The token for fetching the next page of documents.
* @template T - The type of the documents being fetched. Defaults to 'any'.
* @returns {Promise<RestDocuments<T>>} A Promise that resolves to a response object containing fetched Firestore documents.
*/
export async function getDocsEdge<T = any>(
collectionPath: string,
options?: {
limit?: number,
nextPageToken?: string
}): Promise<RestDocuments<T>> {
try {

let qs = new URLSearchParams({
fields: 'documents(fields,name),nextPageToken',
});

if (options?.nextPageToken) {
qs.append('pageToken', options.nextPageToken);
}

const res: any = await fetch(`https://firestore.googleapis.com/v1beta1/projects/speakwiz-app/databases/${typedEnv.FIREBASE_REST_DATABASE_ID}/documents/${collectionPath}?${qs.toString()}&pageSize=${options?.limit || 100}`, {
method: 'GET',
headers: {
...generateFirebaseReqHeaders(typedEnv.FIREBASE_REST_DATABASE_ID)
// "Authorization": "Bearer " + accessToken,
// "x-goog-request-params": `project_id=speakwiz-app&database_id=${options.bucket}`
},
}).then((res) => res.json());

const rawDocs = res?.documents || [];

if (rawDocs?.length > 0) {
const docs = rawDocs.map((docRef: {
name: string,
fields: {
[key: string]: FirestoreDocument
}
}) => {
return formatValuesWithType(docRef);
});

return {
size: docs?.length,
empty: docs?.length === 0,
docs: docs
};
} else {
return {
size: 0,
empty: true,
docs: []
};
}
} catch (error) {
console.error(error);
return {
size: 0,
empty: true,
docs: [],
error: error
};
}
}
11 changes: 11 additions & 0 deletions src/firestore/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { initEdgeFirebase } from "../firebase-auth-utils/initFirebase";
import { deleteDocEdge } from "./deleteDoc";
import { getDocEdge } from "./getDoc";
import { getDocsEdge } from "./getDocs";

export {
initEdgeFirebase,
getDocEdge,
getDocsEdge,
deleteDocEdge
}
Empty file added src/firestore/queryDocs.ts
Empty file.
Empty file added src/firestore/setDoc.ts
Empty file.
Empty file added src/firestore/updateDoc.ts
Empty file.
Loading

0 comments on commit 1559d7d

Please sign in to comment.