├── .gitignore ├── 1-fast-but-not-good-setup ├── final │ ├── .firebaserc │ ├── .gitignore │ ├── .vscode │ │ └── extensions.json │ ├── README.md │ ├── firebase.json │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── public │ │ └── favicon.ico │ ├── src │ │ ├── App.vue │ │ ├── assets │ │ │ └── logo.png │ │ ├── components │ │ │ ├── Dashboard.vue │ │ │ ├── Editor.vue │ │ │ └── Home.vue │ │ ├── config.js │ │ ├── main.js │ │ └── util.js │ └── vite.config.js └── start │ ├── README.md │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── public │ └── favicon.ico │ ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ ├── Dashboard.vue │ │ ├── Editor.vue │ │ └── Home.vue │ ├── config.js │ ├── main.js │ └── util.js │ └── vite.config.js ├── 2-best-practices-setup ├── final │ ├── .firebaserc │ ├── .gitignore │ ├── .vscode │ │ └── extensions.json │ ├── README.md │ ├── firebase.json │ ├── firestore.indexes.json │ ├── firestore.rules │ ├── index.html │ ├── local │ │ ├── auth_export │ │ │ ├── accounts.json │ │ │ └── config.json │ │ ├── firebase-export-metadata.json │ │ └── firestore_export │ │ │ ├── all_namespaces │ │ │ └── all_kinds │ │ │ │ ├── all_namespaces_all_kinds.export_metadata │ │ │ │ └── output-0 │ │ │ └── firestore_export.overall_export_metadata │ ├── package-lock.json │ ├── package.json │ ├── public │ │ └── favicon.ico │ ├── src │ │ ├── App.vue │ │ ├── assets │ │ │ └── logo.png │ │ ├── components │ │ │ ├── Dashboard.vue │ │ │ ├── Editor.vue │ │ │ └── Home.vue │ │ ├── config.js │ │ ├── firebase.js │ │ ├── main.js │ │ └── util.js │ └── vite.config.js └── start │ ├── .firebaserc │ ├── .gitignore │ ├── .vscode │ └── extensions.json │ ├── README.md │ ├── firebase.json │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── public │ └── favicon.ico │ ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ ├── Dashboard.vue │ │ ├── Editor.vue │ │ └── Home.vue │ ├── config.js │ ├── main.js │ └── util.js │ └── vite.config.js ├── 3-cloud-firestore ├── final │ ├── .firebaserc │ ├── .gitignore │ ├── .vscode │ │ └── extensions.json │ ├── README.md │ ├── firebase.json │ ├── firestore.indexes.json │ ├── firestore.rules │ ├── index.html │ ├── local │ │ ├── auth_export │ │ │ ├── accounts.json │ │ │ └── config.json │ │ ├── firebase-export-metadata.json │ │ └── firestore_export │ │ │ ├── all_namespaces │ │ │ └── all_kinds │ │ │ │ ├── all_namespaces_all_kinds.export_metadata │ │ │ │ └── output-0 │ │ │ └── firestore_export.overall_export_metadata │ ├── package-lock.json │ ├── package.json │ ├── public │ │ └── favicon.ico │ ├── seed │ │ └── index.ts │ ├── src │ │ ├── App.vue │ │ ├── assets │ │ │ └── logo.png │ │ ├── components │ │ │ ├── DataTable.vue │ │ │ ├── DataTableCell.vue │ │ │ ├── DataTableHead.vue │ │ │ ├── DataTableRow.vue │ │ │ ├── DataTableRowList.vue │ │ │ ├── ExpenseExcersize.vue │ │ │ └── UserExcersize.vue │ │ ├── config.js │ │ ├── firebase.js │ │ ├── main.js │ │ └── pages │ │ │ ├── Denormalization.vue │ │ │ ├── FundamentalQuerying.vue │ │ │ ├── Home.vue │ │ │ ├── QueryingArrays.vue │ │ │ ├── RangesCursoring.vue │ │ │ └── RealtimeStreams.vue │ ├── tsconfig.json │ └── vite.config.js ├── instruction │ ├── .firebaserc │ ├── .gitignore │ ├── .vscode │ │ └── extensions.json │ ├── README.md │ ├── firebase.json │ ├── firestore.indexes.json │ ├── firestore.rules │ ├── index.html │ ├── local │ │ ├── auth_export │ │ │ ├── accounts.json │ │ │ └── config.json │ │ ├── firebase-export-metadata.json │ │ └── firestore_export │ │ │ ├── all_namespaces │ │ │ └── all_kinds │ │ │ │ ├── all_namespaces_all_kinds.export_metadata │ │ │ │ └── output-0 │ │ │ └── firestore_export.overall_export_metadata │ ├── package-lock.json │ ├── package.json │ ├── public │ │ └── favicon.ico │ ├── seed │ │ └── index.ts │ ├── src │ │ ├── App.vue │ │ ├── assets │ │ │ └── logo.png │ │ ├── components │ │ │ ├── DataTable.vue │ │ │ ├── DataTableCell.vue │ │ │ ├── DataTableHead.vue │ │ │ ├── DataTableRow.vue │ │ │ ├── DataTableRowList.vue │ │ │ ├── ExpenseExcersize.vue │ │ │ └── UserExcersize.vue │ │ ├── config.js │ │ ├── firebase.js │ │ ├── main.js │ │ └── pages │ │ │ └── Home.vue │ ├── tsconfig.json │ └── vite.config.js └── start │ ├── .firebaserc │ ├── .gitignore │ ├── .vscode │ └── extensions.json │ ├── README.md │ ├── firebase.json │ ├── firestore.indexes.json │ ├── firestore.rules │ ├── index.html │ ├── local │ ├── auth_export │ │ ├── accounts.json │ │ └── config.json │ ├── firebase-export-metadata.json │ └── firestore_export │ │ ├── all_namespaces │ │ └── all_kinds │ │ │ ├── all_namespaces_all_kinds.export_metadata │ │ │ ├── output-0 │ │ │ ├── output-1 │ │ │ └── output-2 │ │ └── firestore_export.overall_export_metadata │ ├── package-lock.json │ ├── package.json │ ├── public │ └── favicon.ico │ ├── seed │ └── index.ts │ ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ ├── DataTable.vue │ │ ├── DataTableCell.vue │ │ ├── DataTableHead.vue │ │ ├── DataTableRow.vue │ │ ├── DataTableRowList.vue │ │ ├── ExpenseExcersize.vue │ │ └── UserExcersize.vue │ ├── config.js │ ├── firebase.js │ ├── main.js │ └── pages │ │ ├── CollectionGroup.vue │ │ ├── FundamentalQuerying.vue │ │ ├── Home.vue │ │ ├── QueryingArrays.vue │ │ ├── RangesCursoring.vue │ │ └── RealtimeStreams.vue │ ├── tsconfig.json │ └── vite.config.js ├── 4-firebase-auth ├── final │ ├── .firebaserc │ ├── .gitignore │ ├── .vscode │ │ └── extensions.json │ ├── README.md │ ├── firebase.json │ ├── firestore.rules │ ├── index.html │ ├── local │ │ ├── auth_export │ │ │ ├── accounts.json │ │ │ └── config.json │ │ ├── firebase-export-metadata.json │ │ └── firestore_export │ │ │ ├── all_namespaces │ │ │ └── all_kinds │ │ │ │ ├── all_namespaces_all_kinds.export_metadata │ │ │ │ └── output-0 │ │ │ └── firestore_export.overall_export_metadata │ ├── package-lock.json │ ├── package.json │ ├── public │ │ └── favicon.ico │ ├── seed │ │ └── index.ts │ ├── src │ │ ├── App.vue │ │ ├── assets │ │ │ └── logo.png │ │ ├── config.js │ │ ├── firebase.js │ │ └── main.js │ └── vite.config.js └── start │ ├── .firebaserc │ ├── .gitignore │ ├── .vscode │ └── extensions.json │ ├── README.md │ ├── firebase.json │ ├── firestore.rules │ ├── index.html │ ├── local │ ├── auth_export │ │ ├── accounts.json │ │ └── config.json │ ├── firebase-export-metadata.json │ └── firestore_export │ │ ├── all_namespaces │ │ └── all_kinds │ │ │ ├── all_namespaces_all_kinds.export_metadata │ │ │ └── output-0 │ │ └── firestore_export.overall_export_metadata │ ├── package-lock.json │ ├── package.json │ ├── public │ └── favicon.ico │ ├── seed │ └── index.ts │ ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── config.js │ ├── firebase.js │ └── main.js │ └── vite.config.js ├── 5-security-rules ├── .gitignore ├── final │ ├── .firebaserc │ ├── firebase.json │ ├── firestore.indexes.json │ ├── firestore.rules │ ├── package-lock.json │ ├── package.json │ └── test │ │ └── rules.test.js └── start │ ├── .firebaserc │ ├── firebase.json │ ├── firestore.indexes.json │ ├── firestore.rules │ ├── package-lock.json │ ├── package.json │ └── test │ └── rules.test.js ├── 6-cloud-functions ├── final │ ├── .firebaserc │ ├── .gitignore │ ├── emulators.sh │ ├── firebase.json │ ├── firestore.indexes.json │ ├── firestore.rules │ ├── functions │ │ ├── .gitignore │ │ ├── index.js │ │ ├── package-lock.json │ │ └── package.json │ ├── local │ │ ├── auth_export │ │ │ ├── accounts.json │ │ │ └── config.json │ │ ├── firebase-export-metadata.json │ │ └── firestore_export │ │ │ ├── all_namespaces │ │ │ └── all_kinds │ │ │ │ ├── all_namespaces_all_kinds.export_metadata │ │ │ │ └── output-0 │ │ │ └── firestore_export.overall_export_metadata │ ├── package-lock.json │ ├── package.json │ └── public │ │ └── 404.html └── start │ ├── .firebaserc │ ├── .gitignore │ ├── emulators.sh │ ├── firebase.json │ ├── firestore.indexes.json │ ├── firestore.rules │ ├── functions │ ├── .gitignore │ ├── index.js │ ├── package-lock.json │ └── package.json │ ├── local │ ├── auth_export │ │ ├── accounts.json │ │ └── config.json │ ├── firebase-export-metadata.json │ └── firestore_export │ │ ├── all_namespaces │ │ └── all_kinds │ │ │ ├── all_namespaces_all_kinds.export_metadata │ │ │ └── output-0 │ │ └── firestore_export.overall_export_metadata │ ├── package-lock.json │ ├── package.json │ └── public │ └── 404.html ├── README.md ├── guide ├── .firebase │ └── hosting.ZGlzdA.cache ├── .firebaserc ├── .gitignore ├── .npmrc ├── .vscode │ ├── extensions.json │ └── launch.json ├── README.md ├── astro.config.mjs ├── firebase.json ├── package-lock.json ├── package.json ├── public │ ├── away_towards.svg │ ├── category_cost_composite_index.svg │ ├── code-callout.css │ ├── collection_document.svg │ ├── collection_group_query.svg │ ├── composite_index.png │ ├── concurrency.png │ ├── cost_index_range.svg │ ├── de-old.png │ ├── de-profile.png │ ├── denormalization.svg │ ├── favicon.ico │ ├── firebase-arch.svg │ ├── multiple_indexes.svg │ ├── photo-quote.css │ ├── reads_over_writes.svg │ ├── request_monitor.png │ ├── request_monitor_details.png │ ├── reset.css │ ├── row_doc.svg │ ├── subcollection.svg │ ├── tbl_both_expenses.svg │ ├── tbl_expenses.svg │ ├── tbl_expenses_approval.svg │ ├── tbl_join.svg │ ├── tbl_users.svg │ └── traditional-arch.svg ├── src │ ├── components │ │ ├── ContentListItem.astro │ │ ├── DividerGrid.astro │ │ ├── Fonts.astro │ │ ├── PhotoQuote.astro │ │ ├── TableOfContents.astro │ │ └── TopNav.astro │ ├── layouts │ │ ├── GuideLayout.astro │ │ └── Layout.astro │ ├── pages │ │ ├── 1-intro.md │ │ ├── 2-setup.md │ │ ├── 3-cloud-firestore.md │ │ ├── 4-firebase-authentication.md │ │ ├── 5-security-rules.md │ │ ├── 6-cloud-functions.md │ │ └── index.astro │ └── types.ts └── tsconfig.json └── seed ├── README.md ├── admin.ts ├── batch.ts ├── data ├── categories.json ├── expenses.json ├── users-sm.json └── users.json ├── env.sh ├── expenses.ts ├── index.ts ├── package-lock.json ├── package.json ├── tsconfig.json ├── types.ts ├── users.ts └── util.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | sa.json 4 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/final/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "frontend-masters-firebase" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/final/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | .firebase -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/final/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/final/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Vite 2 | 3 | This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` 14 | 15 | 16 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/final/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "start", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "deploy": "vite build && firebase hosting:channel:deploy fast-but-bad" 10 | }, 11 | "dependencies": { 12 | "firebase": "^9.8.1", 13 | "open-props": "^1.3.16", 14 | "snarkdown": "^2.0.0", 15 | "vue": "^3.2.25", 16 | "vue-router": "^4.0.15" 17 | }, 18 | "devDependencies": { 19 | "@vitejs/plugin-vue": "^2.3.3", 20 | "firebase-tools": "^11.0.0", 21 | "vite": "^2.9.9" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/final/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/1-fast-but-not-good-setup/final/public/favicon.ico -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/final/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 17 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/final/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/1-fast-but-not-good-setup/final/src/assets/logo.png -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/final/src/components/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 65 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/final/src/components/Editor.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 43 | 44 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/final/src/components/Home.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 25 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/final/src/config.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | firebase: { 3 | apiKey: "AIzaSyC0Mlrdz63rpJsUfgJdCXEhZIuJt3LLzMU", 4 | authDomain: "frontend-masters-firebase.firebaseapp.com", 5 | projectId: "frontend-masters-firebase", 6 | storageBucket: "frontend-masters-firebase.appspot.com", 7 | messagingSenderId: "66563583985", 8 | appId: "1:66563583985:web:e5982c7fe56bf0387d1e50", 9 | measurementId: "G-FS54PL4KVB" 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/final/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import { createRouter, createWebHistory } from 'vue-router'; 4 | import Home from './components/Home.vue'; 5 | import Dashboard from './components/Dashboard.vue'; 6 | import Editor from './components/Editor.vue'; 7 | import 'open-props/style'; 8 | import 'open-props/normalize'; 9 | import 'open-props/colors'; 10 | import 'open-props/colors-hsl'; 11 | import 'open-props/sizes'; 12 | 13 | const routes = [ 14 | { path: '/', component: Home }, 15 | { path: '/dashboard/', component: Dashboard }, 16 | { path: '/editor/:id', component: Editor } 17 | ] 18 | 19 | createApp(App) 20 | .use(createRouter({ 21 | history: createWebHistory(), 22 | routes 23 | })) 24 | .mount('#app') 25 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/final/src/util.js: -------------------------------------------------------------------------------- 1 | import snarkdown from 'snarkdown'; 2 | 3 | export function debounce(func, wait, immediate) { 4 | let timeout; 5 | return function() { 6 | let context = this, args = arguments; 7 | let later = function() { 8 | timeout = null; 9 | if (!immediate) func.apply(context, args); 10 | }; 11 | let callNow = immediate && !timeout; 12 | clearTimeout(timeout); 13 | timeout = setTimeout(later, wait); 14 | if (callNow) func.apply(context, args); 15 | }; 16 | }; 17 | 18 | // All the thanks go to: https://github.com/developit/snarkdown/issues/11#issuecomment-813364966 19 | export function snarkdownEnhanced(markdown) { 20 | return markdown 21 | .split(/(?:\r?\n){2,}/) 22 | .map((l) => 23 | [" ", "\t", "#", "-", "*", ">"].some((char) => l.startsWith(char)) 24 | ? snarkdown(l) 25 | : `

