Skip to content

[Chore] Minor Improvements #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM mcr.microsoft.com/devcontainers/python:1-3.11-bullseye

ENV PYTHONUNBUFFERED 1

# [Optional] If your requirements rarely change, uncomment this section to add them to the image.
# COPY requirements.txt /tmp/pip-tmp/
# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
# && rm -rf /tmp/pip-tmp

# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>



24 changes: 24 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/postgres
{
"name": "Python 3 & PostgreSQL",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",

// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},

// Use 'forwardPorts' to make a list of ports inside the container available locally.
// This can be used to network with other containers or the host.
"forwardPorts": [5000, 5432, 8000],

// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "pip install --user -r requirements.txt"

// Configure tool-specific properties.
// "customizations": {},

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
36 changes: 36 additions & 0 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
version: '3.8'

services:
app:
build:
context: ..
dockerfile: .devcontainer/Dockerfile

volumes:
- ../..:/workspaces:cached

# Overrides default command so things don't shut down after the process ends.
command: sleep infinity

# Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function.
network_mode: service:db

# Use "forwardPorts" in **devcontainer.json** to forward an app port locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)

db:
image: postgres:latest
restart: unless-stopped
volumes:
- postgres-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: postgres
POSTGRES_DB: postgres
POSTGRES_PASSWORD: postgres
command: ["postgres", "-c", "wal_level=logical"]

# Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally.
# (Adding the "ports" property to this file will not forward from a Codespace.)

volumes:
postgres-data:
11 changes: 11 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# A default keypair will be used if this is not provided.
POWERSYNC_PRIVATE_KEY=
POWERSYNC_PUBLIC_KEY=

POWERSYNC_URL=http://localhost:8080
DATABASE_NAME=postgres
DATABASE_USER=postgres
DATABASE_PASSWORD=mypassword
DATABASE_HOST=localhost
DJANGO_PORT=8000
DATABASE_PORT=5432
22 changes: 22 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Use the official Python image from the Docker Hub
FROM python:3.9

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# Set work directory
WORKDIR /app

# Install dependencies
COPY requirements.txt /app/
RUN pip install --upgrade pip
RUN pip install -r requirements.txt

# Copy project
COPY . /app/

EXPOSE 8000

# Run migrations on each boot
CMD ["/bin/sh", "-c", " python manage.py migrate && python manage.py runserver"]
5 changes: 0 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,6 @@ python manage.py migrate

Note that one of the migrations creates a test user in the `auth_user` table - you can use it to log into your frontend app. Take note of the user's id and update the hard coded id in the `upload_data` endpoint of `api/views.py` to match this user's id. In production you'd typically want to authenticate the user on this endpoint (using whatever auth mechanism you already have in place) before signing a JWT for use with PowerSync. See an example [here](https://github.com/powersync-ja/powersync-jwks-example/blob/151adf17611bef8a60d9e6cc490827adc4612da9/supabase/functions/powersync-auth/index.ts#L22)

6. Run the following SQL statement on your Postgres database:

```sql
create publication powersync for table lists, todos;
```

## Start App

Expand Down
102 changes: 88 additions & 14 deletions api/app_utils.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,105 @@
import base64
import json
import time
import os
from decouple import config
from jose.constants import ALGORITHMS
from jose.exceptions import JWKError
from jose.jwt import encode
from jose import jwk
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
import random
import string

# Function to decode base64url-encoded data


def base64url_decode(data):
padding = b'=' * (4 - (len(data) % 4))
data = data.replace(b'-', b'+').replace(b'_', b'/') + padding
return base64.b64decode(data)

# Function to generate a new RSA key pair and return in JWK format


def generate_key_pair():
alg = 'RS256'
kid = 'powersync-' + \
''.join(random.choices(string.ascii_lowercase + string.digits, k=10))

private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)

private_key_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)

public_key = private_key.public_key()
public_key_pem = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)

private_key_jwk = jwk.construct(
private_key_pem, algorithm=ALGORITHMS.RS256).to_dict()
private_key_jwk.update({'alg': alg, 'kid': kid})

public_key_jwk = jwk.construct(
public_key_pem, algorithm=ALGORITHMS.RS256).to_dict()
public_key_jwk.update({'alg': alg, 'kid': kid})

