├── sharedCode ├── utils.js └── constants.js ├── client ├── .npmrc ├── src │ ├── global.d.ts │ ├── routes │ │ └── index.svelte │ └── app.html ├── .prettierignore ├── .gitignore ├── .prettierrc ├── svelte.config.js ├── .eslintrc.cjs ├── tsconfig.json ├── package.json └── README.md ├── legacy ├── src │ ├── routeComponents │ │ ├── Home.svelte │ │ ├── Authenticated.svelte │ │ ├── Users.svelte │ │ ├── Signin.svelte │ │ ├── Recover.svelte │ │ ├── Register.svelte │ │ ├── Settings.svelte │ │ └── Profile.svelte │ ├── stores │ │ └── userDataStore.js │ ├── util │ │ └── logging.js │ ├── components │ │ ├── DefaultSpinner.svelte │ │ ├── UserMini.svelte │ │ ├── PersonalData.svelte │ │ ├── Email.svelte │ │ └── Username.svelte │ ├── main.js │ ├── api.js │ ├── App.svelte │ └── firebaseBackend.js └── rollup.config.js ├── .firebaserc ├── .prettierrc ├── firestore.rules ├── storage.rules ├── functions ├── .gitignore ├── tsconfig.json ├── package.json ├── src │ ├── index.ts │ └── users.ts └── tslint.json ├── .env ├── .eslintrc.js ├── .gitignore ├── firestore.indexes.json ├── firebase.json ├── database.rules.json ├── package.json └── readme.md /sharedCode/utils.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /client/src/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /legacy/src/routeComponents/Home.svelte: -------------------------------------------------------------------------------- 1 |

Your home page

2 | -------------------------------------------------------------------------------- /client/.prettierignore: -------------------------------------------------------------------------------- 1 | .svelte-kit/** 2 | static/** 3 | build/** 4 | node_modules/** 5 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /.svelte-kit 4 | /build 5 | /functions 6 | -------------------------------------------------------------------------------- /client/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /legacy/src/stores/userDataStore.js: -------------------------------------------------------------------------------- 1 | import { writable } from "svelte/store" 2 | 3 | export let userDataStore = writable(null) 4 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "svelte-fullstack-starter", 4 | "staging": "svelte-firebase-staging" 5 | } 6 | } -------------------------------------------------------------------------------- /client/src/routes/index.svelte: -------------------------------------------------------------------------------- 1 |

Welcome to SvelteKit

2 |

Visit kit.svelte.dev to read the documentation

3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "tabWidth": 4, 4 | "printWidth": 100, 5 | "plugins": [ 6 | "svelte" 7 | ], 8 | "svelteStrictMode": true 9 | } 10 | -------------------------------------------------------------------------------- /firestore.rules: -------------------------------------------------------------------------------- 1 | service cloud.firestore { 2 | match /databases/{database}/documents { 3 | match /{document=**} { 4 | allow read, write; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /storage.rules: -------------------------------------------------------------------------------- 1 | service firebase.storage { 2 | match /b/{bucket}/o { 3 | match /{allPaths=**} { 4 | allow read, write: if request.auth!=null; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /legacy/src/util/logging.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Logs message if in devmode 3 | * @param message 4 | */ 5 | export function log(message) { 6 | if (!process.env.PRODUCTION) console.log(message) 7 | } 8 | -------------------------------------------------------------------------------- /functions/.gitignore: -------------------------------------------------------------------------------- 1 | ## Compiled JavaScript files 2 | **/*.js 3 | **/*.js.map 4 | 5 | # Typescript v1 declaration files 6 | typings/ 7 | 8 | node_modules/ 9 | 10 | firebase-debug.log 11 | -------------------------------------------------------------------------------- /sharedCode/constants.js: -------------------------------------------------------------------------------- 1 | export const apiBaseURL = "https://us-central1-svelte-fullstack-starter.cloudfunctions.net/" 2 | export const siteBaseURL = "https://svelte-fullstack-starter.firebaseapp.com/" 3 | -------------------------------------------------------------------------------- /legacy/src/components/DefaultSpinner.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /client/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %svelte.head% 8 | 9 | 10 |
%svelte.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | API_KEY= AIzaSyDgkLmjsLTLO8cnEhaZu-0o12wpdisCn5w 2 | AUTH_DOMAIN= svelte-fullstack-starter.firebaseapp.com 3 | DATABASE_URL= https://svelte-fullstack-starter.firebaseio.com 4 | PROJECT_ID= svelte-fullstack-starter 5 | STORAGE_BUCKET= svelte-fullstack-starter.appspot.com 6 | MESSAGING_SENDER_ID= 684795141693 7 | APP_ID= 1:684795141693:web:bb22a3283361cfc381d454 8 | MEASUREMENT_ID= G-Y1SRV3FGND -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true 5 | }, 6 | extends: "eslint:recommended", 7 | globals: { 8 | Atomics: "readonly", 9 | SharedArrayBuffer: "readonly" 10 | }, 11 | parserOptions: { 12 | ecmaVersion: 2018, 13 | sourceType: "module" 14 | }, 15 | rules: { 16 | "no-shadow": [ 17 | "error" 18 | ] 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /functions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "noImplicitReturns": true, 5 | "noUnusedLocals": true, 6 | "outDir": "lib", 7 | "sourceMap": true, 8 | "strict": true, 9 | "strictNullChecks": false, 10 | "target": "es2017", 11 | "noImplicitAny": false, 12 | "noUnusedLocals": false 13 | }, 14 | "compileOnSave": true, 15 | "include": [ 16 | "src" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | node_modules 6 | 7 | # builds 8 | build 9 | dist 10 | 11 | .netlify 12 | .idea 13 | .firebase 14 | 15 | 16 | .DS_Store 17 | public/build 18 | 19 | firestore-debug.log 20 | firebase-debug.log 21 | database-debug.log 22 | pubsub-debug.log 23 | **/package-lock.json 24 | ui-debug.log 25 | 26 | svelte-fullstack-starter-9a3b3faddda9.json 27 | -------------------------------------------------------------------------------- /legacy/src/components/UserMini.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 | User: 11 | 12 | {user.displayName || user.username || user.uid} 13 | 14 |
15 | -------------------------------------------------------------------------------- /legacy/src/routeComponents/Authenticated.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | {#if $userDataStore} 15 | 16 | {/if} 17 | -------------------------------------------------------------------------------- /legacy/src/main.js: -------------------------------------------------------------------------------- 1 | import App from "./App.svelte" 2 | import { backendInit } from "./firebaseBackend" 3 | 4 | function getapp() { 5 | return new App({ 6 | target: document.body, 7 | props: {}, 8 | }) 9 | } 10 | 11 | /** 12 | * Init all the data from firebase and set up our app stores 13 | * Log some info 14 | */ 15 | async function bootstrap() { 16 | if (process.env.EMULATION) console.log("Firebase Emulation being used") 17 | await backendInit() 18 | getapp() 19 | } 20 | bootstrap() 21 | -------------------------------------------------------------------------------- /client/svelte.config.js: -------------------------------------------------------------------------------- 1 | import preprocess from 'svelte-preprocess'; 2 | import firebaseAdapter from 'svelte-adapter-firebase'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://github.com/sveltejs/svelte-preprocess 7 | // for more information about preprocessors 8 | preprocess: preprocess(), 9 | 10 | kit: { 11 | adapter: firebaseAdapter({ firebaseJson: '../firebase.json' }), 12 | 13 | // hydrate the
element in src/app.html 14 | target: '#svelte' 15 | } 16 | }; 17 | 18 | export default config; 19 | -------------------------------------------------------------------------------- /client/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'], 5 | plugins: ['svelte3', '@typescript-eslint'], 6 | ignorePatterns: ['*.cjs'], 7 | overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], 8 | settings: { 9 | 'svelte3/typescript': () => require('typescript') 10 | }, 11 | parserOptions: { 12 | sourceType: 'module', 13 | ecmaVersion: 2019 14 | }, 15 | env: { 16 | browser: true, 17 | es2017: true, 18 | node: true 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /firestore.indexes.json: -------------------------------------------------------------------------------- 1 | { 2 | // Example: 3 | // 4 | // "indexes": [ 5 | // { 6 | // "collectionGroup": "widgets", 7 | // "queryScope": "COLLECTION", 8 | // "fields": [ 9 | // { "fieldPath": "foo", "arrayConfig": "CONTAINS" }, 10 | // { "fieldPath": "bar", "mode": "DESCENDING" } 11 | // ] 12 | // }, 13 | // 14 | // "fieldOverrides": [ 15 | // { 16 | // "collectionGroup": "widgets", 17 | // "fieldPath": "baz", 18 | // "indexes": [ 19 | // { "order": "ASCENDING", "queryScope": "COLLECTION" } 20 | // ] 21 | // }, 22 | // ] 23 | // ] 24 | "indexes": [], 25 | "fieldOverrides": [] 26 | } -------------------------------------------------------------------------------- /legacy/src/routeComponents/Users.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
16 |

