supernote 0.6.2__tar.gz → 0.7.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.
- {supernote-0.6.2 → supernote-0.7.0}/PKG-INFO +1 -1
- {supernote-0.6.2 → supernote-0.7.0}/pyproject.toml +1 -1
- {supernote-0.6.2 → supernote-0.7.0}/supernote/cli/server.py +23 -17
- {supernote-0.6.2 → supernote-0.7.0}/supernote/server/app.py +11 -2
- {supernote-0.6.2 → supernote-0.7.0}/supernote/server/config.py +24 -17
- {supernote-0.6.2 → supernote-0.7.0}/supernote.egg-info/PKG-INFO +1 -1
- {supernote-0.6.2 → supernote-0.7.0}/LICENSE +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/README.md +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/setup.cfg +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/__init__.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/cli/__init__.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/cli/client.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/cli/main.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/cli/notebook.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/client/__init__.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/client/api_model.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/client/auth.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/client/client.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/client/cloud_client.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/client/exceptions.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/client/login_client.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/notebook/__init__.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/notebook/color.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/notebook/converter.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/notebook/decoder.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/notebook/exceptions.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/notebook/fileformat.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/notebook/manipulator.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/notebook/parser.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/notebook/utils.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/py.typed +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/server/__init__.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/server/models/__init__.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/server/models/auth.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/server/models/base.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/server/models/file.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/server/routes/auth.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/server/routes/decorators.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/server/routes/file.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/server/routes/system.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/server/services/__init__.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/server/services/file.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/server/services/state.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/server/services/storage.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote/server/services/user.py +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote.egg-info/SOURCES.txt +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote.egg-info/dependency_links.txt +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote.egg-info/entry_points.txt +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote.egg-info/requires.txt +0 -0
- {supernote-0.6.2 → supernote-0.7.0}/supernote.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: supernote
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: All-in-one toolkit for Supernote devices: parse notebooks, self-host services, access services
|
|
5
5
|
Author-email: jya <jya@wizmy.net>, Allen Porter <allen.porter@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -4,7 +4,7 @@ requires = ["setuptools>=77.0"]
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "supernote"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7.0"
|
|
8
8
|
license = "Apache-2.0"
|
|
9
9
|
license-files = ["LICENSE"]
|
|
10
10
|
description = "All-in-one toolkit for Supernote devices: parse notebooks, self-host services, access services"
|
|
@@ -2,25 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import getpass
|
|
5
|
+
import hashlib
|
|
5
6
|
import secrets
|
|
6
7
|
|
|
7
|
-
import yaml
|
|
8
|
-
|
|
9
8
|
from supernote.server import app as server_app
|
|
10
|
-
from supernote.server.config import
|
|
11
|
-
from supernote.server.services.user import UserService
|
|
9
|
+
from supernote.server.config import ServerConfig, UserEntry
|
|
12
10
|
|
|
13
11
|
|
|
14
|
-
def
|
|
12
|
+
def list_users(config_dir: str | None):
|
|
15
13
|
# Load base config
|
|
16
14
|
server_config = ServerConfig.load(config_dir)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def list_users(config_dir: str | None):
|
|
21
|
-
config = get_auth_config(config_dir)
|
|
22
|
-
service = UserService(config)
|
|
23
|
-
for user in service.list_users():
|
|
15
|
+
for user in server_config.auth.users:
|
|
24
16
|
status = "active" if user.is_active else "inactive"
|
|
25
17
|
print(f"{user.username} ({status})")
|
|
26
18
|
|
|
@@ -30,13 +22,17 @@ def add_user(username: str, password: str | None = None):
|
|
|
30
22
|
password = getpass.getpass(f"Password for {username}: ")
|
|
31
23
|
|
|
32
24
|
# Generate the YAML snippet for a single user
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
password_md5 = hashlib.md5(password.encode()).hexdigest()
|
|
26
|
+
user_entry = UserEntry(
|
|
27
|
+
username=username,
|
|
28
|
+
password_md5=password_md5,
|
|
29
|
+
display_name=username,
|
|
30
|
+
)
|
|
35
31
|
|
|
36
32
|
# Print YAML to stdout
|
|
37
33
|
print("# Generated Supernote User Entry")
|
|
38
|
-
print("#
|
|
39
|
-
print(
|
|
34
|
+
print("# Add this entry to the 'users' list in your config.yaml file.")
|
|
35
|
+
print("- " + user_entry.to_yaml(omit_none=True).strip().replace("\n", "\n "))
|
|
40
36
|
|
|
41
37
|
|
|
42
38
|
def config_init():
|
|
@@ -44,10 +40,20 @@ def config_init():
|
|
|
44
40
|
secret = secrets.token_hex(32)
|
|
45
41
|
config = ServerConfig()
|
|
46
42
|
config.auth.secret_key = secret
|
|
43
|
+
config.trace_log_file = None # Use system storage default
|
|
44
|
+
|
|
45
|
+
# Add a sample user entry to the output
|
|
46
|
+
sample_user = UserEntry(
|
|
47
|
+
username="user@example.com",
|
|
48
|
+
password_md5=hashlib.md5(b"password").hexdigest(),
|
|
49
|
+
display_name="Sample User",
|
|
50
|
+
)
|
|
51
|
+
config.auth.users = [sample_user]
|
|
47
52
|
|
|
48
53
|
print("# Generated Supernote Server Configuration")
|
|
49
54
|
print("# Save this as config.yaml.")
|
|
50
|
-
print(
|
|
55
|
+
print("# You should change the sample user entry below.")
|
|
56
|
+
print(config.to_yaml(omit_none=True))
|
|
51
57
|
|
|
52
58
|
|
|
53
59
|
def generate_secret():
|
|
@@ -57,13 +57,18 @@ async def trace_middleware(
|
|
|
57
57
|
|
|
58
58
|
# Get config from app
|
|
59
59
|
server_config: ServerConfig = request.app["config"]
|
|
60
|
+
if not server_config.trace_log_file:
|
|
61
|
+
return await handler(request)
|
|
62
|
+
|
|
63
|
+
trace_log_path = Path(server_config.trace_log_file)
|
|
60
64
|
|
|
61
65
|
try:
|
|
62
|
-
|
|
66
|
+
trace_log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
with open(trace_log_path, "a") as f:
|
|
63
68
|
f.write(json.dumps(log_entry) + "\n")
|
|
64
69
|
f.flush()
|
|
65
70
|
except Exception as e:
|
|
66
|
-
logger.error(f"Failed to write to trace log: {e}")
|
|
71
|
+
logger.error(f"Failed to write to trace log at {trace_log_path}: {e}")
|
|
67
72
|
|
|
68
73
|
logger.info(
|
|
69
74
|
f"Trace: {request.method} {request.path} (Body: {len(body_bytes) if body_bytes else 0} bytes)"
|
|
@@ -125,6 +130,10 @@ def create_app(
|
|
|
125
130
|
app["file_service"] = FileService(storage_service)
|
|
126
131
|
app["sync_locks"] = {} # user -> (equipment_no, expiry_time)
|
|
127
132
|
|
|
133
|
+
# Resolve trace log path if not set
|
|
134
|
+
if not config.trace_log_file:
|
|
135
|
+
config.trace_log_file = str(storage_service.system_dir / "trace.log")
|
|
136
|
+
|
|
128
137
|
# Register routes
|
|
129
138
|
app.add_routes(system.routes)
|
|
130
139
|
app.add_routes(auth.routes)
|
|
@@ -3,23 +3,26 @@ import os
|
|
|
3
3
|
import secrets
|
|
4
4
|
from dataclasses import dataclass, field
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import Any
|
|
7
6
|
|
|
8
|
-
import
|
|
9
|
-
from mashumaro.mixins.
|
|
7
|
+
from mashumaro.config import TO_DICT_ADD_OMIT_NONE_FLAG, BaseConfig
|
|
8
|
+
from mashumaro.mixins.yaml import DataClassYAMLMixin
|
|
10
9
|
|
|
11
10
|
logger = logging.getLogger(__name__)
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
@dataclass
|
|
15
|
-
class AuthConfig(
|
|
14
|
+
class AuthConfig(DataClassYAMLMixin):
|
|
16
15
|
secret_key: str = ""
|
|
17
16
|
expiration_hours: int = 24
|
|
18
17
|
users: list["UserEntry"] = field(default_factory=list)
|
|
19
18
|
|
|
19
|
+
class Config(BaseConfig):
|
|
20
|
+
omit_none = True
|
|
21
|
+
code_generation_options = [TO_DICT_ADD_OMIT_NONE_FLAG] # type: ignore[list-item]
|
|
22
|
+
|
|
20
23
|
|
|
21
24
|
@dataclass
|
|
22
|
-
class UserEntry(
|
|
25
|
+
class UserEntry(DataClassYAMLMixin):
|
|
23
26
|
username: str
|
|
24
27
|
password_md5: str
|
|
25
28
|
is_active: bool = True
|
|
@@ -29,12 +32,16 @@ class UserEntry(DataClassDictMixin):
|
|
|
29
32
|
avatar: str | None = None
|
|
30
33
|
total_capacity: str = "25485312"
|
|
31
34
|
|
|
35
|
+
class Config(BaseConfig):
|
|
36
|
+
omit_none = True
|
|
37
|
+
code_generation_options = [TO_DICT_ADD_OMIT_NONE_FLAG] # type: ignore[list-item]
|
|
38
|
+
|
|
32
39
|
|
|
33
40
|
@dataclass
|
|
34
|
-
class ServerConfig(
|
|
41
|
+
class ServerConfig(DataClassYAMLMixin):
|
|
35
42
|
host: str = "0.0.0.0"
|
|
36
43
|
port: int = 8080
|
|
37
|
-
trace_log_file: str =
|
|
44
|
+
trace_log_file: str | None = None
|
|
38
45
|
storage_dir: str = "storage"
|
|
39
46
|
auth: AuthConfig = field(default_factory=AuthConfig)
|
|
40
47
|
|
|
@@ -47,25 +54,18 @@ class ServerConfig(DataClassDictMixin):
|
|
|
47
54
|
config_dir_path = Path(config_dir)
|
|
48
55
|
config_file = config_dir_path / "config.yaml"
|
|
49
56
|
|
|
50
|
-
|
|
57
|
+
config = cls()
|
|
51
58
|
if config_file.exists():
|
|
52
59
|
try:
|
|
53
60
|
with open(config_file, "r") as f:
|
|
54
|
-
|
|
61
|
+
config = cls.from_yaml(f.read())
|
|
55
62
|
except Exception as e:
|
|
56
63
|
logger.warning(f"Failed to load config file {config_file}: {e}")
|
|
57
64
|
|
|
58
|
-
try:
|
|
59
|
-
config = cls.from_dict(file_data)
|
|
60
|
-
except Exception as e:
|
|
61
|
-
logger.warning(
|
|
62
|
-
f"Failed to parse config file {config_file}, using defaults: {e}"
|
|
63
|
-
)
|
|
64
|
-
config = cls()
|
|
65
|
-
|
|
66
65
|
# 4. JWT Secret priority: Env > Config > Random(in-memory only)
|
|
67
66
|
env_secret = os.getenv("SUPERNOTE_JWT_SECRET")
|
|
68
67
|
if env_secret:
|
|
68
|
+
logger.info("Using SUPERNOTE_JWT_SECRET")
|
|
69
69
|
config.auth.secret_key = env_secret
|
|
70
70
|
|
|
71
71
|
if not config.auth.secret_key:
|
|
@@ -77,14 +77,21 @@ class ServerConfig(DataClassDictMixin):
|
|
|
77
77
|
# Apply other env var overrides
|
|
78
78
|
if os.getenv("SUPERNOTE_HOST"):
|
|
79
79
|
config.host = os.getenv("SUPERNOTE_HOST", config.host)
|
|
80
|
+
logger.info(f"Using SUPERNOTE_HOST: {config.host}")
|
|
80
81
|
|
|
81
82
|
if os.getenv("SUPERNOTE_PORT"):
|
|
82
83
|
try:
|
|
83
84
|
config.port = int(os.getenv("SUPERNOTE_PORT", str(config.port)))
|
|
85
|
+
logger.info(f"Using SUPERNOTE_PORT: {config.port}")
|
|
84
86
|
except ValueError:
|
|
85
87
|
pass
|
|
86
88
|
|
|
87
89
|
if os.getenv("SUPERNOTE_STORAGE_DIR"):
|
|
88
90
|
config.storage_dir = os.getenv("SUPERNOTE_STORAGE_DIR", config.storage_dir)
|
|
91
|
+
logger.info(f"Using SUPERNOTE_STORAGE_DIR: {config.storage_dir}")
|
|
89
92
|
|
|
90
93
|
return config
|
|
94
|
+
|
|
95
|
+
class Config(BaseConfig):
|
|
96
|
+
omit_none = True
|
|
97
|
+
code_generation_options = [TO_DICT_ADD_OMIT_NONE_FLAG] # type: ignore[list-item]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: supernote
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: All-in-one toolkit for Supernote devices: parse notebooks, self-host services, access services
|
|
5
5
|
Author-email: jya <jya@wizmy.net>, Allen Porter <allen.porter@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|