private_base64 = base64.urlsafe_b64encode(json.dumps(
private_key_jwk).encode('utf-8')).decode('utf-8')
public_base64 = base64.urlsafe_b64encode(json.dumps(
public_key_jwk).encode('utf-8')).decode('utf-8')

return private_base64, public_base64
# Function to ensure keys are available

# Function to ensure keys are available


def ensure_keys():
global power_sync_private_key_json, power_sync_public_key_json

power_sync_private_key_b64 = config('POWERSYNC_PRIVATE_KEY', default=None)
power_sync_public_key_b64 = config('POWERSYNC_PUBLIC_KEY', default=None)

if not power_sync_private_key_b64 or not power_sync_public_key_b64 or not power_sync_private_key_b64.strip() or not power_sync_public_key_b64.strip():
print('Private key has not been supplied in the environment. A temporary key pair will be generated.')
private_key_base64, public_key_base64 = generate_key_pair()
power_sync_private_key_b64 = private_key_base64
power_sync_public_key_b64 = public_key_base64

# Save the keys to environment variables (only for this session, won't persist across restarts)
os.environ['POWERSYNC_PRIVATE_KEY'] = power_sync_private_key_b64
os.environ['POWERSYNC_PUBLIC_KEY'] = power_sync_public_key_b64

power_sync_private_key_bytes = base64url_decode(
power_sync_private_key_b64.encode('utf-8'))
power_sync_private_key_json = json.loads(
power_sync_private_key_bytes.decode('utf-8'))

power_sync_public_key_bytes = base64url_decode(
power_sync_public_key_b64.encode('utf-8'))
power_sync_public_key_json = json.loads(
power_sync_public_key_bytes.decode('utf-8'))


# Ensure keys are available
ensure_keys()

# PowerSync URL
power_sync_url = config('POWERSYNC_URL')


def create_jwt_token(user_id):
try:
jwt_header = {
Expand All @@ -38,17 +126,3 @@ def create_jwt_token(user_id):

except (JWKError, ValueError, KeyError) as e:
raise Exception(f"Error creating JWT token: {str(e)}")

# PowerSync private key
power_sync_private_key_b64 = config('POWERSYNC_PRIVATE_KEY')
power_sync_public_key_b64 = config('POWERSYNC_PUBLIC_KEY')

# PowerSync Url
power_sync_url = config('POWERSYNC_URL')

# PowerSync public key
power_sync_private_key_bytes = base64url_decode(power_sync_private_key_b64.encode('utf-8'))
power_sync_private_key_json = json.loads(power_sync_private_key_bytes.decode('utf-8'))

power_sync_public_key_bytes = base64url_decode(power_sync_public_key_b64.encode('utf-8'))
power_sync_public_key_json = json.loads(power_sync_public_key_bytes.decode('utf-8'))
10 changes: 7 additions & 3 deletions api/migrations/0002_create_test_user.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
from django.utils import timezone
from django.db import migrations
from django.contrib.auth import get_user_model


def create_test_user(apps, schema_editor):
User = get_user_model()
User.objects.create_user(
username='testuser',
password='testpassword'
password='testpassword',
last_login=timezone.now()
)


class Migration(migrations.Migration):

dependencies = [
('api', '0001_initial'),
('api', '0001_initial'),
]

operations = [
migrations.RunPython(create_test_user),
]
]
26 changes: 26 additions & 0 deletions api/migrations/0003_create_publication.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('api', '0002_create_test_user'),
]

operations = [
migrations.RunSQL(
"""
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_publication WHERE pubname = 'powersync'
) THEN
CREATE PUBLICATION powersync FOR TABLE lists, todos;
END IF;
END $$;
""",
reverse_sql="""
DROP PUBLICATION IF EXISTS powersync;
"""
),
]
6 changes: 4 additions & 2 deletions api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
from . import views

urlpatterns = [
path('get_powersync_token/', views.get_powersync_token, name='get_powersync_token'),
path('get_powersync_token/', views.get_powersync_token,
name='get_powersync_token'),
path('get_keys/', views.get_keys, name='get_keys'),
path('get_session/', views.get_session, name='get_session'),
path('auth/', views.auth, name='auth'),
path('register/', views.register, name='register'),
path('upload_data/', views.upload_data, name='upload_data'),
]
]
Loading