List of Users

17 | 18 | {#await promise} 19 | 20 | {:then users} 21 | {#if users} 22 | {#each users as user} 23 | 24 | {/each} 25 | {:else} 26 |

No users yet!

27 | {/if} 28 | {:catch error} 29 |

{error.message}

30 | {/await} 31 | 32 |
33 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "module": "es2020", 5 | "lib": ["es2020"], 6 | "target": "es2019", 7 | /** 8 | svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript 9 | to enforce using \`import type\` instead of \`import\` for Types. 10 | */ 11 | "importsNotUsedAsValues": "error", 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | /** 15 | To have warnings/errors of the Svelte compiler at the correct position, 16 | enable source maps by default. 17 | */ 18 | "sourceMap": true, 19 | "esModuleInterop": true, 20 | "skipLibCheck": true, 21 | "forceConsistentCasingInFileNames": true, 22 | "baseUrl": ".", 23 | "allowJs": true, 24 | "checkJs": true, 25 | "paths": { 26 | "$lib/*": ["src/lib/*"] 27 | } 28 | }, 29 | "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"] 30 | } 31 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "functions": { 4 | "source": "functions", 5 | "predeploy": [ 6 | "npm --prefix \"$RESOURCE_DIR\" run lint", 7 | "npm --prefix \"$RESOURCE_DIR\" run build" 8 | ], 9 | "runtime": "nodejs14" 10 | }, 11 | "database": { 12 | "rules": "./database.rules.json" 13 | }, 14 | "hosting": { 15 | "public": "client/build", 16 | "ignore": [ 17 | "firebase.json", 18 | "**/.*", 19 | "**/node_modules/**" 20 | ], 21 | "rewrites": [ 22 | { 23 | "source": "**", 24 | "function": "sveltekit" 25 | } 26 | ] 27 | }, 28 | "emulators": { 29 | "functions": { 30 | "port": 5002 31 | }, 32 | "firestore": { 33 | "port": 8080 34 | }, 35 | "database": { 36 | "port": 9000 37 | }, 38 | "hosting": { 39 | "port": 5000 40 | }, 41 | "pubsub": { 42 | "port": 8085 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "~TODO~", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "dev": "svelte-kit dev", 6 | "preview": "svelte-kit preview", 7 | "lint": "prettier --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", 8 | "format": "prettier --write --plugin-search-dir=. .", 9 | "build": "npx rimraf client/build && svelte-kit build --verbose" 10 | }, 11 | "devDependencies": { 12 | "@sveltejs/kit": "next", 13 | "@typescript-eslint/eslint-plugin": "^4.19.0", 14 | "@typescript-eslint/parser": "^4.19.0", 15 | "eslint": "^7.22.0", 16 | "eslint-config-prettier": "^8.1.0", 17 | "eslint-plugin-svelte3": "^3.2.0", 18 | "prettier": "~2.2.1", 19 | "prettier-plugin-svelte": "^2.2.0", 20 | "svelte": "^3.34.0", 21 | "svelte-adapter-firebase": "^0.7.4", 22 | "svelte-preprocess": "^4.0.0", 23 | "tslib": "^2.0.0", 24 | "typescript": "^4.0.0" 25 | }, 26 | "type": "module" 27 | } 28 | -------------------------------------------------------------------------------- /database.rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "following": { 4 | "$uid": { 5 | ".read": "auth != null", 6 | ".write": "auth.uid === $uid" 7 | } 8 | }, 9 | 10 | "users": { 11 | ".read": true, 12 | 13 | ".indexOn": "username", 14 | "$uid": { 15 | ".write": "auth.uid === $uid", 16 | "username": { 17 | ".validate": "root.child('username_lookup/'+newData.val()).val() === auth.uid" 18 | } 19 | } 20 | }, 21 | 22 | "username_lookup": { 23 | "$username": { 24 | // not readable, cannot get a list of usernames! 25 | // can only write if this username is not already in the db 26 | ".write": "!data.exists()", 27 | ".read": true, 28 | 29 | // can only write my own uid into this index 30 | ".validate": "newData.val() === auth.uid" 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # create-svelte 2 | 3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte); 4 | 5 | ## Creating a project 6 | 7 | If you're seeing this, you've probably already done this step. Congrats! 8 | 9 | ```bash 10 | # create a new project in the current directory 11 | npm init svelte@next 12 | 13 | # create a new project in my-app 14 | npm init svelte@next my-app 15 | ``` 16 | 17 | > Note: the `@next` is temporary 18 | 19 | ## Developing 20 | 21 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 22 | 23 | ```bash 24 | npm run dev 25 | 26 | # or start the server and open the app in a new browser tab 27 | npm run dev -- --open 28 | ``` 29 | 30 | ## Building 31 | 32 | Before creating a production version of your app, install an [adapter](https://kit.svelte.dev/docs#adapters) for your target environment. Then: 33 | 34 | ```bash 35 | npm run build 36 | ``` 37 | 38 | > You can preview the built app with `npm run preview`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production. 39 | -------------------------------------------------------------------------------- /legacy/src/api.js: -------------------------------------------------------------------------------- 1 | let base = process.env.EMULATION 2 | ? "http://localhost:5002/svelte-fullstack-starter/us-central1" 3 | : "https://us-central1-svelte-fullstack-starter.cloudfunctions.net" 4 | 5 | function send({ method, path, data, token }) { 6 | const opts = { method, headers: {} } 7 | 8 | if (data) { 9 | opts.headers["Content-Type"] = "application/json" 10 | opts.body = JSON.stringify(data) 11 | } 12 | 13 | if (token) { 14 | opts.headers["Authorization"] = `Token ${token}` 15 | } 16 | 17 | return fetch(`${base}/${path}`, opts) 18 | .then((r) => r.text()) 19 | .then((json) => { 20 | try { 21 | return JSON.parse(json) 22 | } catch (err) { 23 | return json 24 | } 25 | }) 26 | } 27 | 28 | export function get(path, token) { 29 | return send({ method: "GET", path, token }) 30 | } 31 | 32 | export function del(path, token) { 33 | return send({ method: "DELETE", path, token }) 34 | } 35 | 36 | export function post(path, data, token) { 37 | return send({ method: "POST", path, data, token }) 38 | } 39 | 40 | export function put(path, data, token) { 41 | return send({ method: "PUT", path, data, token }) 42 | } 43 | -------------------------------------------------------------------------------- /legacy/src/components/PersonalData.svelte: -------------------------------------------------------------------------------- 1 | 26 | 27 |

Personal Settings

28 | 29 |
30 | 31 |
32 | 33 | 34 | {#if pendingApiCall} 35 | 36 | {/if} 37 | 38 | {#if showSuccessMessage} 39 |

Your Personal Data Settings have been updated.

40 | {/if} 41 | -------------------------------------------------------------------------------- /functions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functions", 3 | "scripts": { 4 | "lint": "tslint --project tsconfig.json", 5 | "build": "tsc", 6 | "watch": "tsc -w", 7 | "serve": "npm run build && firebase serve --only functions", 8 | "shell": "npm run build && firebase functions:shell -p 5001", 9 | "start": "npm run shell", 10 | "deploy": "firebase deploy --only functions", 11 | "logs": "firebase functions:log", 12 | "test": "mocha --reporter spec", 13 | "emulate": "firebase emulators:start --only database,functions --inspect-functions --import=./seed --export-on-exit ", 14 | "emulate:functions": "firebase emulators:start --only functions --inspect-functions" 15 | }, 16 | "engines": { 17 | "node": "10" 18 | }, 19 | "main": "lib/index.js", 20 | "dependencies": { 21 | "cors": "^2.8.5", 22 | "express": "^4.17.1", 23 | "firebase-admin": "^8.12.1", 24 | "firebase-functions": "^3.7.0" 25 | }, 26 | "devDependencies": { 27 | "@google-cloud/functions-framework": "^1.6.0", 28 | "firebase-functions-test": "^0.2.1", 29 | "mocha": "^8.0.1", 30 | "tslint": "^6.1.2", 31 | "typescript": "^3.9.5" 32 | }, 33 | "private": true 34 | } 35 | -------------------------------------------------------------------------------- /legacy/src/components/Email.svelte: -------------------------------------------------------------------------------- 1 | 26 | 27 |
28 | 29 |
30 | 31 | 32 | {#if pendingApiCall} 33 | 34 | {/if} 35 | 36 | {#if showSuccessMessage} 37 |

Your Email has been updated.

38 | {/if} 39 |
40 | -------------------------------------------------------------------------------- /functions/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as functions from "firebase-functions" 2 | 3 | const express = require("express") 4 | const cors = require("cors") 5 | 6 | const app = express() 7 | 8 | app.use(cors({ origin: true })) 9 | 10 | import * as admin from "firebase-admin" 11 | import { getSomeUsers } from "./users" 12 | 13 | admin.initializeApp() 14 | 15 | let sveltekitServer; 16 | exports.sveltekit = functions.https.onRequest(async (request, response) => { 17 | if (!sveltekitServer) { 18 | functions.logger.info("Initializing SvelteKit SSR Handler"); 19 | sveltekitServer = require("./sveltekit/index").default; 20 | functions.logger.info("SvelteKit SSR Handler initialised!"); 21 | } 22 | functions.logger.info("Requested resource: " + request.originalUrl); 23 | return await sveltekitServer(request, response); 24 | }); 25 | 26 | app.post("/", async (req, res) => { 27 | const { username, uid } = req.body 28 | try { 29 | await admin.database().ref(`usernames/${username}`).set({ uid: uid }) 30 | res.send("operation save username complete") 31 | } catch (e) { 32 | console.log(e.message) 33 | } 34 | }) 35 | 36 | export const upsertUsername = functions.https.onRequest(app) 37 | 38 | 39 | //todo figure out the best way to have multiple firebase functions (multiple express apps?) 40 | export const listUsers = functions.https.onRequest(async (req, res) => { 41 | res.set("Access-Control-Allow-Origin", "*") 42 | const data = await getSomeUsers(10) 43 | res.json(data) 44 | }) 45 | -------------------------------------------------------------------------------- /legacy/src/routeComponents/Signin.svelte: -------------------------------------------------------------------------------- 1 | 22 | 23 |
24 | 25 |
26 |

Login Details

27 |
28 | 29 | 35 | 41 | 42 | 43 | {#if pendingApiCall} 44 | 45 | {/if} 46 | 47 |

48 | Forgot password? Click 49 | Here to reset it. 50 |

51 | 52 |
53 |
54 | -------------------------------------------------------------------------------- /legacy/src/routeComponents/Recover.svelte: -------------------------------------------------------------------------------- 1 | 29 | 30 |
31 |

Request Account Recovery

32 |
33 | 39 | 40 | 41 | {#if pendingApiCall} 42 | 43 | {/if} 44 | 45 | {#if showSuccessMessage} 46 |

47 | An email has been sent if that account exists to allow you to log in one time. You will 48 | need to set your password immediately in order to log in again. 49 |

50 | {/if} 51 | 52 |
53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-app", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "deploy": "firebase use svelte-fullstack-starter && cd client && npm run build && cd ../ && firebase deploy", 6 | "deploy:staging": "firebase use staging && npm run build && firebase deploy", 7 | "start": "sirv public --single -p 5001", 8 | "lint": "eslint legacy/src " 9 | 10 | }, 11 | "devDependencies": { 12 | "@rollup/plugin-commonjs": "13.0.0", 13 | "@rollup/plugin-node-resolve": "^8.1.0", 14 | "@rollup/plugin-replace": "^2.3.3", 15 | "dotenv": "^8.2.0", 16 | "eslint": "^7.3.1", 17 | "eslint-plugin-import": "^2.21.2", 18 | "eslint-plugin-node": "^11.1.0", 19 | "eslint-plugin-promise": "^4.2.1", 20 | "husky": "^4.2.5", 21 | "integrify": "^3.0.1", 22 | "prettier": "^2.0.5", 23 | "prettier-plugin-svelte": "^1.1.0", 24 | "rollup": "^2.18.1", 25 | "rollup-plugin-livereload": "^1.3.0", 26 | "rollup-plugin-svelte": "^5.2.3", 27 | "rollup-plugin-terser": "^6.1.0", 28 | "svelte": "^3.23.2" 29 | }, 30 | "dependencies": { 31 | "firebase": "^7.15.5", 32 | "gotrue-js": "^0.9.25", 33 | "query-string": "^6.13.1", 34 | "sirv-cli": "^1.0.1", 35 | "svelte-loading-spinners": "0.0.19", 36 | "svelte-routing": "^1.4.2", 37 | "svelte-spinner": "^2.0.2" 38 | }, 39 | "repository": { 40 | "type": "git", 41 | "url": "git+https://github.com/QuantumInformation/svelte-netlify-identity.git" 42 | }, 43 | "keywords": [ 44 | "svelte", 45 | "sveltejs", 46 | "fullstack" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /legacy/src/routeComponents/Register.svelte: -------------------------------------------------------------------------------- 1 | 34 | 35 |
36 | 37 |
38 |

Register

39 |

Account Settings

40 |
41 | 42 | 43 | 44 |
45 | 46 | 47 | 48 |
49 | {#if pendingApiCall} 50 | 51 | {/if} 52 | {#if showSuccessMessage} 53 |

54 | Well done! Your new account has been successfully created 🌱 Please check your email 55 | to verify your email and then you will be able to login. 56 |

57 | {/if} 58 | 59 |
60 |
61 | -------------------------------------------------------------------------------- /legacy/src/routeComponents/Settings.svelte: -------------------------------------------------------------------------------- 1 | 38 | 39 | 40 | 41 |
42 | 43 | 44 | 45 | 46 |
47 |

Security Settings / Danger Zone

48 | 49 | 50 | 51 |
52 | 53 |
54 | 60 |
61 | 62 | {#if pendingApiCall2} 63 | 64 | {/if} 65 | 66 | {#if showSuccessMessage2} 67 |

Your Password has been updated.

68 | {/if} 69 | 70 |
71 |
72 | -------------------------------------------------------------------------------- /legacy/src/routeComponents/Profile.svelte: -------------------------------------------------------------------------------- 1 | 44 | 45 |

User Profile

46 | 47 | {#await promise} 48 | 49 | 50 | {:then result} 51 |

{user.username || 'User has not set a display name'}

52 | {#if isOwnProfile} 53 | It's you! 54 | {/if} 55 | 56 | {#if $userDataStore && !isOwnProfile} 57 | {#if isFollowing} 58 | 59 | {:else} 60 | 61 | {/if} 62 | {/if} 63 | {:catch error} 64 |

error: {error}

65 | {/await} 66 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Why 2 | 3 | An SPA example for people to quickly start churning out fullstack apps in Svelte with firebase. 4 | The Svelte app uses JavaScript while the firebase functions use TypeScript 5 | 6 | # Videos! 7 | Best way to get up to speed is to watch these videos: 8 | 9 | IMAGE ALT TEXT HERE 11 | 12 | 13 | 14 | # svelte app 15 | 16 | This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template. 17 | 18 | To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit): 19 | 20 | ```bash 21 | npx degit sveltejs/template svelte-app 22 | cd svelte-app 23 | ``` 24 | 25 | *Note that you will need to have [Node.js](https://nodejs.org) installed.* 26 | 27 | 28 | ## Get started 29 | 30 | Install the dependencies... 31 | 32 | ```bash 33 | npm install 34 | ``` 35 | 36 | ...then start [Rollup](https://rollupjs.org): 37 | 38 | ```bash 39 | npm run dev 40 | ``` 41 | 42 | Navigate to [localhost:5000](http://localhost:5000). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes. 43 | 44 | By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`. 45 | 46 | 47 | ## Building and running in production mode 48 | 49 | To create an optimised version of the app: 50 | 51 | ```bash 52 | npm run build 53 | ``` 54 | 55 | You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com). 56 | 57 | ## Running emulators 58 | 59 | Assuming you have firebase emulation for Realtime DB and Cloud functions you can pass this env variable in to run `EMULATION="true" npm run dev` 60 | 61 | And your production data will not be touched 62 | 63 | You can also get chrome to open up with no security as there are some cars issues with local emulation or functions with `npm run chrome`. You can then test the local app with the new Chrome window -------------------------------------------------------------------------------- /legacy/src/App.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 | 35 | 36 | 37 |

Svelte Firebase Starter

38 | 41 | {#if $userDataStore} 42 | {#if !$userDataStore.displayName && !$userDataStore.email} 43 |

Please complete your profile in Settings

44 | {:else} 45 |

Logged in as {$userDataStore.displayName || $userDataStore.email}

46 | {/if} 47 | {:else} 48 |

Not logged in

49 | {/if} 50 |
51 | Home 52 | {#if !$userDataStore} 53 | Register 54 | Signin 55 | {:else} 56 | Profile 57 | 58 | Settings 59 | 60 | {/if} 61 | Users 62 | 63 |
64 | 65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |
76 |
77 |
78 | -------------------------------------------------------------------------------- /legacy/rollup.config.js: -------------------------------------------------------------------------------- 1 | import svelte from "rollup-plugin-svelte" 2 | import resolve from "@rollup/plugin-node-resolve" 3 | import commonjs from "@rollup/plugin-commonjs" 4 | import livereload from "rollup-plugin-livereload" 5 | import { terser } from "rollup-plugin-terser" 6 | import replace from "@rollup/plugin-replace" 7 | 8 | import { config } from "dotenv" 9 | const production = !process.env.ROLLUP_WATCH 10 | 11 | export default { 12 | input: "src/main.js", 13 | output: { 14 | sourcemap: true, 15 | format: "iife", 16 | name: "app", 17 | file: "public/build/bundle.js", 18 | }, 19 | plugins: [ 20 | replace({ 21 | // stringify the object 22 | process: JSON.stringify({ 23 | env: { 24 | dev: !production, 25 | EMULATION: process.env.EMULATION, 26 | PRODUCTION: production, 27 | ...config().parsed, // attached the .env config 28 | }, 29 | }), 30 | }), 31 | svelte({ 32 | // enable run-time checks when not in production 33 | dev: !production, 34 | // we'll extract any component CSS out into 35 | // a separate file - better for performance 36 | css: (css) => { 37 | css.write("public/build/bundle.css") 38 | }, 39 | }), 40 | 41 | // If you have external dependencies installed from 42 | // npm, you'll most likely need these plugins. In 43 | // some cases you'll need additional configuration - 44 | // consult the documentation for details: 45 | // https://github.com/rollup/plugins/tree/master/packages/commonjs 46 | resolve({ 47 | browser: true, 48 | dedupe: ["svelte"], 49 | }), 50 | commonjs(), 51 | 52 | // In dev mode, call `npm run start` once 53 | // the bundle has been generated 54 | !production && serve(), 55 | 56 | // Watch the `public` directory and refresh the 57 | // browser on changes when not in production 58 | !production && livereload("public"), 59 | 60 | // If we're building for production (npm run build 61 | // instead of npm run dev), minify 62 | production && terser(), 63 | ], 64 | watch: { 65 | clearScreen: false, 66 | }, 67 | } 68 | 69 | function serve() { 70 | let started = false 71 | 72 | return { 73 | writeBundle() { 74 | if (!started) { 75 | started = true 76 | 77 | require("child_process").spawn("npm", ["run", "start", "--", "--dev"], { 78 | stdio: ["ignore", "inherit", "inherit"], 79 | shell: true, 80 | }) 81 | } 82 | }, 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /legacy/src/components/Username.svelte: -------------------------------------------------------------------------------- 1 | 55 | 56 |
57 |

Username

58 | {#await personalDataPromise()} 59 | 60 | {:then personalData} 61 |
62 | 63 |
64 | 69 |
70 | {#if usernameDirty} 71 | {#if usernameCheckPending} 72 | 73 | {:else if usernameIsFree} 74 | ✅ Username is available 75 | {:else} 76 | ❌ Username {personalDataClone.username} is not available. 77 | {/if} 78 | {/if} 79 |
80 | {#if personalDataClone.username} 81 |

Your profile url will be at {`${siteBaseURL}${personalDataClone.username}`}

82 | {/if} 83 | 84 | {#if pendingApiCall} 85 | 86 | {/if} 87 | {#if showSuccessMessage} 88 |

Your Username has been updated.

89 | {/if} 90 | 91 | {:catch error} 92 |

{error.message}

93 | {/await} 94 | -------------------------------------------------------------------------------- /functions/src/users.ts: -------------------------------------------------------------------------------- 1 | import * as admin from "firebase-admin" 2 | import UserRecord = admin.auth.UserRecord 3 | import ListUsersResult = admin.auth.ListUsersResult 4 | 5 | const totalUsers: any = [] 6 | /*export async function listAllUsers(nextPageToken: string) { 7 | try { 8 | // List batch of users, 1000 at a time. 9 | const listUsersResult = await admin.auth().listUsers(1000, nextPageToken) 10 | 11 | totalUsers.push(listUsersResult) 12 | listUsersResult.users.forEach(function(userRecord) { 13 | console.log("user", userRecord.toJSON()) 14 | }) 15 | if (listUsersResult.pageToken) { 16 | // List next batch of users. 17 | await listAllUsers(listUsersResult.pageToken) 18 | } 19 | const parsedUsers = totalUsers.users.map(stripUserSensitiveInfo) 20 | 21 | return parsedUsers 22 | } catch (error) { 23 | console.log("Error listing users:", error) 24 | throw new Error("Error users") 25 | } 26 | }*/ 27 | 28 | /** 29 | * Several steps here 30 | * 1 Get a list of registered users and strip them 31 | * 2 Create a list of queries with the uid's of the registered users to get data from the users object in the realtime DB 32 | * 3 Combine the uid from the registration DB with the custom data 33 | * 4 remove users with no username set (incomplete profile) 34 | * @param amount 35 | */ 36 | export async function getSomeUsers(amount: number) { 37 | try { 38 | /** Cached reference to '/users' in the database. */ 39 | const allUsersRef = admin.database().ref("users") 40 | /** An array of {uid, displayName} objects for each existing user */ 41 | const strippedUserRecords = await getStrippedUserRecords(amount) 42 | /** An array of Promises that resolve with a user's username */ 43 | const usernamePromises = strippedUserRecords.map((user) => { 44 | return allUsersRef 45 | .child(user.uid) 46 | .child("username") 47 | .once("value") 48 | .then(extractSnapshotValue) 49 | }) 50 | // When all the usernames have been fetched, iterate each one and combine them with the relevant user record 51 | /** An array of filtered user data objects for each existing user */ 52 | const parsedUsers = await Promise.all(usernamePromises).then((usernameArray) => { 53 | // combine each username with the relevant user record 54 | return usernameArray.map((username, i) => { 55 | const userRecord = strippedUserRecords[i] 56 | return { ...userRecord, username } 57 | }) 58 | }) 59 | return parsedUsers.filter((user) => user.username) 60 | } catch (error) { 61 | console.error("Error listing users:", error) 62 | throw new Error("Error listing users: " + error) // or just rethrow error? 63 | } 64 | } 65 | /** 66 | * Memory-friendly function to retrieve only select fields of a user's UserRecord 67 | */ 68 | const getStrippedUserRecords = async function (amount: number) { 69 | const response = await admin.auth().listUsers(amount) 70 | return response.users.map((user) => { 71 | return { uid: user.uid } 72 | }) 73 | // original response should be now marked for disposal from memory 74 | } 75 | const extractSnapshotValue = (snapshot: admin.database.DataSnapshot) => snapshot.val() 76 | 77 | //todo function to clear out unused usernames 78 | // https://stackoverflow.com/questions/25294478/how-do-you-prevent-duplicate-user-properties-in-firebase#comment48336277_25328654 -------------------------------------------------------------------------------- /functions/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | // -- Strict errors -- 4 | // These lint rules are likely always a good idea. 5 | 6 | // Force function overloads to be declared together. This ensures readers understand APIs. 7 | "adjacent-overload-signatures": true, 8 | 9 | // Do not allow the subtle/obscure comma operator. 10 | "ban-comma-operator": true, 11 | 12 | // Do not allow internal modules or namespaces . These are deprecated in favor of ES6 modules. 13 | "no-namespace": true, 14 | 15 | // Do not allow parameters to be reassigned. To avoid bugs, developers should instead assign new values to new vars. 16 | "no-parameter-reassignment": true, 17 | 18 | // Force the use of ES6-style imports instead of /// imports. 19 | "no-reference": true, 20 | 21 | // Do not allow type assertions that do nothing. This is a big warning that the developer may not understand the 22 | // code currently being edited (they may be incorrectly handling a different type case that does not exist). 23 | "no-unnecessary-type-assertion": true, 24 | 25 | // Disallow nonsensical label usage. 26 | "label-position": true, 27 | 28 | // Disallows the (often typo) syntax if (var1 = var2). Replace with if (var2) { var1 = var2 }. 29 | "no-conditional-assignment": true, 30 | 31 | // Disallows constructors for primitive types (e.g. new Number('123'), though Number('123') is still allowed). 32 | "no-construct": true, 33 | 34 | // Do not allow super() to be called twice in a constructor. 35 | "no-duplicate-super": true, 36 | 37 | // Do not allow the same case to appear more than once in a switch block. 38 | "no-duplicate-switch-case": true, 39 | 40 | // Do not allow a variable to be declared more than once in the same block. Consider function parameters in this 41 | // rule. 42 | "no-duplicate-variable": [true, "check-parameters"], 43 | 44 | // Disallows a variable definition in an inner scope from shadowing a variable in an outer scope. Developers should 45 | // instead use a separate variable name. 46 | "no-shadowed-variable": true, 47 | 48 | // Empty blocks are almost never needed. Allow the one general exception: empty catch blocks. 49 | "no-empty": [true, "allow-empty-catch"], 50 | 51 | // Functions must either be handled directly (e.g. with a catch() handler) or returned to another function. 52 | // This is a major source of errors in Cloud Functions and the team strongly recommends leaving this rule on. 53 | "no-floating-promises": true, 54 | 55 | // Do not allow any imports for modules that are not in package.json. These will almost certainly fail when 56 | // deployed. 57 | "no-implicit-dependencies": true, 58 | 59 | // The 'this' keyword can only be used inside of classes. 60 | "no-invalid-this": true, 61 | 62 | // Do not allow strings to be thrown because they will not include stack traces. Throw Errors instead. 63 | "no-string-throw": true, 64 | 65 | // Disallow control flow statements, such as return, continue, break, and throw in finally blocks. 66 | "no-unsafe-finally": true, 67 | 68 | // Expressions must always return a value. Avoids common errors like const myValue = functionReturningVoid(); 69 | "no-void-expression": [true, "ignore-arrow-function-shorthand"], 70 | 71 | // Disallow duplicate imports in the same file. 72 | "no-duplicate-imports": true, 73 | 74 | 75 | // -- Strong Warnings -- 76 | // These rules should almost never be needed, but may be included due to legacy code. 77 | // They are left as a warning to avoid frustration with blocked deploys when the developer 78 | // understand the warning and wants to deploy anyway. 79 | 80 | // Warn when an empty interface is defined. These are generally not useful. 81 | "no-empty-interface": {"severity": "warning"}, 82 | 83 | // Warn when an import will have side effects. 84 | "no-import-side-effect": {"severity": "warning"}, 85 | 86 | // Warn when variables are defined with var. Var has subtle meaning that can lead to bugs. Strongly prefer const for 87 | // most values and let for values that will change. 88 | "no-var-keyword": {"severity": "warning"}, 89 | 90 | // Prefer === and !== over == and !=. The latter operators support overloads that are often accidental. 91 | "triple-equals": {"severity": "warning"}, 92 | 93 | // Warn when using deprecated APIs. 94 | "deprecation": {"severity": "warning"}, 95 | 96 | // -- Light Warnings -- 97 | // These rules are intended to help developers use better style. Simpler code has fewer bugs. These would be "info" 98 | // if TSLint supported such a level. 99 | 100 | // prefer for( ... of ... ) to an index loop when the index is only used to fetch an object from an array. 101 | // (Even better: check out utils.js like .map if transforming an array!) 102 | "prefer-for-of": {"severity": "warning"}, 103 | 104 | // Warns if function overloads could be unified into a single function with optional or rest parameters. 105 | "unified-signatures": {"severity": "warning"}, 106 | 107 | // Prefer const for values that will not change. This better documents code. 108 | "prefer-const": {"severity": "warning"}, 109 | 110 | // Multi-line object literals and function calls should have a trailing comma. This helps avoid merge conflicts. 111 | "trailing-comma": {"severity": "warning"} 112 | }, 113 | 114 | "defaultSeverity": "error" 115 | } 116 | -------------------------------------------------------------------------------- /legacy/src/firebaseBackend.js: -------------------------------------------------------------------------------- 1 | import { navigate } from "svelte-routing" 2 | 3 | import * as firebaseOriginal from "firebase/app" 4 | 5 | let firebase = firebaseOriginal.default 6 | import "firebase/auth" 7 | import "firebase/database" 8 | import { userDataStore } from "./stores/userDataStore" 9 | import { log } from "./util/logging" 10 | 11 | var firebaseConfig = { 12 | apiKey: "AIzaSyDgkLmjsLTLO8cnEhaZu-0o12wpdisCn5w", 13 | authDomain: "client.firebaseapp.com", 14 | databaseURL: process.env.EMULATION 15 | ? "http://localhost:9000/?ns=svelte-fullstack-starter" 16 | : "https://svelte-fullstack-starter.firebaseio.com", 17 | projectId: "client", 18 | storageBucket: "client.appspot.com", 19 | messagingSenderId: "684795141693", 20 | appId: "1:684795141693:web:bb22a3283361cfc381d454", 21 | measurementId: "G-Y1SRV3FGND", 22 | } 23 | // Initialize Firebase 24 | firebase.initializeApp(firebaseConfig) 25 | firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL) 26 | 27 | // firebase specific 28 | let userLoaded = false 29 | 30 | /** 31 | * checks local data if a user is logged in 32 | * @returns {Promise} 33 | */ 34 | function getCurrentUser() { 35 | log("Attempting to get the current user locally") 36 | return new Promise((resolve, reject) => { 37 | if (userLoaded) { 38 | resolve(firebase.auth().currentUser) 39 | } 40 | const unsubscribe = firebase.auth().onAuthStateChanged((user) => { 41 | userLoaded = true 42 | unsubscribe() 43 | log("Current user found successfully") 44 | log(user) 45 | 46 | resolve(user) 47 | }, reject) 48 | }) 49 | } 50 | 51 | export async function logout() { 52 | // let user = await getCurrentUser() 53 | firebase 54 | .auth() 55 | .signOut() 56 | .then(() => { 57 | console.log("logged out") 58 | 59 | // we let the auth route handle this nav now so if they are in a non auth route the dont have to be moved 60 | // navigate("/", { replace: true }); 61 | //change the store after nav to avoid template errors needing to read email 62 | userDataStore.update(() => null) //this will cause a redirect 63 | }) 64 | .catch((e) => { 65 | alert(e.message) 66 | }) 67 | } 68 | 69 | /** 70 | * Updates the email in the firebase Authentication table 71 | * @param email 72 | * @returns {Promise} 73 | */ 74 | export async function updateUserEmail(email) { 75 | try { 76 | // let updatedUser = if need access 77 | await firebase.auth().currentUser.updateEmail(email) 78 | } catch (e) { 79 | alert(e.message) 80 | throw new Error() 81 | } 82 | } 83 | 84 | /** 85 | * Updates the password used with firebase basic authentication 86 | * @param password 87 | * @returns {Promise} 88 | */ 89 | export async function updateUserPassword(password) { 90 | try { 91 | await firebase.auth().currentUser.updatePassword(password) 92 | } catch (e) { 93 | alert(e.message) 94 | throw new Error() 95 | } 96 | } 97 | 98 | /** 99 | * updates Custom data in the realtime datastore users object, except for the username 100 | * @param data 101 | * @returns {Promise} 102 | */ 103 | export async function updatePersonalData(data) { 104 | const { displayName } = data 105 | try { 106 | await firebase 107 | .database() 108 | .ref("users/" + firebase.auth().currentUser.uid) 109 | .update({ 110 | displayName: displayName, 111 | }) 112 | 113 | userDataStore.update((user) => { 114 | return { ...user, displayName: displayName } 115 | }) 116 | } catch (e) { 117 | alert(e.message) 118 | } 119 | } 120 | 121 | /** 122 | * Signs in to firebase using basic authentication 123 | * Then loads the custom data 124 | * @param email 125 | * @param password 126 | * @returns {Promise} 127 | */ 128 | export async function signin(email, password) { 129 | try { 130 | log(`Attempting to sign in`) 131 | 132 | let { user } = await firebase.auth().signInWithEmailAndPassword(email, password) // sometimes the user is wrapped in user, vs get currentUser which isn't wrapped 133 | 134 | log("User from signInWithEmailAndPassword:") 135 | log(user) 136 | 137 | if (user.emailVerified) { 138 | await getUserDataAndStore() 139 | navigate("/", { replace: true }) 140 | } else { 141 | alert("User not found, if you registered before, have you checked your email?") 142 | throw new Error() // no message needed for the UI to stop the spinner 143 | } 144 | console.log(user) 145 | } catch (e) { 146 | alert(e.message) 147 | throw e.message 148 | } 149 | } 150 | 151 | /** 152 | * Runs a query to see if any child of users contains the username 153 | * @param username 154 | * @returns {Promise} 155 | */ 156 | export async function isUsernameFree(username) { 157 | try { 158 | let ref = await firebase.database().ref(`username_lookup/${username}`).once("value") 159 | let uid = ref.val() 160 | return !!uid 161 | } catch (e) { 162 | throw e.message 163 | } 164 | } 165 | 166 | /** 167 | * @param username 168 | * @returns {Promise} 169 | */ 170 | export async function getDBUserByUsername(username) { 171 | try { 172 | let dbUser = await firebase 173 | .database() 174 | .ref(`users`) 175 | .orderByChild("username") 176 | .equalTo(username) 177 | .once("value") 178 | 179 | //todo clean this 180 | 181 | let dbUserVal = dbUser.val() 182 | if (!dbUserVal) { 183 | return null 184 | } 185 | 186 | let key = [Object.keys(dbUserVal)[0]][0] 187 | const user = dbUserVal[key] 188 | log("DBuser found: ") 189 | log(user) 190 | return { ...user, uid: key } 191 | } catch (e) { 192 | throw e.message 193 | } 194 | } 195 | 196 | /** 197 | * Create an account on firebase which will not be using a 3rd party auth provider 198 | * @param email 199 | * @param password 200 | * @param username 201 | * @param displayName 202 | * @returns {Promise} 203 | */ 204 | export async function register(email, password) { 205 | try { 206 | let userCredential = await firebase.auth().createUserWithEmailAndPassword(email, password) 207 | const { user } = userCredential 208 | await userCredential.user.sendEmailVerification() 209 | console.log("registered " + userCredential) 210 | return userCredential 211 | } catch (e) { 212 | alert(e.message) 213 | throw e.message 214 | } 215 | } 216 | 217 | export function requestPasswordRecovery(email) { 218 | return firebase.auth().sendPasswordResetEmail(email) 219 | } 220 | 221 | /** 222 | * updates Users username 223 | * Updates index of usernames to prevent duplicates later 224 | * @param username 225 | * @returns {Promise} 226 | * todo find a way to batch both these writes as a transaction 227 | */ 228 | export async function claimUsername(username) { 229 | try { 230 | const myUid = firebase.auth().currentUser.uid 231 | 232 | // the order of the updates are important due to the database validation rules, as the username_lookup must be set before the `users/${myUid}` 233 | // see database.rules.json 234 | // ".validate": "root.child('username_lookup/'+newData.val()).val() === auth.uid" 235 | 236 | // step 1 237 | log(`Storing username ${username} in lookup table`) 238 | await firebase.database().ref(`username_lookup/${username}`).set(myUid) 239 | 240 | // step 2 241 | log(`updating username ${username} in users`) 242 | await firebase.database().ref(`users/${myUid}`).update({ 243 | username: username, 244 | }) 245 | 246 | userDataStore.update((user) => { 247 | return { ...user, username: username } 248 | }) 249 | } catch (error) { 250 | alert(error.message) 251 | } 252 | } 253 | 254 | /** 255 | * Get a user stored locally if logged in and verified then load custom data 256 | * @returns {Promise} 257 | */ 258 | export async function backendInit() { 259 | try { 260 | let user = await getCurrentUser() 261 | if (user && user.emailVerified) { 262 | await getUserDataAndStore() 263 | } 264 | } catch (e) { 265 | throw e.message 266 | } 267 | } 268 | 269 | /** 270 | * Loads user data for authenticated user and persists in the svelte store 271 | * @returns {Promise} 272 | */ 273 | export async function getUserDataAndStore() { 274 | try { 275 | const uid = firebase.auth().currentUser.uid 276 | log(`Attempting to get the user data stored on Realtime Database for user ${uid}`) 277 | 278 | let query = await firebase.database().ref(`users/${uid}`).once("value") 279 | 280 | const user = query.val() 281 | log("User DB query returned:") 282 | log(user) 283 | if (!user) { 284 | log( 285 | "realtime DB data for user not found, so only storing uid from login info. This can happen if you switch environment or are using emulation" 286 | ) 287 | userDataStore.update(() => { 288 | return { 289 | uid: uid, 290 | } 291 | }) 292 | 293 | return null 294 | } else { 295 | userDataStore.update(() => user) 296 | return user 297 | } 298 | } catch (e) { 299 | throw e.message 300 | } 301 | } 302 | //WIP 303 | 304 | /** 305 | * Follows a user 306 | * @param username 307 | * @returns {Promise} 308 | */ 309 | export async function followUser(user) { 310 | try { 311 | const myUid = firebase.auth().currentUser.uid 312 | log(`${myUid} to follow user ${user.username}`) 313 | await firebase 314 | .database() 315 | .ref(`following/${myUid}/userFollowing`) 316 | .update({ 317 | [user.uid]: true, 318 | }) 319 | return true 320 | } catch (e) { 321 | alert(e.message) 322 | } 323 | } 324 | /** 325 | * UnFollows a user 326 | * @param username 327 | * @returns {Promise} 328 | */ 329 | export async function unFollowUser(user) { 330 | try { 331 | const myUid = firebase.auth().currentUser.uid 332 | log(`${myUid} to unfollow user ${user.username}`) 333 | await firebase.database().ref(`following/${myUid}/userFollowing/${user.uid}`).remove() 334 | return true 335 | } catch (e) { 336 | alert(e.message) 337 | } 338 | } 339 | 340 | /** 341 | * Checks userFollowing for the current user to see if following profile user 342 | * @param uid profile user to check 343 | * @returns {Promise} 344 | */ 345 | export async function amIFollowing(uid) { 346 | try { 347 | const myUid = firebase.auth().currentUser.uid 348 | log(`is ${myUid} following user ${uid}`) 349 | const ref = await firebase 350 | .database() 351 | .ref(`following/${myUid}/userFollowing/${uid}`) 352 | .once("value") 353 | 354 | const result = ref.val() 355 | log(`Following result: ${result}`) 356 | return result 357 | } catch (e) { 358 | throw e.message 359 | } 360 | } 361 | --------------------------------------------------------------------------------