modified: README.rst
modified: index.html modified: index2.html modified: remarkbox/__init__.py modified: remarkbox/lib/mail.py modified: remarkbox/models/meta.py modified: remarkbox/views/__init__.py
This commit is contained in:
parent
fdfc760865
commit
69ada15278
7 changed files with 74 additions and 24 deletions
|
|
@ -72,6 +72,14 @@ What is Remarkbox?
|
||||||
------------------
|
------------------
|
||||||
Remarkbox is a standalone question and answer site (forum) or an embedded comments/product reviews service that works anywhere HTML is supported.
|
Remarkbox is a standalone question and answer site (forum) or an embedded comments/product reviews service that works anywhere HTML is supported.
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
- **Dark Mode Support:** User-configurable theme preferences with automatic theme detection for embedded contexts
|
||||||
|
- **Passwordless Authentication:** One-time-password codes via email for secure registration and login
|
||||||
|
- **Multi-tenant Architecture:** Host multiple forums and comment systems on a single installation
|
||||||
|
- **Customizable Themes:** Plugin-based theme system supporting custom branding and styling
|
||||||
|
- **Embed Anywhere:** Works with static sites, WordPress, or any platform that supports HTML
|
||||||
|
|
||||||
Project Goals
|
Project Goals
|
||||||
==============================================
|
==============================================
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,11 +57,11 @@ Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||||
var thread_title = window.document.title;
|
var thread_title = window.document.title;
|
||||||
var thread_fragment = window.location.hash;
|
var thread_fragment = window.location.hash;
|
||||||
|
|
||||||
<!-- rb owner was here -->
|
|
||||||
var rb_src = "http://127.0.0.1:6543/embed" +
|
var rb_src = "http://127.0.0.1:6543/embed" +
|
||||||
"?rb_owner_key=" + rb_owner_key +
|
"?rb_owner_key=" + rb_owner_key +
|
||||||
"&thread_title=" + escape(thread_title) +
|
"&thread_title=" + escape(thread_title) +
|
||||||
"&thread_uri=" + encodeURIComponent(thread_uri) +
|
"&thread_uri=" + encodeURIComponent(thread_uri) +
|
||||||
|
"&mode=light" +
|
||||||
thread_fragment;
|
thread_fragment;
|
||||||
|
|
||||||
function create_remarkbox_iframe() {
|
function create_remarkbox_iframe() {
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||||
var thread_fragment = window.location.hash;
|
var thread_fragment = window.location.hash;
|
||||||
var email = "russell.ballestrini@gmail.com";
|
var email = "russell.ballestrini@gmail.com";
|
||||||
function create_remarkbox_iframe() {
|
function create_remarkbox_iframe() {
|
||||||
var src = "http://127.0.0.1:6543/embed?rb_owner_key=" + rb_owner_key + "&email=" + email + "&thread_uri=" + thread_uri;
|
var src = "http://127.0.0.1:6543/embed?rb_owner_key=" + rb_owner_key + "&email=" + email + "&thread_uri=" + thread_uri + "&mode=light";
|
||||||
var ifrm = document.createElement("iframe");
|
var ifrm = document.createElement("iframe");
|
||||||
ifrm.setAttribute("id", "remarkbox-iframe");
|
ifrm.setAttribute("id", "remarkbox-iframe");
|
||||||
ifrm.setAttribute("scrolling", "no");
|
ifrm.setAttribute("scrolling", "no");
|
||||||
|
|
@ -86,5 +86,4 @@ Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,6 @@ import re
|
||||||
# needed to load themes.
|
# needed to load themes.
|
||||||
from pkg_resources import iter_entry_points
|
from pkg_resources import iter_entry_points
|
||||||
|
|
||||||
# needed to support expanding ENV vars from ini.
|
|
||||||
from os.path import expandvars
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
@ -43,18 +40,47 @@ def get_int_or_bool_or_none_or_str(value):
|
||||||
Given a string value pulled from a configuration file,
|
Given a string value pulled from a configuration file,
|
||||||
this function attempts to return the value with the proper type.
|
this function attempts to return the value with the proper type.
|
||||||
"""
|
"""
|
||||||
|
# Handle non-string values
|
||||||
|
if not isinstance(value, str):
|
||||||
|
return value
|
||||||
|
|
||||||
|
# Handle string values
|
||||||
try:
|
try:
|
||||||
return int(value)
|
return int(value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
if value.lower() in {"yes", "y", "true", "y"}:
|
value_lower = value.lower()
|
||||||
|
if value_lower in {"yes", "y", "true", "t", "1"}:
|
||||||
return True
|
return True
|
||||||
elif value.lower() in {"no", "n", "false", "f"}:
|
elif value_lower in {"no", "n", "false", "f", "0"}:
|
||||||
return False
|
return False
|
||||||
elif value.lower() == "none":
|
elif value_lower in {"none", "null"}:
|
||||||
return None
|
return None
|
||||||
return str(value)
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
|
def expand_env_vars(value):
|
||||||
|
"""Expand environment variables including ${VAR:-default} syntax."""
|
||||||
|
if not isinstance(value, str):
|
||||||
|
return value
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Handle ${VAR:-default} syntax
|
||||||
|
# Use a non-greedy match to stop at the first closing brace
|
||||||
|
pattern = r"\$\{([^:}]*)(?::-([^}]*?))?\}"
|
||||||
|
|
||||||
|
def replacer(match):
|
||||||
|
var_name = match.group(1)
|
||||||
|
# Handle empty variable name case ${:-default}
|
||||||
|
if not var_name:
|
||||||
|
return match.group(2) if match.group(2) is not None else match.group(0)
|
||||||
|
default_value = match.group(2) if match.group(2) is not None else ""
|
||||||
|
return os.environ.get(var_name, default_value)
|
||||||
|
|
||||||
|
return re.sub(pattern, replacer, value)
|
||||||
|
|
||||||
|
|
||||||
def get_children_settings(settings, parent_key):
|
def get_children_settings(settings, parent_key):
|
||||||
"""
|
"""
|
||||||
Accept a settings dict and parent key, return dict of children
|
Accept a settings dict and parent key, return dict of children
|
||||||
|
|
@ -73,18 +99,14 @@ def get_children_settings(settings, parent_key):
|
||||||
{'hashalg': 'md5'}
|
{'hashalg': 'md5'}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# needed to support expanding ENV vars from ini.
|
|
||||||
from os.path import expandvars
|
|
||||||
|
|
||||||
# the +1 is the . between parent and child settings.
|
# the +1 is the . between parent and child settings.
|
||||||
parent_len = len(parent_key) + 1
|
parent_len = len(parent_key) + 1
|
||||||
children = {}
|
children = {}
|
||||||
for key, value in settings.items():
|
for key, value in settings.items():
|
||||||
if parent_key in key:
|
if parent_key in key:
|
||||||
# expandvars replaces template with ENV vars.
|
# Expand environment variables with support for defaults
|
||||||
children[key[parent_len:]] = get_int_or_bool_or_none_or_str(
|
expanded_value = expand_env_vars(value)
|
||||||
expandvars(value)
|
children[key[parent_len:]] = get_int_or_bool_or_none_or_str(expanded_value)
|
||||||
)
|
|
||||||
return children
|
return children
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -155,6 +177,11 @@ def maybe_root_domain(string):
|
||||||
def main(global_config, **settings):
|
def main(global_config, **settings):
|
||||||
"""This function returns a Pyramid WSGI application."""
|
"""This function returns a Pyramid WSGI application."""
|
||||||
|
|
||||||
|
# Expand environment variables in all settings using our custom function
|
||||||
|
for key, value in list(settings.items()):
|
||||||
|
if isinstance(value, str):
|
||||||
|
settings[key] = expand_env_vars(value)
|
||||||
|
|
||||||
app_settings = get_children_settings(settings, "app")
|
app_settings = get_children_settings(settings, "app")
|
||||||
session_settings = get_children_settings(settings, "session")
|
session_settings = get_children_settings(settings, "session")
|
||||||
|
|
||||||
|
|
@ -175,7 +202,18 @@ def main(global_config, **settings):
|
||||||
session_settings["domain"] = root_domain
|
session_settings["domain"] = root_domain
|
||||||
|
|
||||||
factory = SignedCookieSessionFactory(**session_settings)
|
factory = SignedCookieSessionFactory(**session_settings)
|
||||||
return factory(request)
|
session = factory(request)
|
||||||
|
|
||||||
|
# Log session contents and size on every request
|
||||||
|
import pickle
|
||||||
|
session_dict = dict(session)
|
||||||
|
serialized = pickle.dumps(session_dict)
|
||||||
|
log.info(f"Session total size: {len(serialized)} bytes, keys: {list(session_dict.keys())}")
|
||||||
|
for key, value in session_dict.items():
|
||||||
|
item_size = len(pickle.dumps({key: value}))
|
||||||
|
log.info(f" Session['{key}'] = {item_size} bytes (type: {type(value).__name__})")
|
||||||
|
|
||||||
|
return session
|
||||||
|
|
||||||
# setup session factory to use unencrypted but signed cookies.
|
# setup session factory to use unencrypted but signed cookies.
|
||||||
# session_factory = SignedCookieSessionFactory(**session_settings)
|
# session_factory = SignedCookieSessionFactory(**session_settings)
|
||||||
|
|
|
||||||
|
|
@ -187,12 +187,12 @@ def send_verification_digits_to_email(request, to_email, raw_digits):
|
||||||
message_text = WELCOME_1_TEXT.format(raw_digits)
|
message_text = WELCOME_1_TEXT.format(raw_digits)
|
||||||
message_html = WELCOME_1_HTML.format(subject, raw_digits)
|
message_html = WELCOME_1_HTML.format(subject, raw_digits)
|
||||||
|
|
||||||
if not request.user.verified:
|
if request.user and request.user.verified:
|
||||||
message_text = WELCOME_1_TEXT.format(raw_digits)
|
|
||||||
message_html = WELCOME_1_HTML.format(subject, raw_digits)
|
|
||||||
else:
|
|
||||||
message_text = WELCOME_2_TEXT.format(raw_digits)
|
message_text = WELCOME_2_TEXT.format(raw_digits)
|
||||||
message_html = WELCOME_2_HTML.format(subject, raw_digits)
|
message_html = WELCOME_2_HTML.format(subject, raw_digits)
|
||||||
|
else:
|
||||||
|
message_text = WELCOME_1_TEXT.format(raw_digits)
|
||||||
|
message_html = WELCOME_1_HTML.format(subject, raw_digits)
|
||||||
|
|
||||||
send_pyramid_email(request, to_email, subject, message_text, message_html)
|
send_pyramid_email(request, to_email, subject, message_text, message_html)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ from sqlalchemy import ForeignKey
|
||||||
|
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
|
import base64
|
||||||
import uuid
|
import uuid
|
||||||
from sqlalchemy_utils import UUIDType as TempUUIDType
|
from sqlalchemy_utils import UUIDType as TempUUIDType
|
||||||
|
|
||||||
|
|
@ -75,10 +76,10 @@ def short_id_to_bytes(short_id):
|
||||||
"""Accept a short_id (sanitized url safe base64 string) and return a byte string.
|
"""Accept a short_id (sanitized url safe base64 string) and return a byte string.
|
||||||
|
|
||||||
>>> short_id_to_bytes('dbHeSEFLEeeuz5xONpxxWA')
|
>>> short_id_to_bytes('dbHeSEFLEeeuz5xONpxxWA')
|
||||||
'u\xb1\xdeHAK\x11\xe7\xae\xcf\x9cN6\x9cqX'
|
b'u\xb1\xdeHAK\x11\xe7\xae\xcf\x9cN6\x9cqX'
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return (short_id + "===").replace("_", "/").replace("-", "+").decode("base64")
|
return base64.b64decode((short_id + "===").replace("_", "/").replace("-", "+"))
|
||||||
|
|
||||||
|
|
||||||
def id_to_uuid(the_id):
|
def id_to_uuid(the_id):
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,11 @@ def nodes_pending_verify(request):
|
||||||
|
|
||||||
def set_node_to_pending_in_session(request, node):
|
def set_node_to_pending_in_session(request, node):
|
||||||
"""Update session to make node go into pending_verify state."""
|
"""Update session to make node go into pending_verify state."""
|
||||||
nodes_pending_verify(request).append(str(node.id_without_dashes))
|
pending = nodes_pending_verify(request)
|
||||||
|
node_id = str(node.id_without_dashes)
|
||||||
|
# Only add if not already present and list isn't too large
|
||||||
|
if node_id not in pending and len(pending) < 50:
|
||||||
|
pending.append(node_id)
|
||||||
|
|
||||||
|
|
||||||
def verify_pending_nodes_in_session(request, user):
|
def verify_pending_nodes_in_session(request, user):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue