-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add tRPC application server and Firestore database (#2063)
- Loading branch information
Showing
46 changed files
with
2,877 additions
and
177 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,11 +11,13 @@ APP_NAME=Acme Co. | |
APP_HOSTNAME=localhost | ||
APP_ORIGIN=http://localhost:5173 | ||
API_ORIGIN=https://api-mcfytwakla-uc.a.run.app | ||
APP_STORAGE_BUCKET=example.com | ||
|
||
# Google Cloud | ||
# https://console.cloud.google.com/ | ||
GOOGLE_CLOUD_PROJECT=kriasoft | ||
GOOGLE_CLOUD_REGION=us-central1 | ||
GOOGLE_CLOUD_DATABASE="(default)" | ||
GOOGLE_CLOUD_CREDENTIALS={"type":"service_account","project_id":"example","private_key_id":"xxx","private_key":"-----BEGIN PRIVATE KEY-----\nxxxxx\n-----END PRIVATE KEY-----\n","client_email":"[email protected]","client_id":"xxxxx","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":"https://www.googleapis.com/robot/v1/metadata/x509/application%40example.iam.gserviceaccount.com"} | ||
|
||
# Firebase | ||
|
@@ -24,6 +26,11 @@ FIREBASE_APP_ID=1:736557952746:web:b5ee23841e24c0b883b193 | |
FIREBASE_API_KEY=AIzaSyAZDmdeRWvlYgZpwm6LBCkYJM6ySIMF2Hw | ||
FIREBASE_AUTH_DOMAIN=kriasoft.web.app | ||
|
||
# OpenAI | ||
# https://platform.openai.com/ | ||
OPENAI_ORGANIZATION=xxxxx | ||
OPENAI_API_KEY=xxxxx | ||
|
||
# Cloudflare | ||
# https://dash.cloudflare.com/ | ||
# https://developers.cloudflare.com/api/tokens/create | ||
|
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
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# Firestore Database | ||
|
||
Database schema, security rules, indexes, and seed data for the [Firestore](https://cloud.google.com/firestore) database. | ||
|
||
## Directory Structure | ||
|
||
- [`/models`](./models/) — Database schema definitions using [Zod](https://zod.dev/). | ||
- [`/seeds`](./seeds/) — Sample / reference data for the database. | ||
- [`/scripts`](./scripts/) — Scripts for managing the database. | ||
- [`/firestore.indexes.json`](./firestore.indexes.json) — Firestore indexes. | ||
- [`/firestore.rules`](./firestore.rules) — Firestore security rules. | ||
|
||
## Scripts | ||
|
||
- `yarn workspace db seed` - Seed the database with data from [`/seeds`](./seeds/). | ||
|
||
## References | ||
|
||
- https://zod.dev/ | ||
- https://cloud.google.com/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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"indexes": [ | ||
{ | ||
"collectionGroup": "workspace", | ||
"queryScope": "COLLECTION", | ||
"fields": [ | ||
{ "fieldPath": "ownerId", "order": "ASCENDING" }, | ||
{ "fieldPath": "archived", "order": "DESCENDING" } | ||
] | ||
} | ||
], | ||
"fieldOverrides": [] | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// Firestore security rules. | ||
// https://cloud.google.com/firestore/docs/security/get-started | ||
|
||
rules_version = '2'; | ||
|
||
service cloud.firestore { | ||
match /databases/{database}/documents { | ||
match /workspace/{id} { | ||
allow read: if request.auth != null && ( | ||
resource.data.ownerId = request.auth.uid || | ||
request.auth.token.admin == true | ||
); | ||
} | ||
|
||
match /{document=**} { | ||
allow read, write: if false; | ||
} | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
/* SPDX-FileCopyrightText: 2014-present Kriasoft */ | ||
/* SPDX-License-Identifier: MIT */ | ||
|
||
export * from "./models"; | ||
export { testUsers } from "./seeds/01-users"; | ||
export { testWorkspaces } from "./seeds/02-workspaces"; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
/* SPDX-FileCopyrightText: 2014-present Kriasoft */ | ||
/* SPDX-License-Identifier: MIT */ | ||
|
||
export * from "./workspace"; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
/* SPDX-FileCopyrightText: 2014-present Kriasoft */ | ||
/* SPDX-License-Identifier: MIT */ | ||
|
||
import { Timestamp } from "@google-cloud/firestore"; | ||
import { z } from "zod"; | ||
|
||
export const WorkspaceSchema = z.object({ | ||
name: z.string().max(100), | ||
ownerId: z.string().max(50), | ||
created: z.instanceof(Timestamp), | ||
updated: z.instanceof(Timestamp), | ||
archived: z.instanceof(Timestamp).nullable(), | ||
}); | ||
|
||
export type Workspace = z.output<typeof WorkspaceSchema>; | ||
export type WorkspaceInput = z.input<typeof WorkspaceSchema>; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"name": "db", | ||
"version": "0.0.0", | ||
"private": true, | ||
"type": "module", | ||
"exports": { | ||
".": { | ||
"default": "./index.ts" | ||
}, | ||
"./package.json": "./package.json" | ||
}, | ||
"scripts": { | ||
"seed": "vite-node ./scripts/seed.ts", | ||
"test": "vitest" | ||
}, | ||
"dependencies": { | ||
"@google-cloud/firestore": "^7.1.0", | ||
"@googleapis/identitytoolkit": "^8.0.0", | ||
"zod": "^3.22.4" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^20.10.7", | ||
"dotenv": "^16.3.1", | ||
"ora": "^8.0.1", | ||
"typescript": "~5.3.3", | ||
"vite": "~5.0.11", | ||
"vite-node": "~1.1.3", | ||
"vitest": "~1.1.3" | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* SPDX-FileCopyrightText: 2014-present Kriasoft */ | ||
/* SPDX-License-Identifier: MIT */ | ||
|
||
import { Firestore } from "@google-cloud/firestore"; | ||
import { configDotenv } from "dotenv"; | ||
import { relative, resolve } from "node:path"; | ||
import { oraPromise } from "ora"; | ||
|
||
const rootDir = resolve(__dirname, "../.."); | ||
|
||
// Load environment variables from .env files. | ||
configDotenv({ path: resolve(rootDir, ".env.local") }); | ||
configDotenv({ path: resolve(rootDir, ".env") }); | ||
|
||
let db: Firestore | null = null; | ||
|
||
// Seed the database with test / sample data. | ||
try { | ||
db = new Firestore({ | ||
projectId: process.env.GOOGLE_CLOUD_PROJECT, | ||
databaseId: process.env.GOOGLE_CLOUD_DATABASE, | ||
}); | ||
|
||
// Import all seed modules from the `/seeds` folder. | ||
const files = import.meta.glob<boolean, string, SeedModule>("../seeds/*.ts"); | ||
|
||
// Sequentially seed the database with data from each module. | ||
for (const [path, load] of Object.entries(files)) { | ||
const message = `Seeding ${relative("../seeds", path)}`; | ||
const action = (async () => { | ||
const { seed } = await load(); | ||
await seed(db); | ||
})(); | ||
|
||
await oraPromise(action, message); | ||
} | ||
} finally { | ||
await db?.terminate(); | ||
} | ||
|
||
type SeedModule = { | ||
seed: (db: Firestore) => Promise<void>; | ||
}; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
/* SPDX-FileCopyrightText: 2014-present Kriasoft */ | ||
/* SPDX-License-Identifier: MIT */ | ||
|
||
import { | ||
AuthPlus, | ||
identitytoolkit, | ||
identitytoolkit_v3, | ||
} from "@googleapis/identitytoolkit"; | ||
|
||
/** | ||
* Test user accounts generated by https://randomuser.me/. | ||
*/ | ||
export const testUsers: identitytoolkit_v3.Schema$UserInfo[] = [ | ||
{ | ||
localId: "test-erika", | ||
screenName: "erika", | ||
email: "[email protected]", | ||
emailVerified: true, | ||
phoneNumber: "+14788078434", | ||
displayName: "Erika Pearson", | ||
photoUrl: "https://randomuser.me/api/portraits/women/29.jpg", | ||
rawPassword: "paloma", | ||
createdAt: new Date("2024-01-01T12:00:00Z").getTime().toString(), | ||
lastLoginAt: new Date("2024-01-01T12:00:00Z").getTime().toString(), | ||
}, | ||
{ | ||
localId: "test-ryan", | ||
screenName: "ryan", | ||
email: "[email protected]", | ||
emailVerified: true, | ||
phoneNumber: "+16814758216", | ||
displayName: "Ryan Hunt", | ||
photoUrl: "https://randomuser.me/api/portraits/men/20.jpg", | ||
rawPassword: "baggins", | ||
createdAt: new Date("2024-01-02T12:00:00Z").getTime().toString(), | ||
lastLoginAt: new Date("2024-01-02T12:00:00Z").getTime().toString(), | ||
}, | ||
{ | ||
localId: "test-marian", | ||
screenName: "marian", | ||
email: "[email protected]", | ||
emailVerified: true, | ||
phoneNumber: "+19243007975", | ||
displayName: "Marian Stone", | ||
photoUrl: "https://randomuser.me/api/portraits/women/2.jpg", | ||
rawPassword: "winter1", | ||
createdAt: new Date("2024-01-03T12:00:00Z").getTime().toString(), | ||
lastLoginAt: new Date("2024-01-03T12:00:00Z").getTime().toString(), | ||
}, | ||
{ | ||
localId: "test-kurt", | ||
screenName: "kurt", | ||
email: "[email protected]", | ||
emailVerified: true, | ||
phoneNumber: "+19243007975", | ||
displayName: "Kurt Howard", | ||
photoUrl: "https://randomuser.me/api/portraits/men/23.jpg", | ||
rawPassword: "mayday", | ||
createdAt: new Date("2024-01-04T12:00:00Z").getTime().toString(), | ||
lastLoginAt: new Date("2024-01-04T12:00:00Z").getTime().toString(), | ||
}, | ||
{ | ||
localId: "test-dan", | ||
screenName: "dan", | ||
email: "[email protected]", | ||
emailVerified: true, | ||
phoneNumber: "+12046748092", | ||
displayName: "Dan Day", | ||
photoUrl: "https://randomuser.me/api/portraits/men/65.jpg", | ||
rawPassword: "teresa", | ||
createdAt: new Date("2024-01-05T12:00:00Z").getTime().toString(), | ||
lastLoginAt: new Date("2024-01-05T12:00:00Z").getTime().toString(), | ||
customAttributes: JSON.stringify({ admin: true }), | ||
}, | ||
]; | ||
|
||
/** | ||
* Seeds the Google Identity Platform (Firebase Auth) with test user accounts. | ||
* | ||
* @see https://randomuser.me/ | ||
* @see https://cloud.google.com/identity-platform | ||
*/ | ||
export async function seed() { | ||
const auth = new AuthPlus(); | ||
const { relyingparty } = identitytoolkit({ version: "v3", auth }); | ||
await relyingparty.uploadAccount({ requestBody: { users: testUsers } }); | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/* SPDX-FileCopyrightText: 2014-present Kriasoft */ | ||
/* SPDX-License-Identifier: MIT */ | ||
|
||
import { Firestore, Timestamp } from "@google-cloud/firestore"; | ||
import { WorkspaceInput } from "../models"; | ||
import { testUsers as users } from "./01-users"; | ||
|
||
/** | ||
* Test workspaces. | ||
*/ | ||
export const testWorkspaces: (WorkspaceInput & { id: string })[] = [ | ||
{ | ||
id: "DwYchGFGpk", | ||
ownerId: users[0].localId!, | ||
name: "Personal workspace", | ||
created: Timestamp.fromDate(new Date(+users[0].createdAt!)), | ||
updated: Timestamp.fromDate(new Date(+users[0].createdAt!)), | ||
archived: null, | ||
}, | ||
{ | ||
id: "YfYKTcO9q9", | ||
ownerId: users[1].localId!, | ||
name: "Personal workspace", | ||
created: Timestamp.fromDate(new Date(+users[1].createdAt!)), | ||
updated: Timestamp.fromDate(new Date(+users[1].createdAt!)), | ||
archived: null, | ||
}, | ||
{ | ||
id: "c2OsmUvFMY", | ||
ownerId: users[2].localId!, | ||
name: "Personal workspace", | ||
created: Timestamp.fromDate(new Date(+users[2].createdAt!)), | ||
updated: Timestamp.fromDate(new Date(+users[2].createdAt!)), | ||
archived: null, | ||
}, | ||
{ | ||
id: "uTqcGw4qn7", | ||
ownerId: users[3].localId!, | ||
name: "Personal workspace", | ||
created: Timestamp.fromDate(new Date(+users[3].createdAt!)), | ||
updated: Timestamp.fromDate(new Date(+users[3].createdAt!)), | ||
archived: null, | ||
}, | ||
{ | ||
id: "vBHHgg5ydn", | ||
ownerId: users[4].localId!, | ||
name: "Personal workspace", | ||
created: Timestamp.fromDate(new Date(+users[4].createdAt!)), | ||
updated: Timestamp.fromDate(new Date(+users[4].createdAt!)), | ||
archived: null, | ||
}, | ||
]; | ||
|
||
export async function seed(db: Firestore) { | ||
const batch = db.batch(); | ||
|
||
for (const { id, ...workspace } of testWorkspaces) { | ||
const ref = db.doc(`workspace/${id}`); | ||
batch.set(ref, workspace, { merge: true }); | ||
} | ||
|
||
await batch.commit(); | ||
} |
Oops, something went wrong.