roomz 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- roomz-0.1.0/.claude/settings.local.json +22 -0
- roomz-0.1.0/.github/workflows/ci.yml +112 -0
- roomz-0.1.0/.github/workflows/publish.yml +53 -0
- roomz-0.1.0/.gitignore +53 -0
- roomz-0.1.0/.readthedocs.yaml +20 -0
- roomz-0.1.0/LICENSE +21 -0
- roomz-0.1.0/Makefile +68 -0
- roomz-0.1.0/PKG-INFO +353 -0
- roomz-0.1.0/README.md +305 -0
- roomz-0.1.0/REQUIREMENTS.md +157 -0
- roomz-0.1.0/TODO.md +280 -0
- roomz-0.1.0/analysis/2026-05-14-architecture-review-I1-001.md +654 -0
- roomz-0.1.0/analysis/api-auth-I2-001.md +891 -0
- roomz-0.1.0/analysis/api-broadcast.md +585 -0
- roomz-0.1.0/analysis/api-email-integration.md +655 -0
- roomz-0.1.0/analysis/api-jwt-sessions.md +777 -0
- roomz-0.1.0/analysis/api-private-channels.md +802 -0
- roomz-0.1.0/analysis/api-python-client.md +869 -0
- roomz-0.1.0/analysis/functional.md +681 -0
- roomz-0.1.0/analysis/jwt-channels-architecture.md +234 -0
- roomz-0.1.0/analysis/security-auth-I2-001.md +1476 -0
- roomz-0.1.0/analysis/security-email-integration.md +745 -0
- roomz-0.1.0/analysis/security-jwt-sessions.md +1590 -0
- roomz-0.1.0/analysis/security-private-channels.md +1003 -0
- roomz-0.1.0/analysis/ux-auth-I2-001.md +954 -0
- roomz-0.1.0/analysis/ux-chat-ui.md +560 -0
- roomz-0.1.0/app/__init__.py +521 -0
- roomz-0.1.0/app/auth.py +574 -0
- roomz-0.1.0/app/components/__init__.py +2 -0
- roomz-0.1.0/app/components/auth/__init__.py +16 -0
- roomz-0.1.0/app/components/auth/auth.js +249 -0
- roomz-0.1.0/app/email/__init__.py +70 -0
- roomz-0.1.0/app/email/console.py +60 -0
- roomz-0.1.0/app/email/protocol.py +38 -0
- roomz-0.1.0/app/email/resend.py +203 -0
- roomz-0.1.0/app/models.py +118 -0
- roomz-0.1.0/app/pages/__init__.py +8 -0
- roomz-0.1.0/app/pages/chat/__init__.py +16 -0
- roomz-0.1.0/app/pages/chat/chat.js +247 -0
- roomz-0.1.0/app/static/css/roomz.css +11 -0
- roomz-0.1.0/docs/Makefile +19 -0
- roomz-0.1.0/docs/api.md +441 -0
- roomz-0.1.0/docs/changelog.md +40 -0
- roomz-0.1.0/docs/conf.py +26 -0
- roomz-0.1.0/docs/configuration.md +397 -0
- roomz-0.1.0/docs/contributing.md +108 -0
- roomz-0.1.0/docs/index.md +155 -0
- roomz-0.1.0/docs/installation.md +75 -0
- roomz-0.1.0/docs/quickstart.md +191 -0
- roomz-0.1.0/docs/server.md +337 -0
- roomz-0.1.0/media/chat.png +0 -0
- roomz-0.1.0/media/cli.png +0 -0
- roomz-0.1.0/media/login.png +0 -0
- roomz-0.1.0/media/magic-link.png +0 -0
- roomz-0.1.0/memory/2026-05-14-agile-testing-philosophy.md +214 -0
- roomz-0.1.0/memory/2026-05-14-uv-primary-tool.md +99 -0
- roomz-0.1.0/memory/websocket-testing.md +76 -0
- roomz-0.1.0/pyproject.toml +129 -0
- roomz-0.1.0/reporting/I1-001-minimal-chat-broadcast/consensus.md +208 -0
- roomz-0.1.0/reporting/I1-001-minimal-chat-broadcast/summary.md +270 -0
- roomz-0.1.0/reporting/I1-001-minimal-chat-broadcast/ux-ui-review.md +457 -0
- roomz-0.1.0/reporting/I2-001/consensus.md +810 -0
- roomz-0.1.0/reporting/I2-001/development-summary.md +203 -0
- roomz-0.1.0/reporting/I2-001/summary.md +149 -0
- roomz-0.1.0/reporting/I2-001/ux-ui-review.md +349 -0
- roomz-0.1.0/reporting/I3-001-python-client/summary.md +178 -0
- roomz-0.1.0/reporting/I4-001-jwt-session-tokens/functional-review.md +461 -0
- roomz-0.1.0/reporting/I4-001-jwt-sessions/consensus.md +169 -0
- roomz-0.1.0/reporting/I4-001-jwt-sessions/summary.md +105 -0
- roomz-0.1.0/reporting/I4-002-private-channels/consensus.md +102 -0
- roomz-0.1.0/reporting/I4-002-private-channels/development-summary.md +130 -0
- roomz-0.1.0/reporting/I4-002-private-channels/functional-review.md +227 -0
- roomz-0.1.0/reporting/I4-002-private-channels/summary.md +60 -0
- roomz-0.1.0/reporting/I5-001-email-integration/consensus.md +99 -0
- roomz-0.1.0/reporting/I5-001-email-integration/functional-review.md +196 -0
- roomz-0.1.0/src/roomz/__init__.py +25 -0
- roomz-0.1.0/src/roomz/cli/__init__.py +26 -0
- roomz-0.1.0/src/roomz/cli/__main__.py +27 -0
- roomz-0.1.0/src/roomz/cli/app_tui.py +365 -0
- roomz-0.1.0/src/roomz/cli/styles/chat.tcss +40 -0
- roomz-0.1.0/src/roomz/client/__init__.py +25 -0
- roomz-0.1.0/src/roomz/client/async_client.py +453 -0
- roomz-0.1.0/src/roomz/client/events.py +73 -0
- roomz-0.1.0/src/roomz/client/exceptions.py +23 -0
- roomz-0.1.0/src/roomz/client/state.py +23 -0
- roomz-0.1.0/src/roomz/client/sync_client.py +179 -0
- roomz-0.1.0/tests/WEBSOCKET_TESTING.md +195 -0
- roomz-0.1.0/tests/__init__.py +6 -0
- roomz-0.1.0/tests/conftest.py +300 -0
- roomz-0.1.0/tests/test_app_helpers.py +142 -0
- roomz-0.1.0/tests/test_app_structure.py +378 -0
- roomz-0.1.0/tests/test_auth.py +807 -0
- roomz-0.1.0/tests/test_chat_page.py +146 -0
- roomz-0.1.0/tests/test_email_integration.py +1003 -0
- roomz-0.1.0/tests/test_jwt_integration.py +976 -0
- roomz-0.1.0/tests/test_jwt_security.py +1313 -0
- roomz-0.1.0/tests/test_jwt_unit.py +779 -0
- roomz-0.1.0/tests/test_private_channels.py +470 -0
- roomz-0.1.0/tests/test_python_client.py +748 -0
- roomz-0.1.0/tests/test_session.py +122 -0
- roomz-0.1.0/tests/test_socketio_broadcast.py +526 -0
- roomz-0.1.0/tests/test_websocket.py +762 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Skill(c3:commit)",
|
|
5
|
+
"Skill(c3:python)",
|
|
6
|
+
"Bash(uv run *)",
|
|
7
|
+
"Bash(uv sync *)",
|
|
8
|
+
"Bash(uv pip *)",
|
|
9
|
+
"Skill(c3:readme)",
|
|
10
|
+
"Bash(uv add *)",
|
|
11
|
+
"WebSearch",
|
|
12
|
+
"WebFetch(domain:python-socketio.readthedocs.io)",
|
|
13
|
+
"Skill(c3:textual)",
|
|
14
|
+
"Bash(cat)",
|
|
15
|
+
"Bash(python3 /tmp/fix_websocket.py)",
|
|
16
|
+
"Bash(git restore *)",
|
|
17
|
+
"Bash(make html *)",
|
|
18
|
+
"Bash(make typecheck *)",
|
|
19
|
+
"Bash(make build *)"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, master]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
name: Lint
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Install uv
|
|
17
|
+
uses: astral-sh/setup-uv@v5
|
|
18
|
+
with:
|
|
19
|
+
enable-cache: true
|
|
20
|
+
|
|
21
|
+
- name: Set up Python
|
|
22
|
+
uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.10"
|
|
25
|
+
|
|
26
|
+
- name: Install dependencies
|
|
27
|
+
run: uv sync --extra dev
|
|
28
|
+
|
|
29
|
+
- name: Run ruff linting
|
|
30
|
+
run: uv run ruff check app/ tests/
|
|
31
|
+
|
|
32
|
+
test:
|
|
33
|
+
name: Test (${{ matrix.os }}, Python ${{ matrix.python-version }})
|
|
34
|
+
runs-on: ${{ matrix.os }}
|
|
35
|
+
strategy:
|
|
36
|
+
fail-fast: false
|
|
37
|
+
matrix:
|
|
38
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
39
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v4
|
|
42
|
+
|
|
43
|
+
- name: Install uv
|
|
44
|
+
uses: astral-sh/setup-uv@v5
|
|
45
|
+
with:
|
|
46
|
+
enable-cache: true
|
|
47
|
+
|
|
48
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
49
|
+
uses: actions/setup-python@v5
|
|
50
|
+
with:
|
|
51
|
+
python-version: ${{ matrix.python-version }}
|
|
52
|
+
|
|
53
|
+
- name: Install dependencies
|
|
54
|
+
run: uv sync --extra dev
|
|
55
|
+
|
|
56
|
+
- name: Run tests with coverage
|
|
57
|
+
run: uv run pytest -v --cov=app --cov=src/roomz --cov-report=xml --cov-report=term
|
|
58
|
+
|
|
59
|
+
- name: Upload coverage to Coveralls
|
|
60
|
+
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'
|
|
61
|
+
uses: coverallsapp/github-action@v2
|
|
62
|
+
with:
|
|
63
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
64
|
+
|
|
65
|
+
typecheck:
|
|
66
|
+
name: Type Check
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@v4
|
|
70
|
+
|
|
71
|
+
- name: Install uv
|
|
72
|
+
uses: astral-sh/setup-uv@v5
|
|
73
|
+
with:
|
|
74
|
+
enable-cache: true
|
|
75
|
+
|
|
76
|
+
- name: Set up Python
|
|
77
|
+
uses: actions/setup-python@v5
|
|
78
|
+
with:
|
|
79
|
+
python-version: "3.10"
|
|
80
|
+
|
|
81
|
+
- name: Install dependencies
|
|
82
|
+
run: uv sync --extra dev
|
|
83
|
+
|
|
84
|
+
- name: Run mypy
|
|
85
|
+
run: uv run mypy app/
|
|
86
|
+
|
|
87
|
+
build:
|
|
88
|
+
name: Build Package
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
needs: [lint, test, typecheck]
|
|
91
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
92
|
+
steps:
|
|
93
|
+
- uses: actions/checkout@v4
|
|
94
|
+
|
|
95
|
+
- name: Install uv
|
|
96
|
+
uses: astral-sh/setup-uv@v5
|
|
97
|
+
with:
|
|
98
|
+
enable-cache: true
|
|
99
|
+
|
|
100
|
+
- name: Set up Python
|
|
101
|
+
uses: actions/setup-python@v5
|
|
102
|
+
with:
|
|
103
|
+
python-version: "3.10"
|
|
104
|
+
|
|
105
|
+
- name: Build package
|
|
106
|
+
run: uv build
|
|
107
|
+
|
|
108
|
+
- name: Upload artifacts
|
|
109
|
+
uses: actions/upload-artifact@v4
|
|
110
|
+
with:
|
|
111
|
+
name: dist
|
|
112
|
+
path: dist/
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
target:
|
|
9
|
+
description: 'Target repository'
|
|
10
|
+
required: true
|
|
11
|
+
default: 'testpypi'
|
|
12
|
+
type: choice
|
|
13
|
+
options:
|
|
14
|
+
- testpypi
|
|
15
|
+
- pypi
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
publish:
|
|
19
|
+
name: Build and Publish
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
permissions:
|
|
22
|
+
id-token: write # Required for trusted publishing
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- name: Install uv
|
|
27
|
+
uses: astral-sh/setup-uv@v5
|
|
28
|
+
with:
|
|
29
|
+
enable-cache: true
|
|
30
|
+
|
|
31
|
+
- name: Set up Python
|
|
32
|
+
uses: actions/setup-python@v5
|
|
33
|
+
with:
|
|
34
|
+
python-version: "3.12"
|
|
35
|
+
|
|
36
|
+
- name: Install dependencies
|
|
37
|
+
run: uv sync --extra dev
|
|
38
|
+
|
|
39
|
+
- name: Run tests
|
|
40
|
+
run: uv run pytest -v
|
|
41
|
+
|
|
42
|
+
- name: Build package
|
|
43
|
+
run: uv build
|
|
44
|
+
|
|
45
|
+
- name: Publish to TestPyPI
|
|
46
|
+
if: github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi'
|
|
47
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
48
|
+
with:
|
|
49
|
+
repository-url: https://test.pypi.org/legacy/
|
|
50
|
+
|
|
51
|
+
- name: Publish to PyPI
|
|
52
|
+
if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.target == 'pypi')
|
|
53
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
roomz-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
|
|
23
|
+
# Virtual Environment
|
|
24
|
+
.venv/
|
|
25
|
+
venv/
|
|
26
|
+
ENV/
|
|
27
|
+
|
|
28
|
+
# IDE
|
|
29
|
+
.vscode/
|
|
30
|
+
.idea/
|
|
31
|
+
*.swp
|
|
32
|
+
*.swo
|
|
33
|
+
*~
|
|
34
|
+
|
|
35
|
+
# Testing
|
|
36
|
+
.pytest_cache/
|
|
37
|
+
.coverage
|
|
38
|
+
htmlcov/
|
|
39
|
+
coverage.xml
|
|
40
|
+
.mypy_cache/
|
|
41
|
+
.ruff_cache/
|
|
42
|
+
.tox/
|
|
43
|
+
|
|
44
|
+
# Documentation
|
|
45
|
+
docs/_build/
|
|
46
|
+
|
|
47
|
+
# Project specific
|
|
48
|
+
*.log
|
|
49
|
+
|
|
50
|
+
# Distribution
|
|
51
|
+
uv.lock
|
|
52
|
+
.env.local
|
|
53
|
+
resend_email_test.py
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# .readthedocs.yaml
|
|
2
|
+
# Read the Docs configuration file
|
|
3
|
+
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
|
4
|
+
|
|
5
|
+
version: 2
|
|
6
|
+
|
|
7
|
+
build:
|
|
8
|
+
os: ubuntu-22.04
|
|
9
|
+
tools:
|
|
10
|
+
python: "3.12"
|
|
11
|
+
|
|
12
|
+
sphinx:
|
|
13
|
+
configuration: docs/conf.py
|
|
14
|
+
|
|
15
|
+
python:
|
|
16
|
+
install:
|
|
17
|
+
- method: pip
|
|
18
|
+
path: .
|
|
19
|
+
extra_requirements:
|
|
20
|
+
- docs
|
roomz-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Christophe VG
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
roomz-0.1.0/Makefile
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Makefile for Roomz
|
|
2
|
+
# Utility targets for common development actions
|
|
3
|
+
|
|
4
|
+
-include ~/.claude/Makefile
|
|
5
|
+
|
|
6
|
+
.PHONY: run-env dev-env test test-all lint format typecheck build publish clean all help run
|
|
7
|
+
|
|
8
|
+
## run-env: Install production dependencies
|
|
9
|
+
run-env:
|
|
10
|
+
uv sync
|
|
11
|
+
|
|
12
|
+
## dev-env: Install with all dependencies (dev, extras)
|
|
13
|
+
dev-env:
|
|
14
|
+
uv sync --extra dev
|
|
15
|
+
|
|
16
|
+
## test: Run tests with pytest
|
|
17
|
+
test: dev-env
|
|
18
|
+
uv run pytest -v
|
|
19
|
+
|
|
20
|
+
## test-all: Run tests with tox (all Python versions)
|
|
21
|
+
test-all: dev-env
|
|
22
|
+
uv run tox
|
|
23
|
+
|
|
24
|
+
## test-cov: Run tests with coverage report
|
|
25
|
+
test-cov: dev-env
|
|
26
|
+
uv run pytest --cov=app --cov-report=term-missing
|
|
27
|
+
|
|
28
|
+
## lint: Run ruff linting
|
|
29
|
+
lint: dev-env
|
|
30
|
+
uv run ruff check app/ tests/ src/
|
|
31
|
+
|
|
32
|
+
## format: Format code with ruff
|
|
33
|
+
format: dev-env
|
|
34
|
+
uv run ruff check --fix app/ tests/
|
|
35
|
+
|
|
36
|
+
## typecheck: Run mypy type checking
|
|
37
|
+
typecheck: dev-env
|
|
38
|
+
uv run mypy app/ src/
|
|
39
|
+
|
|
40
|
+
## build: Build distribution packages
|
|
41
|
+
build: dev-env
|
|
42
|
+
uv build
|
|
43
|
+
|
|
44
|
+
## publish: Publish to PyPI (requires credentials)
|
|
45
|
+
publish: build
|
|
46
|
+
uv run twine upload dist/*
|
|
47
|
+
|
|
48
|
+
## publish-test: Publish to TestPyPI (requires credentials)
|
|
49
|
+
publish-test: build
|
|
50
|
+
uv run twine upload --repository testpypi dist/*
|
|
51
|
+
|
|
52
|
+
## clean: Clean build artifacts
|
|
53
|
+
clean:
|
|
54
|
+
rm -rf dist/ build/ *.egg-info .pytest_cache .coverage .mypy_cache .ruff_cache .tox
|
|
55
|
+
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
|
|
56
|
+
|
|
57
|
+
## check: Run format, lint, test, and typecheck
|
|
58
|
+
check: dev-env format lint test typecheck
|
|
59
|
+
|
|
60
|
+
## run: Run the development server
|
|
61
|
+
run: run-env
|
|
62
|
+
uv run gunicorn -k uvicorn.workers.UvicornWorker app:asgi_app --reload
|
|
63
|
+
|
|
64
|
+
## help: Show this help message
|
|
65
|
+
help:
|
|
66
|
+
@echo "Available targets:"
|
|
67
|
+
@echo ""
|
|
68
|
+
@sed -n 's/^## //p' $(MAKEFILE_LIST) | column -t -s ':'
|
roomz-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: roomz
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Real-time chatroom web service with magic link authentication
|
|
5
|
+
Project-URL: Homepage, https://github.com/christophevg/roomz
|
|
6
|
+
Project-URL: Documentation, https://github.com/christophevg/roomz#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/christophevg/roomz
|
|
8
|
+
Project-URL: Issues, https://github.com/christophevg/roomz/issues
|
|
9
|
+
Author-email: Christophe VG <contact@christophe.vg>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: authentication,chat,quart,real-time,socketio,websocket
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Framework :: AsyncIO
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Communications :: Chat
|
|
22
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
25
|
+
Requires-Dist: baseweb>=0.5.2
|
|
26
|
+
Requires-Dist: gunicorn>=21.0.0
|
|
27
|
+
Requires-Dist: pyjwt>=2.8.0
|
|
28
|
+
Requires-Dist: python-dotenv>=1.2.2
|
|
29
|
+
Requires-Dist: python-socketio[asyncio]>=5.10.0
|
|
30
|
+
Requires-Dist: resend>=2.30.1
|
|
31
|
+
Requires-Dist: rich>=13.0.0
|
|
32
|
+
Requires-Dist: textual>=0.47.0
|
|
33
|
+
Requires-Dist: uvicorn[standard]>=0.24.0
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: mypy>=1.9.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: tomli>=2.0.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: tox>=4.0.0; extra == 'dev'
|
|
42
|
+
Requires-Dist: twine>=6.0.0; extra == 'dev'
|
|
43
|
+
Provides-Extra: docs
|
|
44
|
+
Requires-Dist: myst-parser>=2.0.0; extra == 'docs'
|
|
45
|
+
Requires-Dist: sphinx-rtd-theme>=2.0.0; extra == 'docs'
|
|
46
|
+
Requires-Dist: sphinx>=7.0.0; extra == 'docs'
|
|
47
|
+
Description-Content-Type: text/markdown
|
|
48
|
+
|
|
49
|
+
# Roomz
|
|
50
|
+
|
|
51
|
+
[](https://pypi.org/project/roomz/)
|
|
52
|
+
[](https://pypistats.org/packages/roomz)
|
|
53
|
+
[](https://pypi.org/project/roomz/)
|
|
54
|
+
[](https://github.com/christophevg/roomz/blob/main/LICENSE)
|
|
55
|
+
[](https://github.com/christophevg/roomz/actions/workflows/ci.yml)
|
|
56
|
+
[](https://coveralls.io/github/christophevg/roomz)
|
|
57
|
+
[](https://github.com/astral-sh/ruff)
|
|
58
|
+
[](https://mypy.readthedocs.io/)
|
|
59
|
+
[](https://christophe.vg/about/Coding-Agent)
|
|
60
|
+
|
|
61
|
+
A real-time chatroom web service with magic link authentication.
|
|
62
|
+
|
|
63
|
+
## What is Roomz?
|
|
64
|
+
|
|
65
|
+
Roomz is a real-time chat application with secure magic link authentication. Built with modern async technology (Quart + SocketIO), it provides seamless real-time messaging with passwordless login.
|
|
66
|
+
|
|
67
|
+
## Screenshots
|
|
68
|
+
|
|
69
|
+
| Login | Magic Link | Chat | CLI |
|
|
70
|
+
|-------|-------------|------|-----|
|
|
71
|
+
|  |  |  |  |
|
|
72
|
+
|
|
73
|
+
## Features
|
|
74
|
+
|
|
75
|
+
- **Magic Link Authentication**: Passwordless login via email
|
|
76
|
+
- **Instant Messaging**: Messages appear instantly across all connected users
|
|
77
|
+
- **Real-time Updates**: See when users join or leave
|
|
78
|
+
- **Responsive Design**: Works on desktop, tablet, and mobile
|
|
79
|
+
- **Connection Status**: Visual indicator shows when disconnected
|
|
80
|
+
- **Accessibility**: Keyboard navigation and screen reader support
|
|
81
|
+
|
|
82
|
+
## Quick Start
|
|
83
|
+
|
|
84
|
+
### Prerequisites
|
|
85
|
+
|
|
86
|
+
- Python 3.10 or higher
|
|
87
|
+
- [uv](https://docs.astral.sh/uv/) package manager
|
|
88
|
+
|
|
89
|
+
### Installation
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Clone or navigate to the project
|
|
93
|
+
cd /path/to/roomz
|
|
94
|
+
|
|
95
|
+
# Install dependencies
|
|
96
|
+
uv sync
|
|
97
|
+
|
|
98
|
+
# Install dev dependencies (for testing)
|
|
99
|
+
uv sync --extra dev
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Running the Application
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Start the chat server
|
|
106
|
+
uv run gunicorn -k uvicorn.workers.UvicornWorker app:asgi_app
|
|
107
|
+
|
|
108
|
+
# Or for development with auto-reload:
|
|
109
|
+
uv run uvicorn app:asgi_app --reload --host 0.0.0.0 --port 8000
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Open [http://localhost:8000](http://localhost:8000) in your browser.
|
|
113
|
+
|
|
114
|
+
### Configuration
|
|
115
|
+
|
|
116
|
+
Roomz uses environment variables for configuration. Create a `.env` file in the project root:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Required: JWT secret key (minimum 32 characters)
|
|
120
|
+
# Generate with: python -c "import secrets; print(secrets.token_urlsafe(32))"
|
|
121
|
+
JWT_SECRET_KEY=your-256-bit-secret-key-here
|
|
122
|
+
|
|
123
|
+
# Required: Comma-separated list of allowed email addresses
|
|
124
|
+
ALLOWED_EMAILS=user@example.com,other@example.com
|
|
125
|
+
|
|
126
|
+
# Email Configuration (choose one)
|
|
127
|
+
# Development: Log magic links to console (default)
|
|
128
|
+
EMAIL_SENDER=console
|
|
129
|
+
|
|
130
|
+
# Production: Send emails via Resend
|
|
131
|
+
# EMAIL_SENDER=resend
|
|
132
|
+
# RESEND_API_KEY=re_your_api_key_here
|
|
133
|
+
# EMAIL_FROM=noreply@yourdomain.com # Optional, defaults to no-reply@example.com
|
|
134
|
+
|
|
135
|
+
# Optional: JWT token expiration in days (default: 30)
|
|
136
|
+
JWT_EXPIRY_DAYS=30
|
|
137
|
+
|
|
138
|
+
# Optional: Magic link expiration in minutes (default: 15)
|
|
139
|
+
MAGIC_LINK_EXPIRY_MINUTES=15
|
|
140
|
+
|
|
141
|
+
# Optional: Rate limit for magic link requests per email per hour (default: 5)
|
|
142
|
+
MAGIC_LINK_RATE_LIMIT=5
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Security Notes:**
|
|
146
|
+
- `JWT_SECRET_KEY` must be at least 32 characters (256 bits)
|
|
147
|
+
- `ALLOWED_EMAILS` controls who can authenticate
|
|
148
|
+
- Removing an email from `ALLOWED_EMAILS` immediately revokes their access
|
|
149
|
+
- Session cookies are httpOnly and SameSite=Strict
|
|
150
|
+
|
|
151
|
+
**Email Setup:**
|
|
152
|
+
- **Development**: Set `EMAIL_SENDER=console` (or leave unset). Magic links are logged to the server console.
|
|
153
|
+
- **Production**: Set `EMAIL_SENDER=resend` and provide `RESEND_API_KEY`. Get your API key from [resend.com](https://resend.com).
|
|
154
|
+
|
|
155
|
+
### Testing
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# Run all tests
|
|
159
|
+
uv run pytest tests/ -v
|
|
160
|
+
|
|
161
|
+
# Run tests with coverage
|
|
162
|
+
uv run pytest --cov=app --cov-report=term-missing
|
|
163
|
+
|
|
164
|
+
# Run tests across Python versions
|
|
165
|
+
uv run tox
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## How to Use
|
|
169
|
+
|
|
170
|
+
### Authentication
|
|
171
|
+
|
|
172
|
+
1. Open the application in your browser
|
|
173
|
+
2. Enter your email address
|
|
174
|
+
3. Click "Send Magic Link"
|
|
175
|
+
4. Check the server console for the magic link (development mode)
|
|
176
|
+
5. Click the magic link to authenticate
|
|
177
|
+
6. You're now in the chat!
|
|
178
|
+
|
|
179
|
+
### Chatting
|
|
180
|
+
|
|
181
|
+
1. After authentication, you see the chat interface
|
|
182
|
+
2. Type a message in the input field at the bottom
|
|
183
|
+
3. Press **Enter** or click the **Send** button
|
|
184
|
+
4. Your message appears instantly to all connected users
|
|
185
|
+
|
|
186
|
+
### Multiple Users
|
|
187
|
+
|
|
188
|
+
1. Open the application in multiple browser tabs or windows
|
|
189
|
+
2. Authenticate in each tab (can use same or different email)
|
|
190
|
+
3. Type messages in any tab
|
|
191
|
+
4. All tabs see the messages instantly
|
|
192
|
+
5. System messages show when users join or leave
|
|
193
|
+
|
|
194
|
+
## Python Client Library
|
|
195
|
+
|
|
196
|
+
Roomz includes a Python client library for programmatic access to the chat service.
|
|
197
|
+
|
|
198
|
+
### Using the CLI
|
|
199
|
+
|
|
200
|
+
The easiest way to use Roomz from the command line:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# Start the CLI
|
|
204
|
+
uv run roomz-cli
|
|
205
|
+
|
|
206
|
+
# Or with a custom server
|
|
207
|
+
uv run roomz-cli --server http://your-server:8000
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Commands:**
|
|
211
|
+
- `/login <email>` - Request a magic link
|
|
212
|
+
- `/token <token>` - Connect with magic link token
|
|
213
|
+
- `/logout` - Disconnect and clear session
|
|
214
|
+
- `/quit` - Exit the CLI
|
|
215
|
+
|
|
216
|
+
**Features:**
|
|
217
|
+
- Session caching (auto-reconnect on restart)
|
|
218
|
+
- Split-screen TUI with message history
|
|
219
|
+
- Color-coded messages (your messages in green)
|
|
220
|
+
- Multiline support (Enter to send, Ctrl+Enter for new line)
|
|
221
|
+
|
|
222
|
+
### Using the AsyncClient
|
|
223
|
+
|
|
224
|
+
For programmatic access in your Python applications:
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
from roomz.client import AsyncClient
|
|
228
|
+
|
|
229
|
+
# Create client with session caching
|
|
230
|
+
client = AsyncClient(
|
|
231
|
+
server_url="http://localhost:8000",
|
|
232
|
+
session_cache_file="~/.roomz/session.json"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# Register event handlers
|
|
236
|
+
client.on("message", lambda data: print(f"{data['user']['email']}: {data['content']}"))
|
|
237
|
+
client.on("user_joined", lambda data: print(f"{data['user']['email']} joined"))
|
|
238
|
+
|
|
239
|
+
# Request magic link
|
|
240
|
+
await client.login("user@example.com")
|
|
241
|
+
|
|
242
|
+
# Connect with magic link token
|
|
243
|
+
await client.connect(token="magic-link-token")
|
|
244
|
+
|
|
245
|
+
# Or reconnect with cached session
|
|
246
|
+
await client.connect()
|
|
247
|
+
|
|
248
|
+
# Send message
|
|
249
|
+
result = await client.send("Hello, world!")
|
|
250
|
+
if "error" in result:
|
|
251
|
+
print(f"Failed: {result['error']}")
|
|
252
|
+
|
|
253
|
+
# Disconnect
|
|
254
|
+
await client.disconnect()
|
|
255
|
+
client.clear_cached_session()
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Using the SyncClient
|
|
259
|
+
|
|
260
|
+
For synchronous applications:
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
from roomz.client import SyncClient
|
|
264
|
+
|
|
265
|
+
with SyncClient(server_url="http://localhost:8000", session_token="token") as client:
|
|
266
|
+
client.on("message", lambda data: print(data['content']))
|
|
267
|
+
result = client.send("Hello!")
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Session Caching
|
|
271
|
+
|
|
272
|
+
The client can cache session cookies for automatic reconnection:
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
# Enable caching (recommended for CLI apps)
|
|
276
|
+
client = AsyncClient(
|
|
277
|
+
server_url="http://localhost:8000",
|
|
278
|
+
session_cache_file=Path.home() / ".roomz" / "session.json"
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Disable caching (default)
|
|
282
|
+
client = AsyncClient(server_url="http://localhost:8000")
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Technology Stack
|
|
286
|
+
|
|
287
|
+
| Layer | Technology |
|
|
288
|
+
|-------|------------|
|
|
289
|
+
| Backend | Quart (async Flask), SocketIO |
|
|
290
|
+
| Frontend | Vue 3, Vuetify 4 |
|
|
291
|
+
| Framework | Baseweb |
|
|
292
|
+
| Runtime | Python 3.10+ |
|
|
293
|
+
| Server | Gunicorn + Uvicorn |
|
|
294
|
+
| Auth | Magic links with httpOnly cookies |
|
|
295
|
+
|
|
296
|
+
## Architecture
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
Browser (Vue 3 + Vuetify 4)
|
|
300
|
+
↓ HTTP POST /auth/request-magic-link
|
|
301
|
+
Magic Link Email (or console in dev)
|
|
302
|
+
↓ HTTP GET /auth/verify?token=...
|
|
303
|
+
JWT Cookie Set (httpOnly, SameSite=Strict)
|
|
304
|
+
↓ WebSocket with JWT cookie auth
|
|
305
|
+
Quart Server + SocketIO
|
|
306
|
+
↓ JWT Validation + ALLOWED_EMAILS check
|
|
307
|
+
Connected Users
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Stateless Authentication**: Sessions use JWT tokens, enabling server restarts without losing sessions. Access is controlled by the `ALLOWED_EMAILS` environment variable.
|
|
311
|
+
|
|
312
|
+
## Project Structure
|
|
313
|
+
|
|
314
|
+
```
|
|
315
|
+
roomz/
|
|
316
|
+
├── app/
|
|
317
|
+
│ ├── __init__.py # Quart app + SocketIO + Auth endpoints
|
|
318
|
+
│ ├── auth.py # Magic link and session management
|
|
319
|
+
│ ├── models.py # Session and magic link models
|
|
320
|
+
│ ├── components/auth/ # AuthDialog Vue component
|
|
321
|
+
│ └── pages/chat/ # Chat page Vue component
|
|
322
|
+
├── src/roomz/
|
|
323
|
+
│ ├── client/ # Python client library
|
|
324
|
+
│ │ ├── async_client.py # AsyncClient implementation
|
|
325
|
+
│ │ ├── sync_client.py # SyncClient wrapper
|
|
326
|
+
│ │ ├── events.py # Event emitter
|
|
327
|
+
│ │ └── exceptions.py # Client exceptions
|
|
328
|
+
│ └── cli/ # Command-line interface
|
|
329
|
+
│ ├── app_tui.py # Textual TUI application
|
|
330
|
+
│ └── styles/ # TUI stylesheets
|
|
331
|
+
├── tests/ # Test suite
|
|
332
|
+
├── analysis/ # Design documents
|
|
333
|
+
├── reporting/ # Task reports
|
|
334
|
+
├── pyproject.toml # Project configuration
|
|
335
|
+
└── README.md # This file
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Development
|
|
339
|
+
|
|
340
|
+
See [TODO.md](TODO.md) for planned features and [REQUIREMENTS.md](REQUIREMENTS.md) for full requirements list.
|
|
341
|
+
|
|
342
|
+
## License
|
|
343
|
+
|
|
344
|
+
MIT License - See [LICENSE](LICENSE) for details.
|
|
345
|
+
|
|
346
|
+
## Credits
|
|
347
|
+
|
|
348
|
+
Built with:
|
|
349
|
+
- [Baseweb](https://github.com/christophevg/baseweb) — Web framework
|
|
350
|
+
- [Quart](https://pgjones.gitlab.io/quart/) — Async Flask
|
|
351
|
+
- [Socket.IO](https://python-socketio.readthedocs.io/) — Real-time communication
|
|
352
|
+
- [Vue 3](https://vuejs.org/) — Frontend framework
|
|
353
|
+
- [Vuetify 4](https://vuetifyjs.com/) — Material Design components
|