├── 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
UnFollow
59 | {:else}
60 |
Follow
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 |
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 |
Logout
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 |
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 |
--------------------------------------------------------------------------------