This commit is contained in:
Pluwen committed 2021-05-27 11:56:18 +08:00
commit 8b939777ba
298 files changed
+85113 -58081

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
name: Check Code with Prettier
on:
push:
branches:
- "master"
jobs:
format:
runs-on: ubuntu-latest
steps:
- name: Check out repo
uses: actions/checkout@v2
- name: Run prettier
run: |-
npx prettier --write './**/*.{js,jsx,ts,tsx,css,md,json}'
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v2.3.0
with:
commit_message: Apply Prettier changes
branch: master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+5
View File
@@ -101,3 +101,8 @@ typings/
.vscode
.vercel
#include template .env file for docker-compose
!docker/.env
!docker/supabase/.env
!docker/supabase-traefik/.env
+13 -6
View File
@@ -66,12 +66,12 @@ Our client library is modular. Each sub-library is a standalone implementation f
- `realtime-{lang}`: Client library to work with [Realtime](https://github.com/supabase/realtime)
- `gotrue-{lang}`: Client library to work with [GoTrue](https://github.com/netlify/gotrue)
| Repo | Official | Community |
| --------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`supabase-{lang}`** | [`JS`](https://github.com/supabase/supabase-js) | [`C#`](https://github.com/supabase/supabase-csharp) \| [`Dart`](https://github.com/supabase/supabase-dart) \| [`Python`](https://github.com/supabase/supabase-py) \| `Rust` |
| `postgrest-{lang}` | [`JS`](https://github.com/supabase/postgrest-js) | [`C#`](https://github.com/supabase/postgrest-csharp) \| [`Dart`](https://github.com/supabase/postgrest-dart) \| [`Python`](https://github.com/supabase/postgrest-py) \| [`Rust`](https://github.com/supabase/postgrest-rs) |
| `realtime-{lang}` | [`JS`](https://github.com/supabase/realtime-js) | [`C#`](https://github.com/supabase/realtime-csharp) \| [`Dart`](https://github.com/supabase/realtime-dart) \| [`Python`](https://github.com/supabase/realtime-py) \| `Rust` |
| `gotrue-{lang}` | [`JS`](https://github.com/supabase/gotrue-js) | [`C#`](https://github.com/supabase/gotrue-csharp) \| [`Dart`](https://github.com/supabase/gotrue-dart) \| [`Python`](https://github.com/supabase/gotrue-py) \| `Rust` |
| Repo | Official | Community |
| --------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`supabase-{lang}`** | [`JS`](https://github.com/supabase/supabase-js) | [`C#`](https://github.com/supabase/supabase-csharp) \| [`Dart`](https://github.com/supabase/supabase-dart) \| [`Python`](https://github.com/supabase/supabase-py) \| `Rust` \| [`Ruby`](https://github.com/supabase/supabase-rb) |
| `postgrest-{lang}` | [`JS`](https://github.com/supabase/postgrest-js) | [`C#`](https://github.com/supabase/postgrest-csharp) \| [`Dart`](https://github.com/supabase/postgrest-dart) \| [`Python`](https://github.com/supabase/postgrest-py) \| [`Rust`](https://github.com/supabase/postgrest-rs) \| [`Ruby`](https://github.com/supabase/postgrest-rb) |
| `realtime-{lang}` | [`JS`](https://github.com/supabase/realtime-js) | [`C#`](https://github.com/supabase/realtime-csharp) \| [`Dart`](https://github.com/supabase/realtime-dart) \| [`Python`](https://github.com/supabase/realtime-py) \| `Rust` \| `Ruby` |
| `gotrue-{lang}` | [`JS`](https://github.com/supabase/gotrue-js) | [`C#`](https://github.com/supabase/gotrue-csharp) \| [`Dart`](https://github.com/supabase/gotrue-dart) \| [`Python`](https://github.com/supabase/gotrue-py) \| `Rust` \| `Ruby` |
<!--- Remove this list if you're traslating to another language, it's hard to keep updated across multiple files-->
<!--- Keep only the link to the list of translation files-->
@@ -79,21 +79,28 @@ Our client library is modular. Each sub-library is a standalone implementation f
## Translations
- [Arabic | العربية](/i18n/README.ar.md)
- [Danish / Dansk](/i18n/README.da.md)
- [Dutch / Nederlands](/i18n/README.nl.md)
- [English](https://github.com/supabase/supabase)
- [French / Français](/i18n/README.fr.md)
- [German / Deutsch](/i18n/README.de.md)
- [Greek / Ελληνικά](/i18n/README.gr.md)
- [Hindi / हिंदी](/i18n/README.hi.md)
- [Nepali / नेपाली](/i18n/README.ne.md)
- [Indonesian / Bahasa Indonesia](/i18n/README.id.md)
- [Italiano / Italian](/i18n/README.it.md)
- [Japanese / 日本語](/i18n/README.jp.md)
- [Korean / 한국어](/i18n/README.ko.md)
- [Norwegian (Bokmål) / Norsk (Bokmål)](/i18n/README.nb-no.md)
- [Persian / فارسی](/i18n/README.fa.md)
- [Polish / Polski](/i18n/README.pl.md)
- [Portuguese / Portuguese](/i18n/README.pt.md)
- [Portuguese (Brazilian) / Português Brasileiro](/i18n/README.pt-br.md)
- [Romanian / Română](/i18n/README.ro.md)
- [Russian / Pусский](/i18n/README.ru.md)
- [Spanish / Español](/i18n/README.es.md)
- [Simplified Chinese / 简体中文](/i18n/README.zh-cn.md)
- [Swedish / Svenska](/i18n/README.sv.md)
- [Traditional Chinese / 繁体中文](/i18n/README.zh-tw.md)
- [Turkish / Türkçe](/i18n/README.tr.md)
- [Ukrainian / Українська](/i18n/README.uk.md)
+2 -4
View File
@@ -2,24 +2,22 @@
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| All | :white_check_mark: |
| All | :white_check_mark: |
## Reporting a Vulnerability
Contact: security@supabase.io
Canonical: https://supabase.io/.well-known/security.txt
At Supabase, we consider the security of our systems a top priority. But no matter how much effort we put into system security, there can still be vulnerabilities present.
If you discover a vulnerability, we would like to know about it so we can take steps to address it as quickly as possible. We would like to ask you to help us better protect our clients and our systems.
Please do the following:
- E-mail your findings to security@supabase.io.
- E-mail your findings to security@supabase.io.
- Do not take advantage of the vulnerability or problem you have discovered, for example by downloading more data than necessary to demonstrate the vulnerability or deleting or modifying other people's data,
- Do not reveal the problem to others until it has been resolved,
- Do not use attacks on physical security, social engineering, distributed denial of service, spam or applications of third parties, and
+6
View File
@@ -14,9 +14,15 @@ make dev
# tab 2
cd www
npm install
npm run dev
# tab 3
cd web/spec
make
npm run gen:supabase
npm run build
cd web
npm run start
```
+21
View File
@@ -0,0 +1,21 @@
# fill with random passwords
OPERATOR_TOKEN=your-super-secret-operator-token
JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters-long
POSTGRES_PASSWORD=your-super-secret-and-long-postgres-password
# some SMTP server to send your auth-mails with
SMTP_HOST=mail.example.com
SMTP_PORT=
SMTP_USER=
SMTP_PASS=
# predefined; don't change these unless you know what you're doing
POSTGRES_PORT=5432
AUTH_PORT=9999
REST_PORT=3000
REALTIME_PORT=4000
KONG_PORT=8000
KONG_PORT_TLS=8443
+16 -10
View File
@@ -1,18 +1,25 @@
# Supabase Docker
## Docker
You can run Supabase on your local machine or a server using `docker-compose`.
For a more secure setup to use on a server (e.g. using HTTPS), have a look at the *supabase-traefik* setup.
You can run Supabase on your local machine using `docker-compose`:
## Configuration
- Starting all services: `docker-compose up -d`
Add your passwords to the .env file.
For better customization and security, please make sure you read the [self-hosting guide](https://supabase.io/docs/guides/self-hosting#running-supabase).
## Run via docker-compose
- Starting all services: `docker-compose up`
- Stopping all services: `docker-compose down`
**Usage**
## Usage
```js
import { createClient } from '@supabase/supabase-js'
const SUPABASE_URL = 'http://localhost:8000'
const SUPABASE_KEY = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTYwMzk2ODgzNCwiZXhwIjoyNTUwNjUzNjM0LCJhdWQiOiIiLCJzdWIiOiIiLCJSb2xlIjoicG9zdGdyZXMifQ.magCcozTMKNrl76Tj2dsM7XTl_YH0v0ilajzAvIlw3U'
const SUPABASE_KEY = '<anon-apikey-from-kong.yml>'
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
```
@@ -20,8 +27,7 @@ const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
Accessing the services directly:
- Kong: http://localhost:8000
- GoTrue: http://localhost:8000/auth/v1/
- PostgREST: http://localhost:8000/rest/v1/
- Realtime: http://localhost:8000/realtime/v1/
- Postgres: http://localhost:5432
- GoTrue: http://localhost:8000/auth/v1/?apikey=<anon-apikey-from-kong.yml>
- PostgREST: http://localhost:8000/rest/v1/?apikey=<anon-apikey-from-kong.yml>
- Realtime: http://localhost:8000/realtime/v1/?apikey=<anon-apikey-from-kong.yml>
- Postgres: http://localhost:5432
+81 -74
View File
@@ -1,81 +1,88 @@
version: '3.6'
services:
kong:
container_name: supabase-kong
build:
context: ./kong
environment:
KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml
KONG_PLUGINS: request-transformer,cors,key-auth,http-log
ports:
- 8000:8000/tcp
- 8443:8443/tcp
auth:
container_name: supabase-auth
image: supabase/gotrue:latest
ports:
- '9999:9999'
environment:
GOTRUE_JWT_SECRET: super-secret-jwt-token-with-at-least-32-characters-long
GOTRUE_JWT_EXP: 3600
GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
GOTRUE_DB_DRIVER: postgres
DB_NAMESPACE: auth
API_EXTERNAL_URL: localhost
GOTRUE_API_HOST: 0.0.0.0
PORT: 9999
GOTRUE_DISABLE_SIGNUP: "false"
GOTRUE_SITE_URL: localhost
GOTRUE_MAILER_AUTOCONFIRM: "true"
GOTRUE_LOG_LEVEL: DEBUG
GOTRUE_OPERATOR_TOKEN: super-secret-operator-token
DATABASE_URL: "postgres://postgres:postgres@db:5432/postgres?sslmode=disable"
depends_on:
- db
rest:
container_name: supabase-rest
image: postgrest/postgrest:latest
ports:
- '3000:3000'
depends_on:
kong:
container_name: supabase-kong
build:
context: ./dockerfiles/kong
environment:
KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml
KONG_PLUGINS: request-transformer,cors,key-auth,http-log
ports:
- ${KONG_PORT}:8000/tcp
- ${KONG_PORT_TLS}:8443/tcp
auth:
container_name: supabase-auth
image: supabase/gotrue:latest
ports:
- ${AUTH_PORT}
depends_on:
- db
restart: always
environment:
PGRST_DB_URI: postgres://postgres:postgres@db:5432/postgres
PGRST_DB_SCHEMA: public
PGRST_DB_ANON_ROLE: postgres
PGRST_JWT_SECRET: super-secret-jwt-token-with-at-least-32-characters-long
realtime:
container_name: supabase-realtime
image: supabase/realtime:latest
ports:
- '4000:4000'
depends_on:
environment:
GOTRUE_JWT_SECRET: ${JWT_SECRET}
GOTRUE_JWT_EXP: 3600
GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
GOTRUE_DB_DRIVER: postgres
DB_NAMESPACE: auth
API_EXTERNAL_URL: localhost
GOTRUE_API_HOST: 0.0.0.0
PORT: ${AUTH_PORT}
GOTRUE_SMTP_HOST: ${SMTP_HOST}
GOTRUE_SMTP_PORT: ${SMTP_PORT}
GOTRUE_SMTP_USER: ${SMTP_USER}
GOTRUE_SMTP_PASS: ${SMTP_PASS}
GOTRUE_DISABLE_SIGNUP: 'false'
GOTRUE_SITE_URL: localhost
GOTRUE_MAILER_AUTOCONFIRM: 'true'
GOTRUE_LOG_LEVEL: DEBUG
GOTRUE_OPERATOR_TOKEN: ${OPERATOR_TOKEN}
DATABASE_URL: 'postgres://postgres:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/postgres?sslmode=disable'
rest:
container_name: supabase-rest
image: postgrest/postgrest:latest
ports:
- ${REST_PORT}:3000
depends_on:
- db
restart: on-failure
environment:
DB_HOST: db
DB_NAME: postgres
DB_USER: postgres
DB_PASSWORD: postgres
DB_PORT: 5432
PORT: 4000
HOSTNAME: localhost
# Disable JWT Auth locally. The JWT_SECRET will be ignored.
SECURE_CHANNELS: 'false'
JWT_SECRET: super-secret-jwt-token-with-at-least-32-characters-long
db:
container_name: supabase-db
build:
context: ./postgres
ports:
- 5432:5432
command:
restart: always
environment:
PGRST_DB_URI: postgres://postgres:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/postgres
PGRST_DB_SCHEMA: public
PGRST_DB_ANON_ROLE: anon
PGRST_JWT_SECRET: ${JWT_SECRET}
realtime:
container_name: supabase-realtime
image: supabase/realtime:latest
ports:
- ${REALTIME_PORT}
depends_on:
- db
restart: on-failure
environment:
DB_HOST: db
DB_NAME: postgres
DB_USER: postgres
DB_PASSWORD: ${POSTGRES_PASSWORD}
DB_PORT: ${POSTGRES_PORT}
PORT: ${REALTIME_PORT}
HOSTNAME: localhost
# Disable JWT Auth locally. The JWT_SECRET will be ignored.
SECURE_CHANNELS: 'false'
JWT_SECRET: ${JWT_SECRET}
db:
container_name: supabase-db
build:
context: ./dockerfiles/postgres
ports:
- ${POSTGRES_PORT}:${POSTGRES_PORT}
command:
- postgres
- -c
- wal_level=logical
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_PORT: 5432
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_PORT: ${POSTGRES_PORT}
File renamed without changes.
@@ -40,6 +40,9 @@ services:
config:
hide_credentials: true
consumers:
- username: 'private-key'
- username: 'anon-key'
keyauth_credentials:
- key: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTYwMzk2ODgzNCwiZXhwIjoyNTUwNjUzNjM0LCJhdWQiOiIiLCJzdWIiOiIiLCJSb2xlIjoicG9zdGdyZXMifQ.magCcozTMKNrl76Tj2dsM7XTl_YH0v0ilajzAvIlw3U
- key: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTYwMzk2ODgzNCwiZXhwIjoxNjIwNzQyMTM1LCJhdWQiOiIiLCJzdWIiOiIiLCJSb2xlIjoiYW5vbiIsImp0aSI6ImQ1N2ZkMjFlLTNkYjQtNDk3YS1hNGViLTI4ZDY3Yzg2NjEwMiJ9.Uc0w024BLhWjMAAdzMJfkMxaFa82N-6pBxAtEGzrXCQ
- username: 'service-key'
keyauth_credentials:
- key: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJzdXBhYmFzZSIsImlhdCI6MTYwMzk2ODgzNCwiZXhwIjoxNjIwNzQyMTU1LCJhdWQiOiIiLCJzdWIiOiIiLCJSb2xlIjoic2VydmljZV9yb2xlIiwianRpIjoiOTk4MzJiYmItNDdhNS00NzJhLTgxMDQtYzY4ZGVjZDQyZDVlIn0.zQXx74dQyhpkcqZOMxsx961DPsDz4-nWxTLrCAd-ioI
File renamed without changes.
File renamed without changes.
+32
View File
@@ -0,0 +1,32 @@
# fill with random passwords
OPERATOR_TOKEN=your-super-secret-operator-token
JWT_SECRET=your-super-secret-jwt-token-with-at-least-32-characters-long
POSTGRES_PASSWORD=your-super-secret-and-long-postgres-password
# some SMTP server to send your auth-mails with
SMTP_HOST=mail.example.com
SMTP_PORT=
SMTP_USER=
SMTP_PASS=
# Traefik
TRAEFIK_SECURE_ENTRYPOINT=websecure
TRAEFIK_NETWORK=rp
SUPABASE_HOSTNAME=supabase.example.com
# pgAdmin
PGADMIN_DEFAULT_PASSWORD=
PGADMIN_DEFAULT_EMAIL=
# predefined; don't change these unless you know what you're doing
POSTGRES_PORT=5432
AUTH_PORT=9999
REST_PORT=3000
REALTIME_PORT=4000
KONG_PORT=8000
KONG_PORT_TLS=8443
PGADMIN_PORT=80
+143
View File
@@ -0,0 +1,143 @@
# Supabase Docker with Traefik
This guide is covering a self-hosted Supabase setup with [Traefik](https://github.com/traefik/traefik) as a reverse proxy.
Please make sure you read the [self-hosting guide](https://supabase.io/docs/guides/self-hosting#running-supabase).
## Not using Traefik yet? Getting started from scratch
### Goal
- Super simple to install
- Run Supabase on bare metal (maximum privacy) - dedicated/root server or VPS
- Uses Let's Encrypt certificates
- Uses pgAdmin to provide at least some interface to Supabase on the self-hosted Supabase instance
### Requirements/Assumptions
- Machine with Linux and SSH access
- Installed git, docker and docker-compose
- Access to a Traefik supported DNS providers API: [see this list](https://doc.traefik.io/traefik/https/acme/#providers)
- Traefik isn't used yet
- Ports 443 and 80 aren't used yet
### Getting started
Traefik is a proxy that gets its configuration from the docker-compose labels.
The labels needed for Supabase are already provided in the docker-compose.yml.
#### 1. Clone the Supabase repository
```sh
git clone https://github.com/supabase/supabase
```
#### 2. Setup Traefik
##### 2.1 Change the directory into the Traefik folder
First we are setting up Traefik to act as the reverse proxy and certificate generator.
`cd supabase/docker/supabase-traefik/traefik`
##### 2.2 Add your DNS provider for automatic/easy certificate generation
Edit the *docker/supabase-traefik/traefik/.env* file and add your Let's Encrypt contact email address as well as your provider. This example is using PowerDNS.
```.env
# pdns is for PowerDNS - a self-hosted DNS server
ACME_DNS_CHALLENGE_PROVIDER=pdns
ACME_EMAIL=letsencrypt@example.com
# DNS provider specific config for pdns
PDNS_API_KEY=SOME API KEY
PDNS_API_URL=https://some.url.to.your.pdns.server
```
[Learn more about the DNS challenge](https://doc.traefik.io/traefik/user-guides/docker-compose/acme-dns/)
[Learn more about available DNS providers (there are MANY)](https://doc.traefik.io/traefik/https/acme/#providers)
##### 2.3 Add the DNS provider specific details
Edit the *docker/supabase-traefik/traefik/docker-compose.yml* environment section to be compatible with your DNS provider. This example is using PowerDNS.
PDNS_API_KEY and PDNS_API_URL are acting as variables between the .env and the docker-compose file.
```.yaml
environment:
- PDNS_API_KEY=${PDNS_API_KEY}
- PDNS_API_URL=${PDNS_API_URL}
```
##### 2.4 Start Traefik
Run following command in *docker/supabase-traefik/traefik/* to start Traefik detached
`docker-compose up -d`
#### 3. Change the directory into the docker/supabase-traefik folder
`cd ..` (or `cd supabase/docker/supabase-traefik` if you haven't followed Step 2.)
#### 4. Edit the .env file and insert your own environment variables: passwords etc.
##### 4.1. Change your hostname
- Change the SUPABASE_HOSTNAME to the hostname/subdomain you want to use for Supabase
```sh
TRAEFIK_NETWORK=rp
SUPABASE_HOSTNAME=supabase.example.com
```
### 5. Start the Supabase containers with docker-compose
Run following command in *docker/supabase-traefik/* to start the Supabase containers detached
`docker-compose up -d`
### 6. Test the endpoints below
## Use Supabase with an existing Traefik setup
You can also use the docker-compose.yml with an existing Traefik setup.
Just ignore the *traefik* folder and customize the *.env* file and *docker-compose.yml* as it fits your setup. Run `docker-compose up -d` and check if all endpoints below are working. Use them as described in the docker/readme.md.
## Endpoints
### **Kong**
```
supabase.example.com/
```
### **GoTrue**
```
supabase.example.com/auth/v1/
```
you can test GoTrue with
```
supabase.example.com/auth/v1/settings
```
as the default path returns 404
### **PostgREST**
```
supabase.example.com/rest/v1/
```
### **Realtime**
```
supabase.example.com/realtime/v1/
```
### Postgres
The Postgres database is only available internally via the *db* docker network. You can however access it through pgAdmin.
```
supabase.example.com/pgadmin/
```
@@ -0,0 +1,675 @@
# -*- coding: utf-8 -*-
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2021, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
# config.py - Core application configuration settings
#
##########################################################################
import builtins
import logging
import os
import sys
# We need to include the root directory in sys.path to ensure that we can
# find everything we need when running in the standalone runtime.
root = os.path.dirname(os.path.realpath(__file__))
if sys.path[0] != root:
sys.path.insert(0, root)
from pgadmin.utils import env, IS_WIN, fs_short_path
##########################################################################
# Application settings
##########################################################################
# Name of the application to display in the UI
APP_NAME = 'pgAdmin 4'
APP_ICON = 'pg-icon'
##########################################################################
# Application settings
##########################################################################
# NOTE!!!
# If you change any of APP_RELEASE, APP_REVISION or APP_SUFFIX, then you
# must also change APP_VERSION_INT to match.
#
# Application version number components
APP_RELEASE = 5
APP_REVISION = 2
# Application version suffix, e.g. 'beta1', 'dev'. Usually an empty string
# for GA releases.
APP_SUFFIX = ''
# Numeric application version for upgrade checks. Should be in the format:
# [X]XYYZZ, where X is the release version, Y is the revision, with a leading
# zero if needed, and Z represents the suffix, with a leading zero if needed
APP_VERSION_INT = 50200
# DO NOT CHANGE!
# The application version string, constructed from the components
if not APP_SUFFIX:
APP_VERSION = '%s.%s' % (APP_RELEASE, APP_REVISION)
else:
APP_VERSION = '%s.%s-%s' % (APP_RELEASE, APP_REVISION, APP_SUFFIX)
# Copyright string for display in the app
APP_COPYRIGHT = 'Copyright (C) 2013 - 2021, The pgAdmin Development Team'
##########################################################################
# Misc stuff
##########################################################################
# Path to the online help.
HELP_PATH = '../../../docs/en_US/_build/html/'
# Languages we support in the UI
LANGUAGES = {
'en': 'English',
'zh': 'Chinese (Simplified)',
'cs': 'Czech',
'fr': 'French',
'de': 'German',
'it': 'Italian',
'ja': 'Japanese',
'ko': 'Korean',
'pl': 'Polish',
'ru': 'Russian',
'es': 'Spanish',
}
# DO NOT CHANGE UNLESS YOU KNOW WHAT YOU ARE DOING!
# List of modules to skip when dynamically loading
MODULE_BLACKLIST = ['test']
# DO NOT CHANGE UNLESS YOU KNOW WHAT YOU ARE DOING!
# List of treeview browser nodes to skip when dynamically loading
NODE_BLACKLIST = []
##########################################################################
# Server settings
##########################################################################
# The server mode determines whether or not we're running on a web server
# requiring user authentication, or desktop mode which uses an automatic
# default login.
#
# DO NOT DISABLE SERVER MODE IF RUNNING ON A WEBSERVER!!
#
# We only set SERVER_MODE if it's not already set. That's to allow the
# runtime to force it to False.
#
# NOTE: If you change the value of SERVER_MODE in an included config file,
# you may also need to redefine any values below that are derived
# from it, notably various paths such as LOG_FILE and anything
# using DATA_DIR.
if (not hasattr(builtins, 'SERVER_MODE')) or builtins.SERVER_MODE is None:
SERVER_MODE = True
else:
SERVER_MODE = builtins.SERVER_MODE
# HTTP headers to search for CSRF token when it is not provided in the form.
# Default is ['X-CSRFToken', 'X-CSRF-Token']
WTF_CSRF_HEADERS = ['X-pgA-CSRFToken']
# User ID (email address) to use for the default user in desktop mode.
# The default should be fine here, as it's not exposed in the app.
DESKTOP_USER = 'pgadmin4@pgadmin.org'
# This option allows the user to host the application on a LAN
# Default hosting is on localhost (DEFAULT_SERVER='localhost').
# To host pgAdmin4 over LAN set DEFAULT_SERVER='0.0.0.0' (or a specific
# adaptor address.
#
# NOTE: This is NOT recommended for production use, only for debugging
# or testing. Production installations should be run as a WSGI application
# behind Apache HTTPD.
DEFAULT_SERVER = '127.0.0.1'
# The default port on which the app server will listen if not set in the
# environment by the runtime
DEFAULT_SERVER_PORT = 5050
# This param is used to override the default web server information about
# the web technology and the frameworks being used in the application
# An attacker could use this information to fingerprint underlying operating
# system and research known exploits for the specific version of
# software in use
WEB_SERVER = 'Python'
# Enable X-Frame-Option protection.
# Set to one of "SAMEORIGIN", "ALLOW-FROM origin" or "" to disable.
# Note that "DENY" is NOT supported (and will be silently ignored).
# See https://tools.ietf.org/html/rfc7034 for more info.
X_FRAME_OPTIONS = "SAMEORIGIN"
# The Content-Security-Policy header allows you to restrict how resources
# such as JavaScript, CSS, or pretty much anything that the browser loads.
# see https://content-security-policy.com/#source_list for more info
# e.g. "default-src https: data: 'unsafe-inline' 'unsafe-eval';"
CONTENT_SECURITY_POLICY = "default-src http: data: blob: 'unsafe-inline' " \
"'unsafe-eval';"
# STRICT_TRANSPORT_SECURITY_ENABLED when set to True will set the
# Strict-Transport-Security header
STRICT_TRANSPORT_SECURITY_ENABLED = False
# The Strict-Transport-Security header tells the browser to convert all HTTP
# requests to HTTPS, preventing man-in-the-middle (MITM) attacks.
# e.g. 'max-age=31536000; includeSubDomains'
STRICT_TRANSPORT_SECURITY = "max-age=31536000; includeSubDomains"
# The X-Content-Type-Options header forces the browser to honor the response
# content type instead of trying to detect it, which can be abused to
# generate a cross-site scripting (XSS) attack.
# e.g. nosniff
X_CONTENT_TYPE_OPTIONS = "nosniff"
# The browser will try to prevent reflected XSS attacks by not loading the
# page if the request contains something that looks like JavaScript and the
# response contains the same data. e.g. '1; mode=block'
X_XSS_PROTECTION = "1; mode=block"
# This param is used to validate ALLOWED_HOSTS for the application
# This will be used to avoid Host Header Injection attack
# ALLOWED_HOSTS = ['225.0.0.0/8', '226.0.0.0/7', '228.0.0.0/6']
# ALLOWED_HOSTS = ['127.0.0.1', '192.168.0.1']
# if ALLOWED_HOSTS= [] then it will accept all ips (and application will be
# vulnerable to Host Header Injection attack)
ALLOWED_HOSTS = []
# Hashing algorithm used for password storage
SECURITY_PASSWORD_HASH = 'pbkdf2_sha512'
# Reverse Proxy parameters
# You must tell the middleware how many proxies set each header
# so it knows what values to trust.
# See https://tinyurl.com/yyg7r9av
# for more information.
# Number of values to trust for X-Forwarded-For
PROXY_X_FOR_COUNT = 1
# Number of values to trust for X-Forwarded-Proto.
PROXY_X_PROTO_COUNT = 1
# Number of values to trust for X-Forwarded-Host.
PROXY_X_HOST_COUNT = 0
# Number of values to trust for X-Forwarded-Port.
PROXY_X_PORT_COUNT = 1
# Number of values to trust for X-Forwarded-Prefix.
PROXY_X_PREFIX_COUNT = 0
# NOTE: CSRF_SESSION_KEY, SECRET_KEY and SECURITY_PASSWORD_SALT are no
# longer part of the main configuration, but are stored in the
# configuration databases 'keys' table and are auto-generated.
# COMPRESSION
COMPRESS_MIMETYPES = [
'text/html', 'text/css', 'text/xml', 'application/json',
'application/javascript'
]
COMPRESS_LEVEL = 9
COMPRESS_MIN_SIZE = 500
# Set the cache control max age for static files in flask to 1 year
SEND_FILE_MAX_AGE_DEFAULT = 31556952
# This will be added to static urls as url parameter with value as
# APP_VERSION_INT for cache busting on version upgrade. If the value is set as
# None or empty string then it will not be added.
# eg - http:localhost:5050/pgadmin.css?intver=3.13
APP_VERSION_PARAM = 'ver'
# Add the internal version param to below extensions only
APP_VERSION_EXTN = ('.css', '.js', '.html', '.svg', '.png', '.gif', '.ico')
# Data directory for storage of config settings etc. This shouldn't normally
# need to be changed - it's here as various other settings depend on it.
# On Windows, we always store data in %APPDATA%\pgAdmin. On other platforms,
# if we're in server mode we use /var/lib/pgadmin, otherwise ~/.pgadmin
if IS_WIN:
# Use the short path on windows
DATA_DIR = os.path.realpath(
os.path.join(fs_short_path(env('APPDATA')), "pgAdmin")
)
else:
if SERVER_MODE:
DATA_DIR = '/var/lib/pgadmin'
else:
DATA_DIR = os.path.realpath(os.path.expanduser('~/.pgadmin/'))
# An optional login banner to show security warnings/disclaimers etc. at
# login and password recovery etc. HTML may be included for basic formatting,
# For example:
# LOGIN_BANNER = "<h4>Authorised Users Only!</h4>" \
# "Unauthorised use is strictly forbidden."
LOGIN_BANNER = ""
##########################################################################
# Log settings
##########################################################################
# Debug mode?
DEBUG = False
# Application log level - one of:
# CRITICAL 50
# ERROR 40
# WARNING 30
# SQL 25
# INFO 20
# DEBUG 10
# NOTSET 0
CONSOLE_LOG_LEVEL = logging.WARNING
FILE_LOG_LEVEL = logging.WARNING
# Log format.
CONSOLE_LOG_FORMAT = '%(asctime)s: %(levelname)s\t%(name)s:\t%(message)s'
FILE_LOG_FORMAT = '%(asctime)s: %(levelname)s\t%(name)s:\t%(message)s'
# Log file name. This goes in the data directory, except on non-Windows
# platforms in server mode.
if SERVER_MODE and not IS_WIN:
LOG_FILE = '/var/log/pgadmin/pgadmin4.log'
else:
LOG_FILE = os.path.join(DATA_DIR, 'pgadmin4.log')
##########################################################################
# Server Connection Driver Settings
##########################################################################
# The default driver used for making connection with PostgreSQL
PG_DEFAULT_DRIVER = 'psycopg2'
# Maximum allowed idle time in minutes before which releasing the connection
# for the particular session. (in minutes)
MAX_SESSION_IDLE_TIME = 60
##########################################################################
# User account and settings storage
##########################################################################
# The default path to the SQLite database used to store user accounts and
# settings. This default places the file in the same directory as this
# config file, but generates an absolute path for use througout the app.
SQLITE_PATH = env('SQLITE_PATH') or os.path.join(DATA_DIR, 'pgadmin4.db')
# SQLITE_TIMEOUT will define how long to wait before throwing the error -
# OperationError due to database lock. On slower system, you may need to change
# this to some higher value.
# (Default: 500 milliseconds)
SQLITE_TIMEOUT = 500
# Allow database connection passwords to be saved if the user chooses.
# Set to False to disable password saving.
ALLOW_SAVE_PASSWORD = True
# Maximum number of history queries stored per user/server/database
MAX_QUERY_HIST_STORED = 20
##########################################################################
# Server-side session storage path
#
# SESSION_DB_PATH (Default: $HOME/.pgadmin4/sessions)
##########################################################################
#
# We use SQLite for server-side session storage. There will be one
# SQLite database object per session created.
#
# Specify the path used to store your session objects.
#
# If the specified directory does not exist, the setup script will create
# it with permission mode 700 to keep the session database secure.
#
# On certain systems, you can use shared memory (tmpfs) for maximum
# scalability, for example, on Ubuntu:
#
# SESSION_DB_PATH = '/run/shm/pgAdmin4_session'
#
##########################################################################
SESSION_DB_PATH = os.path.join(DATA_DIR, 'sessions')
SESSION_COOKIE_NAME = 'pga4_session'
##########################################################################
# Mail server settings
##########################################################################
# These settings are used when running in web server mode for confirming
# and resetting passwords etc.
# See: http://pythonhosted.org/Flask-Mail/ for more info
MAIL_SERVER = 'localhost'
MAIL_PORT = 25
MAIL_USE_SSL = False
MAIL_USE_TLS = False
MAIL_USERNAME = ''
MAIL_PASSWORD = ''
MAIL_DEBUG = False
# Flask-Security overrides Flask-Mail's MAIL_DEFAULT_SENDER setting, so
# that should be set as such:
SECURITY_EMAIL_SENDER = 'no-reply@localhost'
##########################################################################
# Mail content settings
##########################################################################
# These settings define the content of password reset emails
SECURITY_EMAIL_SUBJECT_PASSWORD_RESET = "Password reset instructions for %s" \
% APP_NAME
SECURITY_EMAIL_SUBJECT_PASSWORD_NOTICE = "Your %s password has been reset" \
% APP_NAME
SECURITY_EMAIL_SUBJECT_PASSWORD_CHANGE_NOTICE = \
"Your password for %s has been changed" % APP_NAME
##########################################################################
# Upgrade checks
##########################################################################
# Check for new versions of the application?
UPGRADE_CHECK_ENABLED = True
# Where should we get the data from?
UPGRADE_CHECK_URL = 'https://www.pgadmin.org/versions.json'
# What key should we look at in the upgrade data file?
UPGRADE_CHECK_KEY = 'pgadmin4'
# Which CA file should we use?
# Default to cacert.pem in the same directory as config.py et al.
CA_FILE = os.path.join(os.path.dirname(os.path.realpath(__file__)),
"cacert.pem")
# Check if the detected browser is supported
CHECK_SUPPORTED_BROWSER = True
##########################################################################
# Storage Manager storage url config settings
# If user sets STORAGE_DIR to empty it will show all volumes if platform
# is Windows, '/' if it is Linux, Mac or any other unix type system.
# For example:
# 1. STORAGE_DIR = get_drive("C") or get_drive() # return C:/ by default
# where C can be any drive character such as "D", "E", "G" etc
# 2. Set path manually like
# STORAGE_DIR = "/path/to/directory/"
##########################################################################
STORAGE_DIR = os.path.join(DATA_DIR, 'storage')
##########################################################################
# Default locations for binary utilities (pg_dump, pg_restore etc)
#
# These are intentionally left empty in the main config file, but are
# expected to be overridden by packagers in config_distro.py.
#
# A default location can be specified for each database driver ID, in
# a dictionary. Either an absolute or relative path can be specified.
# In cases where it may be difficult to know what the working directory
# is, "$DIR" can be specified. This will be replaced with the path to the
# top-level pgAdmin4.py file. For example, on macOS we might use:
#
# $DIR/../../SharedSupport
#
##########################################################################
DEFAULT_BINARY_PATHS = {
"pg": "",
"ppas": "",
"gpdb": ""
}
##########################################################################
# Test settings - used primarily by the regression suite, not for users
##########################################################################
# The default path for SQLite database for testing
TEST_SQLITE_PATH = os.path.join(DATA_DIR, 'test_pgadmin4.db')
##########################################################################
# Allows flask application to response to the each request asynchronously
##########################################################################
THREADED_MODE = True
##########################################################################
# Do not allow SQLALCHEMY to track modification as it is going to be
# deprecated in future
##########################################################################
SQLALCHEMY_TRACK_MODIFICATIONS = False
##########################################################################
# Number of records to fetch in one batch in query tool when query result
# set is large.
##########################################################################
ON_DEMAND_RECORD_COUNT = 1000
##########################################################################
# Allow users to display Gravatar image for their username in Server mode
##########################################################################
SHOW_GRAVATAR_IMAGE = False
##########################################################################
# Set cookie path and options
##########################################################################
COOKIE_DEFAULT_PATH = '/'
COOKIE_DEFAULT_DOMAIN = None
SESSION_COOKIE_DOMAIN = None
SESSION_COOKIE_SAMESITE = 'Lax'
SESSION_COOKIE_SECURE = False
SESSION_COOKIE_HTTPONLY = True
#########################################################################
# Skip storing session in files and cache for specific paths
#########################################################################
SESSION_SKIP_PATHS = [
'/misc/ping'
]
##########################################################################
# Session expiration support
##########################################################################
# SESSION_EXPIRATION_TIME is the interval in Days. Session will be
# expire after the specified number of *days*.
SESSION_EXPIRATION_TIME = 1
# CHECK_SESSION_FILES_INTERVAL is interval in Hours. Application will check
# the session files for cleanup after specified number of *hours*.
CHECK_SESSION_FILES_INTERVAL = 24
# USER_INACTIVITY_TIMEOUT is interval in Seconds. If the pgAdmin screen is left
# unattended for <USER_INACTIVITY_TIMEOUT> seconds then the user will
# be logged out. When set to 0, the timeout will be disabled.
# If pgAdmin doesn't detect any activity in the time specified (in seconds),
# the user will be forcibly logged out from pgAdmin. Set to zero to disable
# the timeout.
# Note: This is applicable only for SERVER_MODE=True.
USER_INACTIVITY_TIMEOUT = 0
# OVERRIDE_USER_INACTIVITY_TIMEOUT when set to True will override
# USER_INACTIVITY_TIMEOUT when long running queries in the Query Tool
# or Debugger are running. When the queries complete, the inactivity timer
# will restart in this case. If set to False, user inactivity may cause
# transactions or in-process debugging sessions to be aborted.
OVERRIDE_USER_INACTIVITY_TIMEOUT = True
##########################################################################
# SSH Tunneling supports only for Python 2.7 and 3.4+
##########################################################################
SUPPORT_SSH_TUNNEL = True
# Allow SSH Tunnel passwords to be saved if the user chooses.
# Set to False to disable password saving.
ALLOW_SAVE_TUNNEL_PASSWORD = False
##########################################################################
# Master password is used to encrypt/decrypt saved server passwords
# Applicable for desktop mode only
##########################################################################
MASTER_PASSWORD_REQUIRED = True
##########################################################################
# Allows pgAdmin4 to create session cookies based on IP address, so even
# if a cookie is stolen, the attacker will not be able to connect to the
# server using that stolen cookie.
# Note: This can cause problems when the server is deployed in dynamic IP
# address hosting environments, such as Kubernetes or behind load
# balancers. In such cases, this option should be set to False.
##########################################################################
ENHANCED_COOKIE_PROTECTION = True
##########################################################################
# External Authentication Sources
##########################################################################
# Default setting is internal
# External Supported Sources: ldap, kerberos
# Multiple authentication can be achieved by setting this parameter to
# ['ldap', 'internal']. pgAdmin will authenticate the user with ldap first,
# in case of failure internal authentication will be done.
AUTHENTICATION_SOURCES = ['internal']
##########################################################################
# LDAP Configuration
##########################################################################
# After ldap authentication, user will be added into the SQLite database
# automatically, if set to True.
# Set it to False, if user should not be added automatically,
# in this case Admin has to add the user manually in the SQLite database.
LDAP_AUTO_CREATE_USER = True
# Connection timeout
LDAP_CONNECTION_TIMEOUT = 10
# Server connection details (REQUIRED)
# example: ldap://<ip-address>:<port> or ldap://<hostname>:<port>
LDAP_SERVER_URI = 'ldap://<ip-address>:<port>'
# The LDAP attribute containing user names. In OpenLDAP, this may be 'uid'
# whilst in AD, 'sAMAccountName' might be appropriate. (REQUIRED)
LDAP_USERNAME_ATTRIBUTE = '<User-id>'
##########################################################################
# 3 ways to configure LDAP as follows (Choose anyone):
# 1. Dedicated User binding
# LDAP Bind User DN Example: cn=username,dc=example,dc=com
# Set this parameter to allow the connection to bind using a dedicated user.
# After the connection is made, the pgadmin login user will be further
# authenticated by the username and password provided
# at the login screen.
LDAP_BIND_USER = None
# LDAP Bind User Password
LDAP_BIND_PASSWORD = None
# OR ####################
# 2. Anonymous Binding
# Set this parameter to allow the anonymous bind.
# After the connection is made, the pgadmin login user will be further
# authenticated by the username and password provided
LDAP_ANONYMOUS_BIND = False
# OR ####################
# 3. Bind as pgAdmin user
# BaseDN (REQUIRED)
# AD example:
# (&(objectClass=user)(memberof=CN=MYGROUP,CN=Users,dc=example,dc=com))
# OpenLDAP example: CN=Users,dc=example,dc=com
LDAP_BASE_DN = '<Base-DN>'
##########################################################################
# Search ldap for further authentication (REQUIRED)
# It can be optional while bind as pgAdmin user
LDAP_SEARCH_BASE_DN = '<Search-Base-DN>'
# Filter string for the user search.
# For OpenLDAP, '(cn=*)' may well be enough.
# For AD, you might use '(objectClass=user)' (REQUIRED)
LDAP_SEARCH_FILTER = '(objectclass=*)'
# Search scope for users (one of BASE, LEVEL or SUBTREE)
LDAP_SEARCH_SCOPE = 'SUBTREE'
# Use TLS? If the URI scheme is ldaps://, this is ignored.
LDAP_USE_STARTTLS = False
# TLS/SSL certificates. Specify if required, otherwise leave empty
LDAP_CA_CERT_FILE = ''
LDAP_CERT_FILE = ''
LDAP_KEY_FILE = ''
##########################################################################
# Kerberos Configuration
##########################################################################
KRB_APP_HOST_NAME = DEFAULT_SERVER
# If the default_keytab_name is not set in krb5.conf or
# the KRB_KTNAME environment variable is not set then, explicitly set
# the Keytab file
KRB_KTNAME = '<KRB5_KEYTAB_FILE>'
# After kerberos authentication, user will be added into the SQLite database
# automatically, if set to True.
# Set it to False, if user should not be added automatically,
# in this case Admin has to add the user manually in the SQLite database.
KRB_AUTO_CREATE_USER = True
##########################################################################
# Local config settings
##########################################################################
# Load distribution-specific config overrides
try:
from config_distro import *
except ImportError:
pass
# Load local config overrides
try:
from config_local import *
except ImportError:
pass
# Load system config overrides. We do this last, so that the sysadmin can
# override anything they want from a config file that's in a protected system
# directory and away from pgAdmin to avoid invalidating signatures.
system_config_dir = '/etc/pgadmin'
if sys.platform.startswith('win32'):
system_config_dir = os.environ['CommonProgramFiles'] + '/pgadmin'
elif sys.platform.startswith('darwin'):
system_config_dir = '/Library/Preferences/pgadmin'
if os.path.exists(system_config_dir + '/config_system.py'):
try:
sys.path.insert(0, system_config_dir)
from config_system import *
except ImportError:
pass
# Override DEFAULT_SERVER value from environment variable.
if 'PGADMIN_CONFIG_DEFAULT_SERVER' in os.environ:
DEFAULT_SERVER = os.environ['PGADMIN_CONFIG_DEFAULT_SERVER']
# Disable USER_INACTIVITY_TIMEOUT when SERVER_MODE=False
if not SERVER_MODE:
USER_INACTIVITY_TIMEOUT = 0
@@ -0,0 +1,13 @@
{
"Servers": {
"1": {
"Name": "Supabase",
"Group": "Servers",
"Port": 5432,
"Username": "postgres",
"Host": "db",
"SSLMode": "disable",
"MaintenanceDB": "postgres"
}
}
}
+127
View File
@@ -0,0 +1,127 @@
version: '3.6'
services:
kong:
container_name: supabase-kong
build:
context: ../dockerfiles/kong
environment:
KONG_DECLARATIVE_CONFIG: /var/lib/kong/kong.yml
KONG_PLUGINS: request-transformer,cors,key-auth,http-log
labels:
traefik.enable: 'true'
traefik.http.routers.supabase-kong.entrypoints: ${TRAEFIK_SECURE_ENTRYPOINT}
traefik.http.routers.supabase-kong.tls: 'true'
traefik.http.services.supabase-kong.loadbalancer.server.port: ${KONG_PORT}
traefik.docker.network: ${TRAEFIK_NETWORK}
traefik.http.routers.supabase-kong.rule: 'Host(`${SUPABASE_HOSTNAME}`)'
networks:
- rp
- db
auth:
container_name: supabase-auth
image: supabase/gotrue:latest
depends_on:
- db
ports:
- ${AUTH_PORT}
environment:
GOTRUE_JWT_SECRET: ${JWT_SECRET}
GOTRUE_JWT_EXP: 3600
GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated
GOTRUE_DB_DRIVER: postgres
DB_NAMESPACE: auth
API_EXTERNAL_URL: ${SUPABASE_HOSTNAME}
GOTRUE_API_HOST: 0.0.0.0
PORT: ${AUTH_PORT}
GOTRUE_DISABLE_SIGNUP: 'false'
GOTRUE_SITE_URL: ${SUPABASE_HOSTNAME}
GOTRUE_MAILER_AUTOCONFIRM: 'true'
GOTRUE_LOG_LEVEL: DEBUG
GOTRUE_OPERATOR_TOKEN: ${OPERATOR_TOKEN}
DATABASE_URL: 'postgres://postgres:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/postgres?sslmode=disable'
GOTRUE_SMTP_HOST: ${SMTP_HOST}
GOTRUE_SMTP_PORT: ${SMTP_PORT}
GOTRUE_SMTP_USER: ${SMTP_USER}
GOTRUE_SMTP_PASS: ${SMTP_PASS}
networks:
- rp
- db
rest:
container_name: supabase-rest
image: postgrest/postgrest:latest
depends_on:
- db
restart: always
environment:
PGRST_DB_URI: postgres://postgres:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/postgres
PGRST_DB_SCHEMA: public
PGRST_DB_ANON_ROLE: anon
PGRST_JWT_SECRET: ${JWT_SECRET}
networks:
- rp
- db
realtime:
container_name: supabase-realtime
image: supabase/realtime:latest
depends_on:
- db
restart: on-failure
environment:
DB_HOST: db
DB_NAME: postgres
DB_USER: postgres
DB_PASSWORD: ${POSTGRES_PASSWORD}
DB_PORT: ${POSTGRES_PORT}
PORT: ${REALTIME_PORT}
HOSTNAME: localhost
# Disable JWT Auth locally. The JWT_SECRET will be ignored.
SECURE_CHANNELS: 'false'
JWT_SECRET: ${JWT_SECRET}
networks:
- rp
- db
db:
container_name: supabase-db
build:
context: ../dockerfiles/postgres
ports:
- ${POSTGRES_PORT}
command:
- postgres
- -c
- wal_level=logical
environment:
POSTGRES_DB: postgres
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_PORT: ${POSTGRES_PORT}
networks:
- db
pgadmin:
container_name: supabase-pgadmin
image: dpage/pgadmin4
restart: always
environment:
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL}
SCRIPT_NAME: /pgadmin
PGADMIN_SERVER_JSON_FILE: /pgadmin4/servers.json
volumes:
- ./config/pgadmin/servers.json:/pgadmin4/servers.json
- ./config/pgadmin/config.py:/pgadmin4/config.py
networks:
- db
- rp
labels:
traefik.enable: 'true'
traefik.http.routers.supabase-pgadmin.entrypoints: ${TRAEFIK_SECURE_ENTRYPOINT}
traefik.http.services.supabase-pgadmin.loadbalancer.server.port: ${PGADMIN_PORT}
traefik.http.routers.supabase-pgadmin.tls: 'true'
traefik.docker.network: ${TRAEFIK_NETWORK}
traefik.http.routers.supabase-pgadmin.rule: 'Host(`${SUPABASE_HOSTNAME}`) && PathPrefix(`/pgadmin`)'
networks:
rp:
name: ${TRAEFIK_NETWORK}
db:
@@ -0,0 +1,40 @@
version: "3.7"
serives:
traefik:
container_name: traefik
image: traefik
restart: always
ports:
- 80:80
- 443:443/udp
- 443:443/tcp
networks:
- rp
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt
- ./traefik/traefik.yml:/traefik/traefik.yaml
environment:
- PDNS_API_KEY=${PDNS_API_KEY}
- PDNS_API_URL=https://${PDNS_API_URL}
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --certificatesresolvers.default.acme.dnschallenge=true
- --certificatesresolvers.default.acme.dnschallenge.provider=${ACME_DNS_CHALLENGE_PROVIDER}
- --certificatesresolvers.default.acme.email=${ACME_EMAIL}
- --certificatesresolvers.default.acme.storage=/letsencrypt/acme.json
- --providers.file.filename=/traefik/traefik.yaml
labels:
traefik.enable: "true"
# redirect to https
traefik.http.routers.http-catchall.rule: "hostregexp(`{host:.+}`)"
traefik.http.routers.http-catchall.entrypoints: "web"
traefik.http.routers.http-catchall.middlewares: "redirect-to-https"
traefik.http.middlewares.redirect-to-https.redirectscheme.scheme: "https"
traefik.http.middlewares.redirect-to-https.redirectscheme.permanent: "true"
networks:
rp:
@@ -0,0 +1,13 @@
tls:
options:
default:
minVersion: VersionTLS12
cipherSuites:
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
stores:
default:
defaultCertificate:
certFile: "/letsencrypt/pem/cert.pem"
keyFile: "/letsencrypt/pem/key.pem"
+1 -3
View File
@@ -13,9 +13,7 @@
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": [
"**/*"
],
"assetBundlePatterns": ["**/*"],
"ios": {
"supportsTablet": true
},
+3 -3
View File
@@ -1,7 +1,7 @@
module.exports = function(api) {
api.cache(true);
module.exports = function (api) {
api.cache(true)
return {
presets: ['babel-preset-expo'],
plugins: ['inline-dotenv'],
}
};
}
+47 -40
View File
@@ -2462,15 +2462,37 @@
}
},
"browserslist": {
"version": "4.14.7",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.7.tgz",
"integrity": "sha512-BSVRLCeG3Xt/j/1cCGj1019Wbty0H+Yvu2AOuZSuoaUWn3RatbL33Cxk+Q4jRMRAbOm0p7SLravLjpnT6s0vzQ==",
"version": "4.16.6",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz",
"integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==",
"requires": {
"caniuse-lite": "^1.0.30001157",
"colorette": "^1.2.1",
"electron-to-chromium": "^1.3.591",
"caniuse-lite": "^1.0.30001219",
"colorette": "^1.2.2",
"electron-to-chromium": "^1.3.723",
"escalade": "^3.1.1",
"node-releases": "^1.1.66"
"node-releases": "^1.1.71"
},
"dependencies": {
"caniuse-lite": {
"version": "1.0.30001228",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz",
"integrity": "sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A=="
},
"colorette": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
"integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w=="
},
"electron-to-chromium": {
"version": "1.3.737",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.737.tgz",
"integrity": "sha512-P/B84AgUSQXaum7a8m11HUsYL8tj9h/Pt5f7Hg7Ty6bm5DxlFq+e5+ouHUoNQMsKDJ7u4yGfI8mOErCmSH9wyg=="
},
"node-releases": {
"version": "1.1.72",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz",
"integrity": "sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw=="
}
}
},
"bser": {
@@ -2583,11 +2605,6 @@
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
},
"caniuse-lite": {
"version": "1.0.30001158",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001158.tgz",
"integrity": "sha512-s5loVYY+yKpuVA3HyW8BarzrtJvwHReuzugQXlv1iR3LKSReoFXRm86mT6hT7PEF5RxW+XQZg+6nYjlywYzQ+g=="
},
"capture-exit": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz",
@@ -3119,11 +3136,6 @@
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
},
"electron-to-chromium": {
"version": "1.3.597",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.597.tgz",
"integrity": "sha512-VJI21MucKaqyFw0oe3j9BIg+nDF4MHzUZAmUwZzrxho+s8zPCD13Fds07Rgu+MTtAadO4tYTKFdAUksKYUyIJw=="
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
@@ -3959,9 +3971,9 @@
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw=="
},
"handlebars": {
"version": "4.7.6",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz",
"integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==",
"version": "4.7.7",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
"integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==",
"requires": {
"minimist": "^1.2.5",
"neo-async": "^2.6.0",
@@ -3974,12 +3986,6 @@
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"uglify-js": {
"version": "3.12.1",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.12.1.tgz",
"integrity": "sha512-o8lHP20KjIiQe5b/67Rh68xEGRrc2SRsCuuoYclXXoC74AfSRGblU1HKzJWH3HxPZ+Ort85fWHpSX7KwBUC9CQ==",
"optional": true
}
}
},
@@ -4690,9 +4696,9 @@
}
},
"lodash": {
"version": "4.17.20",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
"integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"lodash.isequal": {
"version": "4.5.0",
@@ -5661,11 +5667,6 @@
"resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz",
"integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA="
},
"node-releases": {
"version": "1.1.67",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.67.tgz",
"integrity": "sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg=="
},
"node-stream-zip": {
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.12.0.tgz",
@@ -7342,9 +7343,9 @@
"dev": true
},
"ua-parser-js": {
"version": "0.7.22",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.22.tgz",
"integrity": "sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q=="
"version": "0.7.28",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz",
"integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g=="
},
"uglify-es": {
"version": "3.3.9",
@@ -7367,6 +7368,12 @@
}
}
},
"uglify-js": {
"version": "3.13.5",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.13.5.tgz",
"integrity": "sha512-xtB8yEqIkn7zmOyS2zUNBsYCBRhDkvlNxMMY2smuJ/qA8NCHeQvKCF3i9Z4k8FJH4+PJvZRtMrPynfZ75+CSZw==",
"optional": true
},
"ultron": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz",
@@ -7531,9 +7538,9 @@
"integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI="
},
"url-parse": {
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
"integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz",
"integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==",
"requires": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
+1 -2
View File
@@ -7,9 +7,8 @@ How to sign up and login using supabase and supabase-js using HTML and JavaScrip
<img width="558" alt="image" src="https://user-images.githubusercontent.com/458736/88377414-b6fb4180-cdd1-11ea-8061-103ec4577b7b.png">
### running
`npm run dev` (requires npx to be installed)
if you want to make changes without restarting the server run this in a different terminal window:
`npm run watch`
+16 -13
View File
@@ -1,4 +1,3 @@
var SUPABASE_URL = 'https://ernhobnpmmupjnmxpfbt.supabase.co'
var SUPABASE_KEY =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxMzA5ODU0NCwiZXhwIjoxOTI4Njc0NTQ0fQ.Z9bRrfaL2oGhSuyBckFcdcnRIJDelWJ1II98OnEtLO0'
@@ -26,11 +25,9 @@ const signUpSubmitted = (event) => {
const password = event.target[1].value
supabase.auth
.signUp({email, password})
.signUp({ email, password })
.then((response) => {
document.querySelector('#access-token').value = response.data.access_token
document.querySelector('#refresh-token').value = response.data.refresh_token
alert("Logged in as " + response.user.email)
response.error ? alert(response.error.message) : setToken(response)
})
.catch((err) => {
alert(err.response.text)
@@ -43,12 +40,9 @@ const logInSubmitted = (event) => {
const password = event.target[1].value
supabase.auth
.signIn({email, password})
.signIn({ email, password })
.then((response) => {
document.querySelector('#access-token').value = response.data.access_token
document.querySelector('#refresh-token').value = response.data.refresh_token
// response.error ?
alert("Logged in as " + response.user.email)
response.error ? alert(response.error.message) : setToken(response)
})
.catch((err) => {
alert(err.response.text)
@@ -56,10 +50,9 @@ const logInSubmitted = (event) => {
}
const fetchUserDetails = () => {
alert(JSON.stringify(supabase.auth.user()))
alert(JSON.stringify(supabase.auth.user()))
}
const logoutSubmitted = (event) => {
event.preventDefault()
@@ -68,9 +61,19 @@ const logoutSubmitted = (event) => {
.then((response) => {
document.querySelector('#access-token').value = ''
document.querySelector('#refresh-token').value = ''
alert("Logout successful")
alert('Logout successful')
})
.catch((err) => {
alert(err.response.text)
})
}
function setToken(response) {
if (response.data.confirmation_sent_at && !response.data.access_token) {
alert('Confirmation Email Sent')
} else {
document.querySelector('#access-token').value = response.data.access_token
document.querySelector('#refresh-token').value = response.data.refresh_token
alert('Logged in as ' + response.user.email)
}
}
@@ -1,77 +1,63 @@
import { useEffect, useState, useRef, useMemo, useCallback } from "react";
import { MapContainer, TileLayer, Marker, Popup, Circle } from "react-leaflet";
import { RoundToFixDecimals } from "lib/utils";
import { supabase } from "lib/api";
import TextLog from "components/TextLog";
import { useEffect, useState, useRef, useMemo, useCallback } from 'react'
import { MapContainer, TileLayer, Marker, Popup, Circle } from 'react-leaflet'
import { RoundToFixDecimals } from 'lib/utils'
import { supabase } from 'lib/api'
import TextLog from 'components/TextLog'
function DraggableMarker({ initialPos, addCircle }) {
const [position, setPosition] = useState(initialPos);
const markerRef = useRef(null);
const [position, setPosition] = useState(initialPos)
const markerRef = useRef(null)
const eventHandlers = useMemo(
() => ({
dragend() {
const marker = markerRef.current;
const pos = marker.getLatLng();
if (addCircle) addCircle(pos);
if (marker) setPosition(pos);
const marker = markerRef.current
const pos = marker.getLatLng()
if (addCircle) addCircle(pos)
if (marker) setPosition(pos)
},
}),
[addCircle]
);
)
return (
<Marker
draggable={true}
eventHandlers={eventHandlers}
position={position}
ref={markerRef}
>
<Marker draggable={true} eventHandlers={eventHandlers} position={position} ref={markerRef}>
<Popup minWidth={90}>
{`lat: ${position.lat}`}
<br />
{`long: ${position.lng}`}
</Popup>
</Marker>
);
)
}
function MapInput({ clientRef, center, zoom }) {
const [log, setLog] = useState(undefined);
const [circles, setCircles] = useState([]);
const [log, setLog] = useState(undefined)
const [circles, setCircles] = useState([])
useEffect(() => {
let newLog = `Ref: ${clientRef}\nReady to send location...`;
let newLog = `Ref: ${clientRef}\nReady to send location...`
newLog += circles.map((item) => {
return `\nsent lat=${RoundToFixDecimals(
item.lat
)} long=${RoundToFixDecimals(item.lng)}`;
});
setLog(newLog);
}, [circles, clientRef]);
return `\nsent lat=${RoundToFixDecimals(item.lat)} long=${RoundToFixDecimals(item.lng)}`
})
setLog(newLog)
}, [circles, clientRef])
const onAddCircle = useCallback(
async (pos) => {
setCircles([...circles, pos]);
setCircles([...circles, pos])
// insert new location
await supabase
.from("locations")
.insert([
{ latitude: pos.lat, longitude: pos.lng, user_id: clientRef },
]);
.from('locations')
.insert([{ latitude: pos.lat, longitude: pos.lng, user_id: clientRef }])
},
[setCircles, circles, supabase, clientRef]
);
)
function renderCircles() {
return circles.map((item, index) => (
<Circle
key={`${index}`}
center={item}
pathOptions={{ fillColor: "blue" }}
radius={20}
/>
));
<Circle key={`${index}`} center={item} pathOptions={{ fillColor: 'blue' }} radius={20} />
))
}
return (
@@ -96,6 +82,6 @@ function MapInput({ clientRef, center, zoom }) {
}
`}</style>
</div>
);
)
}
export default MapInput;
export default MapInput
@@ -1,49 +1,43 @@
import { useEffect, useReducer } from "react";
import { MapContainer, TileLayer, Polyline } from "react-leaflet";
import { RoundToFixDecimals } from "lib/utils";
import { supabase } from "lib/api";
import TextLog from "components/TextLog";
import { useEffect, useReducer } from 'react'
import { MapContainer, TileLayer, Polyline } from 'react-leaflet'
import { RoundToFixDecimals } from 'lib/utils'
import { supabase } from 'lib/api'
import TextLog from 'components/TextLog'
export default function MapView({ center, zoom }) {
const [state, dispatch] = useReducer(mapReducer, initialState);
const [state, dispatch] = useReducer(mapReducer, initialState)
useEffect(() => {
// Listen to INSERT event on locations table
const subscription = supabase
.from("locations")
.on("INSERT", (payload) => {
console.log("New Position received!", payload);
dispatch({ payload: payload.new });
.from('locations')
.on('INSERT', (payload) => {
console.log('New Position received!', payload)
dispatch({ payload: payload.new })
})
.subscribe();
.subscribe()
return () => {
if (subscription) supabase.removeSubscription(subscription);
};
}, []);
if (subscription) supabase.removeSubscription(subscription)
}
}, [])
function drawPolylines() {
let result = Object.keys(state.userPositions).map((key, idx) => {
const item = state.userPositions[key];
if (!item) return;
const item = state.userPositions[key]
if (!item) return
const { color, data } = item;
const { color, data } = item
const polyline = data.map((item) => {
if (!item) return;
const { lat, lng } = item;
return [lat, lng];
});
if (!item) return
const { lat, lng } = item
return [lat, lng]
})
return (
<Polyline
key={`line-${idx}`}
pathOptions={{ color: color }}
positions={polyline}
/>
);
});
return <Polyline key={`line-${idx}`} pathOptions={{ color: color }} positions={polyline} />
})
return result;
return result
}
return (
@@ -57,35 +51,35 @@ export default function MapView({ center, zoom }) {
</MapContainer>
<TextLog log={state.log} />
</div>
);
)
}
function randomColor() {
var array = ["black", "magenta", "blue", "indigo", "green", "blueviolet"];
return array[Math.floor(Math.random() * array.length)];
var array = ['black', 'magenta', 'blue', 'indigo', 'green', 'blueviolet']
return array[Math.floor(Math.random() * array.length)]
}
const initialState = {
log: "Start listerning...",
log: 'Start listerning...',
userPositions: {},
};
}
const mapReducer = (state, action) => {
const { id, user_id, latitude: lat, longitude: lng } = action.payload;
const position = { id, user_id, lat, lng };
const { id, user_id, latitude: lat, longitude: lng } = action.payload
const position = { id, user_id, lat, lng }
let positions = state.userPositions[user_id];
if (positions) positions.data = [...positions.data, position];
else positions = { color: randomColor(), data: [position] };
const userPositions = { ...state.userPositions, [user_id]: positions };
let positions = state.userPositions[user_id]
if (positions) positions.data = [...positions.data, position]
else positions = { color: randomColor(), data: [position] }
const userPositions = { ...state.userPositions, [user_id]: positions }
let log = state.log;
log += `\nuser_id=${user_id}`;
log += ` lat=${RoundToFixDecimals(lat)}`;
log += ` long=${RoundToFixDecimals(lng)}`;
let log = state.log
log += `\nuser_id=${user_id}`
log += ` lat=${RoundToFixDecimals(lat)}`
log += ` long=${RoundToFixDecimals(lng)}`
return {
log,
userPositions,
};
};
}
}
@@ -1,4 +1,4 @@
import Head from "next/head";
import Head from 'next/head'
export default function PageLayout({ title, children }) {
return (
@@ -10,10 +10,9 @@ export default function PageLayout({ title, children }) {
<footer className="footer">
<a href="https://supabase.io" target="_blank" rel="noopener noreferrer">
Powered by{" "}
<img src="/supabase.svg" alt="Supabase Logo" className="logo" />
Powered by <img src="/supabase.svg" alt="Supabase Logo" className="logo" />
</a>
</footer>
</div>
);
)
}
@@ -1,14 +1,14 @@
import React, { useState } from "react";
import { supabase } from "lib/api";
import React, { useState } from 'react'
import { supabase } from 'lib/api'
/**
* Sign in with username and password
*
* @param {String} role DRIVER/MANAGER. default as DRIVER
*/
export default function SignIn({ role = "DRIVER" }) {
const [formData, updateFormData] = useState({ role });
const [action, setAction] = useState(null);
export default function SignIn({ role = 'DRIVER' }) {
const [formData, updateFormData] = useState({ role })
const [action, setAction] = useState(null)
function onChange(e) {
updateFormData({
@@ -16,37 +16,37 @@ export default function SignIn({ role = "DRIVER" }) {
// Trimming any whitespace
[e.target.name]: e.target.value.trim(),
});
})
}
async function updateUserRole(user) {
if (!user || user.user_metadata?.role) return;
if (!user || user.user_metadata?.role) return
// update user role
await supabase.auth.update({
data: { role },
});
})
}
async function onSubmit(event) {
event.preventDefault();
event.preventDefault()
try {
if (action === "SIGNUP") {
const { email, password, role } = formData;
const { error, user } = await supabase.auth.signUp({ email, password });
if (error) throw error;
if (action === 'SIGNUP') {
const { email, password, role } = formData
const { error, user } = await supabase.auth.signUp({ email, password })
if (error) throw error
// update user role
await supabase.auth.update({
data: { role },
});
} else if (action === "LOGIN") {
const { error } = await supabase.auth.signIn(formData);
if (error) throw error;
})
} else if (action === 'LOGIN') {
const { error } = await supabase.auth.signIn(formData)
if (error) throw error
}
} catch (error) {
alert(error.message);
alert(error.message)
}
}
@@ -60,10 +60,10 @@ export default function SignIn({ role = "DRIVER" }) {
Password
<input name="password" type="password" onChange={onChange} />
</label>
<button type="submit" onClick={() => setAction("SIGNUP")}>
<button type="submit" onClick={() => setAction('SIGNUP')}>
Sign up
</button>
<button type="submit" onClick={() => setAction("LOGIN")}>
<button type="submit" onClick={() => setAction('LOGIN')}>
Login
</button>
<style jsx>{`
@@ -93,5 +93,5 @@ export default function SignIn({ role = "DRIVER" }) {
}
`}</style>
</form>
);
)
}
@@ -4,7 +4,7 @@ export default function TextLog({ log }) {
const textLog = useRef(null)
useEffect(() => {
textLog.current.scrollTop = textLog.current.scrollHeight;
textLog.current.scrollTop = textLog.current.scrollHeight
}, [log])
return (
@@ -18,4 +18,4 @@ export default function TextLog({ log }) {
`}</style>
</>
)
}
}
@@ -2,4 +2,4 @@
"compilerOptions": {
"baseUrl": "."
}
}
}
+3 -9
View File
@@ -1,10 +1,4 @@
import { createClient } from "@supabase/supabase-js";
import {
NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_KEY,
} from "./constants";
import { createClient } from '@supabase/supabase-js'
import { NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_KEY } from './constants'
export const supabase = createClient(
NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_KEY
);
export const supabase = createClient(NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_KEY)
@@ -1,8 +1,8 @@
import { createContext } from "react";
import { createContext } from 'react'
export const NEXT_PUBLIC_SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL;
export const NEXT_PUBLIC_SUPABASE_KEY = process.env.NEXT_PUBLIC_SUPABASE_KEY;
export const NEXT_PUBLIC_SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL
export const NEXT_PUBLIC_SUPABASE_KEY = process.env.NEXT_PUBLIC_SUPABASE_KEY
export const DEFAULT_AVATARS_BUCKET = "avatars";
export const DEFAULT_AVATARS_BUCKET = 'avatars'
export const AppContext = createContext();
export const AppContext = createContext()
@@ -1,12 +1,12 @@
export function RoundToFixDecimals(value, numDecimals = 5) {
function addZero(s, size) {
while (s.length <= (size || 2)) {
s = s + "0";
s = s + '0'
}
return s;
return s
}
const temp = addZero("1", numDecimals);
const tempNum = parseInt(temp, 10);
return Math.round((value + Number.EPSILON) * tempNum) / tempNum;
const temp = addZero('1', numDecimals)
const tempNum = parseInt(temp, 10)
return Math.round((value + Number.EPSILON) * tempNum) / tempNum
}
+12 -12
View File
@@ -1,25 +1,25 @@
import "../styles/globals.css";
import React, { useState, useEffect } from "react";
import { supabase } from "lib/api";
import { AppContext } from "lib/constants";
import '../styles/globals.css'
import React, { useState, useEffect } from 'react'
import { supabase } from 'lib/api'
import { AppContext } from 'lib/constants'
function MyApp({ Component, pageProps }) {
const [session, setSession] = useState(null);
const [session, setSession] = useState(null)
useEffect(() => {
setSession(supabase.auth.session());
setSession(supabase.auth.session())
supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
console.log("session state change", session);
});
}, []);
setSession(session)
console.log('session state change', session)
})
}, [])
return (
<AppContext.Provider value={{ session: session }}>
<Component {...pageProps} />
</AppContext.Provider>
);
)
}
export default MyApp;
export default MyApp
@@ -1,4 +1,4 @@
import Document, { Html, Head, Main, NextScript } from "next/document";
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
render() {
@@ -23,8 +23,8 @@ class MyDocument extends Document {
<NextScript />
</body>
</Html>
);
)
}
}
export default MyDocument;
export default MyDocument
@@ -1,12 +1,12 @@
import { useContext, useEffect, useState } from "react";
import { AppContext } from "lib/constants";
import { supabase } from "lib/api";
import dynamic from "next/dynamic";
import styles from "styles/Map.module.css";
import SignIn from "components/SignIn";
import PageLayout from "components/PageLayout";
import { useContext, useEffect, useState } from 'react'
import { AppContext } from 'lib/constants'
import { supabase } from 'lib/api'
import dynamic from 'next/dynamic'
import styles from 'styles/Map.module.css'
import SignIn from 'components/SignIn'
import PageLayout from 'components/PageLayout'
const MapInput = dynamic(() => import("components/MapInput"), { ssr: false });
const MapInput = dynamic(() => import('components/MapInput'), { ssr: false })
function randomPosition() {
var array = [
@@ -30,25 +30,25 @@ function randomPosition() {
lat: 1.35454,
lng: 103.69746,
},
];
return array[Math.floor(Math.random() * array.length)];
]
return array[Math.floor(Math.random() * array.length)]
}
const center = randomPosition();
const zoomLevel = 14;
const center = randomPosition()
const zoomLevel = 14
export default function Page() {
const { session } = useContext(AppContext);
const [userRole, setRole] = useState(null);
const [username, setUsername] = useState(null);
const { session } = useContext(AppContext)
const [userRole, setRole] = useState(null)
const [username, setUsername] = useState(null)
useEffect(() => {
setRole(session?.user?.user_metadata?.role);
setUsername(session?.user?.user_metadata?.username);
}, [session]);
setRole(session?.user?.user_metadata?.role)
setUsername(session?.user?.user_metadata?.username)
}, [session])
async function onSignOut() {
const { error } = await supabase.auth.signOut();
if (error) console.log("Error logging out:", error.message);
const { error } = await supabase.auth.signOut()
if (error) console.log('Error logging out:', error.message)
}
return (
@@ -63,17 +63,11 @@ export default function Page() {
{!session && <SignIn />}
{session && (
<div className={styles.card}>
{userRole?.toUpperCase() == "DRIVER" && (
<MapInput
clientRef={session.user?.id}
center={center}
zoom={zoomLevel}
/>
{userRole?.toUpperCase() == 'DRIVER' && (
<MapInput clientRef={session.user?.id} center={center} zoom={zoomLevel} />
)}
{userRole?.toUpperCase() == "MANAGER" && (
<p className={styles.error}>
Sorry, You need to sign in as driver
</p>
{userRole?.toUpperCase() == 'MANAGER' && (
<p className={styles.error}>Sorry, You need to sign in as driver</p>
)}
<div className={styles.profile_container}>
Signed in as {username} [{userRole}]<br />
@@ -85,5 +79,5 @@ export default function Page() {
)}
</div>
</PageLayout>
);
)
}
+11 -14
View File
@@ -1,26 +1,23 @@
import { useContext, useEffect } from "react";
import { AppContext } from "lib/constants";
import Link from "next/link";
import styles from "styles/Home.module.css";
import PageLayout from "components/PageLayout";
import { useContext, useEffect } from 'react'
import { AppContext } from 'lib/constants'
import Link from 'next/link'
import styles from 'styles/Home.module.css'
import PageLayout from 'components/PageLayout'
export default function Page() {
const { session } = useContext(AppContext);
const { session } = useContext(AppContext)
useEffect(() => {
console.log("session: ", session);
}, []);
console.log('session: ', session)
}, [])
return (
<PageLayout title="Live Tracker">
<h1 className={styles.title}>Live Tracker</h1>
<p className={styles.description}>
Get started by choosing your role as Driver or Manager.
</p>
<p className={styles.description}>Get started by choosing your role as Driver or Manager.</p>
<p className={styles.sub_description}>
You can only login 1 role at a time. Please use incognito mode to test
multi users.
You can only login 1 role at a time. Please use incognito mode to test multi users.
</p>
<div className={styles.grid}>
@@ -38,5 +35,5 @@ export default function Page() {
</Link>
</div>
</PageLayout>
);
)
}
@@ -1,31 +1,31 @@
import { useContext, useEffect, useState } from "react";
import { AppContext } from "lib/constants";
import { supabase } from "lib/api";
import styles from "styles/Map.module.css";
import dynamic from "next/dynamic";
import SignIn from "components/SignIn";
import PageLayout from "components/PageLayout";
import { useContext, useEffect, useState } from 'react'
import { AppContext } from 'lib/constants'
import { supabase } from 'lib/api'
import styles from 'styles/Map.module.css'
import dynamic from 'next/dynamic'
import SignIn from 'components/SignIn'
import PageLayout from 'components/PageLayout'
const MapView = dynamic(() => import("components/MapView"), { ssr: false });
const MapView = dynamic(() => import('components/MapView'), { ssr: false })
const center = {
lat: 1.3489728457596013,
lng: 103.77043978311998,
};
const zoomLevel = 12;
}
const zoomLevel = 12
export default function Page() {
const { session } = useContext(AppContext);
const [userRole, setRole] = useState(null);
const [username, setUsername] = useState(null);
const { session } = useContext(AppContext)
const [userRole, setRole] = useState(null)
const [username, setUsername] = useState(null)
useEffect(() => {
setRole(session?.user?.user_metadata?.role);
setUsername(session?.user?.user_metadata?.username);
}, [session]);
setRole(session?.user?.user_metadata?.role)
setUsername(session?.user?.user_metadata?.username)
}, [session])
async function onSignOut() {
const { error } = await supabase.auth.signOut();
if (error) console.log("Error logging out:", error.message);
const { error } = await supabase.auth.signOut()
if (error) console.log('Error logging out:', error.message)
}
return (
@@ -33,21 +33,16 @@ export default function Page() {
<h1 className={styles.title}>Manager View</h1>
<p className={styles.description}>
As manager, you can view all drivers' locations as long as the drivers
are ONLINE
As manager, you can view all drivers' locations as long as the drivers are ONLINE
</p>
<div className={styles.grid}>
{!session && <SignIn role="MANAGER" />}
{session && (
<div className={styles.card}>
{userRole?.toUpperCase() == "MANAGER" && (
<MapView center={center} zoom={zoomLevel} />
)}
{userRole?.toUpperCase() == "DRIVER" && (
<p className={styles.error}>
Sorry, you need to sign in as manager
</p>
{userRole?.toUpperCase() == 'MANAGER' && <MapView center={center} zoom={zoomLevel} />}
{userRole?.toUpperCase() == 'DRIVER' && (
<p className={styles.error}>Sorry, you need to sign in as manager</p>
)}
<div className={styles.profile_container}>
Signed in as {username} [{userRole}]<br />
@@ -59,5 +54,5 @@ export default function Page() {
)}
</div>
</PageLayout>
);
)
}
@@ -2,8 +2,8 @@ html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell,
Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
* {
+3
View File
@@ -24,3 +24,6 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Supabase
.supabase
+41 -1
View File
@@ -114,6 +114,20 @@ Visit http://localhost:3000 and start chatting! Open a channel across two browse
## Supabase details
### Role-based access control (RBAC)
Use [plus addressing](https://en.wikipedia.org/wiki/Email_address#Subaddressing) to sign up users with the `admin` & `moderator` roles. Email addresses including `+supaadmin@` will be assigned the `admin` role, and email addresses including `+supamod@` will be assigned the `moderator` role. For example:
```
// admin user
email+supaadmin@example.com
// moderator user
email+supamod@example.com
```
Users with the `moderator` role can delete all messages. Users with the `admin` role can delete all messages and channels (note: it's not recommended to delete the `public` channel).
### Postgres Row level security
This project uses very high-level Authorization using Postgres' Role Level Security.
@@ -124,6 +138,26 @@ We can use these details to provide fine-grained control over what each user can
This is a trimmed-down schema, with the policies:
```sql
-- authorize with role-based access control (RBAC)
CREATE FUNCTION public.authorize(
requested_permission app_permission,
user_id uuid
)
RETURNS BOOLEAN AS $$
DECLARE
bind_permissions int;
BEGIN
SELECT count(*)
FROM public.role_permissions
INNER JOIN public.user_roles on role_permissions.role = user_roles.role
WHERE role_permissions.permission = authorize.requested_permission
AND user_roles.user_id = authorize.user_id
INTO bind_permissions;
RETURN bind_permissions > 0;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- USER PROFILES
CREATE TYPE public.user_status AS ENUM ('ONLINE', 'OFFLINE');
CREATE TABLE public.users (
@@ -143,7 +177,11 @@ CREATE TABLE public.channels (
slug text NOT NULL UNIQUE
);
ALTER TABLE public.channels ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow logged-in full access" on public.channels FOR ALL USING ( auth.role() = 'authenticated' );
CREATE POLICY "Allow logged-in read access" on public.channels FOR SELECT USING ( auth.role() = 'authenticated' );
CREATE POLICY "Allow individual insert access" on public.channels FOR INSERT WITH CHECK ( auth.uid() = created_by );
CREATE POLICY "Allow individual update access" on public.channels FOR UPDATE USING ( auth.uid() = created_by );
CREATE POLICY "Allow individual delete access" on public.channels FOR DELETE USING ( auth.uid() = created_by );
CREATE POLICY "Allow authorized delete access" on public.channels FOR DELETE USING ( authorize('channels.delete', auth.uid()) );
-- MESSAGES
CREATE TABLE public.messages (
@@ -157,6 +195,8 @@ ALTER TABLE public.messages ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Allow logged-in read access" on public.messages FOR SELECT USING ( auth.role() = 'authenticated' );
CREATE POLICY "Allow individual insert access" on public.messages FOR INSERT WITH CHECK ( auth.uid() = user_id );
CREATE POLICY "Allow individual update access" on public.messages FOR UPDATE USING ( auth.uid() = user_id );
CREATE POLICY "Allow individual delete access" on public.messages FOR DELETE USING ( auth.uid() = user_id );
CREATE POLICY "Allow authorized delete access" on public.messages FOR DELETE USING ( authorize('messages.delete', auth.uid()) );
```
## Authors
@@ -1,10 +1,11 @@
import Link from 'next/link'
import { useContext } from 'react'
import UserContext from '~/lib/UserContext'
import { addChannel } from '~/lib/Store'
import { addChannel, deleteChannel } from '~/lib/Store'
import TrashIcon from '~/components/TrashIcon'
export default function Layout(props) {
const { signOut } = useContext(UserContext)
const { signOut, user, userRoles } = useContext(UserContext)
const slugify = (text) => {
return text
@@ -20,7 +21,7 @@ export default function Layout(props) {
const newChannel = async () => {
const slug = prompt('Please enter your name')
if (slug) {
addChannel(slugify(slug))
addChannel(slugify(slug), user.id)
}
}
@@ -41,7 +42,8 @@ export default function Layout(props) {
</button>
</div>
<hr className="m-2" />
<div className="p-2">
<div className="p-2 flex flex-col space-y-2">
<h6 className="text-xs">{user?.email}</h6>
<button
className="bg-blue-900 hover:bg-blue-800 text-white py-2 px-4 rounded w-full transition duration-150"
onClick={() => signOut()}
@@ -57,6 +59,8 @@ export default function Layout(props) {
channel={x}
key={x.id}
isActiveChannel={x.id === props.activeChannelId}
user={user}
userRoles={userRoles}
/>
))}
</ul>
@@ -69,12 +73,17 @@ export default function Layout(props) {
)
}
const SidebarItem = ({ channel, isActiveChannel }) => (
const SidebarItem = ({ channel, isActiveChannel, user, userRoles }) => (
<>
<li>
<li className="flex items-center justify-between">
<Link href="/channels/[id]" as={`/channels/${channel.id}`}>
<a className={isActiveChannel ? 'font-bold' : ''}>{channel.slug}</a>
</Link>
{channel.id !== 1 && (channel.created_by === user?.id || userRoles.includes('admin')) && (
<button onClick={() => deleteChannel(channel.id)}>
<TrashIcon />
</button>
)}
</li>
</>
)
@@ -1,10 +1,27 @@
const Message = ({ message }) => (
<>
<div className="py-1">
<p className="text-blue-700 font-bold">{message.author.username}</p>
<p className="text-white">{message.message}</p>
import { useContext } from 'react'
import UserContext from '~/lib/UserContext'
import { deleteMessage } from '~/lib/Store'
import TrashIcon from '~/components/TrashIcon'
const Message = ({ message }) => {
const { user, userRoles } = useContext(UserContext)
return (
<div className="py-1 flex items-center space-x-2">
<div className="text-gray-100 w-4">
{(user?.id === message.user_id ||
userRoles.some((role) => ['admin', 'moderator'].includes(role))) && (
<button onClick={() => deleteMessage(message.id)}>
<TrashIcon />
</button>
)}
</div>
<div>
<p className="text-blue-700 font-bold">{message.author.username}</p>
<p className="text-white">{message.message}</p>
</div>
</div>
</>
)
)
}
export default Message
@@ -0,0 +1,25 @@
const TrashIcon = (props) => {
const { size = 16 } = props
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="feather feather-trash-2"
>
<polyline points="3 6 5 6 21 6"></polyline>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
<line x1="10" y1="11" x2="10" y2="17"></line>
<line x1="14" y1="11" x2="14" y2="17"></line>
</svg>
)
}
export default TrashIcon
+63 -6
View File
@@ -16,25 +16,29 @@ export const useStore = (props) => {
const [newMessage, handleNewMessage] = useState(null)
const [newChannel, handleNewChannel] = useState(null)
const [newOrUpdatedUser, handleNewOrUpdatedUser] = useState(null)
const [deletedChannel, handleDeletedChannel] = useState(null)
const [deletedMessage, handleDeletedMessage] = useState(null)
// Load initial data and set up listeners
useEffect(() => {
// Get Channels
fetchChannels(setChannels)
// Listen for new messages
// Listen for new and deleted messages
const messageListener = supabase
.from('messages')
.on('INSERT', (payload) => handleNewMessage(payload.new))
.on('DELETE', (payload) => handleDeletedMessage(payload.old))
.subscribe()
// Listen for changes to our users
const userListener = supabase
.from('users')
.on('*', (payload) => handleNewOrUpdatedUser(payload.new))
.subscribe()
// Listen for new channels
// Listen for new and deleted channels
const channelListener = supabase
.from('channels')
.on('INSERT', (payload) => handleNewChannel(payload.new))
.on('DELETE', (payload) => handleDeletedChannel(payload.old))
.subscribe()
// Cleanup on unmount
return () => {
@@ -68,12 +72,24 @@ export const useStore = (props) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [newMessage])
// Deleted message received from postgres
useEffect(() => {
if (deletedMessage) setMessages(messages.filter((message) => message.id !== deletedMessage.id))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [deletedMessage])
// New channel recieved from Postgres
useEffect(() => {
if (newChannel) setChannels(channels.concat(newChannel))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [newChannel])
// Deleted channel received from postgres
useEffect(() => {
if (deletedChannel) setChannels(channels.filter((channel) => channel.id !== deletedChannel.id))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [deletedChannel])
// New or updated user recieved from Postgres
useEffect(() => {
if (newOrUpdatedUser) users.set(newOrUpdatedUser.id, newOrUpdatedUser)
@@ -83,7 +99,7 @@ export const useStore = (props) => {
return {
// We can export computed values here to map the authors to each message
messages: messages.map((x) => ({ ...x, author: users.get(x.user_id) })),
channels: channels.sort((a, b) => a.slug.localeCompare(b.slug)),
channels: channels !== null ? channels.sort((a, b) => a.slug.localeCompare(b.slug)) : [],
users,
}
}
@@ -109,7 +125,7 @@ export const fetchChannels = async (setState) => {
*/
export const fetchUser = async (userId, setState) => {
try {
let { body } = await supabase.from('users').eq('id', userId).select(`*`)
let { body } = await supabase.from('users').select(`*`).eq('id', userId)
let user = body[0]
if (setState) setState(user)
return user
@@ -118,6 +134,20 @@ export const fetchUser = async (userId, setState) => {
}
}
/**
* Fetch all roles for the current user
* @param {function} setState Optionally pass in a hook or callback to set the state
*/
export const fetchUserRoles = async (setState) => {
try {
let { body } = await supabase.from('user_roles').select(`*`)
if (setState) setState(body)
return body
} catch (error) {
console.log('error', error)
}
}
/**
* Fetch all messages and their authors
* @param {number} channelId
@@ -140,10 +170,11 @@ export const fetchMessages = async (channelId, setState) => {
/**
* Insert a new channel into the DB
* @param {string} slug The channel name
* @param {number} user_id The channel creator
*/
export const addChannel = async (slug) => {
export const addChannel = async (slug, user_id) => {
try {
let { body } = await supabase.from('channels').insert([{ slug }])
let { body } = await supabase.from('channels').insert([{ slug, created_by: user_id }])
return body
} catch (error) {
console.log('error', error)
@@ -164,3 +195,29 @@ export const addMessage = async (message, channel_id, user_id) => {
console.log('error', error)
}
}
/**
* Delete a channel from the DB
* @param {number} channel_id
*/
export const deleteChannel = async (channel_id) => {
try {
let { body } = await supabase.from('channels').delete().match({ id: channel_id })
return body
} catch (error) {
console.log('error', error)
}
}
/**
* Delete a message from the DB
* @param {number} message_id
*/
export const deleteMessage = async (message_id) => {
try {
let { body } = await supabase.from('messages').delete().match({ id: message_id })
return body
} catch (error) {
console.log('error', error)
}
}
+23 -30
View File
@@ -2,64 +2,57 @@ import '~/styles/style.scss'
import React, { useState, useEffect } from 'react'
import Router from 'next/router'
import UserContext from 'lib/UserContext'
import { supabase } from 'lib/Store'
import { supabase, fetchUserRoles } from 'lib/Store'
export default function SupabaseSlackClone({Component, pageProps}){
export default function SupabaseSlackClone({ Component, pageProps }) {
const [userLoaded, setUserLoaded] = useState(false)
const [user, setUser] = useState(null)
const [session, setSession] = useState(null);
const [session, setSession] = useState(null)
const [userRoles, setUserRoles] = useState([])
useEffect(() => {
const session = supabase.auth.session();
setSession(session);
setUser(session?.user ?? null);
const session = supabase.auth.session()
setSession(session)
setUser(session?.user ?? null)
setUserLoaded(session ? true : false)
if (user) {
signIn(user.id, user.email)
signIn()
Router.push('/channels/[id]', '/channels/1')
}
const { data: authListener } = supabase.auth.onAuthStateChange(
async (event, session) => {
setSession(session);
const currentUser = session?.user
setUser(currentUser ?? null);
setUserLoaded(!!currentUser)
if (currentUser) {
signIn(currentUser.id, currentUser.email)
Router.push('/channels/[id]', '/channels/1')
}
const { data: authListener } = supabase.auth.onAuthStateChange(async (event, session) => {
setSession(session)
const currentUser = session?.user
setUser(currentUser ?? null)
setUserLoaded(!!currentUser)
if (currentUser) {
signIn(currentUser.id, currentUser.email)
Router.push('/channels/[id]', '/channels/1')
}
)
})
return () => {
authListener.unsubscribe()
}
}, [user])
const signIn = async (id, username) => {
const { body } = await supabase.from('users').select('id, username').eq('id', id)
const result = body[0]
// If the user exists in the users table, update the username.
// If not, create a new row.
result?.id
? await supabase.from('users').update({ id, username }).match({ id }).single()
: await supabase.from('users').insert([{ id, username }]).single()
const signIn = async () => {
await fetchUserRoles((userRoles) => setUserRoles(userRoles.map((userRole) => userRole.role)))
}
const signOut = async () => {
const result = await supabase.auth.signOut()
Router.push('/')
}
}
return (
<UserContext.Provider
value={{
userLoaded,
user,
userRoles,
signIn,
signOut
signOut,
}}
>
<Component {...pageProps} />
@@ -22,6 +22,13 @@ const ChannelsPage = (props) => {
})
}, [messages])
// redirect to public channel when current channel is deleted
useEffect(() => {
if (!channels.some((channel) => channel.id === Number(channelId))) {
router.push('/channels/1')
}
}, [channels, channelId])
// Render the channels and messages
return (
<Layout channels={channels} activeChannelId={channelId}>
+5 -8
View File
@@ -1,25 +1,22 @@
import { useState, useContext } from 'react'
import UserContext from 'lib/UserContext'
import { useState } from 'react'
import { supabase } from 'lib/Store'
const Home = () => {
const { signIn } = useContext(UserContext)
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const handleLogin = async (type, username, password) => {
try {
const { error, user } =
const { error, user } =
type === 'LOGIN'
? await supabase.auth.signIn({email: username, password})
: await supabase.auth.signUp({email: username, password})
? await supabase.auth.signIn({ email: username, password })
: await supabase.auth.signUp({ email: username, password })
// If the user doesn't exist here and an error hasn't been raised yet,
// that must mean that a confirmation email has been sent.
// NOTE: Confirming your email address is required by default.
if (error) {
alert('Error with auth: ' + error.message)
}
else if (!user) alert('Signup successful, confirmation mail should be sent soon!')
} else if (!user) alert('Signup successful, confirmation mail should be sent soon!')
} catch (error) {
console.log('error', error)
alert(error.error_description || error)
+4 -2
View File
@@ -26,7 +26,9 @@
/* Your own custom utilities */
html, body, #__next {
html,
body,
#__next {
height: 100vh;
min-height: 100vh;
}
@@ -35,4 +37,4 @@ h1 {
font-size: 4rem;
font-weight: bold;
display: block;
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ This demonstrates how to use:
- User signups using Supabase [Auth](https://supabase.io/auth).
- User avatar images using Supabase [Storage](https://supabase.io/storage).
- Public profiles restricted with [Policies](https://supabase.io/docs/guides/auth#policies).
- Frontend using [Next.js]([nextjs.org/](https://nextjs.org/)).
- Frontend using [Next.js](<[nextjs.org/](https://nextjs.org/)>).
## Technologies used
@@ -1,4 +1,4 @@
export default function Footer({ }) {
export default function Footer({}) {
return (
<div className="footer">
<div className=""></div>
@@ -8,7 +8,9 @@ export default function ProfileCard({ profile }: { profile: Profile }) {
<Avatar url={profile.avatar_url} size={100} />
<div className="userInfo">
<p className="username">{profile.username}</p>
<a className="website" href={profile.website} target="_blank">{profile.website}</a>
<a className="website" href={profile.website} target="_blank">
{profile.website}
</a>
<p>
<small>
Last updated{' '}
@@ -27,7 +27,7 @@ const handleDatabaseEvent = (state: State, action: Action) => {
}
} else if (action.type === 'set') {
return {
profiles: action.payload
profiles: action.payload,
}
}
return { profiles: [] }
@@ -41,7 +41,7 @@ export default function Home() {
}
return (
<div className="container" style={{padding: '50px 0 100px 0'}}>
<div className="container" style={{ padding: '50px 0 100px 0' }}>
{!session ? (
<Auth />
) : (
@@ -50,7 +50,7 @@ export default function Home() {
<h3>Account</h3>
<Account key={session.user.id} session={session} />
</div>
<div className="col-6" >
<div className="col-6">
<h3>Public Profiles</h3>
<ProfileList profiles={profiles} />
</div>
@@ -43,7 +43,7 @@ body,
position: relative;
width: 100%;
}
.row [class^="col"] {
.row [class^='col'] {
float: left;
margin: 0.5rem 2%;
min-height: 0.125rem;
@@ -99,21 +99,23 @@ body,
width: 96%;
}
.row::after {
content: "";
display: table;
clear: both;
content: '';
display: table;
clear: both;
}
.hidden-sm {
display: none;
}
@media only screen and (min-width: 33.75em) { /* 540px */
@media only screen and (min-width: 33.75em) {
/* 540px */
.container {
width: 80%;
}
}
@media only screen and (min-width: 45em) { /* 720px */
@media only screen and (min-width: 45em) {
/* 720px */
.col-1 {
width: 4.33%;
}
@@ -155,14 +157,14 @@ body,
}
}
@media only screen and (min-width: 60em) { /* 960px */
@media only screen and (min-width: 60em) {
/* 960px */
.container {
width: 75%;
max-width: 60rem;
}
}
/* Forms */
label {
@@ -286,7 +288,7 @@ button.primary,
left: 0;
right: 0;
display: flex;
flex-flow: row;
flex-flow: row;
border-top: var(--custom-border);
background-color: var(--custom-bg-color);
}
@@ -300,23 +302,23 @@ button.primary,
height: 20px;
margin-left: 10px;
}
.footer > div:first-child {
.footer > div:first-child {
display: none;
}
.footer > div:nth-child(2) {
.footer > div:nth-child(2) {
justify-content: left;
}
@media only screen and (min-width: 60em) { /* 960px */
.footer > div:first-child {
@media only screen and (min-width: 60em) {
/* 960px */
.footer > div:first-child {
display: flex;
}
.footer > div:nth-child(2) {
.footer > div:nth-child(2) {
justify-content: center;
}
}
@keyframes spin {
from {
transform: rotate(0deg);
@@ -345,7 +347,6 @@ button.primary,
/* Auth */
.auth-widget {
display: flex;
flex-direction: column;
@@ -7,12 +7,12 @@
"start": "next start"
},
"dependencies": {
"@supabase/supabase-js": "^1.2.1",
"@supabase/ui": "^0.6.1",
"next": "latest",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"swr": "0.2.3"
"@supabase/supabase-js": "^1.11.9",
"@supabase/ui": "^0.24.2",
"next": "10.2.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"swr": "^0.5.5"
},
"license": "MIT"
}
@@ -1,13 +1,13 @@
import { supabase } from '../../utils/initSupabase';
import { supabase } from '../../utils/initSupabase'
// Example of how to verify and get user data server-side.
const getUser = async (req, res) => {
const token = req.headers.token;
const token = req.headers.token
const { data: user, error } = await supabase.auth.api.getUser(token);
const { data: user, error } = await supabase.auth.api.getUser(token)
if (error) return res.status(401).json({ error: error.message });
return res.status(200).json(user);
};
if (error) return res.status(401).json({ error: error.message })
return res.status(200).json(user)
}
export default getUser;
export default getUser
File diff suppressed because it is too large. Load diff
+1 -2
View File
@@ -1,7 +1,6 @@
# Supabase Nuxt.js User Management
This is a WIP!
This is a WIP!
## Build Setup
@@ -151,7 +151,7 @@ button.primary,
left: 0;
right: 0;
display: flex;
flex-flow: row;
flex-flow: row;
border-top: var(--custom-border);
}
.footer div {
@@ -57,17 +57,17 @@ export default {
try {
this.state.loading = true
let { data, error } = await supabase
const { data, error } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', this.stete.propSession.user.id)
.eq('id', this.state.propSession.user.id)
.single()
if (error || !data) {
throw error || new Error('Profile not found')
}
setProfile(data)
this.setProfile(data)
} catch (error) {
console.log('error', error.message)
} finally {
@@ -86,7 +86,7 @@ export default {
updated_at: new Date(),
}
let { error } = await supabase.from('profiles').upsert(updates, {
const { error } = await supabase.from('profiles').upsert(updates, {
returning: 'minimal', // Don't return the value after inserting
})
@@ -13,7 +13,7 @@ production unless you're ok with all your tasks being publicly available
## Required env vars
Make a `.env` in this folder with the following,
Make a `.env` in this folder with the following,
you can get these values from your project dashboard at [app.supabase.io](app.supabase.io):
```bash
@@ -1,8 +1,7 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: rgb(250, 217, 246);
@@ -30,7 +29,7 @@ h1 {
display: block;
}
@media screen and (max-width : 600px) {
@media screen and (max-width: 600px) {
.build {
width: 300px;
}
+4 -1
View File
@@ -43,7 +43,10 @@ const Home = (props) => {
<a href="https://dev.to/awalias/howto-build-collaborative-realtime-task-lists-in-react-4k52">
Tutorial
</a>{' '}
| <a href="https://github.com/supabase/supabase/tree/master/examples/react-todo-list">Github</a>
|{' '}
<a href="https://github.com/supabase/supabase/tree/master/examples/react-todo-list">
Github
</a>
</h3>
<a href="https://dev.to/awalias/howto-build-collaborative-realtime-task-lists-in-react-4k52">
<img
+36 -40
View File
@@ -15,28 +15,26 @@ const isLocalhost = Boolean(
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
)
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
return
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
checkValidServiceWorker(swUrl, config)
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
@@ -44,24 +42,24 @@ export function register(config) {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
)
})
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
registerValidSW(swUrl, config)
}
});
})
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
.then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
const installingWorker = registration.installing
if (installingWorker == null) {
return;
return
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
@@ -72,30 +70,30 @@ function registerValidSW(swUrl, config) {
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
)
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
config.onUpdate(registration)
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
console.log('Content is cached for offline use.')
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
config.onSuccess(registration)
}
}
}
};
};
}
}
})
.catch((error) => {
console.error('Error during service worker registration:', error)
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
@@ -103,39 +101,37 @@ function checkValidServiceWorker(swUrl, config) {
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.then(response => {
.then((response) => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
const contentType = response.headers.get('content-type')
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => {
window.location.reload();
});
});
window.location.reload()
})
})
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
registerValidSW(swUrl, config)
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
console.log('No internet connection found. App is running in offline mode.')
})
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then(registration => {
registration.unregister();
.then((registration) => {
registration.unregister()
})
.catch((error) => {
console.error(error.message)
})
.catch(error => {
console.error(error.message);
});
}
}
+8 -10
View File
@@ -1,12 +1,11 @@
# Todo example using Supabase
- Frontend:
- [create-react-app](https://reactjs.org/docs/create-a-new-react-app.html). React Framework for building single-page applications.
- [Tailwind](https://tailwindcss.com/) for styling and layout.
- [Supabase.js](https://supabase.io/docs/library/getting-started) for user management and realtime data syncing.
- Backend:
- [app.supabase.io/](https://app.supabase.io/): hosted Postgres database with restful API for usage with Supabase.js.
- Frontend:
- [create-react-app](https://reactjs.org/docs/create-a-new-react-app.html). React Framework for building single-page applications.
- [Tailwind](https://tailwindcss.com/) for styling and layout.
- [Supabase.js](https://supabase.io/docs/library/getting-started) for user management and realtime data syncing.
- Backend:
- [app.supabase.io/](https://app.supabase.io/): hosted Postgres database with restful API for usage with Supabase.js.
## Demo
@@ -24,7 +23,7 @@ Sign up to Supabase - [https://app.supabase.io](https://app.supabase.io) and cre
### 2. Run "Todo List" Quickstart
Once your database has started, run the "Todo List" quickstart. Inside of your project, enter the `SQL editor` tab and scroll down until you see `TODO LIST: Build a basic todo list with Row Level Security`
Once your database has started, run the "Todo List" quickstart. Inside of your project, enter the `SQL editor` tab and scroll down until you see `TODO LIST: Build a basic todo list with Row Level Security`
### 3. Get the URL and Key
@@ -36,7 +35,6 @@ The `anon` key is your client-side API key. It allows "anonymous access" to your
**_NOTE_**: The `service_role` key has full access to your data, bypassing any security policies. These keys have to be kept secret and are meant to be used in server environments and never on a client or browser.
You will be asked for a `REACT_APP_SUPABASE_URL` and `REACT_APP_SUPABASE_KEY`. Use the API URL and `anon` key from [step 3](#3.-get-the-url-and-key).
## Supabase details
@@ -76,6 +74,6 @@ create policy "Individuals can delete their own todos." on todos for
## Authors
- [Supabase](https://supabase.io)
- [Supabase](https://supabase.io)
Supabase is open source, we'd love for you to follow along and get involved at https://github.com/supabase/supabase
+7 -10
View File
@@ -1,10 +1,7 @@
module.exports = {
style: {
postcss: {
plugins: [
require('tailwindcss'),
require('autoprefixer'),
],
},
},
}
module.exports = {
style: {
postcss: {
plugins: [require("tailwindcss"), require("autoprefixer")],
},
},
};
+42 -42
View File
@@ -1,44 +1,44 @@
{
"name": "cra-todo-list-auth",
"version": "0.1.0",
"private": true,
"dependencies": {
"@craco/craco": "^6.0.0",
"@supabase/supabase-js": "^1.2.1",
"@tailwindcss/postcss7-compat": "^2.0.2",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"autoprefixer": "9",
"postcss": "7",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-scripts": "4.0.1",
"tailwindcss": "npm:@tailwindcss/postcss7-compat",
"web-vitals": "^0.2.4"
},
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
"name": "cra-todo-list-auth",
"version": "0.1.0",
"private": true,
"dependencies": {
"@craco/craco": "^6.0.0",
"@supabase/supabase-js": "^1.2.1",
"@tailwindcss/postcss7-compat": "^2.0.2",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"autoprefixer": "9",
"postcss": "8",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-scripts": "4.0.1",
"tailwindcss": "npm:@tailwindcss/postcss7-compat",
"web-vitals": "^0.2.4"
},
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
+13 -13
View File
@@ -1,15 +1,15 @@
{
"short_name": "CRA Supabase TodoList Example",
"name": "Create React App Supabase TodoList Example",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
"short_name": "CRA Supabase TodoList Example",
"name": "Create React App Supabase TodoList Example",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
+8 -8
View File
@@ -3,11 +3,11 @@
@tailwind utilities;
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
"Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
+9 -9
View File
@@ -1,14 +1,14 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
// If you want to start measuring performance in your app, pass a function
+7 -5
View File
@@ -1,5 +1,7 @@
import { createClient } from '@supabase/supabase-js';
import { REACT_APP_SUPABASE_URL, REACT_APP_SUPABASE_KEY } from './constants';
export const supabase = createClient(REACT_APP_SUPABASE_URL, REACT_APP_SUPABASE_KEY);
import { createClient } from "@supabase/supabase-js";
import { REACT_APP_SUPABASE_URL, REACT_APP_SUPABASE_KEY } from "./constants";
export const supabase = createClient(
REACT_APP_SUPABASE_URL,
REACT_APP_SUPABASE_KEY
);
+2 -2
View File
@@ -1,2 +1,2 @@
export const REACT_APP_SUPABASE_URL=process.env.REACT_APP_SUPABASE_URL
export const REACT_APP_SUPABASE_KEY=process.env.REACT_APP_SUPABASE_KEY
export const REACT_APP_SUPABASE_URL = process.env.REACT_APP_SUPABASE_URL;
export const REACT_APP_SUPABASE_KEY = process.env.REACT_APP_SUPABASE_KEY;
+12 -10
View File
@@ -1,13 +1,15 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
const reportWebVitals = (onPerfEntry) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import("web-vitals").then(
({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
}
);
}
};
export default reportWebVitals;
+1 -1
View File
@@ -2,4 +2,4 @@
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
import "@testing-library/jest-dom";
+10 -10
View File
@@ -1,11 +1,11 @@
module.exports = {
purge: ['./src/**/*.{js,jsx,ts,tsx}', './public/index.html'],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
}
purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
};
+23 -45
View File
@@ -3391,10 +3391,10 @@ color@^3.0.0, color@^3.1.3:
color-convert "^1.9.1"
color-string "^1.5.4"
colorette@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==
colorette@^1.2.1, colorette@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
combined-stream@^1.0.6, combined-stream@~1.0.6:
version "1.0.8"
@@ -7424,15 +7424,10 @@ nan@^2.12.1:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
nanoid@^3.1.18:
version "3.1.18"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.18.tgz#0680db22ab01c372e89209f5d18283d98de3e96d"
integrity sha512-rndlDjbbHbcV3xi+R2fpJ+PbGMdfBxz5v1fATIQFq0DP64FsicQdwnKLy47K4kZHdRpmQXtz24eGsxQqamzYTA==
nanoid@^3.1.20:
version "3.1.20"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788"
integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==
nanoid@^3.1.22:
version "3.1.23"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81"
integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==
nanomatch@^1.2.9:
version "1.2.13"
@@ -8835,15 +8830,6 @@ postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1:
indexes-of "^1.0.1"
uniq "^1.0.1"
postcss@7, postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.18, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6:
version "7.0.35"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24"
integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==
dependencies:
chalk "^2.4.2"
source-map "^0.6.1"
supports-color "^6.1.0"
postcss@7.0.21:
version "7.0.21"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.21.tgz#06bb07824c19c2021c5d056d5b10c35b989f7e17"
@@ -8853,6 +8839,15 @@ postcss@7.0.21:
source-map "^0.6.1"
supports-color "^6.1.0"
postcss@8, postcss@^8.1.0, postcss@^8.2.1:
version "8.2.10"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.10.tgz#ca7a042aa8aff494b334d0ff3e9e77079f6f702b"
integrity sha512-b/h7CPV7QEdrqIxtAf2j31U5ef05uBDuvoXv6L51Q4rcS1jdlXAVKJv+atCFdUXYl9dyTHGyoMzIepwowRJjFw==
dependencies:
colorette "^1.2.2"
nanoid "^3.1.22"
source-map "^0.6.1"
postcss@^6.0.9:
version "6.0.23"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324"
@@ -8862,24 +8857,14 @@ postcss@^6.0.9:
source-map "^0.6.1"
supports-color "^5.4.0"
postcss@^8.1.0:
version "8.1.9"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.1.9.tgz#20ff4b598a6f5015c5f7fe524b8ed5313d7ecade"
integrity sha512-oWuBpEl1meaMKkQXn0ic78TUrgsMvrAZLE/6ZY0H3LTteq2O3L8PGWwMbPLctpksTJIHjQeossMUMNQW7qRIHQ==
postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.18, postcss@^7.0.2, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6:
version "7.0.35"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.35.tgz#d2be00b998f7f211d8a276974079f2e92b970e24"
integrity sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==
dependencies:
colorette "^1.2.1"
nanoid "^3.1.18"
source-map "^0.6.1"
vfile-location "^3.2.0"
postcss@^8.2.1:
version "8.2.4"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.4.tgz#20a98a39cf303d15129c2865a9ec37eda0031d04"
integrity sha512-kRFftRoExRVXZlwUuay9iC824qmXPcQQVzAjbCCgjpXnkdMCJYBu2gTwAaFBzv8ewND6O8xFb3aELmEkh9zTzg==
dependencies:
colorette "^1.2.1"
nanoid "^3.1.20"
chalk "^2.4.2"
source-map "^0.6.1"
supports-color "^6.1.0"
prelude-ls@^1.2.1:
version "1.2.1"
@@ -11122,11 +11107,6 @@ verror@1.10.0:
core-util-is "1.0.2"
extsprintf "^1.2.0"
vfile-location@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.2.0.tgz#d8e41fbcbd406063669ebf6c33d56ae8721d0f3c"
integrity sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA==
vm-browserify@^1.0.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
@@ -11165,10 +11145,8 @@ watchpack@^1.7.4:
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453"
integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==
dependencies:
chokidar "^3.4.1"
graceful-fs "^4.1.2"
neo-async "^2.5.0"
watchpack-chokidar2 "^2.0.1"
optionalDependencies:
chokidar "^3.4.1"
watchpack-chokidar2 "^2.0.1"
+4 -1
View File
@@ -9,7 +9,10 @@ const {
const supabase = Supabase.createClient(SNOWPACK_PUBLIC_SUPABASE_URL, SNOWPACK_PUBLIC_SUPABASE_KEY)
async function login() {
try {
const res = await supabase.auth.signIn({email: SNOWPACK_PUBLIC_USER, password:SNOWPACK_PUBLIC_PASSWORD})
const res = await supabase.auth.signIn({
email: SNOWPACK_PUBLIC_USER,
password: SNOWPACK_PUBLIC_PASSWORD,
})
console.log({ res })
return res
} catch (error) {
+4 -1
View File
@@ -9,6 +9,9 @@ const {
// const [SNOWPACK_PUBLIC_SUPABASE_URL,SNOWPACK_PUBLIC_SUPABASE_KEY] =
const supabase = Supabase.createClient(SNOWPACK_PUBLIC_SUPABASE_URL, SNOWPACK_PUBLIC_SUPABASE_KEY)
;(async () => {
let { data, user, error } = await supabase.auth.signUp({email: SNOWPACK_PUBLIC_USER, password:SNOWPACK_PUBLIC_PASSWORD})
let { data, user, error } = await supabase.auth.signUp({
email: SNOWPACK_PUBLIC_USER,
password: SNOWPACK_PUBLIC_PASSWORD,
})
console.log({ data, user, error })
})()
+295 -191
View File
@@ -1053,6 +1053,7 @@
"version": "7.11.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz",
"integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.13.4"
}
@@ -2806,32 +2807,48 @@
"rollup-plugin-svelte": "^5.2.3"
}
},
"@supabase/postgrest-js": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-0.17.0.tgz",
"integrity": "sha512-7Dt7ALli+bOQK51JMolraI08sJg2UweNuGFghr8MrX4nc3WoDwL5PP6fNZwy+j/7f7nvvM6s1OUtqNTvqiW+5Q==",
"@supabase/gotrue-js": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/@supabase/gotrue-js/-/gotrue-js-1.15.1.tgz",
"integrity": "sha512-d3UP+UCaakP2wMxe9KzM4Ig0w2P+EifWPVzXXWgnly0jeX1XppIm7OzQveqiTucuIKb/5ZUYjtXBYrry4dlBFw==",
"requires": {
"superagent": "^5.2.1"
"cross-fetch": "^3.0.6"
}
},
"@supabase/postgrest-js": {
"version": "0.28.2",
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-0.28.2.tgz",
"integrity": "sha512-Qv2xCZBR6uDyDXShF33xyMaeVhvKSesWeKGAT1JOUqE+To6G61vXxTrUtitPihSEZYdWgc7k1gQzbXhMCGHb4w==",
"requires": {
"cross-fetch": "^3.0.6"
}
},
"@supabase/realtime-js": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-0.9.0.tgz",
"integrity": "sha512-aYkVsD2hNgD7QPPBV+LALJTexNf8pVhIh9qJC13NnOLYZpudtHbof+rg0dgq5+GIhYa8HzqUMdf+OwwlQVJgsw==",
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-1.0.9.tgz",
"integrity": "sha512-hGClyW7hHXW0PC6reJgaKFL0c3ubC+AVt7U/MxD0VJNjVXIw4PLj7DxgMpCIpNXksHJsLOBL8ht+BMhPb6rE8Q==",
"requires": {
"@babel/runtime": "^7.9.2",
"query-string": "^6.12.1",
"websocket": "^1.0.31"
"@types/websocket": "^1.0.1",
"websocket": "^1.0.34"
}
},
"@supabase/storage-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-1.0.2.tgz",
"integrity": "sha512-cnmdzLcUc06JqqxxqK6grALnUhWbR4d5AyzsG5zlXgMW78JmdghofNz5ToDwMq949RZgxXmknBWZWhS+yhns3Q==",
"requires": {
"cross-fetch": "^3.1.0"
}
},
"@supabase/supabase-js": {
"version": "0.36.5",
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-0.36.5.tgz",
"integrity": "sha512-iiY5lDIrbhQjT8grRgXbUfIU5omD8CfNI4Iio3z/DHjsdrzYNf9o0/kRAzVVITPOuqlN9g0QhPt7bzRQKxLbwA==",
"version": "1.11.12",
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-1.11.12.tgz",
"integrity": "sha512-qrXB5NfimtJSM5G24d2PwnmiqlgccKtqSSotugqvyYmf10To0Ak/33zQMdesC7+dL6v+0o1z/fe+88AGXKosxg==",
"requires": {
"@supabase/postgrest-js": "^0.17.0",
"@supabase/realtime-js": "^0.9.0",
"superagent": "^5.2.1"
"@supabase/gotrue-js": "^1.15.1",
"@supabase/postgrest-js": "^0.28.2",
"@supabase/realtime-js": "^1.0.9",
"@supabase/storage-js": "^1.0.2"
}
},
"@szmarczak/http-timer": {
@@ -3115,8 +3132,7 @@
"@types/node": {
"version": "14.11.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.2.tgz",
"integrity": "sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA==",
"dev": true
"integrity": "sha512-jiE3QIxJ8JLNcb1Ps6rDbysDhN4xa8DJJvuC9prr6w+1tIh+QAbYyNF3tyiZNLDBIuBCf4KEcV2UvQm/V60xfA=="
},
"@types/normalize-package-data": {
"version": "2.4.0",
@@ -3196,6 +3212,14 @@
"@types/jest": "*"
}
},
"@types/websocket": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.2.tgz",
"integrity": "sha512-B5m9aq7cbbD/5/jThEr33nUY8WEfVi6A2YKCTOvw5Ldy7mtsOkqRvGjnzy6g7iMMDsgu7xREuCzqATLDLQVKcQ==",
"requires": {
"@types/node": "*"
}
},
"@types/yargs": {
"version": "15.0.7",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.7.tgz",
@@ -3487,7 +3511,8 @@
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
"dev": true
},
"at-least-node": {
"version": "1.0.0",
@@ -3513,6 +3538,38 @@
"num2fraction": "^1.2.2",
"postcss": "^7.0.32",
"postcss-value-parser": "^4.1.0"
},
"dependencies": {
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"postcss": {
"version": "7.0.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
"integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
"requires": {
"chalk": "^2.4.2",
"source-map": "^0.6.1",
"supports-color": "^6.1.0"
}
}
}
},
"aws-sign2": {
@@ -3776,11 +3833,11 @@
"dev": true
},
"bufferutil": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.1.tgz",
"integrity": "sha512-xowrxvpxojqkagPcWRQVXZl0YXhRhAtBEIq3VoER1NH5Mw1n1o0ojdspp+GS2J//2gCVyrzQDApQ4unGF+QOoA==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.3.tgz",
"integrity": "sha512-yEYTwGndELGvfXsImMBLop58eaGW+YdONi1fNjTINSY98tmMmFijBG6WXgdkfuLNt4imzQNtIE+eBp1PVpMCSw==",
"requires": {
"node-gyp-build": "~3.7.0"
"node-gyp-build": "^4.2.0"
}
},
"builtin-modules": {
@@ -4177,6 +4234,7 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"requires": {
"delayed-stream": "~1.0.0"
}
@@ -4207,7 +4265,8 @@
"component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==",
"dev": true
},
"compressible": {
"version": "2.0.18",
@@ -4270,11 +4329,6 @@
"safe-buffer": "~5.1.1"
}
},
"cookiejar": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz",
"integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA=="
},
"copy-descriptor": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
@@ -4324,6 +4378,14 @@
"yaml": "^1.10.0"
}
},
"cross-fetch": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz",
"integrity": "sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==",
"requires": {
"node-fetch": "2.6.1"
}
},
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@@ -4737,6 +4799,7 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
@@ -4750,7 +4813,8 @@
"decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
"dev": true
},
"decompress-response": {
"version": "6.0.0",
@@ -4851,7 +4915,8 @@
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"dev": true
},
"dependency-graph": {
"version": "0.9.0",
@@ -5344,9 +5409,9 @@
},
"dependencies": {
"type": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz",
"integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA=="
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/type/-/type-2.5.0.tgz",
"integrity": "sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw=="
}
}
},
@@ -5518,11 +5583,6 @@
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
"dev": true
},
"fast-safe-stringify": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz",
"integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA=="
},
"fastparse": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
@@ -5639,11 +5699,6 @@
"mime-types": "^2.1.12"
}
},
"formidable": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz",
"integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q=="
},
"fragment-cache": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
@@ -9577,11 +9632,6 @@
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true
},
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
},
"micromatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
@@ -9592,20 +9642,17 @@
"picomatch": "^2.0.5"
}
},
"mime": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz",
"integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA=="
},
"mime-db": {
"version": "1.44.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
"integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg=="
"integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==",
"dev": true
},
"mime-types": {
"version": "2.1.27",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
"integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
"dev": true,
"requires": {
"mime-db": "1.44.0"
}
@@ -9723,7 +9770,14 @@
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"nanoid": {
"version": "3.1.23",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz",
"integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==",
"dev": true
},
"nanomatch": {
"version": "1.2.13",
@@ -9775,10 +9829,15 @@
"lodash.toarray": "^4.4.0"
}
},
"node-fetch": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
},
"node-gyp-build": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.7.0.tgz",
"integrity": "sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w=="
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz",
"integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg=="
},
"node-int64": {
"version": "0.4.0",
@@ -10217,34 +10276,21 @@
"dev": true
},
"postcss": {
"version": "7.0.34",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.34.tgz",
"integrity": "sha512-H/7V2VeNScX9KE83GDrDZNiGT1m2H+UTnlinIzhjlLX9hfMUn1mHNnGeX81a1c8JSBdBvqk7c2ZOG6ZPn5itGw==",
"version": "8.2.10",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.10.tgz",
"integrity": "sha512-b/h7CPV7QEdrqIxtAf2j31U5ef05uBDuvoXv6L51Q4rcS1jdlXAVKJv+atCFdUXYl9dyTHGyoMzIepwowRJjFw==",
"dev": true,
"requires": {
"chalk": "^2.4.2",
"source-map": "^0.6.1",
"supports-color": "^6.1.0"
"colorette": "^1.2.2",
"nanoid": "^3.1.22",
"source-map": "^0.6.1"
},
"dependencies": {
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"requires": {
"has-flag": "^3.0.0"
}
}
}
"colorette": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
"integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
"dev": true
}
}
},
@@ -10290,6 +10336,41 @@
"universalify": "^1.0.0"
}
},
"postcss": {
"version": "7.0.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
"integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
"dev": true,
"requires": {
"chalk": "^2.4.2",
"source-map": "^0.6.1",
"supports-color": "^6.1.0"
},
"dependencies": {
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
}
}
},
"universalify": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz",
@@ -10351,6 +10432,38 @@
"requires": {
"camelcase-css": "^2.0.1",
"postcss": "^7.0.18"
},
"dependencies": {
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"postcss": {
"version": "7.0.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
"integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
"requires": {
"chalk": "^2.4.2",
"source-map": "^0.6.1",
"supports-color": "^6.1.0"
}
}
}
},
"postcss-load-config": {
@@ -10581,6 +10694,38 @@
"requires": {
"postcss": "^7.0.32",
"postcss-selector-parser": "^6.0.2"
},
"dependencies": {
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"postcss": {
"version": "7.0.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
"integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
"requires": {
"chalk": "^2.4.2",
"source-map": "^0.6.1",
"supports-color": "^6.1.0"
}
}
}
},
"postcss-reporter": {
@@ -10615,6 +10760,28 @@
"chalk": "^2.0.1"
}
},
"postcss": {
"version": "7.0.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
"integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
"dev": true,
"requires": {
"chalk": "^2.4.2",
"source-map": "^0.6.1",
"supports-color": "^6.1.0"
},
"dependencies": {
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
"integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
"dev": true,
"requires": {
"has-flag": "^3.0.0"
}
}
}
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@@ -10850,16 +11017,6 @@
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
"dev": true
},
"query-string": {
"version": "6.13.5",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-6.13.5.tgz",
"integrity": "sha512-svk3xg9qHR39P3JlHuD7g3nRnyay5mHbrPctEBDUxUkHRifPHXJDhBUycdCC0NBjXoDf44Gb+IsOZL1Uwn8M/Q==",
"requires": {
"decode-uri-component": "^0.2.0",
"split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0"
}
},
"querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
@@ -10924,16 +11081,6 @@
"type-fest": "^0.8.1"
}
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
},
"readdirp": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
@@ -10993,7 +11140,8 @@
"regenerator-runtime": {
"version": "0.13.7",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==",
"dev": true
},
"regenerator-transform": {
"version": "0.14.5",
@@ -11258,19 +11406,6 @@
"fsevents": "~2.1.2"
}
},
"rollup-plugin-commonjs": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz",
"integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==",
"dev": true,
"requires": {
"estree-walker": "^0.6.1",
"is-reference": "^1.1.2",
"magic-string": "^0.25.2",
"resolve": "^1.11.0",
"rollup-pluginutils": "^2.8.1"
}
},
"rollup-plugin-inject": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz",
@@ -11994,11 +12129,6 @@
"integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==",
"dev": true
},
"split-on-first": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="
},
"split-string": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
@@ -12073,11 +12203,6 @@
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=",
"dev": true
},
"strict-uri-encode": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY="
},
"string-argv": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz",
@@ -12184,21 +12309,6 @@
}
}
},
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"requires": {
"safe-buffer": "~5.2.0"
},
"dependencies": {
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
}
}
},
"stringify-object": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz",
@@ -12252,46 +12362,6 @@
"min-indent": "^1.0.0"
}
},
"superagent": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-5.3.1.tgz",
"integrity": "sha512-wjJ/MoTid2/RuGCOFtlacyGNxN9QLMgcpYLDQlWFIhhdJ93kNscFonGvrpAHSCVjRVj++DGCglocF7Aej1KHvQ==",
"requires": {
"component-emitter": "^1.3.0",
"cookiejar": "^2.1.2",
"debug": "^4.1.1",
"fast-safe-stringify": "^2.0.7",
"form-data": "^3.0.0",
"formidable": "^1.2.2",
"methods": "^1.1.2",
"mime": "^2.4.6",
"qs": "^6.9.4",
"readable-stream": "^3.6.0",
"semver": "^7.3.2"
},
"dependencies": {
"form-data": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz",
"integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==",
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
},
"qs": {
"version": "6.9.4",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz",
"integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ=="
},
"semver": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
}
}
},
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
@@ -12408,6 +12478,40 @@
"pretty-hrtime": "^1.0.3",
"reduce-css-calc": "^2.1.6",
"resolve": "^1.14.2"
},
"dependencies": {
"postcss": {
"version": "7.0.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz",
"integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==",
"requires": {
"chalk": "^2.4.2",
"source-map": "^0.6.1",
"supports-color": "^6.1.0"
},
"dependencies": {
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"dependencies": {
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"requires": {
"has-flag": "^3.0.0"
}
}
}
}
}
}
}
},
"tar": {
@@ -12766,11 +12870,11 @@
"dev": true
},
"utf-8-validate": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.2.tgz",
"integrity": "sha512-SwV++i2gTD5qh2XqaPzBnNX88N6HdyhQrNNRykvcS0QKvItV9u3vPEJr+X5Hhfb1JC0r0e1alL0iB09rY8+nmw==",
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.5.tgz",
"integrity": "sha512-+pnxRYsS/axEpkrrEpzYfNZGXp0IjC/9RIxwM5gntY4Koi8SHmUGSfxfWqxZdRxrtaoVstuOzUp/rbs3JSPELQ==",
"requires": {
"node-gyp-build": "~3.7.0"
"node-gyp-build": "^4.2.0"
}
},
"util-deprecate": {
@@ -12869,9 +12973,9 @@
"dev": true
},
"websocket": {
"version": "1.0.32",
"resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.32.tgz",
"integrity": "sha512-i4yhcllSP4wrpoPMU2N0TQ/q0O94LRG/eUQjEAamRltjQ1oT1PFFKOG4i877OlJgCG8rw6LrrowJp+TYCEWF7Q==",
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz",
"integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==",
"requires": {
"bufferutil": "^4.0.1",
"debug": "^2.2.0",
+1 -1
View File
@@ -27,7 +27,7 @@
"husky": "^4.2.5",
"jest": "^25.4.0",
"lint-staged": "^10.2.10",
"postcss": "^7.0.32",
"postcss": "^8.2.10",
"postcss-cli": "^7.1.1",
"postcss-load-config": "^2.1.0",
"prettier": "2.0.5",
+16 -17
View File
@@ -1,14 +1,14 @@
import run from '@rollup/plugin-run'
import svelte from 'rollup-plugin-svelte'
// import { skypackResolver } from '@vinicius73/rollup-plugin-skypack-resolver'
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import json from '@rollup/plugin-json';
import injectProcessEnv from 'rollup-plugin-inject-process-env';
import json from '@rollup/plugin-json'
import injectProcessEnv from 'rollup-plugin-inject-process-env'
// env can also be done by replacing strings -> '@rollup/plugin-replace'
// import replace from '@rollup/plugin-replace'
import dotenv from "dotenv"
import dotenv from 'dotenv'
dotenv.config()
export default {
@@ -19,8 +19,8 @@ export default {
format: 'iife',
// format: 'es',
name: 'app',
file: 'public/build/bundle.js'
// file: 'bin/dist/prerender.js',
file: 'public/build/bundle.js',
// file: 'bin/dist/prerender.js',
},
plugins: [
// skypackResolver({
@@ -28,12 +28,11 @@ export default {
// cdnHost: "https://cdn.skypack.dev"
// }),
json(),
svelte({
}),
svelte({}),
resolve({
browser: true,
dedupe: ['svelte'] // '@supabase/supabase-js','cypress-svelte-unit-test'
}),
browser: true,
dedupe: ['svelte'], // '@supabase/supabase-js','cypress-svelte-unit-test'
}),
commonjs({
include: 'node_modules/**',
}),
@@ -46,8 +45,8 @@ export default {
injectProcessEnv(process.env),
run(),
],
onwarn (warning, warn) {
if (warning.code === "UNRESOLVED_IMPORT") return;
warn(warning);
}
}
onwarn(warning, warn) {
if (warning.code === 'UNRESOLVED_IMPORT') return
warn(warning)
},
}
@@ -1,12 +1,12 @@
{
"extends": "@snowpack/app-scripts-svelte",
"install": ["@vinicius73/rollup-plugin-skypack-resolver","@supabase/supabase-js"],
"install": ["@vinicius73/rollup-plugin-skypack-resolver", "@supabase/supabase-js"],
"scripts": {
"mount:src": "mount src --to /_dist_",
"mount:public": "mount public --to /"
},
"plugins": [
"@snowpack/plugin-dotenv",
["@snowpack/plugin-svelte", {"compilerOptions":{ "hydratable": true, "css": false }}]
["@snowpack/plugin-svelte", { "compilerOptions": { "hydratable": true, "css": false } }]
]
}
+7 -7
View File
@@ -1,8 +1,8 @@
import { render } from "@testing-library/svelte";
import App from "./App";
import { render } from '@testing-library/svelte'
import App from './App'
test("renders learn svelte link", () => {
const { getByText } = render(App);
const linkElement = getByText(/learn svelte/i);
expect(linkElement).toBeInTheDocument();
});
test('renders learn svelte link', () => {
const { getByText } = render(App)
const linkElement = getByText(/learn svelte/i)
expect(linkElement).toBeInTheDocument()
})
+1 -1
View File
@@ -1,6 +1,6 @@
// BUG: https://github.com/pikapkg/snowpack/pull/444
// adding "namedExports": ["@supabase/supabase-js"], to snowPack config doesn't work
import {createClient} from '@supabase/supabase-js'
import { createClient } from '@supabase/supabase-js'
// import { Socket } = '@supabase/realtime-js'
// var socket = new Socket(process.env.REALTIME_URL)
+1 -1
View File
@@ -1,4 +1,4 @@
import {createClient} from '@supabase/supabase-js'
import { createClient } from '@supabase/supabase-js'
let importEnv = true
try {
@@ -1,12 +1,10 @@
describe('New todo', () => {
it('it should create new todo', () => {
cy.visit('/');
cy.contains('h1', 'todos');
cy.visit('/')
cy.contains('h1', 'todos')
cy.get('.new-todo')
.type('Demo')
.type('{enter}');
cy.get('.new-todo').type('Demo').type('{enter}')
cy.get('.main .todo-list .view').contains('Demo');
});
});
cy.get('.main .todo-list .view').contains('Demo')
})
})
@@ -12,14 +12,11 @@
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
// @bahmutov/cy-rollup is already a dependency of cypress-svelte-unit-test
const filePreprocessor = require('@bahmutov/cy-rollup');
on('file:preprocessor', filePreprocessor());
};
const filePreprocessor = require('@bahmutov/cy-rollup')
on('file:preprocessor', filePreprocessor())
}
@@ -14,7 +14,7 @@
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands';
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')
+4 -1
View File
@@ -85,23 +85,26 @@ create policy "Individuals can delete their own todos." on todos for
Supabase is open source, we'd love for you to follow along and get involved at https://github.com/supabase/supabase
## Local development
## Local development
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
@@ -6,22 +6,24 @@
/* Write your own custom component styles here */
.btn-black {
@apply border border-black bg-black text-white font-bold py-2 px-4 rounded text-center;
};
@apply border border-black bg-black text-white font-bold py-2 px-4 rounded text-center;
}
.btn-black:hover {
@apply transition duration-150 bg-white text-black;
@apply transition duration-150 bg-white text-black;
}
.btn-black-outline {
@apply border border-black text-black py-2 px-4 rounded font-bold text-center;
@apply border border-black text-black py-2 px-4 rounded font-bold text-center;
}
.btn-black-outline:hover {
@apply transition duration-150 bg-black text-white;
@apply transition duration-150 bg-black text-white;
}
html, body, #app {
height: 100vh;
min-height: 100vh;
}
html,
body,
#app {
height: 100vh;
min-height: 100vh;
}
@@ -7,4 +7,4 @@ export function getParameterByName(name: string, url?: string) {
if (!results) return null
if (!results[2]) return ''
return decodeURIComponent(results[2].replace(/\+/g, ' '))
}
}
@@ -9,10 +9,7 @@ const allTodos = ref<Todo[]>([])
*/
async function fetchTodos() {
try {
const { data: todos, error } = await supabase
.from('todos')
.select('*')
.order('id')
const { data: todos, error } = await supabase.from('todos').select('*').order('id')
if (error) {
console.log('error', error)
@@ -36,10 +33,7 @@ async function fetchTodos() {
*/
async function addTodo(todo: Todo): Promise<null | Todo> {
try {
const { data, error } = await supabase
.from('todos')
.insert(todo)
.single()
const { data, error } = await supabase.from('todos').insert(todo).single()
if (error) {
alert(error.message)
@@ -85,10 +79,7 @@ async function updateTaskCompletion(todo: Todo, isCompleted: boolean) {
*/
async function deleteTodo(todo: Todo) {
try {
await supabase
.from('todos')
.delete()
.eq('id', todo.id)
await supabase.from('todos').delete().eq('id', todo.id)
console.log('deleted todo', todo.id)
} catch (error) {
console.error('error', error)
+5 -22
View File
@@ -11,29 +11,12 @@
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"types": ["webpack-env"],
"paths": {
"@/*": [
"src/*"
]
"@/*": ["src/*"]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
"lib": ["esnext", "dom", "dom.iterable", "scripthost"]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "tests/**/*.ts", "tests/**/*.tsx"],
"exclude": ["node_modules"]
}
+2 -3
View File
@@ -2,11 +2,10 @@
Cloudflare Workers does not support `XMLHttpRequest` object and only support native `fetch`. This means this `supabase-js` client does not work on Cloudflare Workers. Supabase JS Client internally uses `cross-fetch` package to polyfill `fetch` which relies on `XMLHttpRequest`.
To resolve this, we can use `patch-package` to patch `cross-fetch` package as minimal as possible.
To resolve this, we can use `patch-package` to patch `cross-fetch` package as minimal as possible.
This repo contains minimal Cloudflare Workers project with the patch automatically applied.
### Credits
This example was [originally](https://github.com/nascode/cloudflare-workers-supabase-fix) created by [@nascode](https://github.com/nascode).
This example was [originally](https://github.com/nascode/cloudflare-workers-supabase-fix) created by [@nascode](https://github.com/nascode).
+9 -9
View File
@@ -1,11 +1,11 @@
import { createClient } from "@supabase/supabase-js";
import { createClient } from '@supabase/supabase-js'
// Fill you client credential here.
const db = createClient("YOUR_SUPABASE_URL", "YOUR_SUPABASE_KEY");
const db = createClient('YOUR_SUPABASE_URL', 'YOUR_SUPABASE_KEY')
addEventListener("fetch", (event) => {
event.respondWith(handleRequest(event.request));
});
addEventListener('fetch', (event) => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
try {
@@ -16,10 +16,10 @@ async function handleRequest(request) {
// .select("name");
// console.log(JSON.stringify(data, null, 2));
} catch (error) {
console.log(error);
console.log(error)
}
return new Response("Hello worker!", {
headers: { "content-type": "text/plain" },
});
return new Response('Hello worker!', {
headers: { 'content-type': 'text/plain' },
})
}
+1 -3
View File
@@ -2,9 +2,7 @@
"parserOptions": {
"project": "./tsconfig.json"
},
"extends": [
"plugin:@stencil/recommended"
],
"extends": ["plugin:@stencil/recommended"],
"rules": {
"@stencil/async-methods": "error",
"@stencil/ban-prefix": ["error", ["stencil", "stnl", "st"]],
+5 -2
View File
@@ -1,6 +1,6 @@
# Stencil & Supabase Starter
This is a boilerplate for creating a WebApp using [Stencil](https://github.com/ionic-team/stencil) + [Supabase](https://supabase.io/) + [Stencil Store](https://stenciljs.com/docs/stencil-store).
This is a boilerplate for creating a WebApp using [Stencil](https://github.com/ionic-team/stencil) + [Supabase](https://supabase.io/) + [Stencil Store](https://stenciljs.com/docs/stencil-store).
<img src="./login-page.png" alt="Login Page">
@@ -38,13 +38,14 @@ src -> config -> config.ts
```js
const supabaseConfig = {
supabaseUrl: '',
supabaseKey: ''
supabaseKey: '',
};
```
You can find more about _Supabase Client_ config [on the official documentation page](https://supabase.io/docs/reference/javascript/initializing).
To start a project, run:
```sh
npm run start
```
@@ -72,7 +73,9 @@ npm run test.watch
You can check the details of the [structure of our project through this file](./STRUCTURE.md)
## Contributing 👏
- [Pull requests](https://github.com/ftonato/stencil-supabase/pulls) and 🌟 stars are always welcome.
## License
[MIT &copy;](LICENSE)
+19 -2
View File
@@ -1,46 +1,63 @@
## Project structure 🗄️
### Service
This class will handle application authenticate features to supabase.
```
src -> services -> auth.service.ts -> Class SupabaseAuthService
```
You can create more classes to group a functionality.
### Store
Application store
```
src -> store -> app.store.ts
```
### Interfaces
Interfaces used in the application
```
src -> interface -> interface.ts
```
### Utility
Utility for parsing application messages and holding form configurations
```
src -> util -> util.ts
```
### Configuration
Application level configurations
```
src -> config -> config.ts
```
### Assets
Application level assets
```
src -> assets
```
### Components
Application level components
```
src -> components ->
1. <app-flash-message> for showing alert messages
2. <app-home> Dashboard
2. <app-home> Dashboard
3. <app-root> entry component of the application
5. <page> for showing pages
6. <auth> -> having login/register components
@@ -48,4 +65,4 @@ src -> components ->
### Styling 💀
I have used `scss` for styling components. Ths boilerplate have a basic design i.e it focus more on configuring and using supabase in stencil web app along with stencil store. You can customize the looks as per your needs.
I have used `scss` for styling components. Ths boilerplate have a basic design i.e it focus more on configuring and using supabase in stencil web app along with stencil store. You can customize the looks as per your needs.
Loaded 100 of 298 files, more files were not shown because too many files have changed in this diff. Show more