diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index d4ac5837f2..f69558a6a4 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -10,7 +10,7 @@ Checklists - you can remove one that is not applicable (ie. remove backend check If you need help with any of these, please ask :) ---> **Backend checklist** -- [ ] Formatted my code by running `autoflake --exclude src/proto -r -i --remove-all-unused-imports src && isort . && black .` in `app/backend` +- [ ] Formatted my code by running `ruff check --select I --fix . && ruff check . && ruff format .` in `app/backend` - [ ] Added tests for any new code or added a regression test if fixing a bug - [ ] All tests pass - [ ] Run the backend locally and it works diff --git a/app/.devcontainer/docker-compose.yml b/app/.devcontainer/docker-compose.yml index 3141c4565f..0f17ba67c0 100644 --- a/app/.devcontainer/docker-compose.yml +++ b/app/.devcontainer/docker-compose.yml @@ -35,4 +35,3 @@ services: # Overrides default command so things don't shut down after the process ends. command: /bin/sh -c "while sleep 1000; do :; done" - diff --git a/app/.gitlab-ci.yml b/app/.gitlab-ci.yml index c25ee5a4d5..c17741ac9c 100644 --- a/app/.gitlab-ci.yml +++ b/app/.gitlab-ci.yml @@ -266,9 +266,9 @@ test:backend-format: default: false script: - cd app/backend - - autoflake --exclude src/proto -r -i --remove-all-unused-imports --check src - - isort --check --diff . - - black --check --diff . + - ruff check . + - ruff check --diff . + - ruff format --diff . rules: - if: ($DO_CHECKS == "true") && ($CI_COMMIT_BRANCH == $RELEASE_BRANCH) changes: diff --git a/app/backend/.pre-commit-config.yaml b/app/backend/.pre-commit-config.yaml new file mode 100644 index 0000000000..886ad98828 --- /dev/null +++ b/app/backend/.pre-commit-config.yaml @@ -0,0 +1,33 @@ +default_language_version: + python: python3.12 + +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: end-of-file-fixer + exclude: ^(.*.csv|.*.json|.*.svg) + - id: trailing-whitespace + - id: check-toml + - id: check-yaml + - id: check-added-large-files + name: Check for added large files + description: Prevent giant files from being committed + entry: check-added-large-files + language: python + args: ['--maxkb=350', '--enforce-all'] + - id: detect-private-key + +- repo: https://github.com/charliermarsh/ruff-pre-commit + rev: 'v0.4.8' + hooks: + - id: ruff + description: Linter checks (without import sorting) + files: ^app/backend/ + args: ['--ignore', 'I'] + - id: ruff + files: ^app/backend/ + args: ['--select', 'I', '--fix'] + description: Sort imports + - id: ruff-format + files: ^app/backend/ diff --git a/app/backend/pyproject.toml b/app/backend/pyproject.toml index 859ffab874..59a353c0fb 100644 --- a/app/backend/pyproject.toml +++ b/app/backend/pyproject.toml @@ -1,12 +1,30 @@ -[tool.black] +[tool.ruff] line-length = 120 -[tool.isort] -skip_gitignore = true -include_trailing_comma = true -multi_line_output = 3 -line_length = 120 -known_first_party = ["couchers", "proto"] +[tool.ruff.lint] +select = [ + "E", # pycodestyle + "F", # pyflakes + "UP", # pyupgrade + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "A", # flake8-builtins + "I", # isort +] +ignore = [ + "B007", # Loop control variable not used within loop body + "C408", # Unnecessary `dict` call (rewrite as a literal) + "E501", # Line too long + "E711", # Comparison to `None` should be `cond is None` + "E712", # Avoid equality comparisons to `True`; use `if approved:` for truth checks + "F811", # Redefinition of unused variable + "F841", # Local variable is assigned to but never used + "UP015", # Unnecessary open mode parameters +] + +[tool.ruff.lint.isort] +known-first-party = ["couchers", "proto", "tests", "dummy_data"] +extra-standard-library = ["zoneinfo"] [tool.coverage.run] omit = ["src/proto/*", "src/couchers/migrations/*", "src/dummy_data.py"] diff --git a/app/backend/requirements.in b/app/backend/requirements.in index af13950f61..79e54f5f83 100644 --- a/app/backend/requirements.in +++ b/app/backend/requirements.in @@ -1,15 +1,13 @@ alembic -autoflake -black boto3 cryptography geoalchemy2 grpcio http_ece -isort Jinja2 luhn phonenumbers +pre-commit prometheus-client protobuf psycopg2-binary @@ -21,6 +19,7 @@ python-dateutil pytz pyyaml requests +ruff sentry-sdk Shapely sqlalchemy diff --git a/app/backend/requirements.txt b/app/backend/requirements.txt index 7a10cfd930..1723fcf52d 100644 --- a/app/backend/requirements.txt +++ b/app/backend/requirements.txt @@ -6,10 +6,6 @@ # alembic==1.13.1 # via -r requirements.in -autoflake==2.3.1 - # via -r requirements.in -black==24.4.2 - # via -r requirements.in boto3==1.34.122 # via -r requirements.in botocore==1.34.122 @@ -24,10 +20,10 @@ cffi==1.16.0 # via # cryptography # pynacl +cfgv==3.4.0 + # via pre-commit charset-normalizer==3.3.2 # via requests -click==8.1.7 - # via black coverage[toml]==7.5.0 # via pytest-cov cryptography==42.0.8 @@ -35,6 +31,10 @@ cryptography==42.0.8 # -r requirements.in # http-ece # py-vapid +distlib==0.3.8 + # via virtualenv +filelock==3.14.0 + # via virtualenv geoalchemy2==0.15.1 # via -r requirements.in greenlet==3.0.3 @@ -43,12 +43,12 @@ grpcio==1.64.1 # via -r requirements.in http-ece==1.2.0 # via -r requirements.in +identify==2.5.36 + # via pre-commit idna==3.7 # via requests iniconfig==2.0.0 # via pytest -isort==5.13.2 - # via -r requirements.in jinja2==3.1.4 # via -r requirements.in jmespath==1.0.1 @@ -63,23 +63,22 @@ markupsafe==2.1.5 # via # jinja2 # mako -mypy-extensions==1.0.0 - # via black +nodeenv==1.9.1 + # via pre-commit numpy==1.26.4 # via shapely packaging==24.0 # via - # black # geoalchemy2 # pytest -pathspec==0.12.1 - # via black phonenumbers==8.13.37 # via -r requirements.in -platformdirs==4.2.1 - # via black +platformdirs==4.2.2 + # via virtualenv pluggy==1.5.0 # via pytest +pre-commit==3.7.1 + # via -r requirements.in prometheus-client==0.20.0 # via -r requirements.in protobuf==5.27.1 @@ -90,8 +89,6 @@ py-vapid==1.9.1 # via -r requirements.in pycparser==2.22 # via cffi -pyflakes==3.2.0 - # via autoflake pynacl==1.5.0 # via -r requirements.in pytest==8.2.2 @@ -107,11 +104,15 @@ python-dateutil==2.9.0.post0 pytz==2024.1 # via -r requirements.in pyyaml==6.0.1 - # via -r requirements.in + # via + # -r requirements.in + # pre-commit requests==2.32.3 # via # -r requirements.in # stripe +ruff==0.4.8 + # via -r requirements.in s3transfer==0.10.1 # via boto3 sentry-sdk==2.3.1 @@ -140,3 +141,5 @@ urllib3==2.2.1 # botocore # requests # sentry-sdk +virtualenv==20.26.2 + # via pre-commit diff --git a/app/backend/src/app.py b/app/backend/src/app.py index 5dfb8791e3..7b8920ff48 100644 --- a/app/backend/src/app.py +++ b/app/backend/src/app.py @@ -3,9 +3,8 @@ import sys import sentry_sdk -from sentry_sdk.integrations import argv, atexit, dedupe +from sentry_sdk.integrations import argv, atexit, dedupe, modules, stdlib, threading from sentry_sdk.integrations import logging as sentry_logging -from sentry_sdk.integrations import modules, stdlib, threading from sqlalchemy.sql import text from couchers.config import check_config, config @@ -58,28 +57,28 @@ def log_unhandled_exception(exc_type, exc_value, exc_traceback): sys.excepthook = log_unhandled_exception -logger.info(f"Checking DB connection") +logger.info("Checking DB connection") with session_scope() as session: res = session.execute(text("SELECT 42;")) if list(res) != [(42,)]: raise Exception("Failed to connect to DB") -logger.info(f"Running DB migrations") +logger.info("Running DB migrations") apply_migrations() if config["ADD_DUMMY_DATA"]: add_dummy_data() -logger.info(f"Starting") +logger.info("Starting") if config["ROLE"] in ["api", "all"]: server = create_main_server(port=1751) server.start() media_server = create_media_server(port=1753) media_server.start() - logger.info(f"Serving on 1751 (secure) and 1753 (media)") + logger.info("Serving on 1751 (secure) and 1753 (media)") if config["ROLE"] in ["scheduler", "all"]: scheduler = start_jobs_scheduler() diff --git a/app/backend/src/couchers/email/__init__.py b/app/backend/src/couchers/email/__init__.py index c39b8df196..888fa63a0d 100644 --- a/app/backend/src/couchers/email/__init__.py +++ b/app/backend/src/couchers/email/__init__.py @@ -104,7 +104,8 @@ def queue_email( ) -def enqueue_email_from_template(recipient, template_file, template_args={}, _footer_unsub_link=None): +def enqueue_email_from_template(recipient, template_file, template_args=None, _footer_unsub_link=None): + template_args = template_args or {} frontmatter, plain, html = _render_email(template_file, template_args, _footer_unsub_link=_footer_unsub_link) queue_email( config["NOTIFICATION_EMAIL_SENDER"], @@ -116,7 +117,8 @@ def enqueue_email_from_template(recipient, template_file, template_args={}, _foo ) -def enqueue_email_from_template_to_user(user, template_file, template_args={}, is_critical_email=False): +def enqueue_email_from_template_to_user(user, template_file, template_args=None, is_critical_email=False): + template_args = template_args or {} if user.do_not_email and not is_critical_email: logger.info(f"Not emailing {user} based on template {template_file} due to emails turned off") return diff --git a/app/backend/src/couchers/helpers/clusters.py b/app/backend/src/couchers/helpers/clusters.py index 0f26f1017d..0fdb8f859a 100644 --- a/app/backend/src/couchers/helpers/clusters.py +++ b/app/backend/src/couchers/helpers/clusters.py @@ -24,7 +24,7 @@ def create_cluster( admin_ids: List, is_community: bool, ): - type = "community" if is_community else "group" + cluster_type = "community" if is_community else "group" cluster = Cluster( name=name, description=description, @@ -45,7 +45,7 @@ def create_cluster( page_version = PageVersion( page=main_page, editor_user_id=creator_user_id, - title=DEFAULT_PAGE_TITLE_TEMPLATE.format(name=name, type=type), + title=DEFAULT_PAGE_TITLE_TEMPLATE.format(name=name, type=cluster_type), content=DEFAULT_PAGE_CONTENT, ) session.add(page_version) diff --git a/app/backend/src/couchers/interceptors.py b/app/backend/src/couchers/interceptors.py index 09d4d3a656..ff6f0bbf72 100644 --- a/app/backend/src/couchers/interceptors.py +++ b/app/backend/src/couchers/interceptors.py @@ -306,7 +306,7 @@ def sanitizing_function(req, context): # the code is one of the RPC error codes if this was failed through abort(), otherwise it's None if not code: logger.exception(e) - logger.info(f"Probably an unknown error! Sanitizing...") + logger.info("Probably an unknown error! Sanitizing...") context.abort(grpc.StatusCode.INTERNAL, errors.UNKNOWN_ERROR) else: logger.warning(f"RPC error: {code}") diff --git a/app/backend/src/couchers/jobs/handlers.py b/app/backend/src/couchers/jobs/handlers.py index f1cd55a55d..f05b386c2a 100644 --- a/app/backend/src/couchers/jobs/handlers.py +++ b/app/backend/src/couchers/jobs/handlers.py @@ -107,7 +107,7 @@ def send_email(payload): def purge_login_tokens(payload): - logger.info(f"Purging login tokens") + logger.info("Purging login tokens") with session_scope() as session: session.execute(delete(LoginToken).where(~LoginToken.is_valid).execution_options(synchronize_session=False)) @@ -117,7 +117,7 @@ def purge_login_tokens(payload): def purge_password_reset_tokens(payload): - logger.info(f"Purging login tokens") + logger.info("Purging login tokens") with session_scope() as session: session.execute( delete(PasswordResetToken).where(~PasswordResetToken.is_valid).execution_options(synchronize_session=False) @@ -129,7 +129,7 @@ def purge_password_reset_tokens(payload): def purge_account_deletion_tokens(payload): - logger.info(f"Purging account deletion tokens") + logger.info("Purging account deletion tokens") with session_scope() as session: session.execute( delete(AccountDeletionToken) @@ -147,25 +147,23 @@ def send_message_notifications(payload): Sends out email notifications for messages that have been unseen for a long enough time """ # very crude and dumb algorithm - logger.info(f"Sending out email notifications for unseen messages") + logger.info("Sending out email notifications for unseen messages") with session_scope() as session: # users who have unnotified messages older than 5 minutes in any group chat users = ( session.execute( - ( - select(User) - .join(GroupChatSubscription, GroupChatSubscription.user_id == User.id) - .join(Message, Message.conversation_id == GroupChatSubscription.group_chat_id) - .where(not_(GroupChatSubscription.is_muted)) - .where(User.is_visible) - .where(Message.time >= GroupChatSubscription.joined) - .where(or_(Message.time <= GroupChatSubscription.left, GroupChatSubscription.left == None)) - .where(Message.id > User.last_notified_message_id) - .where(Message.id > GroupChatSubscription.last_seen_message_id) - .where(Message.time < now() - timedelta(minutes=5)) - .where(Message.message_type == MessageType.text) # TODO: only text messages for now - ) + select(User) + .join(GroupChatSubscription, GroupChatSubscription.user_id == User.id) + .join(Message, Message.conversation_id == GroupChatSubscription.group_chat_id) + .where(not_(GroupChatSubscription.is_muted)) + .where(User.is_visible) + .where(Message.time >= GroupChatSubscription.joined) + .where(or_(Message.time <= GroupChatSubscription.left, GroupChatSubscription.left == None)) + .where(Message.id > User.last_notified_message_id) + .where(Message.id > GroupChatSubscription.last_seen_message_id) + .where(Message.time < now() - timedelta(minutes=5)) + .where(Message.message_type == MessageType.text) # TODO: only text messages for now ) .scalars() .unique() @@ -211,7 +209,7 @@ def send_message_notifications(payload): template_args={ "user": user, "total_unseen_message_count": total_unseen_message_count, - "unseen_messages": [ + "unseen_messages": [ # noqa: C416 (group_chat, latest_message, count) for group_chat, latest_message, count in unseen_messages ], "group_chats_link": urls.messages_link(), @@ -227,7 +225,7 @@ def send_request_notifications(payload): """ Sends out email notifications for unseen messages in host requests (as surfer or host) """ - logger.info(f"Sending out email notifications for unseen messages in host requests") + logger.info("Sending out email notifications for unseen messages in host requests") with session_scope() as session: # requests where this user is surfing @@ -293,7 +291,7 @@ def send_onboarding_emails(payload): """ Sends out onboarding emails """ - logger.info(f"Sending out onboarding emails") + logger.info("Sending out onboarding emails") with session_scope() as session: # first onboarding email @@ -336,7 +334,7 @@ def send_reference_reminders(payload): """ Sends out reminders to write references after hosting/staying """ - logger.info(f"Sending out reference reminder emails") + logger.info("Sending out reference reminder emails") # Keep this in chronological order! reference_reminder_schedule = [ @@ -433,10 +431,10 @@ def send_reference_reminders(payload): def add_users_to_email_list(payload): if not config["LISTMONK_ENABLED"]: - logger.info(f"Not adding users to mailing list") + logger.info("Not adding users to mailing list") return - logger.info(f"Adding users to mailing list") + logger.info("Adding users to mailing list") while True: with session_scope() as session: @@ -444,7 +442,7 @@ def add_users_to_email_list(payload): select(User).where(User.is_visible).where(User.in_sync_with_newsletter == False).limit(1) ).scalar_one_or_none() if not user: - logger.info(f"Finished adding users to mailing list") + logger.info("Finished adding users to mailing list") return if user.opt_out_of_newsletter: diff --git a/app/backend/src/couchers/jobs/worker.py b/app/backend/src/couchers/jobs/worker.py index 56c290a272..4844c8c95e 100644 --- a/app/backend/src/couchers/jobs/worker.py +++ b/app/backend/src/couchers/jobs/worker.py @@ -38,7 +38,7 @@ def process_job(): Attempt to process one job from the job queue. Returns False if no job was found, True if a job was processed, regardless of failure/success. """ - logger.debug(f"Looking for a job") + logger.debug("Looking for a job") with session_scope(isolation_level="REPEATABLE READ") as session: # a combination of REPEATABLE READ and SELECT ... FOR UPDATE SKIP LOCKED makes sure that only one transaction @@ -54,7 +54,7 @@ def process_job(): ) if not job: - logger.debug(f"No pending jobs") + logger.debug("No pending jobs") return False # we've got a lock for a job now, it's "pending" until we commit or the lock is gone @@ -106,7 +106,7 @@ def service_jobs(): if not process_job(): sleep(1) finally: - logger.info(f"Closing prometheus server") + logger.info("Closing prometheus server") t.server_close() diff --git a/app/backend/src/couchers/migrations/versions/bf12729fa8eb_sms_verification_constraints.py b/app/backend/src/couchers/migrations/versions/bf12729fa8eb_sms_verification_constraints.py index 253bf4e28b..2f4a4b5715 100644 --- a/app/backend/src/couchers/migrations/versions/bf12729fa8eb_sms_verification_constraints.py +++ b/app/backend/src/couchers/migrations/versions/bf12729fa8eb_sms_verification_constraints.py @@ -24,4 +24,4 @@ def upgrade(): def downgrade(): - raise hell + raise Exception("hell") diff --git a/app/backend/src/couchers/models.py b/app/backend/src/couchers/models.py index 998e0fbc46..c47cd39437 100644 --- a/app/backend/src/couchers/models.py +++ b/app/backend/src/couchers/models.py @@ -18,16 +18,18 @@ Index, Integer, Interval, + MetaData, + Sequence, + String, + UniqueConstraint, ) from sqlalchemy import LargeBinary as Binary -from sqlalchemy import MetaData, Sequence, String, UniqueConstraint from sqlalchemy.dialects.postgresql import TSTZRANGE, ExcludeConstraint from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property from sqlalchemy.orm import backref, column_property, declarative_base, deferred, relationship -from sqlalchemy.sql import and_, func +from sqlalchemy.sql import and_, func, text from sqlalchemy.sql import select as sa_select -from sqlalchemy.sql import text from couchers import urls from couchers.config import config @@ -797,7 +799,7 @@ def should_notify(self): We currently send if expertise is listed, or if they list a way to help outside of a set list """ - return (self.expertise != None) | (not set(self.contribute_ways).issubset(set(["community", "blog", "other"]))) + return (self.expertise != None) | (not set(self.contribute_ways).issubset({"community", "blog", "other"})) class SignupFlow(Base): diff --git a/app/backend/src/couchers/notifications/background.py b/app/backend/src/couchers/notifications/background.py index e8029a1d49..fdf53849f7 100644 --- a/app/backend/src/couchers/notifications/background.py +++ b/app/backend/src/couchers/notifications/background.py @@ -40,8 +40,8 @@ def _send_email_notification(user: User, notification: Notification): - def _generate_unsub(type, one_click=False): - return generate_unsub(user, notification, type, one_click) + def _generate_unsub(unsub_type, one_click=False): + return generate_unsub(user, notification, unsub_type, one_click) rendered = render_notification(user, notification) template_args = { @@ -170,7 +170,7 @@ def handle_notification(payload: jobs_pb2.HandleNotificationPayload): def send_raw_push_notification(payload: jobs_pb2.SendRawPushNotificationPayload): if not config["PUSH_NOTIFICATIONS_ENABLED"]: - logger.info(f"Not sending push notification due to push notifications disabled") + logger.info("Not sending push notification due to push notifications disabled") with session_scope() as session: if len(payload.data) > 3072: @@ -223,7 +223,7 @@ def handle_email_digests(payload: empty_pb2.Empty): That is, we don't send out an email unless there's something new, but if we do send one out, we send new and old stuff. """ - logger.info(f"Sending out email digests") + logger.info("Sending out email digests") with session_scope() as session: # already sent email notifications @@ -243,22 +243,20 @@ def handle_email_digests(payload: empty_pb2.Empty): # users who have unsent "digest" type notifications but not sent email notifications users_to_send_digests_to = ( session.execute( - ( - select(User) - .where(User.digest_frequency != None) - .where(User.last_digest_sent < func.now() - User.digest_frequency) - # todo: tz - .join(Notification, Notification.user_id == User.id) - .join(NotificationDelivery, NotificationDelivery.notification_id == Notification.id) - .where(NotificationDelivery.delivery_type == NotificationDeliveryType.digest) - .where(NotificationDelivery.delivered == None) - .outerjoin( - delivered_email_notifications, - delivered_email_notifications.c.notification_id == Notification.id, - ) - .where(delivered_email_notifications.c.notification_delivery_id == None) - .group_by(User) + select(User) + .where(User.digest_frequency != None) + .where(User.last_digest_sent < func.now() - User.digest_frequency) + # todo: tz + .join(Notification, Notification.user_id == User.id) + .join(NotificationDelivery, NotificationDelivery.notification_id == Notification.id) + .where(NotificationDelivery.delivery_type == NotificationDeliveryType.digest) + .where(NotificationDelivery.delivered == None) + .outerjoin( + delivered_email_notifications, + delivered_email_notifications.c.notification_id == Notification.id, ) + .where(delivered_email_notifications.c.notification_delivery_id == None) + .group_by(User) ) .scalars() .all() @@ -269,14 +267,12 @@ def handle_email_digests(payload: empty_pb2.Empty): for user in users_to_send_digests_to: # digest notifications that haven't been delivered yet notifications_and_deliveries = session.execute( - ( - select(Notification, NotificationDelivery) - .join(NotificationDelivery, NotificationDelivery.notification_id == Notification.id) - .where(NotificationDelivery.delivery_type == NotificationDeliveryType.digest) - .where(NotificationDelivery.delivered == None) - .where(Notification.user_id == user.id) - .order_by(Notification.created) - ) + select(Notification, NotificationDelivery) + .join(NotificationDelivery, NotificationDelivery.notification_id == Notification.id) + .where(NotificationDelivery.delivery_type == NotificationDeliveryType.digest) + .where(NotificationDelivery.delivered == None) + .where(Notification.user_id == user.id) + .order_by(Notification.created) ).all() if notifications_and_deliveries: diff --git a/app/backend/src/couchers/notifications/push_api.py b/app/backend/src/couchers/notifications/push_api.py index f0b5b4e20c..e43f38d82c 100644 --- a/app/backend/src/couchers/notifications/push_api.py +++ b/app/backend/src/couchers/notifications/push_api.py @@ -29,7 +29,7 @@ def generate_vapid_authorization(endpoint, vapid_sub, vapid_private_key): url = urlparse(endpoint) vapid_claim = { "sub": vapid_sub, - "aud": "{}://{}".format(url.scheme, url.netloc), + "aud": f"{url.scheme}://{url.netloc}", "exp": int(time()) + (12 * 60 * 60), } return Vapid.from_string(private_key=vapid_private_key).sign(vapid_claim)["Authorization"] diff --git a/app/backend/src/couchers/notifications/render.py b/app/backend/src/couchers/notifications/render.py index 7bbef88d77..ecf8910425 100644 --- a/app/backend/src/couchers/notifications/render.py +++ b/app/backend/src/couchers/notifications/render.py @@ -297,7 +297,7 @@ def render_notification(user, notification) -> RenderedNotification: email_list_unsubscribe_url=generate_unsub(user, notification, "topic_action"), ) elif notification.topic_action.display == "donation:received": - title = f"Thank you for your donation to Couchers.org!" + title = "Thank you for your donation to Couchers.org!" message = f"Thank you so much for your donation of ${data.amount} to Couchers.org." return RenderedNotification( is_critical=True, @@ -363,7 +363,7 @@ def render_notification(user, notification) -> RenderedNotification: push_url=urls.app_link(), ) elif notification.topic_action.display == "account_deletion:complete": - title = f"Your Couchers.org account has been deleted" + title = "Your Couchers.org account has been deleted" return RenderedNotification( is_critical=True, email_subject=title, @@ -379,8 +379,8 @@ def render_notification(user, notification) -> RenderedNotification: push_url=urls.app_link(), ) elif notification.topic_action.display == "account_deletion:recovered": - title = f"Your Couchers.org account has been recovered!" - subtitle = f"We have recovered your Couchers.org account as per your request! Welcome back!" + title = "Your Couchers.org account has been recovered!" + subtitle = "We have recovered your Couchers.org account as per your request! Welcome back!" return RenderedNotification( is_critical=True, email_subject=title, @@ -397,7 +397,7 @@ def render_notification(user, notification) -> RenderedNotification: elif notification.topic_action.display == "chat:message": return RenderedNotification( email_subject=data.message, - email_preview=f"You received a message on Couchers.org!", + email_preview="You received a message on Couchers.org!", email_template_name="chat_message", email_template_args={ "author": data.author, @@ -427,7 +427,7 @@ def render_notification(user, notification) -> RenderedNotification: ) return RenderedNotification( email_subject=f'{data.inviting_user.name} invited you to "{event.title}"', - email_preview=f"You've been invited to a new event on Couchers.org!", + email_preview="You've been invited to a new event on Couchers.org!", email_template_name="event_create", email_template_args={ "inviting_user": data.inviting_user, @@ -458,7 +458,7 @@ def render_notification(user, notification) -> RenderedNotification: body += event.content return RenderedNotification( email_subject=f'{data.updating_user.name} updated "{event.title}"', - email_preview=f"An event you are subscribed to was updated.", + email_preview="An event you are subscribed to was updated.", email_template_name="event_update", email_template_args={ "updating_user": data.updating_user, @@ -514,7 +514,7 @@ def render_notification(user, notification) -> RenderedNotification: body += event.content return RenderedNotification( email_subject=f'{data.inviting_user.name} invited you to co-organize "{event.title}"', - email_preview=f"You were invited to co-organize an event on Couchers.org.", + email_preview="You were invited to co-organize an event on Couchers.org.", email_template_name="event_invite_organizer", email_template_args={ "inviting_user": data.inviting_user, diff --git a/app/backend/src/couchers/notifications/settings.py b/app/backend/src/couchers/notifications/settings.py index 4a62ff5ae5..2440974ca2 100644 --- a/app/backend/src/couchers/notifications/settings.py +++ b/app/backend/src/couchers/notifications/settings.py @@ -228,7 +228,6 @@ def check_settings(): actions_by_topic_check = {} - all_settings_topics = [] for heading, group in settings_layout: for topic, name, items in group: actions = [] diff --git a/app/backend/src/couchers/notifications/unsubscribe.py b/app/backend/src/couchers/notifications/unsubscribe.py index f104d5712f..c253b5d22c 100644 --- a/app/backend/src/couchers/notifications/unsubscribe.py +++ b/app/backend/src/couchers/notifications/unsubscribe.py @@ -54,14 +54,14 @@ def generate_unsub_topic_action(notification): ) -def generate_unsub(user, notification, type, one_click=False): +def generate_unsub(user, notification, unsub_type, one_click=False): if one_click: raise NotImplementedError("One click unsubscribe not implemented yet") - if type == "do_not_email": + if unsub_type == "do_not_email": return generate_do_not_email(user.id) - elif type == "topic_key": + elif unsub_type == "topic_key": return generate_unsub_topic_key(notification) - elif type == "topic_action": + elif unsub_type == "topic_action": return generate_unsub_topic_action(notification) else: return ValueError("Unknown unsub type") diff --git a/app/backend/src/couchers/resources.py b/app/backend/src/couchers/resources.py index 681248742a..6e7e89a4c1 100644 --- a/app/backend/src/couchers/resources.py +++ b/app/backend/src/couchers/resources.py @@ -130,7 +130,7 @@ def copy_resources_to_database(session): raise Exception("Missing timezone_areas.sql and not running in dev") timezone_areas_file = resources_folder / "timezone_areas.sql-fake" - logger.info(f"Using fake timezone areas") + logger.info("Using fake timezone areas") with open(timezone_areas_file, "r") as f: tz_sql = f.read() diff --git a/app/backend/src/couchers/servicers/auth.py b/app/backend/src/couchers/servicers/auth.py index 39c47cc3d1..cbb9dc4c09 100644 --- a/app/backend/src/couchers/servicers/auth.py +++ b/app/backend/src/couchers/servicers/auth.py @@ -361,10 +361,10 @@ def Login(self, request, context): ).scalar_one_or_none() if user: if user.has_password: - logger.debug(f"Found user with password") + logger.debug("Found user with password") return auth_pb2.LoginRes(next_step=auth_pb2.LoginRes.LoginStep.NEED_PASSWORD) else: - logger.debug(f"Found user without password, sending login email") + logger.debug("Found user without password, sending login email") send_login_email(session, user) return auth_pb2.LoginRes(next_step=auth_pb2.LoginRes.LoginStep.SENT_LOGIN_EMAIL) else: # user not found @@ -377,7 +377,7 @@ def Login(self, request, context): session.commit() context.abort(grpc.StatusCode.FAILED_PRECONDITION, errors.SIGNUP_FLOW_EMAIL_STARTED_SIGNUP) - logger.debug(f"Didn't find user") + logger.debug("Didn't find user") context.abort(grpc.StatusCode.NOT_FOUND, errors.USER_NOT_FOUND) def CompleteTokenLogin(self, request, context): @@ -420,21 +420,21 @@ def Authenticate(self, request, context): select(User).where_username_or_email(request.user).where(~User.is_deleted) ).scalar_one_or_none() if user: - logger.debug(f"Found user") + logger.debug("Found user") if not user.has_password: - logger.debug(f"User doesn't have a password!") + logger.debug("User doesn't have a password!") context.abort(grpc.StatusCode.FAILED_PRECONDITION, errors.NO_PASSWORD) if verify_password(user.hashed_password, request.password): - logger.debug(f"Right password") + logger.debug("Right password") # correct password create_session(context, session, user, request.remember_device) return _auth_res(user) else: - logger.debug(f"Wrong password") + logger.debug("Wrong password") # wrong password context.abort(grpc.StatusCode.NOT_FOUND, errors.INVALID_USERNAME_OR_PASSWORD) else: # user not found - logger.debug(f"Didn't find user") + logger.debug("Didn't find user") # do about as much work as if the user was found, reduces timing based username enumeration attacks hash_password(request.password) context.abort(grpc.StatusCode.NOT_FOUND, errors.INVALID_USERNAME_OR_PASSWORD) @@ -487,7 +487,7 @@ def ResetPassword(self, request, context): ), ) else: # user not found - logger.debug(f"Didn't find user") + logger.debug("Didn't find user") return empty_pb2.Empty() diff --git a/app/backend/src/couchers/servicers/conversations.py b/app/backend/src/couchers/servicers/conversations.py index 4348b09444..8f3fcb030b 100644 --- a/app/backend/src/couchers/servicers/conversations.py +++ b/app/backend/src/couchers/servicers/conversations.py @@ -269,7 +269,7 @@ def ListGroupChats(self, request, context): for result in results[:page_size] ], last_message_id=( - min(map(lambda g: g.Message.id if g.Message else 1, results[:page_size])) if len(results) > 0 else 0 + min(g.Message.id if g.Message else 1 for g in results[:page_size]) if len(results) > 0 else 0 ), # TODO no_more=len(results) <= page_size, ) @@ -487,16 +487,13 @@ def SearchMessages(self, request, context): def CreateGroupChat(self, request, context): with session_scope() as session: - recipient_user_ids = [ - user_id - for user_id in ( - session.execute( - select(User.id).where_users_visible(context).where(User.id.in_(request.recipient_user_ids)) - ) - .scalars() - .all() + recipient_user_ids = list( + session.execute( + select(User.id).where_users_visible(context).where(User.id.in_(request.recipient_user_ids)) ) - ] + .scalars() + .all() + ) # make sure all requested users are visible if len(recipient_user_ids) != len(request.recipient_user_ids): diff --git a/app/backend/src/couchers/servicers/donations.py b/app/backend/src/couchers/servicers/donations.py index 7f789f58ed..ccde78a3fb 100644 --- a/app/backend/src/couchers/servicers/donations.py +++ b/app/backend/src/couchers/servicers/donations.py @@ -51,7 +51,7 @@ def InitiateDonation(self, request, context): "currency": "usd", "unit_amount": request.amount * 100, # input is in cents "product_data": { - "name": f"Couchers financial supporter (one-time)", + "name": "Couchers financial supporter (one-time)", "images": ["https://couchers.org/img/share.jpg"], }, }, diff --git a/app/backend/src/couchers/servicers/events.py b/app/backend/src/couchers/servicers/events.py index 60d0d3925c..60f12dcefc 100644 --- a/app/backend/src/couchers/servicers/events.py +++ b/app/backend/src/couchers/servicers/events.py @@ -221,7 +221,7 @@ def get_users_to_notify_for_new_event(session, occurrence): """ cluster = occurrence.event.parent_node.official_cluster if cluster.parent_node_id == 1: - logger.info(f"The Global Community is too big for email notifications.") + logger.info("The Global Community is too big for email notifications.") return [], False elif occurrence.creator_user in cluster.admins or cluster.is_leaf: return list(cluster.members), False @@ -253,7 +253,6 @@ def generate_event_create_notifications(payload: jobs_pb2.GenerateEventCreateNot event, occurrence = _get_event_and_occurrence_one(session, occurrence_id=payload.occurrence_id) creator = occurrence.creator_user - community_node = None users, is_geom_search = get_users_to_notify_for_new_event(session, occurrence) inviting_user = session.execute(select(User).where(User.id == payload.inviting_user_id)).scalar_one_or_none() @@ -471,12 +470,10 @@ def ScheduleEvent(self, request, context): if not request.content: context.abort(grpc.StatusCode.INVALID_ARGUMENT, errors.MISSING_EVENT_CONTENT) if request.HasField("online_information"): - online = True geom = None address = None link = request.online_information.link elif request.HasField("offline_information"): - online = False if not ( request.offline_information.address and request.offline_information.lat diff --git a/app/backend/src/couchers/servicers/notifications.py b/app/backend/src/couchers/servicers/notifications.py index 0ecacea254..f6278c0f37 100644 --- a/app/backend/src/couchers/servicers/notifications.py +++ b/app/backend/src/couchers/servicers/notifications.py @@ -68,7 +68,7 @@ def SetNotificationSettings(self, request, context): delivery_type = NotificationDeliveryType[preference.delivery_method] try: set_preference(session, user.id, topic_action, delivery_type, preference.enabled) - except PreferenceNotUserEditableError as e: + except PreferenceNotUserEditableError: context.abort(grpc.StatusCode.FAILED_PRECONDITION, errors.CANNOT_EDIT_THAT_NOTIFICATION_PREFERENCE) return notifications_pb2.GetNotificationSettingsRes( do_not_email_enabled=user.do_not_email, @@ -132,6 +132,6 @@ def SendTestPushNotification(self, request, context): push_to_user( context.user_id, title="Checking push notifications work!", - body=f"If you see this, then it's working :)", + body="If you see this, then it's working :)", ) return empty_pb2.Empty() diff --git a/app/backend/src/couchers/servicers/requests.py b/app/backend/src/couchers/servicers/requests.py index 27040b607c..da21c6768d 100644 --- a/app/backend/src/couchers/servicers/requests.py +++ b/app/backend/src/couchers/servicers/requests.py @@ -450,7 +450,7 @@ def GetHostRequestMessages(self, request, context): no_more = len(messages) <= pagination - last_message_id = min(map(lambda m: m.id if m else 1, messages[:pagination])) if len(messages) > 0 else 0 + last_message_id = min(m.id if m else 1 for m in messages[:pagination]) if len(messages) > 0 else 0 return requests_pb2.GetHostRequestMessagesRes( last_message_id=last_message_id, @@ -558,9 +558,7 @@ def GetHostRequestUpdates(self, request, context): no_more = len(res) <= pagination - last_message_id = ( - min(map(lambda m: m.Message.id if m else 1, res[:pagination])) if len(res) > 0 else 0 - ) # TODO + last_message_id = min(m.Message.id if m else 1 for m in res[:pagination]) if len(res) > 0 else 0 # TODO return requests_pb2.GetHostRequestUpdatesRes( no_more=no_more, diff --git a/app/backend/src/couchers/servicers/search.py b/app/backend/src/couchers/servicers/search.py index ec6b83c163..d784a28826 100644 --- a/app/backend/src/couchers/servicers/search.py +++ b/app/backend/src/couchers/servicers/search.py @@ -42,10 +42,13 @@ def _join_with_space(coalesces): return out -def _build_tsv(A, B=[], C=[], D=[]): +def _build_tsv(A, B=None, C=None, D=None): """ Given lists for A, B, C, and D, builds a tsvector from them. """ + B = B or [] + C = C or [] + D = D or [] tsv = func.setweight(func.to_tsvector(REGCONFIG, _join_with_space([func.coalesce(bit, "") for bit in A])), "A") if B: tsv = tsv.concat( @@ -62,10 +65,13 @@ def _build_tsv(A, B=[], C=[], D=[]): return tsv -def _build_doc(A, B=[], C=[], D=[]): +def _build_doc(A, B=None, C=None, D=None): """ Builds the raw document (without to_tsvector and weighting), used for extracting snippet """ + B = B or [] + C = C or [] + D = D or [] doc = _join_with_space([func.coalesce(bit, "") for bit in A]) if B: doc += " " + _join_with_space([func.coalesce(bit, "") for bit in B]) @@ -80,7 +86,7 @@ def _similarity(statement, text): return func.word_similarity(func.unaccent(statement), func.unaccent(text)) -def _gen_search_elements(statement, title_only, next_rank, page_size, A, B=[], C=[], D=[]): +def _gen_search_elements(statement, title_only, next_rank, page_size, A, B=None, C=None, D=None): """ Given an sql statement and four sets of fields, (A, B, C, D), generates a bunch of postgres expressions for full text search. @@ -90,6 +96,9 @@ def _gen_search_elements(statement, title_only, next_rank, page_size, A, B=[], C If title_only=True, we only perform a trigram search against A only """ + B = B or [] + C = C or [] + D = D or [] if not title_only: # a postgres tsquery object that can be used to match against a tsvector tsq = func.websearch_to_tsquery(REGCONFIG, statement) @@ -116,12 +125,10 @@ def execute_search_statement(session, orig_statement): Does the right search filtering, limiting, and ordering for the initial statement """ return session.execute( - ( - orig_statement.where(or_(tsv.op("@@")(tsq), sim > TRI_SIMILARITY_THRESHOLD)) - .where(rank <= next_rank if next_rank is not None else True) - .order_by(rank.desc()) - .limit(page_size + 1) - ) + orig_statement.where(or_(tsv.op("@@")(tsq), sim > TRI_SIMILARITY_THRESHOLD)) + .where(rank <= next_rank if next_rank is not None else True) + .order_by(rank.desc()) + .limit(page_size + 1) ).all() else: @@ -145,12 +152,10 @@ def execute_search_statement(session, orig_statement): Does the right search filtering, limiting, and ordering for the initial statement """ return session.execute( - ( - orig_statement.where(sim > TRI_SIMILARITY_THRESHOLD) - .where(rank <= next_rank if next_rank is not None else True) - .order_by(rank.desc()) - .limit(page_size + 1) - ) + orig_statement.where(sim > TRI_SIMILARITY_THRESHOLD) + .where(rank <= next_rank if next_rank is not None else True) + .order_by(rank.desc()) + .limit(page_size + 1) ).all() return rank, snippet, execute_search_statement diff --git a/app/backend/src/couchers/templates/v2.py b/app/backend/src/couchers/templates/v2.py index de68f1b769..519547f8fc 100644 --- a/app/backend/src/couchers/templates/v2.py +++ b/app/backend/src/couchers/templates/v2.py @@ -92,7 +92,8 @@ def add_filters(env): add_filters(env) -def email_user(user, template_name, template_args={}, frontmatter=None, override_recipient=None): +def email_user(user, template_name, template_args=None, frontmatter=None, override_recipient=None): + template_args = template_args or {} if not frontmatter: # Titles/config are from {template_name}.yaml, plaintext from {template_name}.txt, and html from generated_html/{template_name}.html (generated from {template_name}.mjml) frontmatter_template = env.get_template(f"{template_name}.yaml") diff --git a/app/backend/src/dummy_data.py b/app/backend/src/dummy_data.py index 4e1c4aef58..d4af1d5664 100644 --- a/app/backend/src/dummy_data.py +++ b/app/backend/src/dummy_data.py @@ -47,7 +47,7 @@ def add_dummy_users(): - logger.info(f"Adding dummy users") + logger.info("Adding dummy users") with session_scope() as session: if session.execute(select(func.count()).select_from(User)).scalar_one() > 0: logger.info("Users not empty, not adding dummy users") @@ -189,7 +189,7 @@ def invocation_metadata(self): def add_dummy_communities(): - logger.info(f"Adding dummy communities") + logger.info("Adding dummy communities") with session_scope() as session: if session.execute(select(func.count()).select_from(Node)).scalar_one() > 0: logger.info("Nodes not empty, not adding dummy communities") diff --git a/app/backend/src/tests/test_account.py b/app/backend/src/tests/test_account.py index 37afaf7889..39ff0828b7 100644 --- a/app/backend/src/tests/test_account.py +++ b/app/backend/src/tests/test_account.py @@ -754,7 +754,6 @@ def test_multiple_delete_tokens(db): Make sure deletion tokens are deleted on delete """ user, token = generate_user() - user_id = user.id with account_session(token) as account: account.DeleteAccount(account_pb2.DeleteAccountReq(confirm=True)) diff --git a/app/backend/src/tests/test_admin.py b/app/backend/src/tests/test_admin.py index 74a0f9ead0..ea17de02bb 100644 --- a/app/backend/src/tests/test_admin.py +++ b/app/backend/src/tests/test_admin.py @@ -31,7 +31,7 @@ def _(testconfig): def test_access_by_normal_user(db): - with session_scope() as session: + with session_scope(): normal_user, normal_token = generate_user() with real_admin_session(normal_token) as api: @@ -46,7 +46,7 @@ def test_access_by_normal_user(db): def test_GetUserDetails(db): - with session_scope() as session: + with session_scope(): super_user, super_token = generate_user(is_superuser=True) normal_user, normal_token = generate_user() @@ -82,7 +82,7 @@ def test_GetUserDetails(db): def test_ChangeUserGender(db): - with session_scope() as session: + with session_scope(): super_user, super_token = generate_user(is_superuser=True) normal_user, normal_token = generate_user() @@ -98,7 +98,7 @@ def test_ChangeUserGender(db): def test_ChangeUserBirthdate(db): - with session_scope() as session: + with session_scope(): super_user, super_token = generate_user(is_superuser=True) normal_user, normal_token = generate_user(birthdate=date(year=2000, month=1, day=1)) @@ -165,7 +165,7 @@ def test_AddAdminNote(db): def test_DeleteUser(db): - with session_scope() as session: + with session_scope(): super_user, super_token = generate_user(is_superuser=True) normal_user, normal_token = generate_user() @@ -266,7 +266,7 @@ def test_CreateApiKey(db, push_collector): def test_CreateCommunity_invalid_geojson(db): - with session_scope() as session: + with session_scope(): super_user, super_token = generate_user(is_superuser=True) normal_user, normal_token = generate_user() with real_admin_session(super_token) as api: @@ -304,7 +304,7 @@ def test_CreateCommunity(db): def test_GetChats(db): - with session_scope() as session: + with session_scope(): super_user, super_token = generate_user(is_superuser=True) normal_user, normal_token = generate_user() @@ -314,7 +314,7 @@ def test_GetChats(db): def test_badges(db): - with session_scope() as session: + with session_scope(): super_user, super_token = generate_user(is_superuser=True) normal_user, normal_token = generate_user() diff --git a/app/backend/src/tests/test_api.py b/app/backend/src/tests/test_api.py index e8cc07d8d9..318447ca07 100644 --- a/app/backend/src/tests/test_api.py +++ b/app/backend/src/tests/test_api.py @@ -68,7 +68,7 @@ def test_ping(db): assert res.user.about_me == user.about_me assert res.user.my_travels == user.my_travels assert res.user.things_i_like == user.things_i_like - assert set(language_ability.code for language_ability in res.user.language_abilities) == set(["fin", "fra"]) + assert {language_ability.code for language_ability in res.user.language_abilities} == {"fin", "fra"} assert res.user.about_place == user.about_place assert res.user.regions_visited == ["FIN", "REU", "CHE"] # Tests alphabetization by region name assert res.user.regions_lived == ["EST", "FRA", "ESP"] # Ditto diff --git a/app/backend/src/tests/test_auth.py b/app/backend/src/tests/test_auth.py index 335de9999a..7e0e665402 100644 --- a/app/backend/src/tests/test_auth.py +++ b/app/backend/src/tests/test_auth.py @@ -250,8 +250,6 @@ def _quick_signup(): assert not res.need_feedback assert not res.need_verify_email - user_id = res.auth_res.user_id - # make sure we got the right token in a cookie with session_scope() as session: token = ( diff --git a/app/backend/src/tests/test_bg_jobs.py b/app/backend/src/tests/test_bg_jobs.py index d2ed6e77ff..6f534751bf 100644 --- a/app/backend/src/tests/test_bg_jobs.py +++ b/app/backend/src/tests/test_bg_jobs.py @@ -67,7 +67,6 @@ def _check_job_counter(job, status, attempt, exception): def test_login_email_full(db): user, api_token = generate_user() - user_email = user.email with session_scope() as session: login_token = send_login_email(session, user) diff --git a/app/backend/src/tests/test_conversations.py b/app/backend/src/tests/test_conversations.py index 68a4325fd1..482ff37445 100644 --- a/app/backend/src/tests/test_conversations.py +++ b/app/backend/src/tests/test_conversations.py @@ -337,7 +337,6 @@ def test_get_group_chat_messages_joined_left(db): make_friends(user1, user2) make_friends(user1, user3) make_friends(user1, user4) - start_time = now() with conversations_session(token1) as c: res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user4.id])) @@ -609,7 +608,7 @@ def test_make_remove_group_chat_admin(db): res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id)) assert user1.id in res.admin_user_ids - assert not user2.id in res.admin_user_ids + assert user2.id not in res.admin_user_ids with conversations_session(token2) as c: # shouldn't be able to make admin if not admin @@ -682,7 +681,7 @@ def test_leave_invite_to_group_chat(db): with conversations_session(token2) as c: res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=group_chat_id)) - assert not user3.id in res.member_user_ids + assert user3.id not in res.member_user_ids # only_admins_invite defaults to true so shouldn't be able to invite with pytest.raises(grpc.RpcError) as e: @@ -859,7 +858,6 @@ def test_search_messages_left_joined(db): make_friends(user1, user2) make_friends(user1, user3) make_friends(user1, user4) - start_time = now() with conversations_session(token1) as c: res = c.CreateGroupChat(conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user4.id])) @@ -1128,7 +1126,7 @@ def test_last_seen(db): res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) assert res.unseen_message_count == backward_offset - c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text=f"test message ...")) + c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text="test message ...")) res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) assert res.unseen_message_count == 0 @@ -1138,7 +1136,7 @@ def test_last_seen(db): # created + 7 normal assert res.unseen_message_count == 8 - c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text=f"test message ...")) + c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text="test message ...")) res = c.GetGroupChat(conversations_pb2.GetGroupChatReq(group_chat_id=gcid)) assert res.unseen_message_count == 0 @@ -1250,14 +1248,12 @@ def test_total_unseen(db): # distractions make_friends(user1, user4) - start_time = now() - with conversations_session(token1) as c: # distractions gcid_distraction = c.CreateGroupChat( conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user4.id]) ).group_chat_id - c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text=f"distraction...")) + c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text="distraction...")) gcid = c.CreateGroupChat( conversations_pb2.CreateGroupChatReq(recipient_user_ids=[user2.id, user3.id]) @@ -1267,7 +1263,7 @@ def test_total_unseen(db): c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text=f"test message {i}")) # distractions - c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text=f"distraction...")) + c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text="distraction...")) # messages are automatically marked as seen when you send a new message with api_session(token1) as api: @@ -1287,14 +1283,14 @@ def test_total_unseen(db): with conversations_session(token1) as c: # distractions - c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text=f"distraction...")) + c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text="distraction...")) # send more stuff without user 2 for i in range(3): c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text=f"test message {i}")) # distractions - c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text=f"distraction...")) + c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text="distraction...")) with api_session(token2) as api: # seen messages becomes 0 when leaving @@ -1309,7 +1305,7 @@ def test_total_unseen(db): c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid, text=f"test message {i}")) # distractions - c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text=f"distraction...")) + c.SendMessage(conversations_pb2.SendMessageReq(group_chat_id=gcid_distraction, text="distraction...")) with api_session(token2) as api: # joined + 12 normal @@ -1351,7 +1347,7 @@ def test_regression_ListGroupChats_pagination(db): seen_group_chat_ids.extend([chat.group_chat_id for chat in res.group_chats]) - assert set(seen_group_chat_ids) == set(x[0] for x in group_chat_and_message_ids), "Not all group chats returned" + assert set(seen_group_chat_ids) == {x[0] for x in group_chat_and_message_ids}, "Not all group chats returned" def test_muting(db): diff --git a/app/backend/src/tests/test_donations.py b/app/backend/src/tests/test_donations.py index 800fd4130b..8fa20f8609 100644 --- a/app/backend/src/tests/test_donations.py +++ b/app/backend/src/tests/test_donations.py @@ -64,7 +64,7 @@ def test_one_time_donation_flow(db, monkeypatch): "currency": "usd", "unit_amount": 10000, "product_data": { - "name": f"Couchers financial supporter (one-time)", + "name": "Couchers financial supporter (one-time)", "images": ["https://couchers.org/img/share.jpg"], }, }, diff --git a/app/backend/src/tests/test_email.py b/app/backend/src/tests/test_email.py index 7a180259a0..6d46ed4b57 100644 --- a/app/backend/src/tests/test_email.py +++ b/app/backend/src/tests/test_email.py @@ -64,7 +64,7 @@ def test_login_email(db): def test_signup_verification_email(db): request_email = f"{random_hex(12)}@couchers.org.invalid" - with session_scope() as session: + with session_scope(): flow = SignupFlow(name="Frodo", email=request_email) with patch("couchers.email.queue_email") as mock: @@ -195,7 +195,7 @@ def test_email_patching_fails(db): printing function was called instead, this makes sure the patching is actually done """ - with session_scope() as session: + with session_scope(): to_user, to_token = generate_user() from_user, from_token = generate_user() @@ -204,7 +204,7 @@ def test_email_patching_fails(db): def mock_queue_email(**kwargs): raise Exception(patched_msg) - with patch("couchers.notifications.background.queue_email", mock_queue_email) as mock: + with patch("couchers.notifications.background.queue_email", mock_queue_email): with pytest.raises(Exception) as e: with api_session(from_token) as api: api.SendFriendRequest(api_pb2.SendFriendRequestReq(user_id=to_user.id)) @@ -367,7 +367,7 @@ def test_send_donation_email(db, monkeypatch): ), ) - with patch("couchers.email.smtp.smtplib.SMTP") as mock: + with patch("couchers.email.smtp.smtplib.SMTP"): handle_notifications_bg() with session_scope() as session: diff --git a/app/backend/src/tests/test_events.py b/app/backend/src/tests/test_events.py index a48ef5d706..9112dd9d6e 100644 --- a/app/backend/src/tests/test_events.py +++ b/app/backend/src/tests/test_events.py @@ -1693,7 +1693,6 @@ def test_ListEventOccurrences(db): with session_scope() as session: c_id = create_community(session, 0, 2, "Community", [user2], [], None).id - time_before = now() start = now() event_ids = [] @@ -1802,7 +1801,7 @@ def new_event(hours_from_now, community_id, online=True): e4 = api.CreateEvent(new_event(4, c_id, True)).event_id with events_session(token4) as api: - e6 = api.CreateEvent((new_event(6, c2_id, True))).event_id + e6 = api.CreateEvent(new_event(6, c2_id, True)).event_id with events_session(token1) as api: api.InviteEventOrganizer(events_pb2.InviteEventOrganizerReq(event_id=e3, user_id=user3.id)) diff --git a/app/backend/src/tests/test_fixtures.py b/app/backend/src/tests/test_fixtures.py index b52490bfce..873498b16d 100644 --- a/app/backend/src/tests/test_fixtures.py +++ b/app/backend/src/tests/test_fixtures.py @@ -309,7 +309,7 @@ def invocation_metadata(self): session.refresh(user) # this loads the user's timezone info which is lazy loaded, otherwise we'll get issues if we try to refer to it - user.timezone + user.timezone # noqa: B018 # allows detaches the user from the session, allowing its use outside this session session.expunge(user) @@ -919,7 +919,7 @@ def __getattr__(self, attr): try: return self.kwargs[attr] except KeyError: - raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{attr}'") + raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{attr}'") from None def __repr__(self): kwargs_disp = ", ".join(f"'{key}'='{val}'" for key, val in self.kwargs.items()) @@ -953,5 +953,5 @@ def assert_user_has_single_matching(self, user_id, **kwargs): collector = PushCollector() - with patch("couchers.notifications.push._push_to_user", collector.push_to_user) as mock: + with patch("couchers.notifications.push._push_to_user", collector.push_to_user): yield collector diff --git a/app/backend/src/tests/test_interceptors.py b/app/backend/src/tests/test_interceptors.py index 67fe51fd1a..3947b83493 100644 --- a/app/backend/src/tests/test_interceptors.py +++ b/app/backend/src/tests/test_interceptors.py @@ -110,7 +110,7 @@ def test_logging_interceptor_all_ignored(): message = random_hex() def TestRpc(request, context): - context.abort(status_code, message) + context.abort(status_code, message) # noqa: B023 with interceptor_dummy_api(TestRpc, interceptors=[ErrorSanitizationInterceptor()]) as call_rpc: with pytest.raises(grpc.RpcError) as e: @@ -121,7 +121,7 @@ def TestRpc(request, context): def test_logging_interceptor_assertion(): def TestRpc(request, context): - assert False + raise AssertionError() with interceptor_dummy_api(TestRpc, interceptors=[ErrorSanitizationInterceptor()]) as call_rpc: with pytest.raises(grpc.RpcError) as e: @@ -132,7 +132,7 @@ def TestRpc(request, context): def test_logging_interceptor_div0(): def TestRpc(request, context): - 1 / 0 + 1 / 0 # noqa: B018 with interceptor_dummy_api(TestRpc, interceptors=[ErrorSanitizationInterceptor()]) as call_rpc: with pytest.raises(grpc.RpcError) as e: @@ -223,7 +223,7 @@ def TestRpc(request, context): request_type=auth_pb2.SignupAccount, response_type=auth_pb2.AuthReq, ) as call_rpc: - with pytest.raises(Exception): + with pytest.raises(Exception, match="Some error message"): call_rpc(auth_pb2.SignupAccount(password="should be removed", username="not removed")) with session_scope() as session: @@ -250,7 +250,7 @@ def TestRpc(request, context): request_type=auth_pb2.SignupAccount, response_type=auth_pb2.AuthReq, ) as call_rpc: - with pytest.raises(Exception): + with pytest.raises(Exception, match="now a grpc abort"): call_rpc(auth_pb2.SignupAccount(password="should be removed", username="not removed")) with session_scope() as session: diff --git a/app/backend/src/tests/test_model_constraints.py b/app/backend/src/tests/test_model_constraints.py index 2b618960f6..f54dc8a1c9 100644 --- a/app/backend/src/tests/test_model_constraints.py +++ b/app/backend/src/tests/test_model_constraints.py @@ -20,15 +20,15 @@ def test_node_constraints(db): node = Node(geom=to_multi(create_1d_polygon(0, 2))) session.add(node) cluster1 = Cluster( - name=f"Testing community, cluster 1", - description=f"Testing community description", + name="Testing community, cluster 1", + description="Testing community description", parent_node=node, is_official_cluster=True, ) session.add(cluster1) cluster2 = Cluster( - name=f"Testing community, cluster 2", - description=f"Testing community description", + name="Testing community, cluster 2", + description="Testing community description", parent_node=node, is_official_cluster=True, ) @@ -58,7 +58,7 @@ def test_page_constraints(db): PageVersion( page=page, editor_user_id=user.id, - title=f"Title", + title="Title", content="Content", ) ) @@ -69,8 +69,8 @@ def test_page_constraints(db): node = Node(geom=to_multi(create_polygon_lat_lng([[0, 0], [0, 2], [2, 2], [2, 0], [0, 0]]))) session.add(node) cluster = Cluster( - name=f"Testing Community", - description=f"Description for testing community", + name="Testing Community", + description="Description for testing community", parent_node=node, ) session.add(cluster) @@ -94,7 +94,7 @@ def test_page_constraints(db): PageVersion( page=page, editor_user_id=user.id, - title=f"Title", + title="Title", content="Content", ) ) @@ -117,7 +117,7 @@ def test_page_constraints(db): PageVersion( page=main_page, editor_user_id=user.id, - title=f"Main page for the testing community", + title="Main page for the testing community", content="Empty.", ) ) @@ -139,7 +139,7 @@ def test_page_constraints(db): PageVersion( page=main_page1, editor_user_id=user.id, - title=f"Main page 1 for the testing community", + title="Main page 1 for the testing community", content="Empty.", ) ) @@ -155,7 +155,7 @@ def test_page_constraints(db): PageVersion( page=main_page2, editor_user_id=user.id, - title=f"Main page 2 for the testing community", + title="Main page 2 for the testing community", content="Empty.", ) ) diff --git a/app/backend/src/tests/test_pages.py b/app/backend/src/tests/test_pages.py index 48c1760e50..8c8399bc5f 100644 --- a/app/backend/src/tests/test_pages.py +++ b/app/backend/src/tests/test_pages.py @@ -574,8 +574,8 @@ def test_page_transfer(db): node = Node(geom=to_multi(create_polygon_lat_lng([[0, 0], [0, 2], [2, 2], [2, 0], [0, 0]]))) session.add(node) community_cluster = Cluster( - name=f"Testing Community", - description=f"Description for testing community", + name="Testing Community", + description="Description for testing community", parent_node=node, is_official_cluster=True, ) @@ -592,7 +592,7 @@ def test_page_transfer(db): PageVersion( page=main_page, editor_user_id=user2.id, - title=f"Main page for the testing community", + title="Main page for the testing community", content="Empty.", ) ) @@ -611,8 +611,8 @@ def test_page_transfer(db): # create a group group_cluster = Cluster( - name=f"Testing Group", - description=f"Description for testing group", + name="Testing Group", + description="Description for testing group", parent_node=node, ) session.add(group_cluster) @@ -628,7 +628,7 @@ def test_page_transfer(db): PageVersion( page=main_page, editor_user_id=user2.id, - title=f"Main page for the testing community", + title="Main page for the testing community", content="Empty.", ) ) diff --git a/app/backend/src/tests/test_requests.py b/app/backend/src/tests/test_requests.py index b6014e98f7..e5e8131eae 100644 --- a/app/backend/src/tests/test_requests.py +++ b/app/backend/src/tests/test_requests.py @@ -978,7 +978,7 @@ def test_request_notifications(db, push_collector): ) ).host_request_id - mock.assert_called_once + mock.assert_called_once() e = email_fields(mock) assert e.recipient == host.email assert "host request" in e.subject.lower() diff --git a/app/backend/src/tests/test_verification.py b/app/backend/src/tests/test_verification.py index 42c9179ffd..dad3abeae9 100644 --- a/app/backend/src/tests/test_verification.py +++ b/app/backend/src/tests/test_verification.py @@ -139,7 +139,7 @@ def test_VerifyPhone_antibrute(): user.phone_verification_sent = now() user.phone = "+46701740605" - for i in range(10): + for _ in range(10): with pytest.raises(grpc.RpcError) as e: account.VerifyPhone(account_pb2.VerifyPhoneReq(token="123455")) if e.value.code() != grpc.StatusCode.NOT_FOUND: diff --git a/app/client/pyproject.toml b/app/client/pyproject.toml index f35f7b0d64..08484146f6 100644 --- a/app/client/pyproject.toml +++ b/app/client/pyproject.toml @@ -5,11 +5,28 @@ requires = [ ] build-backend = "setuptools.build_meta" -[tool.black] +[tool.ruff] line-length = 120 -[tool.isort] -skip_gitignore = true -include_trailing_comma = true -multi_line_output = 3 -line_length = 120 +[tool.ruff.lint] +select = [ + "E", # pycodestyle + "F", # pyflakes + "UP", # pyupgrade + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "A", # flake8-builtins + "I", # isort +] +ignore = [ + "B007", # Loop control variable not used within loop body + "E501", # Line too long + "E711", # Comparison to `None` should be `cond is None` + "E712", # Avoid equality comparisons to `True`; use `if approved:` for truth checks + "F811", # Redefinition of unused variable + "F841", # Local variable is assigned to but never used + "UP015", # Unnecessary open mode parameters +] + +[tool.ruff.lint.isort] +known-first-party = ["couchers"] diff --git a/app/client/requirements.txt b/app/client/requirements.txt index b5418a7d1a..8c21468a5f 100644 --- a/app/client/requirements.txt +++ b/app/client/requirements.txt @@ -1,7 +1,5 @@ -autoflake -black build grpcio==1.38.0 -isort protobuf +ruff twine diff --git a/app/client/src/couchers/__init__.py b/app/client/src/couchers/__init__.py index 97ec048e7c..c1fbbae2af 100644 --- a/app/client/src/couchers/__init__.py +++ b/app/client/src/couchers/__init__.py @@ -1,2 +1,2 @@ -from .client import get_client +from .client import get_client # noqa: F401 from .client import version as __version__ # noqa diff --git a/app/media/src/media/server.py b/app/media/src/media/server.py index 00c626f739..a34caa7a1e 100644 --- a/app/media/src/media/server.py +++ b/app/media/src/media/server.py @@ -9,9 +9,8 @@ import pyvips import sentry_sdk from flask import Flask, abort, request, send_file -from sentry_sdk.integrations import argv, atexit, dedupe +from sentry_sdk.integrations import argv, atexit, dedupe, modules, stdlib, threading from sentry_sdk.integrations import logging as sentry_logging -from sentry_sdk.integrations import modules, stdlib, threading from werkzeug.utils import secure_filename from media.crypto import verify_hash_signature @@ -50,7 +49,6 @@ def create_app( media_upload_location: Path, thumbnail_size: int, ): - # Create the directories media_upload_location.mkdir(exist_ok=True, parents=True) (media_upload_location / "full").mkdir(exist_ok=True, parents=True) diff --git a/app/media/src/tests/test_server.py b/app/media/src/tests/test_server.py index 8bdc7d9e0c..326515b9be 100644 --- a/app/media/src/tests/test_server.py +++ b/app/media/src/tests/test_server.py @@ -1,6 +1,5 @@ import io import json -import os from base64 import urlsafe_b64encode from concurrent import futures from contextlib import contextmanager @@ -18,7 +17,7 @@ from PIL.JpegImagePlugin import JpegImageFile from media.server import create_app -from proto import api_pb2, media_pb2, media_pb2_grpc +from proto import media_pb2, media_pb2_grpc DATADIR = Path(__file__).parent / "data" diff --git a/app/mobile/src/.prettierignore b/app/mobile/src/.prettierignore index 9d1d357785..745a56784f 100644 --- a/app/mobile/src/.prettierignore +++ b/app/mobile/src/.prettierignore @@ -15,4 +15,4 @@ npm-debug.* *.orig.* web-build/ .DS_Store -*.md \ No newline at end of file +*.md diff --git a/app/mobile/src/Dockerfile b/app/mobile/src/Dockerfile index e80d36a34e..7158179422 100644 --- a/app/mobile/src/Dockerfile +++ b/app/mobile/src/Dockerfile @@ -16,4 +16,4 @@ ARG EXPO_ROBOT_TOKEN='EXPO_TOKEN_NOT_SET' ENV EXPO_TOKEN=$EXPO_ROBOT_TOKEN # this cmd is not the entry point for ci/cd as gitlab specifies its own. -CMD echo $EXPO_TOKEN \ No newline at end of file +CMD echo $EXPO_TOKEN diff --git a/app/mobile/src/README.md b/app/mobile/src/README.md index 4d83588578..4fed1113cb 100644 --- a/app/mobile/src/README.md +++ b/app/mobile/src/README.md @@ -22,4 +22,4 @@ that can be scanned to start the application. We have added a pipeline for mobile ci/cd, using the same strategy as for the web front end. Tagging any branch with a tag that matches `v[0-9]\.[0-9]\.[0-9]-mobile-preview/` (ie `v0.0.1-mobile-preview`) -will cause the version under consideration to be pushed to Expo Go, effectively acting as a 'preview release'. \ No newline at end of file +will cause the version under consideration to be pushed to Expo Go, effectively acting as a 'preview release'. diff --git a/app/mobile/src/storybook/stories/CenterView/index.tsx b/app/mobile/src/storybook/stories/CenterView/index.tsx index a383b589af..649c7407c9 100644 --- a/app/mobile/src/storybook/stories/CenterView/index.tsx +++ b/app/mobile/src/storybook/stories/CenterView/index.tsx @@ -17,4 +17,4 @@ const styles = StyleSheet.create({ alignContent: "center", backgroundColor: "#F5FCFF", }, -}) \ No newline at end of file +}) diff --git a/app/proto/notification_data.proto b/app/proto/notification_data.proto index 816fdc4ee3..a0a73fedd2 100644 --- a/app/proto/notification_data.proto +++ b/app/proto/notification_data.proto @@ -154,7 +154,6 @@ message ReferenceReceiveHostRequest { string text = 3; } - message ReferenceReminder { int64 host_request_id = 1; org.couchers.api.core.User other_user = 2; diff --git a/app/proto/requests.proto b/app/proto/requests.proto index 162ced2e56..ba0067d673 100644 --- a/app/proto/requests.proto +++ b/app/proto/requests.proto @@ -159,7 +159,6 @@ message ResponseRateMost { google.protobuf.Duration response_time_p66 = 3; } - message ResponseRateAlmostAll { // the 33rd percentile response time google.protobuf.Duration response_time_p33 = 2; diff --git a/app/proto/search.proto b/app/proto/search.proto index 9563836f43..495e058868 100644 --- a/app/proto/search.proto +++ b/app/proto/search.proto @@ -135,7 +135,6 @@ message UserSearchReq { google.protobuf.BoolValue camping_ok = 27; google.protobuf.BoolValue profile_completed = 31; - uint32 page_size = 1; string page_token = 2; } diff --git a/app/web/.sentryclirc b/app/web/.sentryclirc index b2e261a528..2f6cc6e163 100644 --- a/app/web/.sentryclirc +++ b/app/web/.sentryclirc @@ -1,3 +1,3 @@ [defaults] project=frontend -org=couchers \ No newline at end of file +org=couchers diff --git a/app/web/markdown/blog/2021/12/10/talk-of-the-town.md b/app/web/markdown/blog/2021/12/10/talk-of-the-town.md index e5f34d4a93..b18d548b90 100644 --- a/app/web/markdown/blog/2021/12/10/talk-of-the-town.md +++ b/app/web/markdown/blog/2021/12/10/talk-of-the-town.md @@ -98,4 +98,3 @@ _Written by Marlies. Published on 2021/12/10_ _Marlies is a Global Health masters student from Australia, currently living, studying and working in Denmark. She has surfed in Europe, Latin America and Asia and is currently hosting in the reconverted church where she lives in Copenhagen._ **Want to submit to our blog? [Sign up](/volunteer) and let us know.** - diff --git a/app/web/markdown/blog/2022/03/13/hosting-refugees-and-finding-shelter-with-couchers-org-ro.md b/app/web/markdown/blog/2022/03/13/hosting-refugees-and-finding-shelter-with-couchers-org-ro.md index 2a3f84368b..85dd0b2208 100644 --- a/app/web/markdown/blog/2022/03/13/hosting-refugees-and-finding-shelter-with-couchers-org-ro.md +++ b/app/web/markdown/blog/2022/03/13/hosting-refugees-and-finding-shelter-with-couchers-org-ro.md @@ -24,7 +24,7 @@ Am trimis un e-mail tuturor membrilor noștri prin care îi rugăm să ofere loc Te rugăm să te înregistrezi pentru [crearea unui cont](https://couchers.org/signup) pe Couchers.org. Adresa ta aproximativă va fi afișată pe o hartă pentru ca oamenii să te găsească. Te rugăm [editează-ți profilul](https://couchers.org/profile/edit) pentru a indica în mod vizibil în secțiunea "Cine sunt" că vei accepta refugiați care să stea în casa ta. -Completează-ți profilul de gazdă cât mai clar posibil. Dacă cineva dorește să stea la tine, vei primi o alertă prin e-mail. Vei putea apoi să discuți pe platformă sau să faci schimb de numere de telefon pentru a stabili condițiile de cazare. +Completează-ți profilul de gazdă cât mai clar posibil. Dacă cineva dorește să stea la tine, vei primi o alertă prin e-mail. Vei putea apoi să discuți pe platformă sau să faci schimb de numere de telefon pentru a stabili condițiile de cazare. Nu vei putea solicita plată pentru cazare, iar oricine va face acest lucru va fi șters de pe site. diff --git a/app/web/markdown/blog/2022/03/13/hosting-refugees-and-finding-shelter-with-couchers-org-uk.md b/app/web/markdown/blog/2022/03/13/hosting-refugees-and-finding-shelter-with-couchers-org-uk.md index 81b76f89b5..4d6f93073d 100644 --- a/app/web/markdown/blog/2022/03/13/hosting-refugees-and-finding-shelter-with-couchers-org-uk.md +++ b/app/web/markdown/blog/2022/03/13/hosting-refugees-and-finding-shelter-with-couchers-org-uk.md @@ -1,5 +1,5 @@ --- -title: Прийом біженців і пошук притулку за допомогою Couchers.org +title: Прийом біженців і пошук притулку за допомогою Couchers.org slug: hosting-refugees-and-finding-shelter-with-couchers-org-uk date: 2022/03/13 author: Itsi diff --git a/app/web/markdown/blog/2022/08/30/setting-up-your-profile.md b/app/web/markdown/blog/2022/08/30/setting-up-your-profile.md index b2ca82f013..d6dc941b04 100644 --- a/app/web/markdown/blog/2022/08/30/setting-up-your-profile.md +++ b/app/web/markdown/blog/2022/08/30/setting-up-your-profile.md @@ -7,23 +7,23 @@ author: Bob share_image: https://couchers.org/img/share.jpg --- -Most of us are quite familiar with being asked to set up profiles online. How we respond to those requests often depends on why we are using the particular website. Online shopping apps, for instance, ask you to set up a profile containing information that they will use to flood you with offers they feel will appeal to your particular demographic. Many of us don’t even bother with anything except a username and a password. +Most of us are quite familiar with being asked to set up profiles online. How we respond to those requests often depends on why we are using the particular website. Online shopping apps, for instance, ask you to set up a profile containing information that they will use to flood you with offers they feel will appeal to your particular demographic. Many of us don’t even bother with anything except a username and a password. -With our social media apps, we may put in a bit more time. We may use our real name. We add a picture, our age (often required), and perhaps a couple of details about where we live, where we went to school, and our relationship status. Some of us choose to keep this information private, which is our right. +With our social media apps, we may put in a bit more time. We may use our real name. We add a picture, our age (often required), and perhaps a couple of details about where we live, where we went to school, and our relationship status. Some of us choose to keep this information private, which is our right. When setting up a profile for a couch surfing website such as Couchers.org, however, it is good to remember that this platform is built on trust. Staying with or hosting strangers from another country or culture requires a level of assurance that the person you are engaging with is someone you wouldn’t mind being with in close quarters. ## Required information -Obviously, for Couchers.org to work for everyone, it requires certain details to promote confidence in the platform. Some of the necessary details you will be asked are: +Obviously, for Couchers.org to work for everyone, it requires certain details to promote confidence in the platform. Some of the necessary details you will be asked are: * Username * Whether you use your real name or an alias, keep it appropriate and remember it will be the first thing people see about you. * Email address * This serves as your point of contact where you will receive notifications of messages, requests, etc. * Date of birth - * Be honest with your age. This isn’t a dating site. + * Be honest with your age. This isn’t a dating site. ## First impressions matter @@ -33,22 +33,22 @@ Your profile will usually be the first introduction that a prospective host or c * Profile picture - * A picture of your face, not hiding behind sunglasses, is best. + * A picture of your face, not hiding behind sunglasses, is best. * Your current area of residence * This helps to set your time zone within the site, lets you know of events in your area, and gives any prospective surfers an idea of where they’ll be staying. NOTE: you do NOT have to put your actual address, only a general location. * Personal pronouns * Couchers is inclusive of all, with choices of pronouns: he/him; she/her; they/them; something you choose; or leave it blank. (NOTE: this can only be changed by an admin once set). -After this, if you wish, you can let others know if you are able to host or to meet up when they visit your area. You can tell them where you’re from, as many of us are living in places far from where we grew up. Your job or education can be included. +After this, if you wish, you can let others know if you are able to host or to meet up when they visit your area. You can tell them where you’re from, as many of us are living in places far from where we grew up. Your job or education can be included. ## Let them know the real you Don’t let those things define you, however. The next part is where you can really let others know who you are. You can write as little or as much as you want about why you use Couchers.org, what your goals are, and interesting travel stories. -Tell other members about your hobbies, what types of entertainment you prefer, countries where you have lived or traveled, and languages you can speak. There’s space for you to write any other information that you feel is important about yourself. These are the items that really tell the story of who you are. It also allows for others to find things you may have in common and start a better conversation. +Tell other members about your hobbies, what types of entertainment you prefer, countries where you have lived or traveled, and languages you can speak. There’s space for you to write any other information that you feel is important about yourself. These are the items that really tell the story of who you are. It also allows for others to find things you may have in common and start a better conversation. -Hopefully you’ll find these suggestions helpful as you begin the amazing journey that being a member of Couchers.org can offer. +Hopefully you’ll find these suggestions helpful as you begin the amazing journey that being a member of Couchers.org can offer. ## Any more questions? diff --git a/app/web/markdown/blog/2022/09/20/creating-a-good-request.md b/app/web/markdown/blog/2022/09/20/creating-a-good-request.md index 5445e9e90b..9e91e2124b 100644 --- a/app/web/markdown/blog/2022/09/20/creating-a-good-request.md +++ b/app/web/markdown/blog/2022/09/20/creating-a-good-request.md @@ -9,7 +9,7 @@ share_image: https://couchers.org/img/share.jpg Now that the pandemic-related travel restrictions are all but lifted, travelers are again beginning to scratch their itch to get out and explore. This includes couch surfers who have begun to use couch surfing apps like Couchers.org to plan their accommodations. -For some of you, writing a request to stay with someone else is a new experience. For others, it may have been a while since sending your last one. Here are some hints to writing a good couch request that has a better chance of being accepted. +For some of you, writing a request to stay with someone else is a new experience. For others, it may have been a while since sending your last one. Here are some hints to writing a good couch request that has a better chance of being accepted. ## 1. Use their name! @@ -26,19 +26,19 @@ Hosts are individuals. They have different circumstances, likes/dislikes, schedu ## 3. State why you’d like to stay with them -To many hosts, a request that simply says that a guest is coming and wants to stay for a certain number of nights and says nothing about why the guest is requesting to stay with them, is regarded as insulting. Hosts are not running hotels or hostels for the convenience of travelers. We signed up for this to have interaction with like-minded travelers. Mention something specific that you read in their profile (see #2) that you appreciate about them. This can start the bonding process. +To many hosts, a request that simply says that a guest is coming and wants to stay for a certain number of nights and says nothing about why the guest is requesting to stay with them, is regarded as insulting. Hosts are not running hotels or hostels for the convenience of travelers. We signed up for this to have interaction with like-minded travelers. Mention something specific that you read in their profile (see #2) that you appreciate about them. This can start the bonding process. ## 4. Include information about yourself (fill out your profile) -Hotels, hostels, AirBnb hosts don’t really care about your personality, your hobbies, or your previous travels. They are simply about the monetary transaction. It’s not that your Couchers.org hosts are nosy, but they would like to know who you are, what you like, and get an idea of whether you would be a good fit or not before they accept your request. Don’t make them ask a lot of questions. This is also why it’s crucial to fill out your own profile. You can refer to that when making the request. +Hotels, hostels, AirBnb hosts don’t really care about your personality, your hobbies, or your previous travels. They are simply about the monetary transaction. It’s not that your Couchers.org hosts are nosy, but they would like to know who you are, what you like, and get an idea of whether you would be a good fit or not before they accept your request. Don’t make them ask a lot of questions. This is also why it’s crucial to fill out your own profile. You can refer to that when making the request. ## 5. Clearly state your intentions Hosts are often busy people with jobs and social lives. If you don’t give them specific dates that you wish to stay, or include some information about what you are planning to do during your stay, it’s difficult for them to know if they can accommodate you. If you plan to go clubbing in their city until the wee hours, it may interfere with their sleep to let you in when you finish. Or, maybe they would like to join you, but that’s best decided beforehand. -Staying with hospitable strangers and hosting travelers can form lasting relationships. But these must be built on trust. The first step of that trust may be simply writing a good couch request. +Staying with hospitable strangers and hosting travelers can form lasting relationships. But these must be built on trust. The first step of that trust may be simply writing a good couch request. ## Any more questions? diff --git a/app/web/markdown/blog/2022/10/11/traveling-in-a-post-covid-world.md b/app/web/markdown/blog/2022/10/11/traveling-in-a-post-covid-world.md index 1513f5dd6a..32485f6ca8 100644 --- a/app/web/markdown/blog/2022/10/11/traveling-in-a-post-covid-world.md +++ b/app/web/markdown/blog/2022/10/11/traveling-in-a-post-covid-world.md @@ -9,11 +9,11 @@ share_image: https://couchers.org/img/share.jpg To say that the last two-and-a-half years have been a challenge for everyone would be a massive understatement. For travelers, this period had been nothing short of a nightmare. -Most of us had to alter or indefinitely postpone travel plans. Some of us had to cut short our journeys due to travel restrictions, oftentimes paying big money for hurried changes in flight arrangements. And a few of us found ourselves stranded for months in places we had only intended to visit for a short time. +Most of us had to alter or indefinitely postpone travel plans. Some of us had to cut short our journeys due to travel restrictions, oftentimes paying big money for hurried changes in flight arrangements. And a few of us found ourselves stranded for months in places we had only intended to visit for a short time. With the pandemic seemingly in a slowdown and entry/visa restrictions being lifted in most places, many travelers are champing at the bit to get back out there and explore. -Others are still a bit wary, especially if it involves couch surfing. Opening your home to strangers or staying with a host whom you don’t know at all may raise a lot of questions that didn’t exist before. +Others are still a bit wary, especially if it involves couch surfing. Opening your home to strangers or staying with a host whom you don’t know at all may raise a lot of questions that didn’t exist before. * Are they vaccinated? * Do they expect me to wear a mask? diff --git a/app/web/markdown/documents/governance.md b/app/web/markdown/documents/governance.md index 36d45c9432..2b29e45baf 100644 --- a/app/web/markdown/documents/governance.md +++ b/app/web/markdown/documents/governance.md @@ -4,7 +4,7 @@ title: Governance and Leadership Structure This document outlines the powers and responsibilities of different members of the organization, and how decisions are made. -In brief, at the highest level is the **Board** who appoint the **Executive Director** (ED) who is primarily responsible for running the organization. The ED may appoint a **Steering Group** (SG) to help with decision making processes and delegation of responsibilities. +In brief, at the highest level is the **Board** who appoint the **Executive Director** (ED) who is primarily responsible for running the organization. The ED may appoint a **Steering Group** (SG) to help with decision making processes and delegation of responsibilities. Most responsibilities will fall into one of four departments: **Product**, **Operations**, **Marketing**, and **Community**. Each department has a **Department Head**, and coordinates the various constituent teams, each with a **Team Lead**. Department Heads are members of the Steering Group. @@ -23,13 +23,13 @@ There are many cases where it is unclear if the decision falls within the scope For this to work, we need to consider: - communication and consultation must be high. While no one needs authority, it is still worth discussing things over slack and the forum, especially when there will be significant impact. - we will **make mistakes**, and decisions will need to be overridden. No one should feel bad in these situations, it is a good outcome, as it means people are taking action and pushing things forward. People must be frank with one another when this happens. -- **scope must be clearly defined**. If anyone is unsure about their responsibilities, this should be quickly corrected so people know what they should be taking initiative over. +- **scope must be clearly defined**. If anyone is unsure about their responsibilities, this should be quickly corrected so people know what they should be taking initiative over. ## Responsibilities ### The Board -The Board is a supervisory body that ensures that the organization is on the right path to sustainably meet its mission and vision, which it also sets. It is removed from the executive decisions of the organization, and acts to provide stability to the organization. It vests its authority in the Executive Director and other potential executive roles, and has the power to select and dismiss them. +The Board is a supervisory body that ensures that the organization is on the right path to sustainably meet its mission and vision, which it also sets. It is removed from the executive decisions of the organization, and acts to provide stability to the organization. It vests its authority in the Executive Director and other potential executive roles, and has the power to select and dismiss them. The Board meets at least once a quarter. It should be removed from the day-to-day operations of the organization, but provides high level strategy and advises those carrying out executive tasks. @@ -45,7 +45,7 @@ The ED has significant powers, and is held accountable by the Board. Volunteers The SG is the core executive body of the organization. It is appointed by and run by the ED. It makes practical decisions for the execution of the mission, and leads the core team. Although the ED can and does make executive decisions, it is prudent to consult with this body to come to consensus and delegate ownership of tasks. -In general, it is composed of very active members of the organization who have significant responsibilities, and who should be consulted in regular decision making. Members of the SG hold each other accountable for commitments to their responsibilities, and contribute to cross-departmental decision making. +In general, it is composed of very active members of the organization who have significant responsibilities, and who should be consulted in regular decision making. Members of the SG hold each other accountable for commitments to their responsibilities, and contribute to cross-departmental decision making. ### Department Heads We can separate most regular responsibilities into one of four departments: Product, Operations, Marketing, and Community. Each department has a Department Head who operates it, setting its plan and making sure that plan is carried out. Department Heads report to and work closely with the ED. diff --git a/app/web/markdown/documents/volunteer-guide.md b/app/web/markdown/documents/volunteer-guide.md index 6fbd0a1e26..6c3a20d113 100644 --- a/app/web/markdown/documents/volunteer-guide.md +++ b/app/web/markdown/documents/volunteer-guide.md @@ -26,11 +26,11 @@ To get started, do the following tasks: ## The Couchers.org Vision -Please have a read through our [Mission and Values](/mission). They are very important to what we do. We think that both couch surfing and belonging to couch surfing communities improve people’s lives, and grow society to be more open, empathetic and tolerant. +Please have a read through our [Mission and Values](/mission). They are very important to what we do. We think that both couch surfing and belonging to couch surfing communities improve people’s lives, and grow society to be more open, empathetic and tolerant. The project was initially started by Aapeli and Itsi. In early 2020, they laid out a vision by identifying [issues with the current couch surfing landscape](/issues) and suggesting a [set of solutions](/plan). These form the basic "founding documents" and are the groundwork for the vision and plan on what we are building. Everyone who joins the team must agree on this unified vision in order for us to be on the same page on what we're working on. This also ensures that you know that any work you contribute now will not go towards any different vision in the future. -In short: our goal is to replicate the good elements of existing platforms while making some fundamental improvements to fix systematic issues with them. Once we have reached this point, we will continue to further iterate and improve the platform. +In short: our goal is to replicate the good elements of existing platforms while making some fundamental improvements to fix systematic issues with them. Once we have reached this point, we will continue to further iterate and improve the platform. Our belief is that through good design and curation of the community, we can make couch surfing a far safer, accessible and easier experience for all members, and grow the couch surfing community to more and more people in the world. Over the long term we have an expansive and inclusive outset. We believe that couch surfing is a wonderful experience for individuals, and has a broader benefit to society in making people more generous, open-minded and trusting. We want to introduce as many people in the world as possible to couch surfing in a sustainable way that preserves the integrity of the overall community. diff --git a/app/web/markdown/mission.md b/app/web/markdown/mission.md index 99b107d2cd..af30a4b7b6 100644 --- a/app/web/markdown/mission.md +++ b/app/web/markdown/mission.md @@ -51,7 +51,7 @@ We want others to benefit from the tools we build to the fullest extent possible ### Non-transactional -People do not pay each other in money or in kind to participate in activities or the community. By surfing, you are not obligated to host in the future. +People do not pay each other in money or in kind to participate in activities or the community. By surfing, you are not obligated to host in the future. Note: people may pool together to cover external costs. diff --git a/app/web/markdown/volunteer/backend-developer.md b/app/web/markdown/volunteer/backend-developer.md index 2dfc5ad2bb..f9c11b7303 100644 --- a/app/web/markdown/volunteer/backend-developer.md +++ b/app/web/markdown/volunteer/backend-developer.md @@ -16,7 +16,7 @@ The Couchers.org codebase is open-sourced under an MIT license and we accept occ We pride ourselves on good documentation, ease of contribution, and ability for contributors to make large impacts. You will be working alongside experienced professionals who are motivated to develop their skills, meet other professionals, and develop this critical platform for the couch surfing community. -### Duties +### Duties - Developing and expanding features; - Debugging, documentation, and testing; @@ -33,7 +33,7 @@ We pride ourselves on good documentation, ease of contribution, and ability for - Good understanding of REST APIs; - Experience with React and Typescript for occasional frontend implementation; -- Experience with PostgreSQL and SQLAlchemy; +- Experience with PostgreSQL and SQLAlchemy; ### Expectations/Commitment diff --git a/app/web/markdown/volunteer/blog-writer.md b/app/web/markdown/volunteer/blog-writer.md index bb77bda428..aa658e83b0 100644 --- a/app/web/markdown/volunteer/blog-writer.md +++ b/app/web/markdown/volunteer/blog-writer.md @@ -11,7 +11,7 @@ description: "Write for the Couchers.org blog!" Couchers.org is seeking one or two blog writers to write all kinds of topics for our [blog](/blog). We are looking for all kind of articles from in depth research, personal stories, and updates from the Couchers team! -### Duties +### Duties - Work closely with the Web Content Specialist and Blog Editor to develop blog post topics and create blog content; - Write blog posts on a consistent basis per availability and agreement with the Blog Editor; diff --git a/app/web/markdown/volunteer/frontend-developer.md b/app/web/markdown/volunteer/frontend-developer.md index 5d0ba1df1a..7705d3e322 100644 --- a/app/web/markdown/volunteer/frontend-developer.md +++ b/app/web/markdown/volunteer/frontend-developer.md @@ -15,7 +15,7 @@ The Couchers.org codebase is open-sourced under an MIT license and we accept occ We pride ourselves on good documentation, ease of contribution, and ability for contributors to make large impacts. You will be working alongside experienced professionals who are motivated to develop their skills, to meet other professionals, and to develop this critical platform for the couch surfing community. -### Duties +### Duties - Developing and expanding features; - Improving UI based on wireframes; diff --git a/app/web/markdown/volunteer/handbook-project-manager.md b/app/web/markdown/volunteer/handbook-project-manager.md index cb589edfe8..065fbedbc6 100644 --- a/app/web/markdown/volunteer/handbook-project-manager.md +++ b/app/web/markdown/volunteer/handbook-project-manager.md @@ -14,7 +14,7 @@ We are recruiting a Project Manager to handle the development and maintenance of The Handbook Project Manager (HPM) will be responsible for developing, editing, publishing, and maintaining the Handbook and Help page on the Couchers website. The HPM will create and oversee a team of content writers with couch surfing experience to assist with these goals. The HPM reports to the Head of Community & Support. -### Duties +### Duties - Design a framework for the development of the Handbook; - Use analytics and feedback from other teams and the community to curate, expand and improve content; diff --git a/app/web/markdown/volunteer/head-of-marketing.md b/app/web/markdown/volunteer/head-of-marketing.md index 1c780bf98c..81ad494258 100644 --- a/app/web/markdown/volunteer/head-of-marketing.md +++ b/app/web/markdown/volunteer/head-of-marketing.md @@ -11,7 +11,7 @@ description: "Apply to become the Head of Marketing for Couchers.org" As Marketing Lead, you’ll lead a team to market the Couchers.org brand to a global audience of both experienced and new couch surfers. You should have experience in developing cohesive marketing strategies spanning product or service-based digital marketing, public relations, content marketing, and social media management. -### Duties +### Duties - Plan, present, implement, and manage marketing strategy to sustainably grow the number of users globally; - Set marketing goals and objectives; diff --git a/app/web/markdown/volunteer/mobile-development-lead.md b/app/web/markdown/volunteer/mobile-development-lead.md index 126531b417..5baa78b177 100644 --- a/app/web/markdown/volunteer/mobile-development-lead.md +++ b/app/web/markdown/volunteer/mobile-development-lead.md @@ -15,10 +15,10 @@ The Couchers.org codebase is open-sourced under an MIT license and we accept occ We pride ourselves on good documentation, ease of contribution, and ability for contributors to make large impacts. You will be working alongside experienced professionals who are motivated to develop their skills, to meet other professionals, and to develop this critical platform for the couch surfing community. -### Duties +### Duties - Providing strategic vision for mobile development including technical design; -- Work with our backend, web, and UX teams as part of our software lifecycle; +- Work with our backend, web, and UX teams as part of our software lifecycle; - Lead a mobile team in delivering the mobile product, including mentoring and necessary training of engineers; - Assist in maintaining our documentation; - Assist in our recruitment process for the onboarding of mobile developers; diff --git a/app/web/markdown/volunteer/technical-support-analyst.md b/app/web/markdown/volunteer/technical-support-analyst.md index 37116c5ff8..70213fc19c 100644 --- a/app/web/markdown/volunteer/technical-support-analyst.md +++ b/app/web/markdown/volunteer/technical-support-analyst.md @@ -9,7 +9,7 @@ description: "Apply to become a Technical Support Analyst for Couchers.org" **This is a remote position** -We are recruiting a Technical Support Analyst (TSA) to manage our technical support queue. The TSA will be responsible for reviewing, sorting and responding to technical support tickets in the context of user support and quality assurance of our product (the Couchers.org platform). +We are recruiting a Technical Support Analyst (TSA) to manage our technical support queue. The TSA will be responsible for reviewing, sorting and responding to technical support tickets in the context of user support and quality assurance of our product (the Couchers.org platform). The TSA will possess the skills to be able to identify the cause(s) of reported issues–whether poor design, potential bug, or simply user error–and the appropriate solution. In addition to recording, analyzing, and relaying data on reported issues, the TSA will be responsible for identifying ways to reduce the number of user errors and improve the efficiency and effectiveness of responses to tickets. The TSA reports to the Community & Support Team Leader. @@ -27,13 +27,13 @@ The TSA will possess the skills to be able to identify the cause(s) of reported ### Skills & Experience An ideal candidate would have the following skills: -- Online customer support experience; -- Familiarity with the Couchers.org website, help page, and forum; -- Familiarity with some kind of ticketing system (OTRS, OTOBO, or GitHub a plus); -- Ability to organize and analyze data; -- Previous experience with a couch surfing platform; -- Experience with software development or QA/QC is a plus; -- Excellent communication, interpersonal and organizational skills; +- Online customer support experience; +- Familiarity with the Couchers.org website, help page, and forum; +- Familiarity with some kind of ticketing system (OTRS, OTOBO, or GitHub a plus); +- Ability to organize and analyze data; +- Previous experience with a couch surfing platform; +- Experience with software development or QA/QC is a plus; +- Excellent communication, interpersonal and organizational skills; - Native (C2) fluency of English both written and spoken; additional languages a plus. ### Expectations/Commitment diff --git a/app/web/markdown/volunteer/volunteer-coordinator.md b/app/web/markdown/volunteer/volunteer-coordinator.md index e28d40d2d7..653cec476d 100644 --- a/app/web/markdown/volunteer/volunteer-coordinator.md +++ b/app/web/markdown/volunteer/volunteer-coordinator.md @@ -10,7 +10,7 @@ description: "Apply to become a Volunteer Coordinator for Couchers.org" **This is a remote position** -We are recruiting up to four volunteers that will be coordinating the Volunteer Management Program (VMP). The main goal of the VMP is to ensure that Couchers.org has the volunteer capacity to fulfil its mission. To meet that goal, the group of Volunteer Coordinators must perform a variety of duties involving recruitment, training and program planning. +We are recruiting up to four volunteers that will be coordinating the Volunteer Management Program (VMP). The main goal of the VMP is to ensure that Couchers.org has the volunteer capacity to fulfil its mission. To meet that goal, the group of Volunteer Coordinators must perform a variety of duties involving recruitment, training and program planning. The VMP consists of 5 main areas: @@ -20,9 +20,9 @@ The VMP consists of 5 main areas: 4. Engagement & Retention: recognition and appreciation of volunteer efforts 5. Offboarding -This is a job-sharing/collaborative volunteer position. The group will be working together and reporting to the Executive Director. +This is a job-sharing/collaborative volunteer position. The group will be working together and reporting to the Executive Director. -### Duties +### Duties - Helping to create process and procedures for the Volunteer Management Program - Recruiting, onboarding and supervising new volunteers diff --git a/app/web/readme.md b/app/web/readme.md index df9a2b6fc0..ddcb3722e5 100644 --- a/app/web/readme.md +++ b/app/web/readme.md @@ -55,7 +55,7 @@ Alternatively, you can use `yarn start` if you update your local environment var
Common problem: Getting logged out right after logging in - + If you're getting logged out right after logging in, it's possible that 3rd party cookies are blocked in your browser. Since you're using localhost:3000, the cookie `couchers-sesh` coming from `https://dev-api.couchershq.org` is considered a 3rd party cookie. - Chrome allows to enable 3rd party cookies for specific websites in the cookie settings > Sites that can always use cookies. Enable "Including third-party cookies on this site" diff --git a/docs/contributing.md b/docs/contributing.md index b09068b5df..d03956d1d0 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -54,9 +54,11 @@ All python code should live in the `couchers` namespace (i.e. a folder within th ## Code style -We adhere to [PEP8](https://www.python.org/dev/peps/pep-0008/), but it's automatically done with [black](https://github.com/psf/black). We also sort imports with `isort` and we remove unused imports with `autoflake`. +We adhere to [PEP8](https://www.python.org/dev/peps/pep-0008/), but it's automatically done with the [ruff](https://docs.astral.sh/ruff/) formatter, which also sorts imports. Additionally, we use the ruff linter to perform a static code check. -All are installed automatically if you install the requirements on your computer (or you can install them with `pip install black isort autoflake`, e.g. if you work with Docker). Run `autoflake --exclude src/proto -r -i --remove-all-unused-imports src && isort . && black .` in the `//app/backend` folder before you commit (or before asking for review) so that it picks up the config in `pyproject.toml`. +`ruff` is installed automatically if you install the requirements on your computer (or you can install it with `pip install ruff`, e.g. if you work with Docker). Run `ruff check --select I --fix . && ruff check . && ruff format .` in the `//app/backend` folder before you commit (or before asking for review) so that it picks up the config in `pyproject.toml`. + +You can run `ruff` linting and autoformatting automatically before each commit via `pre-commit` (It comes with the dependencies, or you can install it via `pip install pre-commit`). For this you have to once run `pre-commit install`. If you don't want to run the pre-commit hook, you can skip commit hooks with the `--no-verify` flag: `git commit --no-verify -m "commit message"`. Additionally, we strive to use the ["Google" docstring format](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html). We will auto-generate docs from code, so it's important to adhere to a uniform docstring style. diff --git a/docs/couchers app.drawio b/docs/couchers app.drawio index d6b4f8b982..20e0ab4944 100644 --- a/docs/couchers app.drawio +++ b/docs/couchers app.drawio @@ -1 +1 @@ -5VrLcuI6EP0aqpIFKdsC4ywDeS1mqlJD5mayuiWwMJrIliPLPObrr2RL2MIOYwIEqJtFbLWkttSn+3RLSQsMwsUDg/H0O/URaTmWv2iB25bj2LbnioeULHNJz+3lgoBhXw0qBEP8BymhpaQp9lFiDOSUEo5jUzimUYTG3JBBxujcHDahxPxqDANUEQzHkFSlL9jn01zqda1C/ohwMNVfti3VE0I9WAmSKfTpvCQCdy0wYJTy/C1cDBCRxtN2yefdf9C7WhhDEW8yYTB4fv/t//N+O3n818azNH19+N4GXbU4vtQ7Rr4wgGpSxqc0oBEkd4W0z2ga+UiqtUSrGPON0lgIbSH8jThfKjRhyqkQTXlIVC9aYP6r9P4qVV11Vet2oTRnjaVuRJwtf5UbpVmyWUzLWnpewiHjN9IThGBMYJLgsRbfY6KXVLWmMnBCUzZGG0zoKK+ELEB8k6k7+UBp39IXFFgPiIZIrFsMYIhAjmemA0Llx8FqXAG1eFFob4F8xzom8gXar2Wwa5H/KgT3B4ya+kSxWIpjKTJ0PevK7rmdntX1PAd0FTMqYry2TH25PykVa1iv1vR5+NWeZ5Ckaj8txyXCcH0fz8RrIF9HcPyGIl/3iA+VOmvGa9GIrUv+OvUiFpYbSLTeCSTjKQpVO2Dx+LK54jWXLhxWush8ijkaxjDzhrlIWKZzfuhBM8Q4Wmz0DY2xZnuFqq0TxbxIHrYeMy0lDtf62J12inPnqHFuMnzvqBRP4AiRvnDpINvegBLKRFdEI7QTfYCGCeC0+B9UGeDx+fmpJf3lPo0JhX6t43yTVjTBhgQHkTS9MB0SJu3LeMGihrpRHSH2/dyvUIL/wFGmTyIXS3rLdtbtt7q3nwBpo89X4nZVJ6o1GKVYXTxbV9eO1zNCWuO9YzZodxxTrQ1MFXQySdBhuL9zOpTgnGnV1zTobe+kot7xKlEf/HgatF/QSEipCBjxyHngXGO/s7/Y96xexwzS7mFi31nL/oeLfdCk7pswKtA8QOF3IQDPqjqGoDisZ6/ioIwYhqSd4ssN9Zt12PpN12veses1u3dMct7mXFZwc0HHr1pfU25WwV0mZmsnYu40JOa9H/p2q8YaxaUhKnmI+57KW6T+OGfMG7nsYHRh5QGmH5eZBmsiYrs9V64sh0aUhZCUOpPMS2SX7cSLvIPgCLWnpVm2p7r0xxvQQxLD6G8LbznAyn6qmn9mVemARhMsVswxjUosk+tucurcRloSyTwpc6TcQSxCTNqg1wUND6Lnkj41/eyePtvWle26B6mdu70vS5dHZeNt7kcPXvHu/fhaj61nmzcoYD3THvhiTJePG4k4RD6GX3otNhGIvp3vPRgAx66rHPdcIvlMr8H0SffcKi99V2SeiKuZ3q71n3PI6dr193EkBkCXqnu+DlsjjJU7HT7H29U7kRomjhldLL+E8lE0o8vzoXrgndqfPECjHO5D4fYwQV+TxmnCAxHu5wOrd3qwVuN0yCkTNlFcTaggWVkrYYKSZcJRWLGi2Dw3TZVwRt/QGrEKDWRN1JzV67AxC4lPpuAtwFsvv2rAu67Bbv0ecn/YXVewuwshJomEB2WJIE1wFIjnzctQ/B7eDf+v4LnABM+tCTxrP+CJZvEPSHk+Lf6NC9z9Bw== \ No newline at end of file +5VrLcuI6EP0aqpIFKdsC4ywDeS1mqlJD5mayuiWwMJrIliPLPObrr2RL2MIOYwIEqJtFbLWkttSn+3RLSQsMwsUDg/H0O/URaTmWv2iB25bj2LbnioeULHNJz+3lgoBhXw0qBEP8BymhpaQp9lFiDOSUEo5jUzimUYTG3JBBxujcHDahxPxqDANUEQzHkFSlL9jn01zqda1C/ohwMNVfti3VE0I9WAmSKfTpvCQCdy0wYJTy/C1cDBCRxtN2yefdf9C7WhhDEW8yYTB4fv/t//N+O3n818azNH19+N4GXbU4vtQ7Rr4wgGpSxqc0oBEkd4W0z2ga+UiqtUSrGPON0lgIbSH8jThfKjRhyqkQTXlIVC9aYP6r9P4qVV11Vet2oTRnjaVuRJwtf5UbpVmyWUzLWnpewiHjN9IThGBMYJLgsRbfY6KXVLWmMnBCUzZGG0zoKK+ELEB8k6k7+UBp39IXFFgPiIZIrFsMYIhAjmemA0Llx8FqXAG1eFFob4F8xzom8gXar2Wwa5H/KgT3B4ya+kSxWIpjKTJ0PevK7rmdntX1PAd0FTMqYry2TH25PykVa1iv1vR5+NWeZ5Ckaj8txyXCcH0fz8RrIF9HcPyGIl/3iA+VOmvGa9GIrUv+OvUiFpYbSLTeCSTjKQpVO2Dx+LK54jWXLhxWush8ijkaxjDzhrlIWKZzfuhBM8Q4Wmz0DY2xZnuFqq0TxbxIHrYeMy0lDtf62J12inPnqHFuMnzvqBRP4AiRvnDpINvegBLKRFdEI7QTfYCGCeC0+B9UGeDx+fmpJf3lPo0JhX6t43yTVjTBhgQHkTS9MB0SJu3LeMGihrpRHSH2/dyvUIL/wFGmTyIXS3rLdtbtt7q3nwBpo89X4nZVJ6o1GKVYXTxbV9eO1zNCWuO9YzZodxxTrQ1MFXQySdBhuL9zOpTgnGnV1zTobe+kot7xKlEf/HgatF/QSEipCBjxyHngXGO/s7/Y96xexwzS7mFi31nL/oeLfdCk7pswKtA8QOF3IQDPqjqGoDisZ6/ioIwYhqSd4ssN9Zt12PpN12veses1u3dMct7mXFZwc0HHr1pfU25WwV0mZmsnYu40JOa9H/p2q8YaxaUhKnmI+57KW6T+OGfMG7nsYHRh5QGmH5eZBmsiYrs9V64sh0aUhZCUOpPMS2SX7cSLvIPgCLWnpVm2p7r0xxvQQxLD6G8LbznAyn6qmn9mVemARhMsVswxjUosk+tucurcRloSyTwpc6TcQSxCTNqg1wUND6Lnkj41/eyePtvWle26B6mdu70vS5dHZeNt7kcPXvHu/fhaj61nmzcoYD3THvhiTJePG4k4RD6GX3otNhGIvp3vPRgAx66rHPdcIvlMr8H0SffcKi99V2SeiKuZ3q71n3PI6dr193EkBkCXqnu+DlsjjJU7HT7H29U7kRomjhldLL+E8lE0o8vzoXrgndqfPECjHO5D4fYwQV+TxmnCAxHu5wOrd3qwVuN0yCkTNlFcTaggWVkrYYKSZcJRWLGi2Dw3TZVwRt/QGrEKDWRN1JzV67AxC4lPpuAtwFsvv2rAu67Bbv0ecn/YXVewuwshJomEB2WJIE1wFIjnzctQ/B7eDf+v4LnABM+tCTxrP+CJZvEPSHk+Lf6NC9z9Bw== diff --git a/docs/database.md b/docs/database.md index 7ddaabef59..306b6cf58a 100644 --- a/docs/database.md +++ b/docs/database.md @@ -40,7 +40,7 @@ cd backend DATABASE_CONNECTION_STRING="postgresql://postgres:203d805f4b62c0a1b2f1f6b82d4583dfe563ec1619b83ce22ee414e8376a25e7@localhost:6545/postgres" PYTHONPATH=src alembic revision --autogenerate -m "Modify the database" # now format the migration -autoflake --exclude src/proto -r -i --remove-all-unused-imports src && isort . && black . +ruff format . # important: look through the migration and make sure it makes sense diff --git a/docs/meetings/dev/2024-04-21.md b/docs/meetings/dev/2024-04-21.md index d3cd20020e..344e7883c9 100644 --- a/docs/meetings/dev/2024-04-21.md +++ b/docs/meetings/dev/2024-04-21.md @@ -12,7 +12,7 @@ just solving some issues with a crypto package of python which seems to not work How the media upload code it's done and why, and explained a bit of context of it. ### interviwing new volunteers -Let's interview more people even if they don't have experience with cs, they might want to contribute anyway, they might want to be part of something, learn, and if we have give them what they want they might want to stay & contribute! +Let's interview more people even if they don't have experience with cs, they might want to contribute anyway, they might want to be part of something, learn, and if we have give them what they want they might want to stay & contribute! note: ask them what they expect/are looking for to know if the project fits diff --git a/docs/meetings/dev/2024-05-07.md b/docs/meetings/dev/2024-05-07.md index ab7dc53922..907a2822da 100644 --- a/docs/meetings/dev/2024-05-07.md +++ b/docs/meetings/dev/2024-05-07.md @@ -20,6 +20,6 @@ Present: Aapeli, Yannic, Tristan, David, Jesse, Krishna * Krishna: issues with sign up - Big discussion about sign up problems * Form is confusing - * Add more + * Add more - Sign in also confusing with two screens * Map search issues diff --git a/docs/meetings/web/2020-12-02.md b/docs/meetings/web/2020-12-02.md index bd50e5d2f4..364095b1cd 100644 --- a/docs/meetings/web/2020-12-02.md +++ b/docs/meetings/web/2020-12-02.md @@ -47,6 +47,3 @@ Darren - will check issues Sherri - Styling for UI/UX guidelines and messages Lucas - Locations in jail/profile edit/profiles/signup? and then continue profiles - - - diff --git a/docs/meetings/web/2020-12-30.md b/docs/meetings/web/2020-12-30.md index 7a28e45806..fa37669702 100644 --- a/docs/meetings/web/2020-12-30.md +++ b/docs/meetings/web/2020-12-30.md @@ -17,4 +17,3 @@ - Aapeli talked about what he is working on (GIS features) - We could use more devs (backend and frontend). If you are reading this, recruit your mates :D - diff --git a/docs/meetings/web/2021-04-07.md b/docs/meetings/web/2021-04-07.md index 37450fcbc7..dfc5098044 100644 --- a/docs/meetings/web/2021-04-07.md +++ b/docs/meetings/web/2021-04-07.md @@ -34,5 +34,3 @@ - Josh will work on Places - Darren is working on Discussions - - diff --git a/docs/meetings/web/2021-04-21.md b/docs/meetings/web/2021-04-21.md index a5e23242bc..36bbcb596a 100644 --- a/docs/meetings/web/2021-04-21.md +++ b/docs/meetings/web/2021-04-21.md @@ -31,5 +31,3 @@ - Darren - Discussions - Lucas - Search - Jesus - TBC - - diff --git a/docs/meetings/web/2021-10-08.md b/docs/meetings/web/2021-10-08.md index 597f969a3f..78c84e9b6d 100644 --- a/docs/meetings/web/2021-10-08.md +++ b/docs/meetings/web/2021-10-08.md @@ -37,4 +37,3 @@ Present: Aapeli, Darren, Lucas, Ricardo **Darren** - Start integrating i18next? - diff --git a/docs/meetings/web/2022-04-06.md b/docs/meetings/web/2022-04-06.md index 72617171ef..e22a593efb 100644 --- a/docs/meetings/web/2022-04-06.md +++ b/docs/meetings/web/2022-04-06.md @@ -8,4 +8,3 @@ - Dashboard redesign - Context for user in profiles slightly annoying but necessary - We should move the meeting time - diff --git a/docs/meetings/web/2022-07-14.md b/docs/meetings/web/2022-07-14.md index ca9df389ac..e943a1b30a 100644 --- a/docs/meetings/web/2022-07-14.md +++ b/docs/meetings/web/2022-07-14.md @@ -7,7 +7,7 @@ - Frontend refactor for new backend - Difficult to find time for such a large refactor for current volunteers - Also considering the backend is not complete, dev feedback loop is very slow (fields missing, tell backend team, wait for changes, etc.) - + - Still many suitable issues to work on such as Profile/other redesigns, bugfixes, translation keys, etc. - Darren will review profile translation key PR diff --git a/docs/web-frontend.md b/docs/web-frontend.md index 8918cd9960..59de288316 100644 --- a/docs/web-frontend.md +++ b/docs/web-frontend.md @@ -53,7 +53,7 @@ index 3c2a0bf..833ede8 100644 +++ b/Dockerfile.stage @@ -36,9 +36,7 @@ COPY . . # ADD http://couchers-dev-assets.s3.amazonaws.com/proto_may_27_2022.tar.gz /app/ - + # Expand our protos into place, set the right env vars into place, then build our static assets -RUN tar -xf proto_may_27_2022.tar.gz && \ - rm -f proto_may_27_2022.tar.gz && \