${snarkdown(l)}

` 26 | ) 27 | .join("\n"); 28 | } 29 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/final/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()] 7 | }) 8 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/start/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Vite 2 | 3 | This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` 14 | 15 | 16 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "start", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "firebase": "^9.8.1", 12 | "open-props": "^1.3.16", 13 | "snarkdown": "^2.0.0", 14 | "vue": "^3.2.25", 15 | "vue-router": "^4.0.15" 16 | }, 17 | "devDependencies": { 18 | "@vitejs/plugin-vue": "^2.3.3", 19 | "firebase-tools": "^11.0.0", 20 | "vite": "^2.9.9" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/start/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/1-fast-but-not-good-setup/start/public/favicon.ico -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/start/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 17 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/start/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/1-fast-but-not-good-setup/start/src/assets/logo.png -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/start/src/components/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 34 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/start/src/components/Editor.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 28 | 29 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/start/src/components/Home.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/start/src/config.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | firebase: { 3 | // Go to the console to get your configuration object 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/start/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import { createRouter, createWebHistory } from 'vue-router'; 4 | import Home from './components/Home.vue'; 5 | import Dashboard from './components/Dashboard.vue'; 6 | import Editor from './components/Editor.vue'; 7 | import 'open-props/style'; 8 | import 'open-props/normalize'; 9 | import 'open-props/colors'; 10 | import 'open-props/colors-hsl'; 11 | import 'open-props/sizes'; 12 | 13 | const routes = [ 14 | { path: '/', component: Home }, 15 | { path: '/dashboard/', component: Dashboard }, 16 | { path: '/editor/:id', component: Editor } 17 | ] 18 | 19 | createApp(App) 20 | .use(createRouter({ 21 | history: createWebHistory(), 22 | routes 23 | })) 24 | .mount('#app') 25 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/start/src/util.js: -------------------------------------------------------------------------------- 1 | import snarkdown from 'snarkdown'; 2 | 3 | export function debounce(func, wait, immediate) { 4 | let timeout; 5 | return function() { 6 | let context = this, args = arguments; 7 | let later = function() { 8 | timeout = null; 9 | if (!immediate) func.apply(context, args); 10 | }; 11 | let callNow = immediate && !timeout; 12 | clearTimeout(timeout); 13 | timeout = setTimeout(later, wait); 14 | if (callNow) func.apply(context, args); 15 | }; 16 | }; 17 | 18 | // All the thanks go to: https://github.com/developit/snarkdown/issues/11#issuecomment-813364966 19 | export function snarkdownEnhanced(markdown) { 20 | return markdown 21 | .split(/(?:\r?\n){2,}/) 22 | .map((l) => 23 | [" ", "\t", "#", "-", "*", ">"].some((char) => l.startsWith(char)) 24 | ? snarkdown(l) 25 | : `

${snarkdown(l)}

` 26 | ) 27 | .join("\n"); 28 | } 29 | -------------------------------------------------------------------------------- /1-fast-but-not-good-setup/start/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()] 7 | }) 8 | -------------------------------------------------------------------------------- /2-best-practices-setup/final/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "frontend-masters-firebase" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /2-best-practices-setup/final/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | .firebase -------------------------------------------------------------------------------- /2-best-practices-setup/final/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /2-best-practices-setup/final/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Vite 2 | 3 | This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` 14 | 15 | 16 | -------------------------------------------------------------------------------- /2-best-practices-setup/final/local/auth_export/accounts.json: -------------------------------------------------------------------------------- 1 | {"kind":"identitytoolkit#DownloadAccountResponse","users":[{"localId":"KQhw3dWeMF6Sd2FpUAHcstaxlW92","createdAt":"1652993391815","lastLoginAt":"1652993391815","validSince":"1652993517","emailVerified":false,"disabled":false},{"localId":"lFNp4xsf51oXKJbGz0bhI9yollGs","createdAt":"1652993536430","lastLoginAt":"1652993536430","lastRefreshAt":"2022-05-19T20:52:16.430Z"}]} -------------------------------------------------------------------------------- /2-best-practices-setup/final/local/auth_export/config.json: -------------------------------------------------------------------------------- 1 | {"signIn":{"allowDuplicateEmails":false},"usageMode":"DEFAULT"} -------------------------------------------------------------------------------- /2-best-practices-setup/final/local/firebase-export-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "11.0.0", 3 | "firestore": { 4 | "version": "1.14.3", 5 | "path": "firestore_export", 6 | "metadata_file": "firestore_export/firestore_export.overall_export_metadata" 7 | }, 8 | "auth": { 9 | "version": "11.0.0", 10 | "path": "auth_export" 11 | } 12 | } -------------------------------------------------------------------------------- /2-best-practices-setup/final/local/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/2-best-practices-setup/final/local/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata -------------------------------------------------------------------------------- /2-best-practices-setup/final/local/firestore_export/all_namespaces/all_kinds/output-0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/2-best-practices-setup/final/local/firestore_export/all_namespaces/all_kinds/output-0 -------------------------------------------------------------------------------- /2-best-practices-setup/final/local/firestore_export/firestore_export.overall_export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/2-best-practices-setup/final/local/firestore_export/firestore_export.overall_export_metadata -------------------------------------------------------------------------------- /2-best-practices-setup/final/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "start", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "concurrently npm:emulators vite", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "emulators": "firebase emulators:start --only firestore,auth --import=./local --export-on-exit", 10 | "deploy": "vite build && firebase hosting:channel:deploy fast-but-bad" 11 | }, 12 | "dependencies": { 13 | "firebase": "^9.8.1", 14 | "open-props": "^1.3.16", 15 | "snarkdown": "^2.0.0", 16 | "vue": "^3.2.25", 17 | "vue-router": "^4.0.15" 18 | }, 19 | "devDependencies": { 20 | "@vitejs/plugin-vue": "^2.3.3", 21 | "concurrently": "^7.2.0", 22 | "firebase-tools": "^11.0.0", 23 | "vite": "^2.9.9" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /2-best-practices-setup/final/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/2-best-practices-setup/final/public/favicon.ico -------------------------------------------------------------------------------- /2-best-practices-setup/final/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 17 | -------------------------------------------------------------------------------- /2-best-practices-setup/final/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/2-best-practices-setup/final/src/assets/logo.png -------------------------------------------------------------------------------- /2-best-practices-setup/final/src/components/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 62 | -------------------------------------------------------------------------------- /2-best-practices-setup/final/src/components/Editor.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 39 | 40 | -------------------------------------------------------------------------------- /2-best-practices-setup/final/src/components/Home.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 30 | -------------------------------------------------------------------------------- /2-best-practices-setup/final/src/config.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | firebase: { 3 | apiKey: "AIzaSyC0Mlrdz63rpJsUfgJdCXEhZIuJt3LLzMU", 4 | authDomain: "frontend-masters-firebase.firebaseapp.com", 5 | projectId: "frontend-masters-firebase", 6 | storageBucket: "frontend-masters-firebase.appspot.com", 7 | messagingSenderId: "66563583985", 8 | appId: "1:66563583985:web:e5982c7fe56bf0387d1e50", 9 | measurementId: "G-FS54PL4KVB" 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /2-best-practices-setup/final/src/firebase.js: -------------------------------------------------------------------------------- 1 | import { initializeApp, getApps } from 'firebase/app'; 2 | import { getFirestore, connectFirestoreEmulator } from 'firebase/firestore'; 3 | import { getAuth, connectAuthEmulator } from 'firebase/auth'; 4 | import { config } from './config'; 5 | 6 | function initialize() { 7 | const firebaseApp = initializeApp(config.firebase); 8 | const auth = getAuth(firebaseApp); 9 | const firestore = getFirestore(firebaseApp); 10 | return { firebaseApp, auth, firestore }; 11 | } 12 | 13 | function connectToEmulators({ firebaseApp, auth, firestore }) { 14 | if(location.hostname === 'localhost') { 15 | connectAuthEmulator(auth, 'http://localhost:9099', { disableWarnings: true }); 16 | connectFirestoreEmulator(firestore, 'localhost', 8080); 17 | } 18 | return { firebaseApp, auth, firestore }; 19 | } 20 | 21 | export function getFirebase() { 22 | const existingApp = getApps().at(0); 23 | if(existingApp) return initialize(); 24 | return connectToEmulators(initialize()); 25 | } 26 | -------------------------------------------------------------------------------- /2-best-practices-setup/final/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import { createRouter, createWebHistory } from 'vue-router'; 4 | import Home from './components/Home.vue'; 5 | import Dashboard from './components/Dashboard.vue'; 6 | import Editor from './components/Editor.vue'; 7 | import 'open-props/style'; 8 | import 'open-props/normalize'; 9 | import 'open-props/colors'; 10 | import 'open-props/colors-hsl'; 11 | import 'open-props/sizes'; 12 | 13 | const routes = [ 14 | { path: '/', component: Home }, 15 | { path: '/dashboard/', component: Dashboard }, 16 | { path: '/editor/:id', component: Editor } 17 | ] 18 | 19 | createApp(App) 20 | .use(createRouter({ 21 | history: createWebHistory(), 22 | routes 23 | })) 24 | .mount('#app') 25 | -------------------------------------------------------------------------------- /2-best-practices-setup/final/src/util.js: -------------------------------------------------------------------------------- 1 | import snarkdown from 'snarkdown'; 2 | 3 | export function debounce(func, wait, immediate) { 4 | let timeout; 5 | return function() { 6 | let context = this, args = arguments; 7 | let later = function() { 8 | timeout = null; 9 | if (!immediate) func.apply(context, args); 10 | }; 11 | let callNow = immediate && !timeout; 12 | clearTimeout(timeout); 13 | timeout = setTimeout(later, wait); 14 | if (callNow) func.apply(context, args); 15 | }; 16 | }; 17 | 18 | // All the thanks go to: https://github.com/developit/snarkdown/issues/11#issuecomment-813364966 19 | export function snarkdownEnhanced(markdown) { 20 | return markdown 21 | .split(/(?:\r?\n){2,}/) 22 | .map((l) => 23 | [" ", "\t", "#", "-", "*", ">"].some((char) => l.startsWith(char)) 24 | ? snarkdown(l) 25 | : `

${snarkdown(l)}

` 26 | ) 27 | .join("\n"); 28 | } 29 | -------------------------------------------------------------------------------- /2-best-practices-setup/final/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()] 7 | }) 8 | -------------------------------------------------------------------------------- /2-best-practices-setup/start/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "frontend-masters-firebase" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /2-best-practices-setup/start/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | .firebase -------------------------------------------------------------------------------- /2-best-practices-setup/start/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /2-best-practices-setup/start/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Vite 2 | 3 | This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` 14 | 15 | 16 | -------------------------------------------------------------------------------- /2-best-practices-setup/start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "start", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "deploy": "vite build && firebase hosting:channel:deploy fast-but-bad" 10 | }, 11 | "dependencies": { 12 | "firebase": "^9.8.1", 13 | "open-props": "^1.3.16", 14 | "snarkdown": "^2.0.0", 15 | "vue": "^3.2.25", 16 | "vue-router": "^4.0.15" 17 | }, 18 | "devDependencies": { 19 | "@vitejs/plugin-vue": "^2.3.3", 20 | "firebase-tools": "^11.0.0", 21 | "vite": "^2.9.9" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /2-best-practices-setup/start/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/2-best-practices-setup/start/public/favicon.ico -------------------------------------------------------------------------------- /2-best-practices-setup/start/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 17 | -------------------------------------------------------------------------------- /2-best-practices-setup/start/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/2-best-practices-setup/start/src/assets/logo.png -------------------------------------------------------------------------------- /2-best-practices-setup/start/src/components/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 65 | -------------------------------------------------------------------------------- /2-best-practices-setup/start/src/components/Editor.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 43 | 44 | -------------------------------------------------------------------------------- /2-best-practices-setup/start/src/components/Home.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 25 | -------------------------------------------------------------------------------- /2-best-practices-setup/start/src/config.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | firebase: { 3 | apiKey: "AIzaSyC0Mlrdz63rpJsUfgJdCXEhZIuJt3LLzMU", 4 | authDomain: "frontend-masters-firebase.firebaseapp.com", 5 | projectId: "frontend-masters-firebase", 6 | storageBucket: "frontend-masters-firebase.appspot.com", 7 | messagingSenderId: "66563583985", 8 | appId: "1:66563583985:web:e5982c7fe56bf0387d1e50", 9 | measurementId: "G-FS54PL4KVB" 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /2-best-practices-setup/start/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import { createRouter, createWebHistory } from 'vue-router'; 4 | import Home from './components/Home.vue'; 5 | import Dashboard from './components/Dashboard.vue'; 6 | import Editor from './components/Editor.vue'; 7 | import 'open-props/style'; 8 | import 'open-props/normalize'; 9 | import 'open-props/colors'; 10 | import 'open-props/colors-hsl'; 11 | import 'open-props/sizes'; 12 | 13 | const routes = [ 14 | { path: '/', component: Home }, 15 | { path: '/dashboard/', component: Dashboard }, 16 | { path: '/editor/:id', component: Editor } 17 | ] 18 | 19 | createApp(App) 20 | .use(createRouter({ 21 | history: createWebHistory(), 22 | routes 23 | })) 24 | .mount('#app') 25 | -------------------------------------------------------------------------------- /2-best-practices-setup/start/src/util.js: -------------------------------------------------------------------------------- 1 | import snarkdown from 'snarkdown'; 2 | 3 | export function debounce(func, wait, immediate) { 4 | let timeout; 5 | return function() { 6 | let context = this, args = arguments; 7 | let later = function() { 8 | timeout = null; 9 | if (!immediate) func.apply(context, args); 10 | }; 11 | let callNow = immediate && !timeout; 12 | clearTimeout(timeout); 13 | timeout = setTimeout(later, wait); 14 | if (callNow) func.apply(context, args); 15 | }; 16 | }; 17 | 18 | // All the thanks go to: https://github.com/developit/snarkdown/issues/11#issuecomment-813364966 19 | export function snarkdownEnhanced(markdown) { 20 | return markdown 21 | .split(/(?:\r?\n){2,}/) 22 | .map((l) => 23 | [" ", "\t", "#", "-", "*", ">"].some((char) => l.startsWith(char)) 24 | ? snarkdown(l) 25 | : `

${snarkdown(l)}

` 26 | ) 27 | .join("\n"); 28 | } 29 | -------------------------------------------------------------------------------- /2-best-practices-setup/start/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()] 7 | }) 8 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "frontend-masters-firebase" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Vite 2 | 3 | This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` 12 | 13 | 14 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/local/auth_export/config.json: -------------------------------------------------------------------------------- 1 | {"signIn":{"allowDuplicateEmails":false},"usageMode":"DEFAULT"} -------------------------------------------------------------------------------- /3-cloud-firestore/final/local/firebase-export-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "10.9.2", 3 | "firestore": { 4 | "version": "1.14.3", 5 | "path": "firestore_export", 6 | "metadata_file": "firestore_export/firestore_export.overall_export_metadata" 7 | }, 8 | "auth": { 9 | "version": "10.9.2", 10 | "path": "auth_export" 11 | } 12 | } -------------------------------------------------------------------------------- /3-cloud-firestore/final/local/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/3-cloud-firestore/final/local/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata -------------------------------------------------------------------------------- /3-cloud-firestore/final/local/firestore_export/all_namespaces/all_kinds/output-0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/3-cloud-firestore/final/local/firestore_export/all_namespaces/all_kinds/output-0 -------------------------------------------------------------------------------- /3-cloud-firestore/final/local/firestore_export/firestore_export.overall_export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/3-cloud-firestore/final/local/firestore_export/firestore_export.overall_export_metadata -------------------------------------------------------------------------------- /3-cloud-firestore/final/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "start", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "concurrently npm:emulators vite", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "emulators": "firebase emulators:start --only firestore,auth --import=./local" 10 | }, 11 | "dependencies": { 12 | "firebase": "^9.8.1", 13 | "open-props": "^1.3.16", 14 | "vue": "^3.2.25", 15 | "vue-router": "^4.0.15" 16 | }, 17 | "devDependencies": { 18 | "@vitejs/plugin-vue": "^2.3.3", 19 | "concurrently": "^7.2.0", 20 | "firebase-tools": "^10.9.2", 21 | "ts-node": "^10.7.0", 22 | "typescript": "^4.6.4", 23 | "vite": "^2.9.9" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/3-cloud-firestore/final/public/favicon.ico -------------------------------------------------------------------------------- /3-cloud-firestore/final/seed/index.ts: -------------------------------------------------------------------------------- 1 | import { seed } from '../../../seed'; 2 | 3 | seed().then(console.log); 4 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 86 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/3-cloud-firestore/final/src/assets/logo.png -------------------------------------------------------------------------------- /3-cloud-firestore/final/src/components/DataTable.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 23 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/src/components/DataTableCell.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/src/components/DataTableHead.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/src/components/DataTableRow.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/src/components/DataTableRowList.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 14 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/src/components/ExpenseExcersize.vue: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 25 | 26 | 28 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/src/components/UserExcersize.vue: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 26 | 27 | 29 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/src/config.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | firebase: { 3 | apiKey: "AIzaSyC0Mlrdz63rpJsUfgJdCXEhZIuJt3LLzMU", 4 | authDomain: "frontend-masters-firebase.firebaseapp.com", 5 | projectId: "frontend-masters-firebase", 6 | storageBucket: "frontend-masters-firebase.appspot.com", 7 | messagingSenderId: "66563583985", 8 | appId: "1:66563583985:web:e5982c7fe56bf0387d1e50", 9 | measurementId: "G-FS54PL4KVB" 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/src/firebase.js: -------------------------------------------------------------------------------- 1 | import { initializeApp, getApps } from 'firebase/app'; 2 | import { getFirestore, connectFirestoreEmulator, enableMultiTabIndexedDbPersistence } from 'firebase/firestore'; 3 | import { getAuth, connectAuthEmulator } from 'firebase/auth'; 4 | import { config } from './config'; 5 | 6 | function initialize() { 7 | const firebaseApp = initializeApp(config.firebase); 8 | const auth = getAuth(firebaseApp); 9 | const firestore = getFirestore(firebaseApp); 10 | return { firebaseApp, auth, firestore }; 11 | } 12 | 13 | function connectToEmulators({ firebaseApp, auth, firestore }) { 14 | if(location.hostname === 'localhost') { 15 | connectAuthEmulator(auth, 'http://localhost:9099', { disableWarnings: true }); 16 | connectFirestoreEmulator(firestore, 'localhost', 8080); 17 | } 18 | return { firebaseApp, auth, firestore }; 19 | } 20 | 21 | function enableOffline({ firestore, firebaseApp, auth }) { 22 | enableMultiTabIndexedDbPersistence(firestore); 23 | return { firestore, firebaseApp, auth }; 24 | } 25 | 26 | export function getFirebase() { 27 | const existingApp = getApps().at(0); 28 | if(existingApp) return initialize(); 29 | const services = connectToEmulators(initialize()); 30 | return enableOffline(services); 31 | } 32 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import { createRouter, createWebHistory } from 'vue-router'; 3 | import App from './App.vue' 4 | import Home from './pages/Home.vue'; 5 | import RealtimeStreams from './pages/RealtimeStreams.vue'; 6 | import FundamentalQuerying from './pages/FundamentalQuerying.vue'; 7 | import QueryingArrays from './pages/QueryingArrays.vue'; 8 | import RangesCursoring from './pages/RangesCursoring.vue'; 9 | import Denormalization from './pages/Denormalization.vue'; 10 | import 'open-props/style'; 11 | import 'open-props/normalize'; 12 | import 'open-props/colors-hsl'; 13 | import 'open-props/sizes'; 14 | 15 | const routes = [ 16 | { path: '/', component: Home }, 17 | { path: '/1/creating-realtime-streams', component: RealtimeStreams }, 18 | { path: '/2/fundamental-querying', component: FundamentalQuerying }, 19 | { path: '/3/querying-arrays', component: QueryingArrays }, 20 | { path: '/4/ranges-cursoring', component: RangesCursoring }, 21 | { path: '/5/joins-denormalization', component: Denormalization }, 22 | ] 23 | 24 | createApp(App) 25 | .use(createRouter({ 26 | history: createWebHistory(), 27 | routes 28 | })) 29 | .mount('#app') 30 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/src/pages/FundamentalQuerying.vue: -------------------------------------------------------------------------------- 1 | 90 | 91 | 100 | 101 | 104 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/src/pages/Home.vue: -------------------------------------------------------------------------------- 1 | 2 | 27 | 28 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/src/pages/QueryingArrays.vue: -------------------------------------------------------------------------------- 1 | 81 | 82 | 83 | 92 | 93 | 96 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/src/pages/RangesCursoring.vue: -------------------------------------------------------------------------------- 1 | 96 | 97 | 106 | 107 | 110 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/src/pages/RealtimeStreams.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 38 | 47 | 48 | 51 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": false, 8 | "skipLibCheck": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /3-cloud-firestore/final/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | server: { 8 | hmr: false 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "frontend-masters-firebase" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Vite 2 | 3 | This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` 12 | 13 | 14 | -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/local/auth_export/config.json: -------------------------------------------------------------------------------- 1 | {"signIn":{"allowDuplicateEmails":false},"usageMode":"DEFAULT"} -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/local/firebase-export-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "10.9.2", 3 | "firestore": { 4 | "version": "1.14.3", 5 | "path": "firestore_export", 6 | "metadata_file": "firestore_export/firestore_export.overall_export_metadata" 7 | }, 8 | "auth": { 9 | "version": "10.9.2", 10 | "path": "auth_export" 11 | } 12 | } -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/local/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/3-cloud-firestore/instruction/local/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/local/firestore_export/all_namespaces/all_kinds/output-0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/3-cloud-firestore/instruction/local/firestore_export/all_namespaces/all_kinds/output-0 -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/local/firestore_export/firestore_export.overall_export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/3-cloud-firestore/instruction/local/firestore_export/firestore_export.overall_export_metadata -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "start", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "concurrently npm:emulators vite", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "emulators": "firebase emulators:start --only firestore,auth --import ./local" 10 | }, 11 | "dependencies": { 12 | "firebase": "^9.8.1", 13 | "open-props": "^1.3.16", 14 | "vue": "^3.2.25", 15 | "vue-router": "^4.0.15" 16 | }, 17 | "devDependencies": { 18 | "@vitejs/plugin-vue": "^2.3.3", 19 | "concurrently": "^7.2.0", 20 | "firebase-tools": "^10.9.2", 21 | "ts-node": "^10.7.0", 22 | "typescript": "^4.6.4", 23 | "vite": "^2.9.9" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/3-cloud-firestore/instruction/public/favicon.ico -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/seed/index.ts: -------------------------------------------------------------------------------- 1 | import { seed } from '../../../seed'; 2 | 3 | seed().then(console.log); 4 | -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 86 | -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/3-cloud-firestore/instruction/src/assets/logo.png -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/src/components/DataTable.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 23 | -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/src/components/DataTableCell.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/src/components/DataTableHead.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/src/components/DataTableRow.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/src/components/DataTableRowList.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 14 | -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/src/components/ExpenseExcersize.vue: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 25 | 26 | 28 | -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/src/components/UserExcersize.vue: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 26 | 27 | 29 | -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/src/config.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | firebase: { 3 | apiKey: "AIzaSyC0Mlrdz63rpJsUfgJdCXEhZIuJt3LLzMU", 4 | authDomain: "frontend-masters-firebase.firebaseapp.com", 5 | projectId: "frontend-masters-firebase", 6 | storageBucket: "frontend-masters-firebase.appspot.com", 7 | messagingSenderId: "66563583985", 8 | appId: "1:66563583985:web:e5982c7fe56bf0387d1e50", 9 | measurementId: "G-FS54PL4KVB" 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/src/firebase.js: -------------------------------------------------------------------------------- 1 | import { initializeApp, getApps } from 'firebase/app'; 2 | import { getFirestore, connectFirestoreEmulator, enableMultiTabIndexedDbPersistence } from 'firebase/firestore'; 3 | import { getAuth, connectAuthEmulator } from 'firebase/auth'; 4 | import { config } from './config'; 5 | 6 | function initialize() { 7 | const firebaseApp = initializeApp(config.firebase); 8 | const auth = getAuth(firebaseApp); 9 | const firestore = getFirestore(firebaseApp); 10 | return { firebaseApp, auth, firestore }; 11 | } 12 | 13 | function connectToEmulators({ firebaseApp, auth, firestore }) { 14 | if(location.hostname === 'localhost') { 15 | connectAuthEmulator(auth, 'http://localhost:9099', { disableWarnings: true }); 16 | connectFirestoreEmulator(firestore, 'localhost', 8080); 17 | } 18 | return { firebaseApp, auth, firestore }; 19 | } 20 | 21 | function enableOffline({ firestore, firebaseApp, auth }) { 22 | enableMultiTabIndexedDbPersistence(firestore); 23 | return { firestore, firebaseApp, auth }; 24 | } 25 | 26 | export function getFirebase() { 27 | const existingApp = getApps().at(0); 28 | if(existingApp) return initialize(); 29 | const services = connectToEmulators(initialize()); 30 | return enableOffline(services); 31 | } 32 | -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import { createRouter, createWebHistory } from 'vue-router'; 3 | import App from './App.vue' 4 | import Home from './pages/Home.vue'; 5 | import 'open-props/style'; 6 | import 'open-props/normalize'; 7 | import 'open-props/colors-hsl'; 8 | import 'open-props/sizes'; 9 | 10 | const routes = [ 11 | { path: '/', component: Home } 12 | ] 13 | 14 | createApp(App) 15 | .use(createRouter({ 16 | history: createWebHistory(), 17 | routes 18 | })) 19 | .mount('#app') 20 | -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/src/pages/Home.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 64 | 65 | 68 | -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": false, 8 | "skipLibCheck": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /3-cloud-firestore/instruction/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | server: { 8 | hmr: false 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "frontend-masters-firebase" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Vite 2 | 3 | This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` 12 | 13 | 14 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/local/auth_export/config.json: -------------------------------------------------------------------------------- 1 | {"signIn":{"allowDuplicateEmails":false},"usageMode":"DEFAULT"} -------------------------------------------------------------------------------- /3-cloud-firestore/start/local/firebase-export-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "11.0.1", 3 | "firestore": { 4 | "version": "1.14.3", 5 | "path": "firestore_export", 6 | "metadata_file": "firestore_export/firestore_export.overall_export_metadata" 7 | }, 8 | "auth": { 9 | "version": "11.0.1", 10 | "path": "auth_export" 11 | } 12 | } -------------------------------------------------------------------------------- /3-cloud-firestore/start/local/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/3-cloud-firestore/start/local/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata -------------------------------------------------------------------------------- /3-cloud-firestore/start/local/firestore_export/all_namespaces/all_kinds/output-0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/3-cloud-firestore/start/local/firestore_export/all_namespaces/all_kinds/output-0 -------------------------------------------------------------------------------- /3-cloud-firestore/start/local/firestore_export/all_namespaces/all_kinds/output-1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/3-cloud-firestore/start/local/firestore_export/all_namespaces/all_kinds/output-1 -------------------------------------------------------------------------------- /3-cloud-firestore/start/local/firestore_export/all_namespaces/all_kinds/output-2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/3-cloud-firestore/start/local/firestore_export/all_namespaces/all_kinds/output-2 -------------------------------------------------------------------------------- /3-cloud-firestore/start/local/firestore_export/firestore_export.overall_export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/3-cloud-firestore/start/local/firestore_export/firestore_export.overall_export_metadata -------------------------------------------------------------------------------- /3-cloud-firestore/start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "start", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "concurrently npm:emulators vite", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "emulators": "firebase emulators:start --only firestore,auth --import ./local" 10 | }, 11 | "dependencies": { 12 | "firebase": "^9.8.1", 13 | "open-props": "^1.3.16", 14 | "vue": "^3.2.25", 15 | "vue-router": "^4.0.15" 16 | }, 17 | "devDependencies": { 18 | "@vitejs/plugin-vue": "^2.3.3", 19 | "concurrently": "^7.2.0", 20 | "firebase-tools": "^10.9.2", 21 | "ts-node": "^10.7.0", 22 | "typescript": "^4.6.4", 23 | "vite": "^2.9.9" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/3-cloud-firestore/start/public/favicon.ico -------------------------------------------------------------------------------- /3-cloud-firestore/start/seed/index.ts: -------------------------------------------------------------------------------- 1 | import { seedUsersForFirestore } from '../../../seed/users'; 2 | import { seedExpensesInlineUid, seedExpensesUnderUid } from '../../../seed/expenses'; 3 | 4 | seedUsersForFirestore().then(async users => { 5 | await seedExpensesInlineUid(users); 6 | await seedExpensesUnderUid(users) 7 | }); 8 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 86 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/3-cloud-firestore/start/src/assets/logo.png -------------------------------------------------------------------------------- /3-cloud-firestore/start/src/components/DataTable.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 23 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/src/components/DataTableCell.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/src/components/DataTableHead.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/src/components/DataTableRow.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/src/components/DataTableRowList.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 14 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/src/components/ExpenseExcersize.vue: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 25 | 26 | 28 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/src/components/UserExcersize.vue: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 26 | 27 | 29 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/src/config.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | firebase: { 3 | apiKey: "AIzaSyC0Mlrdz63rpJsUfgJdCXEhZIuJt3LLzMU", 4 | authDomain: "frontend-masters-firebase.firebaseapp.com", 5 | projectId: "frontend-masters-firebase", 6 | storageBucket: "frontend-masters-firebase.appspot.com", 7 | messagingSenderId: "66563583985", 8 | appId: "1:66563583985:web:e5982c7fe56bf0387d1e50", 9 | measurementId: "G-FS54PL4KVB" 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/src/firebase.js: -------------------------------------------------------------------------------- 1 | import { initializeApp, getApps } from 'firebase/app'; 2 | import { getFirestore, connectFirestoreEmulator, enableMultiTabIndexedDbPersistence } from 'firebase/firestore'; 3 | import { getAuth, connectAuthEmulator } from 'firebase/auth'; 4 | import { config } from './config'; 5 | 6 | function initialize() { 7 | const firebaseApp = initializeApp(config.firebase); 8 | const auth = getAuth(firebaseApp); 9 | const firestore = getFirestore(firebaseApp); 10 | return { firebaseApp, auth, firestore }; 11 | } 12 | 13 | function connectToEmulators({ firebaseApp, auth, firestore }) { 14 | if(location.hostname === 'localhost') { 15 | connectAuthEmulator(auth, 'http://localhost:9099', { disableWarnings: true }); 16 | connectFirestoreEmulator(firestore, 'localhost', 8080); 17 | } 18 | return { firebaseApp, auth, firestore }; 19 | } 20 | 21 | function enableOffline({ firestore, firebaseApp, auth }) { 22 | enableMultiTabIndexedDbPersistence(firestore); 23 | return { firestore, firebaseApp, auth }; 24 | } 25 | 26 | export function getFirebase() { 27 | const existingApp = getApps().at(0); 28 | if(existingApp) return initialize(); 29 | const services = connectToEmulators(initialize()); 30 | return enableOffline(services); 31 | } 32 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import { createRouter, createWebHistory } from 'vue-router'; 3 | import App from './App.vue' 4 | import Home from './pages/Home.vue'; 5 | import RealtimeStreams from './pages/RealtimeStreams.vue'; 6 | import FundamentalQuerying from './pages/FundamentalQuerying.vue'; 7 | import QueryingArrays from './pages/QueryingArrays.vue'; 8 | import RangesCursoring from './pages/RangesCursoring.vue'; 9 | import CollectionGroup from './pages/CollectionGroup.vue'; 10 | import 'open-props/style'; 11 | import 'open-props/normalize'; 12 | import 'open-props/colors-hsl'; 13 | import 'open-props/sizes'; 14 | 15 | const routes = [ 16 | { path: '/', component: Home }, 17 | { path: '/1/creating-realtime-streams', component: RealtimeStreams }, 18 | { path: '/2/fundamental-querying', component: FundamentalQuerying }, 19 | { path: '/3/querying-arrays', component: QueryingArrays }, 20 | { path: '/4/ranges-cursoring', component: RangesCursoring }, 21 | { path: '/5/collection-group-queries', component: CollectionGroup }, 22 | ] 23 | 24 | createApp(App) 25 | .use(createRouter({ 26 | history: createWebHistory(), 27 | routes 28 | })) 29 | .mount('#app') 30 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/src/pages/CollectionGroup.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 55 | 64 | 65 | 68 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/src/pages/FundamentalQuerying.vue: -------------------------------------------------------------------------------- 1 | 82 | 83 | 92 | 93 | 96 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/src/pages/Home.vue: -------------------------------------------------------------------------------- 1 | 2 | 27 | 28 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/src/pages/QueryingArrays.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 67 | 76 | 77 | 80 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/src/pages/RangesCursoring.vue: -------------------------------------------------------------------------------- 1 | 77 | 78 | 87 | 88 | 91 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/src/pages/RealtimeStreams.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 34 | 43 | 44 | 47 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": false, 8 | "skipLibCheck": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /3-cloud-firestore/start/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | server: { 8 | hmr: false 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /4-firebase-auth/final/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "frontend-masters-firebase" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /4-firebase-auth/final/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /4-firebase-auth/final/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /4-firebase-auth/final/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Vite 2 | 3 | This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` 12 | 13 | 14 | -------------------------------------------------------------------------------- /4-firebase-auth/final/local/auth_export/config.json: -------------------------------------------------------------------------------- 1 | {"signIn":{"allowDuplicateEmails":false},"usageMode":"DEFAULT"} -------------------------------------------------------------------------------- /4-firebase-auth/final/local/firebase-export-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "11.0.0", 3 | "firestore": { 4 | "version": "1.14.3", 5 | "path": "firestore_export", 6 | "metadata_file": "firestore_export/firestore_export.overall_export_metadata" 7 | }, 8 | "auth": { 9 | "version": "11.0.0", 10 | "path": "auth_export" 11 | } 12 | } -------------------------------------------------------------------------------- /4-firebase-auth/final/local/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/4-firebase-auth/final/local/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata -------------------------------------------------------------------------------- /4-firebase-auth/final/local/firestore_export/all_namespaces/all_kinds/output-0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/4-firebase-auth/final/local/firestore_export/all_namespaces/all_kinds/output-0 -------------------------------------------------------------------------------- /4-firebase-auth/final/local/firestore_export/firestore_export.overall_export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/4-firebase-auth/final/local/firestore_export/firestore_export.overall_export_metadata -------------------------------------------------------------------------------- /4-firebase-auth/final/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "4-firebase-auth", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "concurrently npm:emulators vite", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "emulators": "firebase emulators:start --only firestore,auth --import=./local" 10 | }, 11 | "dependencies": { 12 | "firebase": "^9.8.1", 13 | "open-props": "^1.3.16", 14 | "vue": "^3.2.25" 15 | }, 16 | "devDependencies": { 17 | "@vitejs/plugin-vue": "^2.3.3", 18 | "concurrently": "^7.2.1", 19 | "firebase-tools": "^11.0.0", 20 | "ts-node": "^10.8.0", 21 | "tsc": "^2.0.4", 22 | "vite": "^2.9.9" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /4-firebase-auth/final/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/4-firebase-auth/final/public/favicon.ico -------------------------------------------------------------------------------- /4-firebase-auth/final/seed/index.ts: -------------------------------------------------------------------------------- 1 | import { seed } from '../../../seed'; 2 | 3 | seed().then(console.log); 4 | -------------------------------------------------------------------------------- /4-firebase-auth/final/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/4-firebase-auth/final/src/assets/logo.png -------------------------------------------------------------------------------- /4-firebase-auth/final/src/config.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | firebase: { 3 | apiKey: "AIzaSyC0Mlrdz63rpJsUfgJdCXEhZIuJt3LLzMU", 4 | authDomain: "frontend-masters-firebase.firebaseapp.com", 5 | projectId: "frontend-masters-firebase", 6 | storageBucket: "frontend-masters-firebase.appspot.com", 7 | messagingSenderId: "66563583985", 8 | appId: "1:66563583985:web:e5982c7fe56bf0387d1e50", 9 | measurementId: "G-FS54PL4KVB" 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /4-firebase-auth/final/src/firebase.js: -------------------------------------------------------------------------------- 1 | import { initializeApp, getApps } from 'firebase/app'; 2 | import { getFirestore, connectFirestoreEmulator, enableMultiTabIndexedDbPersistence } from 'firebase/firestore'; 3 | import { getAuth, connectAuthEmulator } from 'firebase/auth'; 4 | import { config } from './config'; 5 | 6 | function initialize() { 7 | const firebaseApp = initializeApp(config.firebase); 8 | const auth = getAuth(firebaseApp); 9 | const firestore = getFirestore(firebaseApp); 10 | return { firebaseApp, auth, firestore }; 11 | } 12 | 13 | function connectToEmulators({ firebaseApp, auth, firestore }) { 14 | if(location.hostname === 'localhost') { 15 | connectAuthEmulator(auth, 'http://localhost:9099', { disableWarnings: true }); 16 | connectFirestoreEmulator(firestore, 'localhost', 8080); 17 | } 18 | return { firebaseApp, auth, firestore }; 19 | } 20 | 21 | function enableOffline({ firestore, firebaseApp, auth }) { 22 | enableMultiTabIndexedDbPersistence(firestore); 23 | return { firestore, firebaseApp, auth }; 24 | } 25 | 26 | export function getFirebase() { 27 | const existingApp = getApps().at(0); 28 | if(existingApp) return initialize(); 29 | const services = connectToEmulators(initialize()); 30 | return enableOffline(services); 31 | } 32 | -------------------------------------------------------------------------------- /4-firebase-auth/final/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import 'open-props/style'; 4 | import 'open-props/normalize'; 5 | import 'open-props/colors-hsl'; 6 | import 'open-props/sizes'; 7 | 8 | createApp(App).mount('#app') 9 | -------------------------------------------------------------------------------- /4-firebase-auth/final/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()] 7 | }) 8 | -------------------------------------------------------------------------------- /4-firebase-auth/start/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "frontend-masters-firebase" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /4-firebase-auth/start/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /4-firebase-auth/start/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /4-firebase-auth/start/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Vite 2 | 3 | This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 ` 12 | 13 | 14 | -------------------------------------------------------------------------------- /4-firebase-auth/start/local/auth_export/config.json: -------------------------------------------------------------------------------- 1 | {"signIn":{"allowDuplicateEmails":false},"usageMode":"DEFAULT"} -------------------------------------------------------------------------------- /4-firebase-auth/start/local/firebase-export-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "11.0.0", 3 | "firestore": { 4 | "version": "1.14.3", 5 | "path": "firestore_export", 6 | "metadata_file": "firestore_export/firestore_export.overall_export_metadata" 7 | }, 8 | "auth": { 9 | "version": "11.0.0", 10 | "path": "auth_export" 11 | } 12 | } -------------------------------------------------------------------------------- /4-firebase-auth/start/local/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/4-firebase-auth/start/local/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata -------------------------------------------------------------------------------- /4-firebase-auth/start/local/firestore_export/all_namespaces/all_kinds/output-0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/4-firebase-auth/start/local/firestore_export/all_namespaces/all_kinds/output-0 -------------------------------------------------------------------------------- /4-firebase-auth/start/local/firestore_export/firestore_export.overall_export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/4-firebase-auth/start/local/firestore_export/firestore_export.overall_export_metadata -------------------------------------------------------------------------------- /4-firebase-auth/start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "4-firebase-auth", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "concurrently npm:emulators vite", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "emulators": "firebase emulators:start --only firestore,auth --import=./local" 10 | }, 11 | "dependencies": { 12 | "firebase": "^9.8.1", 13 | "open-props": "^1.3.16", 14 | "vue": "^3.2.25" 15 | }, 16 | "devDependencies": { 17 | "@vitejs/plugin-vue": "^2.3.3", 18 | "concurrently": "^7.2.1", 19 | "firebase-admin": "^10.2.0", 20 | "firebase-tools": "^11.0.0", 21 | "ts-node": "^10.8.0", 22 | "tsc": "^2.0.4", 23 | "vite": "^2.9.9" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /4-firebase-auth/start/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/4-firebase-auth/start/public/favicon.ico -------------------------------------------------------------------------------- /4-firebase-auth/start/seed/index.ts: -------------------------------------------------------------------------------- 1 | import { initializeApp, cert } from 'firebase-admin/app'; 2 | const serviceAccount = require('./sa.json'); 3 | -------------------------------------------------------------------------------- /4-firebase-auth/start/src/App.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 112 | 113 | 132 | -------------------------------------------------------------------------------- /4-firebase-auth/start/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/4-firebase-auth/start/src/assets/logo.png -------------------------------------------------------------------------------- /4-firebase-auth/start/src/config.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | firebase: { 3 | apiKey: "AIzaSyC0Mlrdz63rpJsUfgJdCXEhZIuJt3LLzMU", 4 | authDomain: "frontend-masters-firebase.firebaseapp.com", 5 | projectId: "frontend-masters-firebase", 6 | storageBucket: "frontend-masters-firebase.appspot.com", 7 | messagingSenderId: "66563583985", 8 | appId: "1:66563583985:web:e5982c7fe56bf0387d1e50", 9 | measurementId: "G-FS54PL4KVB" 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /4-firebase-auth/start/src/firebase.js: -------------------------------------------------------------------------------- 1 | import { initializeApp, getApps } from 'firebase/app'; 2 | import { getFirestore, connectFirestoreEmulator, enableMultiTabIndexedDbPersistence } from 'firebase/firestore'; 3 | import { getAuth, connectAuthEmulator } from 'firebase/auth'; 4 | import { config } from './config'; 5 | 6 | function initialize() { 7 | const firebaseApp = initializeApp(config.firebase); 8 | const auth = getAuth(firebaseApp); 9 | const firestore = getFirestore(firebaseApp); 10 | return { firebaseApp, auth, firestore }; 11 | } 12 | 13 | function connectToEmulators({ firebaseApp, auth, firestore }) { 14 | if(location.hostname === 'localhost') { 15 | connectAuthEmulator(auth, 'http://localhost:9099', { disableWarnings: true }); 16 | connectFirestoreEmulator(firestore, 'localhost', 8080); 17 | } 18 | return { firebaseApp, auth, firestore }; 19 | } 20 | 21 | function enableOffline({ firestore, firebaseApp, auth }) { 22 | enableMultiTabIndexedDbPersistence(firestore); 23 | return { firestore, firebaseApp, auth }; 24 | } 25 | 26 | export function getFirebase() { 27 | const existingApp = getApps().at(0); 28 | if(existingApp) return initialize(); 29 | const services = connectToEmulators(initialize()); 30 | return enableOffline(services); 31 | } 32 | -------------------------------------------------------------------------------- /4-firebase-auth/start/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import 'open-props/style'; 4 | import 'open-props/normalize'; 5 | import 'open-props/colors-hsl'; 6 | import 'open-props/sizes'; 7 | 8 | createApp(App).mount('#app') 9 | -------------------------------------------------------------------------------- /4-firebase-auth/start/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()] 7 | }) 8 | -------------------------------------------------------------------------------- /5-security-rules/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | firebase-debug.log* 8 | firebase-debug.*.log* 9 | 10 | # Firebase cache 11 | .firebase/ 12 | 13 | # Firebase config 14 | 15 | # Uncomment this if you'd like others to create their own Firebase project. 16 | # For a team working on the same Firebase project(s), it is recommended to leave 17 | # it commented so all members can deploy to the same project(s) in .firebaserc. 18 | # .firebaserc 19 | 20 | # Runtime data 21 | pids 22 | *.pid 23 | *.seed 24 | *.pid.lock 25 | 26 | # Directory for instrumented libs generated by jscoverage/JSCover 27 | lib-cov 28 | 29 | # Coverage directory used by tools like istanbul 30 | coverage 31 | 32 | # nyc test coverage 33 | .nyc_output 34 | 35 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 36 | .grunt 37 | 38 | # Bower dependency directory (https://bower.io/) 39 | bower_components 40 | 41 | # node-waf configuration 42 | .lock-wscript 43 | 44 | # Compiled binary addons (http://nodejs.org/api/addons.html) 45 | build/Release 46 | 47 | # Dependency directories 48 | node_modules/ 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Optional REPL history 57 | .node_repl_history 58 | 59 | # Output of 'npm pack' 60 | *.tgz 61 | 62 | # Yarn Integrity file 63 | .yarn-integrity 64 | 65 | # dotenv environment variables file 66 | .env 67 | -------------------------------------------------------------------------------- /5-security-rules/final/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "frontend-masters-firebase" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /5-security-rules/final/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "dist", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "rewrites": [ 10 | { 11 | "source": "**", 12 | "destination": "/index.html" 13 | } 14 | ] 15 | }, 16 | "firestore": { 17 | "rules": "firestore.rules", 18 | "indexes": "firestore.indexes.json" 19 | }, 20 | "emulators": { 21 | "auth": { 22 | "port": 9099 23 | }, 24 | "firestore": { 25 | "port": 8080 26 | }, 27 | "hosting": { 28 | "port": 5055 29 | }, 30 | "ui": { 31 | "enabled": true 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /5-security-rules/final/firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | "indexes": [ 3 | { 4 | "collectionGroup": "expenses", 5 | "queryScope": "COLLECTION", 6 | "fields": [ 7 | { 8 | "fieldPath": "category", 9 | "order": "ASCENDING" 10 | }, 11 | { 12 | "fieldPath": "cost", 13 | "order": "ASCENDING" 14 | } 15 | ] 16 | } 17 | ], 18 | "fieldOverrides": [] 19 | } 20 | -------------------------------------------------------------------------------- /5-security-rules/final/firestore.rules: -------------------------------------------------------------------------------- 1 | rules_version = '2'; 2 | service cloud.firestore { 3 | match /databases/{database}/documents { 4 | function isUserOwned(uid) { 5 | return uid == request.auth.uid; 6 | } 7 | 8 | function isValidEmail() { 9 | return request.resource.data.email.matches('^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,5}$'); 10 | } 11 | 12 | function hasUserAllKeys() { 13 | return request.resource.data.keys().hasAll([ 14 | 'first', 15 | 'email', 16 | ]); 17 | } 18 | 19 | function isUserObject() { 20 | return hasUserAllKeys() && isValidEmail(); 21 | } 22 | 23 | function costsMoreThanZero() { 24 | let data = request.resource.data; 25 | return data.cost is number && data.cost > 0; 26 | } 27 | 28 | function isExpenseObject() { 29 | return request.resource.data.keys().hasAll([ 30 | 'cost', 31 | 'date', 32 | ]) && costsMoreThanZero(); 33 | } 34 | 35 | function dateIsNotModified() { 36 | let diff = request.resource.data.diff(resource.data); 37 | return diff.unchangedKeys().hasOnly([ "date" ]); 38 | } 39 | 40 | function isBudgetCollaborator(requestUid, budgetId) { 41 | let collaborator = get(/databases/$(database)/documents/budgets/$(budgetId)/collaborators/$(requestUid)); 42 | return collaborator.data.role in ['collaborator', 'admin']; 43 | } 44 | 45 | // requestUid - the request of the current authenticated user 46 | // collectionUid - the owner of the expense's uid 47 | function isUserOrCollaborator(requestUid, collectionUid, budgetId) { 48 | return isBudgetCollaborator(requestUid, budgetId) || isUserOwned(collectionUid); 49 | } 50 | 51 | match /users/{uid} { 52 | allow read: if isUserOwned(uid); 53 | allow write: if isUserOwned(uid) && isUserObject(); 54 | 55 | match /expenses/{id} { 56 | allow read: if isUserOrCollaborator(request.auth.uid, uid, resource.data.budgetId); 57 | allow create: if isUserOrCollaborator(request.auth.uid, uid, resource.data.budgetId) && isExpenseObject(); 58 | allow update: if isUserOrCollaborator(request.auth.uid, uid, resource.data.budgetId) && isExpenseObject() && dateIsNotModified(); 59 | } 60 | } 61 | 62 | match /budgets/{budgetId} { 63 | allow read, write: if isBudgetCollaborator(request.auth.uid, budgetId); 64 | 65 | match /collaborators/{uid} { 66 | allow read, write: if isBudgetCollaborator(request.auth.uid, budgetId); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /5-security-rules/final/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "5-security-rules", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "ava" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "@firebase/rules-unit-testing": "^2.0.2", 15 | "ava": "^4.2.0", 16 | "firebase-tools": "^11.0.1", 17 | "ts-node": "^10.8.0", 18 | "tsc": "^2.0.4", 19 | "typescript": "^4.7.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /5-security-rules/start/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "frontend-masters-firebase" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /5-security-rules/start/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "dist", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "rewrites": [ 10 | { 11 | "source": "**", 12 | "destination": "/index.html" 13 | } 14 | ] 15 | }, 16 | "firestore": { 17 | "rules": "firestore.rules", 18 | "indexes": "firestore.indexes.json" 19 | }, 20 | "emulators": { 21 | "auth": { 22 | "port": 9099 23 | }, 24 | "firestore": { 25 | "port": 8080 26 | }, 27 | "hosting": { 28 | "port": 5055 29 | }, 30 | "ui": { 31 | "enabled": true 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /5-security-rules/start/firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | "indexes": [ 3 | { 4 | "collectionGroup": "expenses", 5 | "queryScope": "COLLECTION", 6 | "fields": [ 7 | { 8 | "fieldPath": "category", 9 | "order": "ASCENDING" 10 | }, 11 | { 12 | "fieldPath": "cost", 13 | "order": "ASCENDING" 14 | } 15 | ] 16 | } 17 | ], 18 | "fieldOverrides": [] 19 | } 20 | -------------------------------------------------------------------------------- /5-security-rules/start/firestore.rules: -------------------------------------------------------------------------------- 1 | rules_version = '2'; 2 | service cloud.firestore { 3 | match /databases/{database}/documents { 4 | match /{document=**} { 5 | allow read, write: if true; 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /5-security-rules/start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "5-security-rules", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "ava" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "@firebase/rules-unit-testing": "^2.0.2", 15 | "ava": "^4.2.0", 16 | "firebase-tools": "^11.0.1", 17 | "ts-node": "^10.8.0", 18 | "tsc": "^2.0.4", 19 | "typescript": "^4.7.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /5-security-rules/start/test/rules.test.js: -------------------------------------------------------------------------------- 1 | import testing from "@firebase/rules-unit-testing" 2 | import { readFileSync } from 'fs'; 3 | import test from 'ava'; 4 | 5 | const { 6 | assertFails, 7 | assertSucceeds, 8 | initializeTestEnvironment, 9 | RulesTestEnvironment, 10 | } = testing; 11 | 12 | let testEnv = await initializeTestEnvironment({ 13 | projectId: 'frontend-masters-firebase', 14 | firestore: { 15 | rules: readFileSync('firestore.rules', 'utf8'), 16 | host: 'localhost', 17 | port: 8080, 18 | }, 19 | }); 20 | 21 | function assertPermissionDenied(t, result) { 22 | t.is(result.code, 'permission-denied'); 23 | } 24 | 25 | // 1 26 | test('An unauthenticated user fails to write to a profile', async (t) => { 27 | const context = testEnv.unauthenticatedContext(); 28 | const userDoc = context.firestore().doc('users/david_123'); 29 | const result = await assertFails(userDoc.set({ name: 'Im david', email: 'blah@email.com' })); 30 | assertPermissionDenied(t, result); 31 | }); 32 | 33 | // 2 34 | test('An authenticated user can write their profile', async (t) => { 35 | 36 | }); 37 | 38 | // 3 39 | test('An unauthenticated user fails to write to expenses', async (t) => { 40 | 41 | }); 42 | 43 | // 4 44 | test('An authenticated user can write to expenses', async (t) => { 45 | 46 | }); 47 | 48 | // 4 49 | test('A user must have a first name and email', async (t) => { 50 | 51 | }); 52 | 53 | // 5 54 | test('An expense must have a cost greater than 0', async (t) => { 55 | 56 | }); 57 | 58 | // 6 59 | test('After expenses are created you cannot modify their dates', async (t) => { 60 | 61 | }); 62 | 63 | // 7 64 | test('Collaborators can read expenses', async (t) => { 65 | 66 | }); 67 | 68 | // 8 69 | test('Unauthenticated users cant read budgets', async (t) => { 70 | 71 | }); 72 | 73 | // 9 74 | test('Authenticated users can read budgets', async (t) => { 75 | 76 | }); 77 | -------------------------------------------------------------------------------- /6-cloud-functions/final/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "frontend-masters-firebase" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /6-cloud-functions/final/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | firebase-debug.log* 8 | firebase-debug.*.log* 9 | 10 | # Firebase cache 11 | .firebase/ 12 | 13 | # Firebase config 14 | 15 | # Uncomment this if you'd like others to create their own Firebase project. 16 | # For a team working on the same Firebase project(s), it is recommended to leave 17 | # it commented so all members can deploy to the same project(s) in .firebaserc. 18 | # .firebaserc 19 | 20 | # Runtime data 21 | pids 22 | *.pid 23 | *.seed 24 | *.pid.lock 25 | 26 | # Directory for instrumented libs generated by jscoverage/JSCover 27 | lib-cov 28 | 29 | # Coverage directory used by tools like istanbul 30 | coverage 31 | 32 | # nyc test coverage 33 | .nyc_output 34 | 35 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 36 | .grunt 37 | 38 | # Bower dependency directory (https://bower.io/) 39 | bower_components 40 | 41 | # node-waf configuration 42 | .lock-wscript 43 | 44 | # Compiled binary addons (http://nodejs.org/api/addons.html) 45 | build/Release 46 | 47 | # Dependency directories 48 | node_modules/ 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Optional REPL history 57 | .node_repl_history 58 | 59 | # Output of 'npm pack' 60 | *.tgz 61 | 62 | # Yarn Integrity file 63 | .yarn-integrity 64 | 65 | # dotenv environment variables file 66 | .env 67 | -------------------------------------------------------------------------------- /6-cloud-functions/final/emulators.sh: -------------------------------------------------------------------------------- 1 | export FIREBASE_AUTH_EMULATOR_HOST="127.0.0.1:9099" 2 | export FIRESTORE_EMULATOR_HOST="localhost:8080" 3 | npx firebase emulators:start --only firestore,functions,auth --import=./local 4 | -------------------------------------------------------------------------------- /6-cloud-functions/final/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "firestore": { 3 | "rules": "firestore.rules", 4 | "indexes": "firestore.indexes.json" 5 | }, 6 | "hosting": { 7 | "public": "public", 8 | "ignore": [ 9 | "firebase.json", 10 | "**/.*", 11 | "**/node_modules/**" 12 | ], 13 | "rewrites": [{ 14 | "source": "**", 15 | "function": "ssr" 16 | }] 17 | }, 18 | "emulators": { 19 | "auth": { 20 | "port": 9099 21 | }, 22 | "functions": { 23 | "port": 5001 24 | }, 25 | "firestore": { 26 | "port": 8080 27 | }, 28 | "hosting": { 29 | "port": 5021 30 | }, 31 | "ui": { 32 | "enabled": true 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /6-cloud-functions/final/firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | "indexes": [ 3 | { 4 | "collectionGroup": "expenses", 5 | "queryScope": "COLLECTION", 6 | "fields": [ 7 | { 8 | "fieldPath": "category", 9 | "order": "ASCENDING" 10 | }, 11 | { 12 | "fieldPath": "cost", 13 | "order": "ASCENDING" 14 | } 15 | ] 16 | } 17 | ], 18 | "fieldOverrides": [] 19 | } 20 | -------------------------------------------------------------------------------- /6-cloud-functions/final/firestore.rules: -------------------------------------------------------------------------------- 1 | rules_version = '2'; 2 | service cloud.firestore { 3 | match /databases/{database}/documents { 4 | match /{document=**} { 5 | allow read, write: if 6 | request.time < timestamp.date(2022, 6, 18); 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /6-cloud-functions/final/functions/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /6-cloud-functions/final/functions/index.js: -------------------------------------------------------------------------------- 1 | const functions = require('firebase-functions'); 2 | const { initializeApp } = require('firebase-admin/app'); 3 | const { getFirestore } = require('firebase-admin/firestore'); 4 | const { getAuth } = require('firebase-admin/auth'); 5 | 6 | const firebaseApp = initializeApp(functions.config().firebase); 7 | 8 | exports.ssr = functions.https.onRequest((request, response) => { 9 | functions.logger.info("Hello logs!", {structuredData: true}); 10 | const ONE_HOUR_IN_SECONDS = 3600; 11 | response.set(`Cache-Control', 'max-age=600, s-maxage=${ONE_HOUR_IN_SECONDS}`); 12 | response.send(` 13 |

${Date.now()}

14 | `); 15 | }); 16 | 17 | exports.updateUserExpenses = functions.firestore 18 | .document('/users/{uid}') 19 | .onUpdate(async (change) => { 20 | const after = change.after.data(); 21 | functions.logger.info(change.after.data(), {structuredData: true}); 22 | const db = getFirestore(firebaseApp); 23 | const query = db.collection('expenses').where('uid', '==', after.uid); 24 | const snapshot = await query.get(); 25 | const batch = db.batch(); 26 | // What if the batch grows more than 500? 27 | // - Use multiple arrays 28 | // What if the batch is potentially more than 10k records? 29 | // - Use a cursor to iterate through the data set (~500 at time) and issue batches 30 | // What if the batch is well more than 10k? More like 50k! 31 | // - Look into chron jobs or long running instances like Cloud Run 32 | snapshot.docs.forEach(d => { 33 | batch.update(d.ref, { user: after }); 34 | }); 35 | return batch.commit(); 36 | }); 37 | 38 | exports.addCollaborator = functions.firestore 39 | .document('/budgets/{budgetId}/collaboratorRequests/{email}') 40 | .onCreate(async (snapshot, context) => { 41 | // Find the user by their email 42 | const { budgetId, email } = context.params; 43 | const auth = getAuth(firebaseApp); 44 | try { 45 | const user = await auth.getUserByEmail(email); 46 | // add to collaborators with uid 47 | const db = getFirestore(firebaseApp); 48 | return db.doc(`/budgets/${budgetId}/collaborators/${user.uid}`) 49 | .set({ role: 'admin' }); 50 | } catch (error) { 51 | // Send an email out inviting them to the system. 52 | // Check out Firebase Extensions to do this with minimal code. 53 | return; 54 | } 55 | }); 56 | 57 | exports.userCreated = functions.auth.user().onCreate(async user => { 58 | const db = getFirestore(firebaseApp); 59 | const groupQuery = db.collectionGroup('collaboratorRequests') 60 | .where('email', '==', user.email); 61 | // Add to collaboratos for each one 62 | const snapshots = await groupQuery.get(); 63 | const batch = db.batch(); 64 | snapshots.docs.forEach(d => { 65 | // bugdets/good_budget 66 | const budgetRef = d.ref.parent.parent; 67 | // budgets/good_budget/collaborators/david_123 68 | const collabDoc = budgetRef.collection('collaborators').doc(user.uid); 69 | batch.set(collabDoc, { role: 'admin' }); 70 | }); 71 | return batch.commit(); 72 | }) 73 | -------------------------------------------------------------------------------- /6-cloud-functions/final/functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "description": "Cloud Functions for Firebase", 4 | "scripts": { 5 | "serve": "firebase emulators:start --only functions", 6 | "shell": "firebase functions:shell", 7 | "start": "npm run shell", 8 | "deploy": "firebase deploy --only functions", 9 | "logs": "firebase functions:log" 10 | }, 11 | "engines": { 12 | "node": "16" 13 | }, 14 | "main": "index.js", 15 | "dependencies": { 16 | "firebase-admin": "^10.0.2", 17 | "firebase-functions": "^3.18.0" 18 | }, 19 | "devDependencies": { 20 | "firebase-functions-test": "^0.2.0" 21 | }, 22 | "private": true 23 | } 24 | -------------------------------------------------------------------------------- /6-cloud-functions/final/local/auth_export/config.json: -------------------------------------------------------------------------------- 1 | {"signIn":{"allowDuplicateEmails":false},"usageMode":"DEFAULT"} -------------------------------------------------------------------------------- /6-cloud-functions/final/local/firebase-export-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "10.9.2", 3 | "firestore": { 4 | "version": "1.14.3", 5 | "path": "firestore_export", 6 | "metadata_file": "firestore_export/firestore_export.overall_export_metadata" 7 | }, 8 | "auth": { 9 | "version": "10.9.2", 10 | "path": "auth_export" 11 | } 12 | } -------------------------------------------------------------------------------- /6-cloud-functions/final/local/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/6-cloud-functions/final/local/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata -------------------------------------------------------------------------------- /6-cloud-functions/final/local/firestore_export/all_namespaces/all_kinds/output-0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/6-cloud-functions/final/local/firestore_export/all_namespaces/all_kinds/output-0 -------------------------------------------------------------------------------- /6-cloud-functions/final/local/firestore_export/firestore_export.overall_export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/6-cloud-functions/final/local/firestore_export/firestore_export.overall_export_metadata -------------------------------------------------------------------------------- /6-cloud-functions/final/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "6-cloud-functions", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "emulators": "sh ./emulators.sh" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "firebase-tools": "^11.0.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /6-cloud-functions/final/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Page Not Found 7 | 8 | 23 | 24 | 25 |
26 |

404

27 |

Page Not Found

28 |

The specified file was not found on this website. Please check the URL for mistakes and try again.

29 |

Why am I seeing this?

30 |

This page was generated by the Firebase Command-Line Interface. To modify it, edit the 404.html file in your project's configured public directory.

31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /6-cloud-functions/start/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "frontend-masters-firebase" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /6-cloud-functions/start/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | firebase-debug.log* 8 | firebase-debug.*.log* 9 | 10 | # Firebase cache 11 | .firebase/ 12 | 13 | # Firebase config 14 | 15 | # Uncomment this if you'd like others to create their own Firebase project. 16 | # For a team working on the same Firebase project(s), it is recommended to leave 17 | # it commented so all members can deploy to the same project(s) in .firebaserc. 18 | # .firebaserc 19 | 20 | # Runtime data 21 | pids 22 | *.pid 23 | *.seed 24 | *.pid.lock 25 | 26 | # Directory for instrumented libs generated by jscoverage/JSCover 27 | lib-cov 28 | 29 | # Coverage directory used by tools like istanbul 30 | coverage 31 | 32 | # nyc test coverage 33 | .nyc_output 34 | 35 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 36 | .grunt 37 | 38 | # Bower dependency directory (https://bower.io/) 39 | bower_components 40 | 41 | # node-waf configuration 42 | .lock-wscript 43 | 44 | # Compiled binary addons (http://nodejs.org/api/addons.html) 45 | build/Release 46 | 47 | # Dependency directories 48 | node_modules/ 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Optional REPL history 57 | .node_repl_history 58 | 59 | # Output of 'npm pack' 60 | *.tgz 61 | 62 | # Yarn Integrity file 63 | .yarn-integrity 64 | 65 | # dotenv environment variables file 66 | .env 67 | -------------------------------------------------------------------------------- /6-cloud-functions/start/emulators.sh: -------------------------------------------------------------------------------- 1 | export FIREBASE_AUTH_EMULATOR_HOST="127.0.0.1:9099" 2 | export FIRESTORE_EMULATOR_HOST="localhost:8080" 3 | npx firebase emulators:start --only firestore,functions,auth --import=./local 4 | -------------------------------------------------------------------------------- /6-cloud-functions/start/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "firestore": { 3 | "rules": "firestore.rules", 4 | "indexes": "firestore.indexes.json" 5 | }, 6 | "hosting": { 7 | "public": "public", 8 | "ignore": [ 9 | "firebase.json", 10 | "**/.*", 11 | "**/node_modules/**" 12 | ], 13 | "rewrites": [{ 14 | "source": "**", 15 | "function": "ssr" 16 | }] 17 | }, 18 | "emulators": { 19 | "auth": { 20 | "port": 9099 21 | }, 22 | "functions": { 23 | "port": 5001 24 | }, 25 | "firestore": { 26 | "port": 8080 27 | }, 28 | "hosting": { 29 | "port": 5021 30 | }, 31 | "ui": { 32 | "enabled": true 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /6-cloud-functions/start/firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | "indexes": [ 3 | { 4 | "collectionGroup": "expenses", 5 | "queryScope": "COLLECTION", 6 | "fields": [ 7 | { 8 | "fieldPath": "category", 9 | "order": "ASCENDING" 10 | }, 11 | { 12 | "fieldPath": "cost", 13 | "order": "ASCENDING" 14 | } 15 | ] 16 | } 17 | ], 18 | "fieldOverrides": [] 19 | } 20 | -------------------------------------------------------------------------------- /6-cloud-functions/start/firestore.rules: -------------------------------------------------------------------------------- 1 | rules_version = '2'; 2 | service cloud.firestore { 3 | match /databases/{database}/documents { 4 | match /{document=**} { 5 | allow read, write: if 6 | request.time < timestamp.date(2022, 6, 18); 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /6-cloud-functions/start/functions/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /6-cloud-functions/start/functions/index.js: -------------------------------------------------------------------------------- 1 | const functions = require('firebase-functions'); 2 | const { initializeApp } = require('firebase-admin/app'); 3 | const { getFirestore } = require('firebase-admin/firestore'); 4 | const { getAuth } = require('firebase-admin/auth'); 5 | 6 | const firebaseApp = initializeApp(functions.config().firebase); 7 | 8 | // 1. Return a webpage with the current date, set a CDN cache for an hour 9 | exports.ssr = functions.https.onRequest((request, response) => { 10 | 11 | }); 12 | 13 | // 2. When a user updates their info, copy it across their expenses 14 | exports.updateUserExpenses = functions.firestore 15 | .document('/users/{uid}') 16 | .onUpdate(async (change) => { 17 | 18 | }); 19 | 20 | // 3. When a user adds a collaboratorRequest to their "budget", look up the 21 | // uid by their email, and if they exist add them as a collaborator. 22 | exports.addCollaborator = functions.firestore 23 | .document('/budgets/{budgetId}/collaboratorRequests/{email}') 24 | .onCreate(async (snapshot, context) => { 25 | 26 | }); 27 | 28 | // 4. When a user is created, check if they have any collaboratorRequests that 29 | // exist and set them as collaborators. 30 | exports.userCreated = functions.auth.user().onCreate(async user => { 31 | 32 | }); 33 | -------------------------------------------------------------------------------- /6-cloud-functions/start/functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "description": "Cloud Functions for Firebase", 4 | "scripts": { 5 | "serve": "firebase emulators:start --only functions", 6 | "shell": "firebase functions:shell", 7 | "start": "npm run shell", 8 | "deploy": "firebase deploy --only functions", 9 | "logs": "firebase functions:log" 10 | }, 11 | "engines": { 12 | "node": "16" 13 | }, 14 | "main": "index.js", 15 | "dependencies": { 16 | "firebase-admin": "^10.0.2", 17 | "firebase-functions": "^3.18.0" 18 | }, 19 | "devDependencies": { 20 | "firebase-functions-test": "^0.2.0" 21 | }, 22 | "private": true 23 | } 24 | -------------------------------------------------------------------------------- /6-cloud-functions/start/local/auth_export/config.json: -------------------------------------------------------------------------------- 1 | {"signIn":{"allowDuplicateEmails":false},"usageMode":"DEFAULT"} -------------------------------------------------------------------------------- /6-cloud-functions/start/local/firebase-export-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "10.9.2", 3 | "firestore": { 4 | "version": "1.14.3", 5 | "path": "firestore_export", 6 | "metadata_file": "firestore_export/firestore_export.overall_export_metadata" 7 | }, 8 | "auth": { 9 | "version": "10.9.2", 10 | "path": "auth_export" 11 | } 12 | } -------------------------------------------------------------------------------- /6-cloud-functions/start/local/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/6-cloud-functions/start/local/firestore_export/all_namespaces/all_kinds/all_namespaces_all_kinds.export_metadata -------------------------------------------------------------------------------- /6-cloud-functions/start/local/firestore_export/all_namespaces/all_kinds/output-0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/6-cloud-functions/start/local/firestore_export/all_namespaces/all_kinds/output-0 -------------------------------------------------------------------------------- /6-cloud-functions/start/local/firestore_export/firestore_export.overall_export_metadata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/6-cloud-functions/start/local/firestore_export/firestore_export.overall_export_metadata -------------------------------------------------------------------------------- /6-cloud-functions/start/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "6-cloud-functions", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "emulators": "sh ./emulators.sh" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "firebase-tools": "^11.0.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /6-cloud-functions/start/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Page Not Found 7 | 8 | 23 | 24 | 25 |
26 |

404

27 |

Page Not Found

28 |

The specified file was not found on this website. Please check the URL for mistakes and try again.

29 |

Why am I seeing this?

30 |

This page was generated by the Firebase Command-Line Interface. To modify it, edit the 404.html file in your project's configured public directory.

31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Frontend Masters + Firebase! 2 | 3 | > The source materials and companion site 4 | 5 | ### Download these first 6 | - [Java 18](https://www.oracle.com/java/technologies/downloads/#jdk18-mac) 7 | - [Node 16](https://nodejs.org/) 8 | 9 | ### Setup the companion guide 10 | 11 | ```bash 12 | cd guide 13 | npm i 14 | npm run dev 15 | ``` 16 | 17 | ### The course structure 18 | Each section of the course is numered and has it's own accompanying `package.json` to install dependencies and run the demos. Within each section you'll find a `start` and `final` folder showing the beginning code state and then the complete state as well. To run any of these folders: 19 | 20 | ```bash 21 | # Use whatever section you need 22 | cd ./1-fast-but-not-good-setup/start 23 | npm i 24 | npm run dev 25 | ``` 26 | 27 | #### Happy coding! 28 | -------------------------------------------------------------------------------- /guide/.firebase/hosting.ZGlzdA.cache: -------------------------------------------------------------------------------- 1 | code-callout.css,1654544914921,0ea2f041b9dd2efb260204943f5efab4a63a58ffbedab4bdad98db57b5eec093 2 | .DS_Store,1654544914918,8980445f8e11c53acc6ec8a758d19467cc419fa7c4bbd52c95e7a1160f779ed7 3 | away_towards.svg,1654544914919,c308a0685ab1150864d0c2440b723ca771e4786932d345c2bbd99eea0b3954a5 4 | collection_group_query.svg,1654544914924,4d52b1041a45de7f381f29ffb41ae8a1adfa664d2a9d89d490b2b2735fc48050 5 | favicon.ico,1654544914935,c56572d48671c79a7006ece82f81cf063434dc7f43dd0b85318dfabc4cea2002 6 | cost_index_range.svg,1654544914930,22f1bddf7c2989822717d242a0a7f6b40dc7570e46846b74c175e79505d8e6e2 7 | index.html,1654544915267,4e44480d9e92bf938576185401449c9c9f0d1446155ac78bc980596a5aa6392b 8 | firebase-arch.svg,1654544914936,995ab07e1b88380f5f7139783fbbc92c545f020178a795e7483cd1e88a559819 9 | photo-quote.css,1654544914938,2e02d6fc172804aed36a4a6b35f5b6d75deb1d32727991b0ca8b1b0edc0c6b9a 10 | reads_over_writes.svg,1654544914939,c1bddfe98de6738ef342f06284abe0a4cd1d422f6e70fc17a277f01a58e11f69 11 | reset.css,1654544914944,725cdd656122d0617009495a67448520195e2f23a7c1f8b8b2762cd1fdbba593 12 | subcollection.svg,1654544914946,d8f5c0fea710db838ce72a97db10b41da6507526fc1d311a7fd7b23111fba232 13 | row_doc.svg,1654544914945,21669b1a20a306a47961c26fb564f9011d5ab0e6548cf5bc7abdb848ab0180ad 14 | 1-intro/index.html,1654544915297,0ad7b177e69e1721374a8ea52a9a9f14ba5dcaeffa4d6747b9d52e9b4f88faf9 15 | 2-setup/index.html,1654544915300,f9fe1793b4b5c9325daf3c340f8cb21c567e48d0d2c3acdb29f55eec5410b5e6 16 | traditional-arch.svg,1654544914957,9f38490c23451d804f50e0f5dac045f9bd87c6ca17ebf4c3aa84cca59854030c 17 | 4-firebase-authentication/index.html,1654544915271,8e392b0158609000bbde0969c0511bc7890514089b87ebab8c41214b60707747 18 | assets/asset.04061eae.css,1654544915036,21c9c00fba420f13a49a877e4ad087e0f555d2bed2d41e27e058fcfb170d00bb 19 | assets/asset.97b7ec66.css,1654544915036,24c0942caa1b084d4104530ab0780032d23099fae3f3e312fda1542aa3be9599 20 | 5-security-rules/index.html,1654544915294,d88a14772a9ecb8f87afb55d10062a6b467d645b6878b192b81caf78b73af573 21 | 6-cloud-functions/index.html,1654544915290,3558e1afa6e01cb25592bdfbeb10adb6a5896074c7ca4767aa61131af8714cd9 22 | de-old.png,1654544914931,3685cdc3a52f3be3b6f0aca2cbfb29115db1e1808fcfe3e8938c530b0f21bf5c 23 | category_cost_composite_index.svg,1654544914920,b47f1912b470c39622bc0569681d145ff0e9285a89a1d4794c033d9d5ca61d67 24 | denormalization.svg,1654544914934,ac450ba9556f6835d11e3f417c7ce45ff785e8e2a3d15173e68be6d19d2782a1 25 | multiple_indexes.svg,1654544914937,31b2ba3392b2e620b56072963933bc2de376c007c4522fada4f9bf7fe5a2516a 26 | tbl_expenses.svg,1654544914949,5ebb5e8d6475144887f63cee113731b15d122361984efff35a9abbe934e92c65 27 | de-profile.png,1654544914933,b33edfebe82a7b6c54d94db03a793663e437746078846a5aa112f0c3e06d7023 28 | 3-cloud-firestore/index.html,1654544915287,315378d81c7fb6144e24f3cb000f0587d85784492d62830d3945ab7e466daa6c 29 | collection_document.svg,1654544914922,81e35168a2e3b66c592f46ef7e411310596fd83db97c2eccb6c74888f6944fe9 30 | composite_index.png,1654544914925,35fd5486a8d4dfa7e48e01fd4486068d05c631a1f67dad93572cdef66f6cc13a 31 | tbl_users.svg,1654544914956,6b0bb92dbaff04ade1ffa6de2ef3e04a7c98fdec3e071cf24a5be89abbe22b3c 32 | tbl_expenses_approval.svg,1654544914952,6faecdba0a5b7dbacf70605be1ede792a52a53c0eabf0c0e7176192b128c73c7 33 | request_monitor.png,1654544914941,d60a9932d7fb2c33db6317bd61ddf7ee275cca9e7261f750f95669cc9e760846 34 | request_monitor_details.png,1654544914943,a194de685d1177794acc51868aa44d61900506a973caf26cbd8a310a0f188040 35 | concurrency.png,1654544914929,91c3502fe1869890953d0bfd08b9a861307505c5b824ede301ed3cf29bde3a1a 36 | tbl_join.svg,1654544914954,aaf902f232f83d99d44f9f5da9d9d0c8ba7d2915b2df51fde64bf929cfa75935 37 | tbl_both_expenses.svg,1654544914948,d075b0f66d8fe934d29d5f9a13bacbb5a62c77d4eb714f47bd0ff21c26b53554 38 | -------------------------------------------------------------------------------- /guide/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "frontend-masters-firebase" 4 | } 5 | } -------------------------------------------------------------------------------- /guide/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | .output/ 4 | 5 | # dependencies 6 | node_modules/ 7 | 8 | # logs 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | pnpm-debug.log* 13 | 14 | 15 | # environment variables 16 | .env 17 | .env.production 18 | 19 | # macOS-specific files 20 | .DS_Store 21 | -------------------------------------------------------------------------------- /guide/.npmrc: -------------------------------------------------------------------------------- 1 | # Expose Astro dependencies for `pnpm` users 2 | shamefully-hoist=true 3 | -------------------------------------------------------------------------------- /guide/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /guide/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /guide/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to [Astro](https://astro.build) 2 | 3 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/starter) 4 | 5 | > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! 6 | 7 | ## 🚀 Project Structure 8 | 9 | Inside of your Astro project, you'll see the following folders and files: 10 | 11 | ``` 12 | / 13 | ├── public/ 14 | │ └── favicon.ico 15 | ├── src/ 16 | │ ├── components/ 17 | │ │ └── Layout.astro 18 | │ └── pages/ 19 | │ └── index.astro 20 | └── package.json 21 | ``` 22 | 23 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. 24 | 25 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components or layouts. 26 | 27 | Any static assets, like images, can be placed in the `public/` directory. 28 | 29 | ## 🧞 Commands 30 | 31 | All commands are run from the root of the project, from a terminal: 32 | 33 | | Command | Action | 34 | | :---------------- | :------------------------------------------- | 35 | | `npm install` | Installs dependencies | 36 | | `npm run dev` | Starts local dev server at `localhost:3000` | 37 | | `npm run build` | Build your production site to `./dist/` | 38 | | `npm run preview` | Preview your build locally, before deploying | 39 | 40 | ## 👀 Want to learn more? 41 | 42 | Feel free to check [our documentation](https://github.com/withastro/astro) or jump into our [Discord server](https://astro.build/chat). 43 | -------------------------------------------------------------------------------- /guide/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config'; 2 | 3 | // https://astro.build/config 4 | export default defineConfig({ 5 | site: 'https://frontend-masters-firebase.web.app/', 6 | server: { 7 | port: 3033, 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /guide/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "dist" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /guide/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@example/basics", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "astro dev", 7 | "start": "astro dev", 8 | "build": "astro build", 9 | "preview": "astro preview" 10 | }, 11 | "devDependencies": { 12 | "astro": "^1.0.0-beta.31" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /guide/public/code-callout.css: -------------------------------------------------------------------------------- 1 | ul.code-callout { 2 | list-style: none !important; 3 | margin-inline: 0; 4 | margin-block: var(--space-lg); 5 | padding: 0; 6 | display: grid; 7 | gap: var(--space-xs); 8 | grid-column: content; 9 | word-break: break-word; 10 | } 11 | 12 | ul.code-callout li { 13 | font-family: var(--font-mono); 14 | font-size: var(--text-lg); 15 | } 16 | 17 | .content p + ul.code-callout { 18 | margin-block-start: var(--space-xs) !important; 19 | } 20 | -------------------------------------------------------------------------------- /guide/public/composite_index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/guide/public/composite_index.png -------------------------------------------------------------------------------- /guide/public/concurrency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/guide/public/concurrency.png -------------------------------------------------------------------------------- /guide/public/de-old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/guide/public/de-old.png -------------------------------------------------------------------------------- /guide/public/de-profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/guide/public/de-profile.png -------------------------------------------------------------------------------- /guide/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/guide/public/favicon.ico -------------------------------------------------------------------------------- /guide/public/photo-quote.css: -------------------------------------------------------------------------------- 1 | figure.photo-quote { 2 | width: 100%; 3 | display: flex; 4 | flex-wrap: wrap; 5 | justify-content: center; 6 | align-items: center; 7 | column-gap: var(--space-lg); 8 | margin-block: var(--space-lg); 9 | } 10 | figure.photo-quote img { 11 | max-height: 186px; 12 | max-width: 186px; 13 | } 14 | figure.photo-quote figcaption { 15 | display: grid; 16 | gap: var(--space-xxs); 17 | } 18 | figure.photo-quote figcaption p { 19 | font-size: 1.5rem; 20 | margin: 0 !important; 21 | } 22 | figure.photo-quote figcaption p:nth-child(2) { 23 | color: var(--gray-6); 24 | } 25 | -------------------------------------------------------------------------------- /guide/public/request_monitor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/guide/public/request_monitor.png -------------------------------------------------------------------------------- /guide/public/request_monitor_details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davideast/firebase-fundamentals-frontend-masters/7b12cb1068003b840e8f2253b7abed0bb4f43704/guide/public/request_monitor_details.png -------------------------------------------------------------------------------- /guide/public/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1; 33 | } 34 | ol, ul { 35 | list-style: none; 36 | } 37 | blockquote, q { 38 | quotes: none; 39 | } 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: ''; 43 | content: none; 44 | } 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } 49 | -------------------------------------------------------------------------------- /guide/src/components/ContentListItem.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { Header } from '../types'; 3 | 4 | const { header, pathname } = Astro.props as { header: Header; pathname: string; }; 5 | const hasChildren = header.children.length > 0; 6 | --- 7 | 8 |
  • 9 | 10 | {header.text} 11 | 12 | 13 | {hasChildren && } 22 | 23 |
  • 24 | 25 | -------------------------------------------------------------------------------- /guide/src/components/DividerGrid.astro: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 | -------------------------------------------------------------------------------- /guide/src/components/PhotoQuote.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { src, alt } = Astro.props; 3 | --- 4 | 5 |
    6 | {alt} 7 |
    8 | 9 |
    10 |
    11 | 12 | -------------------------------------------------------------------------------- /guide/src/components/TableOfContents.astro: -------------------------------------------------------------------------------- 1 | --- 2 | // import { Debug } from 'astro/components' 3 | import { Header } from '../types'; 4 | import ContentListItem from './ContentListItem.astro'; 5 | const props = Astro.props; 6 | 7 | function structureHeaders(headers: Header[]) { 8 | let structured = [] 9 | let currentParent = null 10 | for (let [index, header] of headers.entries()) { 11 | if (index === 0) { 12 | header.children = [] 13 | structured = [...structured, header] 14 | currentParent = header 15 | continue 16 | } 17 | 18 | // Is the header depth "greater" than its parent and therefore 19 | // a "smaller" header? 20 | if (currentParent.depth < header.depth) { 21 | currentParent.children = [...currentParent.children, header] 22 | } else if (currentParent.depth === header.depth) { 23 | header.children = [] 24 | structured = [...structured, header] 25 | currentParent = header 26 | } 27 | } 28 | return structured 29 | } 30 | 31 | const headers = structureHeaders(props.headers); 32 | const navClass = props.headers.length > 20 ? '' : 'sticky'; 33 | --- 34 | 35 | 45 | 46 | -------------------------------------------------------------------------------- /guide/src/components/TopNav.astro: -------------------------------------------------------------------------------- 1 | 26 | 27 | 43 | -------------------------------------------------------------------------------- /guide/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface Header { 2 | children: Header[]; 3 | depth: string; 4 | slug: string; 5 | text: string; 6 | } 7 | -------------------------------------------------------------------------------- /guide/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Enable top-level await, and other modern ESM features. 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | // Enable node-style module resolution, for things like npm package imports. 7 | "moduleResolution": "node", 8 | // Enable JSON imports. 9 | "resolveJsonModule": true, 10 | // Enable stricter transpilation for better output. 11 | "isolatedModules": true, 12 | // Add type definitions for our Vite runtime. 13 | "types": ["vite/client"] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /seed/README.md: -------------------------------------------------------------------------------- 1 | # Seeding the database 2 | 3 | This part of the repo is contains files needed to seed the database for exercises. You don't need to run them directly, as they are included in the commands in the `start` and `final` folders. 4 | 5 | ### This is cool though, how does it work? 6 | This data seeding system uses the `firebase-admin` node.js SDK to read JSON data generated from [Mockaroo](https://mockaroo.com), and write it into different data structures in the Firestore Emulator (not prod!) to run different types of queries. 7 | 8 | The scripts are designed to run against the Firestore Emulator so everything works locally. 9 | -------------------------------------------------------------------------------- /seed/admin.ts: -------------------------------------------------------------------------------- 1 | import { initializeApp, cert } from 'firebase-admin/app'; 2 | import { getFirestore } from 'firebase-admin/firestore'; 3 | import { getAuth, UserRecord } from 'firebase-admin/auth'; 4 | import { MockUser, CreatedUser } from './types'; 5 | const serviceAccount = require('./sa.json'); 6 | 7 | export const firebaseApp = initializeApp({ 8 | credential: cert(serviceAccount) 9 | }); 10 | 11 | export const firestore = getFirestore(firebaseApp); 12 | export const auth = getAuth(firebaseApp); 13 | export const usersCol = firestore.collection('users'); 14 | export const expensesCol = firestore.collection('expenses'); 15 | 16 | export async function createUsers(usersArray: MockUser[]) { 17 | let users = [] as CreatedUser[]; 18 | for await (const user of usersArray) { 19 | const userRecord = await auth.createUser({ 20 | email: user.email, 21 | emailVerified: true, 22 | displayName: `${user.first} ${user.last}`, 23 | disabled: false, 24 | }); 25 | users = [...users, { ...user, uid: userRecord.uid }] 26 | } 27 | return users; 28 | } 29 | 30 | export async function getAllUsers(users: UserRecord[] = []): Promise { 31 | const listResult = await auth.listUsers(); 32 | if(listResult.pageToken != null) { 33 | users = [...users, ...listResult.users]; 34 | return getAllUsers(users); 35 | } 36 | return users; 37 | } 38 | 39 | export async function deleteAllAuthUsers() { 40 | const allUsers = await getAllUsers(); 41 | const allUids = allUsers.map(u => u.uid); 42 | return auth.deleteUsers(allUids); 43 | } 44 | -------------------------------------------------------------------------------- /seed/batch.ts: -------------------------------------------------------------------------------- 1 | import type { CollectionReference, WriteBatch } from 'firebase-admin/firestore'; 2 | 3 | export type BatchConfig = { 4 | colRef: CollectionReference; 5 | arrayData: T[]; 6 | indexKey?: string; 7 | count?: number; 8 | currentBatchIndex?: number; 9 | batches?: WriteBatch[]; 10 | } 11 | 12 | export function batchUp(config: BatchConfig): WriteBatch[] { 13 | config = { count: 0, currentBatchIndex: 0, batches: [], ...config, } 14 | let { indexKey, colRef, arrayData, count, currentBatchIndex, batches } = config; 15 | const BATCH_SIZE = 500; 16 | const BATCH_LIMIT = (BATCH_SIZE * currentBatchIndex) + BATCH_SIZE; 17 | while(count < BATCH_LIMIT && count <= arrayData.length - 1) { 18 | const record = arrayData[count]; 19 | const indexKeyValue: string = indexKey != null ? record[indexKey] : colRef.doc().id; 20 | if(batches[currentBatchIndex] == null) { 21 | batches[currentBatchIndex] = colRef.firestore.batch() as any; 22 | batches[1] 23 | } 24 | const docRef = colRef.doc(indexKeyValue) as any; 25 | batches[currentBatchIndex].set(docRef, record); 26 | count = count + 1; 27 | currentBatchIndex = Math.floor(count / BATCH_SIZE); 28 | } 29 | if(arrayData.length > count) { 30 | return batchUp({ indexKey, colRef, arrayData: arrayData, count, currentBatchIndex, batches }); 31 | } 32 | return batches; 33 | } 34 | 35 | export function commitBatches(batches: WriteBatch[]) { 36 | return Promise.all(batches.map(b => b.commit())); 37 | } 38 | -------------------------------------------------------------------------------- /seed/data/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | "housing", 3 | "transportation", 4 | "taxes", 5 | "food", 6 | "kids", 7 | "healthcare", 8 | "insurance", 9 | "utilities", 10 | "personal", 11 | "pets", 12 | "clothes", 13 | "home", 14 | "gifts", 15 | "fun", 16 | "services" 17 | ] 18 | -------------------------------------------------------------------------------- /seed/env.sh: -------------------------------------------------------------------------------- 1 | export FIREBASE_AUTH_EMULATOR_HOST="127.0.0.1:9099" 2 | export FIRESTORE_EMULATOR_HOST="localhost:8080" -------------------------------------------------------------------------------- /seed/expenses.ts: -------------------------------------------------------------------------------- 1 | import { expensesCol, usersCol } from './admin'; 2 | import { batchUp, commitBatches } from './batch'; 3 | import { CreatedExpense } from './types'; 4 | import { convertMockarooData, getRandomInt } from './util'; 5 | const expensesMockData = require('./data/expenses.json'); 6 | const categoriesData: string[] = require('./data/categories.json'); 7 | 8 | function convertMockExpenseData(users: Partial<{ uid: string }[]>) { 9 | return convertMockarooData(expensesMockData, expense => { 10 | if(expense.date != null) { 11 | expense.date = new Date(expense.date); 12 | } 13 | expense.uid = users[getRandomInt(0, users.length - 1)].uid; 14 | const categoryOne = expense.category as any; 15 | const categoryTwo = categoriesData[getRandomInt(0, categoriesData.length - 1)]; 16 | let categories = categoryOne === categoryTwo ? [categoryOne] : [categoryOne, categoryTwo]; 17 | expense.categories = categories; 18 | return expense; 19 | }); 20 | } 21 | 22 | export async function seedExpensesInlineUid(users: Partial<{ uid: string }[]>, limit = 5000) { 23 | const expenses = convertMockExpenseData(users).slice(0, limit); 24 | const batches = batchUp({ 25 | arrayData: expenses, 26 | colRef: expensesCol, 27 | }); 28 | await commitBatches(batches); 29 | return expenses; 30 | } 31 | 32 | export async function seedExpensesReference(users: Partial<{ uid: string }[]>, limit = 5000) { 33 | const expenses = convertMockExpenseData(users).slice(0, limit) 34 | .map(ex => ({ ...ex, user: usersCol.doc(ex.uid) })); 35 | const batches = batchUp({ 36 | arrayData: expenses, 37 | colRef: expensesCol, 38 | }); 39 | await commitBatches(batches); 40 | return expenses; 41 | } 42 | 43 | export async function seedExpensesDernormalized(users: Partial<{ uid: string, first: string; last: string; }[]>, limit = 2500) { 44 | const expenses = convertMockExpenseData(users).slice(0, limit) 45 | .map(ex => { 46 | const user = users.find(u => u.uid === ex.uid); 47 | const userRef = usersCol.doc(ex.uid); 48 | return { ...ex, user, userRef }; 49 | }); 50 | const batches = batchUp({ 51 | arrayData: expenses, 52 | colRef: expensesCol, 53 | }); 54 | await commitBatches(batches); 55 | return expenses; 56 | } 57 | 58 | 59 | 60 | export async function seedExpensesByMonth(users: Partial<{ uid: string }[]>, limit = 5000) { 61 | const expenses = convertMockExpenseData(users).slice(0, limit); 62 | const promises = expenses.map(expense => { 63 | const year = expense.date.getFullYear(); 64 | const month = expense.date.getMonth(); 65 | return usersCol.doc(expense.uid).collection(`${year}-${month}`).add(expense); 66 | }); 67 | return Promise.all(promises); 68 | } 69 | 70 | export async function seedExpensesUnderUid(users: Partial<{ uid: string }[]>, limit = 5000) { 71 | const expenses = convertMockExpenseData(users).slice(0, limit); 72 | const promises = expenses.map(expense => { 73 | return usersCol.doc(expense.uid).collection(`expenses`).add(expense); 74 | }); 75 | return Promise.all(promises); 76 | } 77 | -------------------------------------------------------------------------------- /seed/index.ts: -------------------------------------------------------------------------------- 1 | import { seedUsersForFirestore } from './users'; 2 | import { seedExpensesDernormalized } from './expenses'; 3 | 4 | export function seed() { 5 | return seedUsersForFirestore().then(seedExpensesDernormalized); 6 | } 7 | -------------------------------------------------------------------------------- /seed/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "seed", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "@types/node": "^17.0.35", 14 | "firebase-admin": "^10.2.0", 15 | "ts-node": "^10.7.0", 16 | "typescript": "^4.6.4" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /seed/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": false, 8 | "skipLibCheck": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /seed/types.ts: -------------------------------------------------------------------------------- 1 | export type MockUser = { 2 | first: string; 3 | last: string; 4 | email: string; 5 | birthday: string | Date; 6 | } 7 | 8 | export interface CreatedUser extends MockUser { 9 | uid: string; 10 | } 11 | 12 | export type MockExpense = { 13 | category: string; 14 | cost: number; 15 | date: string | Date; 16 | } 17 | 18 | export interface CreatedExpense extends MockExpense { 19 | uid: string; 20 | date: Date; 21 | categories: string[]; 22 | } 23 | -------------------------------------------------------------------------------- /seed/users.ts: -------------------------------------------------------------------------------- 1 | import { createUsers, usersCol, deleteAllAuthUsers, getAllUsers } from './admin' 2 | import { convertMockarooData } from './util' 3 | import { batchUp, commitBatches } from './batch'; 4 | import type { MockUser } from './types' 5 | const userData = require('./data/users-sm.json'); 6 | 7 | export async function seedUsers() { 8 | const mockUsers = convertMockarooData(userData, (user) => { 9 | if (user.birthday) { 10 | user.birthday = new Date(user.birthday) 11 | } 12 | return user 13 | }) 14 | return createUsers(mockUsers) 15 | } 16 | 17 | export async function seedUsersForFirestore() { 18 | const createdUsers = await seedUsers(); 19 | const batches = batchUp({ 20 | colRef: usersCol, 21 | indexKey: 'uid', 22 | arrayData: createdUsers, 23 | }); 24 | await commitBatches(batches); 25 | return createdUsers; 26 | } 27 | 28 | export async function deleteAllUsersForFirestore() { 29 | const refList = await usersCol.listDocuments() 30 | const deletePromises = refList.map(ref => ref.delete()); 31 | return Promise.all(deletePromises); 32 | } 33 | 34 | export async function deleteAllUsers() { 35 | await deleteAllUsersForFirestore(); 36 | await deleteAllAuthUsers(); 37 | } 38 | 39 | export { getAllUsers }; 40 | -------------------------------------------------------------------------------- /seed/util.ts: -------------------------------------------------------------------------------- 1 | export function getRandomInt(min: number, max: number) { 2 | min = Math.ceil(min); 3 | max = Math.floor(max); 4 | return Math.floor(Math.random() * (max - min + 1)) + min; 5 | } 6 | 7 | export function convertMockarooData(array: any[], mapCallback: (value: T) => T) { 8 | return Object.entries(array).map(arr => arr[1]).flat().map(value => { 9 | return mapCallback(value as T); 10 | }) 11 | } 12 | --------------------------------------------------------------------------------