Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

11 central db #48

Merged
merged 14 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ services:
dockerfile: smib-fast.Dockerfile
ports:
- "4123:4123"
depends_on:
- smib-db
environment:
- WEBSERVER_HOST=smib-webserver
- WEBSOCKET_ALLOWED_HOSTS=smib-webserver,smib-webserver.smib-bridge-network
- MONGO_DB_HOST=smib-db

# Passed in from HOST
- SLACK_APP_TOKEN
Expand Down Expand Up @@ -36,7 +39,33 @@ services:
- smib-bridge-network
command: "python -m smib.webserver"

smib-db:

# Specific version - latest that works on a pi
image: mongo:4.4.18
container_name: smib-db
restart: always
ports:
- 27017:27017
networks:
- smib-bridge-network

smib-db-ui:
image: mongo-express
container_name: smib-db-ui
depends_on:
- smib-db
restart: always
ports:
- 8082:8081
environment:
ME_CONFIG_MONGODB_URL: mongodb://smib-db:27017/
ME_CONFIG_BASICAUTH: true

networks:
- smib-bridge-network

networks:
smib-bridge-network:
name: smib-bridge-network
driver: bridge
driver: bridge
288 changes: 203 additions & 85 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ fastapi = "^0.110.0"
uvicorn = "^0.29.0"
dataclasses-json = "^0.6.4"
aiohttp = "^3.9.5"
pymongo = "^4.7.0"
mogo = "^0.6.0"


[build-system]
Expand Down
12 changes: 6 additions & 6 deletions smib-fast.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ FROM python:3.11-buster as builder
RUN pip install poetry==1.4.2

# Install tzdata, curl, and jq.
RUN apt-get update && apt-get install -y tzdata curl jq
# RUN apt-get update && apt-get install -y tzdata curl jq

# Fetch the timezone using the API, set the TZ environment variable to the fetched timezone.
RUN TIMEZONE=$(curl -s http://worldtimeapi.org/api/ip | jq -r .timezone) && \
ln -fs /usr/share/zoneinfo/$TIMEZONE /etc/localtime && \
dpkg-reconfigure -f noninteractive tzdata && \
echo "TZ=$TIMEZONE" >> /etc/environment
#RUN TIMEZONE=$(curl -s http://worldtimeapi.org/api/ip | jq -r .timezone) && \
# ln -fs /usr/share/zoneinfo/$TIMEZONE /etc/localtime && \
# dpkg-reconfigure -f noninteractive tzdata && \
# echo "TZ=$TIMEZONE" >> /etc/environment

ENV POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_IN_PROJECT=1 \
Expand Down Expand Up @@ -40,4 +40,4 @@ COPY --from=builder /etc/localtime /etc/localtime

WORKDIR /app

COPY smib ./smib
COPY smib ./smib
8 changes: 8 additions & 0 deletions smib/common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@
WEBSOCKET_URL = urlparse(f"{WEBSOCKET_SCHEME}://{WEBSOCKET_HOST}:{WEBSOCKET_PORT}/{WEBSOCKET_PATH}")
WEBSOCKET_ALLOWED_HOSTS = config('WEBSOCKET_ALLOWED_HOSTS', default='localhost,127.0.0.1,::1', cast=Csv())

MONGO_DB_HOST = config('MONGO_DB_HOST', default='localhost')
MONGO_DB_PORT = config('MONGO_DB_PORT', default=27017, cast=int)

MONGO_DB_DEFAULT_DB = config("MONGO_DB_DEFAULT_DB", default="smib_default")
MONGO_DB_CONNECT_TIMEOUT_SECONDS = config("MONGO_DB_CONNECT_TIMEOUT_SECONDS", default=5, cast=int)

MONGO_DB_URL = f"mongodb://{MONGO_DB_HOST}:{MONGO_DB_PORT}/"

PLUGINS_DIRECTORY = config('PLUGINS_DIRECTORY', default=ROOT_DIRECTORY / 'slack' / 'plugins', cast=Path)

SPACE_OPEN_ANNOUNCE_CHANNEL_ID = config('SPACE_OPEN_ANNOUNCE_CHANNEL_ID', default='C06UDPLQRP1')
16 changes: 5 additions & 11 deletions smib/common/logging_/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import logging

from smib.common.config import ROOT_DIRECTORY
from smib.common.utils import get_module_name
from injectable import injectable_factory, load_injection_container, inject


Expand All @@ -16,21 +17,14 @@ def setup_logging(path=ROOT_DIRECTORY / 'logging.json'):
logging.config.dictConfig(read_logging_json(path))


def _get_module_name(stack_num: int = 4):
frame = inspect.stack()[stack_num]
module = inspect.getmodule(frame[0])
module_name = module.__name__
return module_name
@injectable_factory(logging.Logger, qualifier="plugin_logger")
def plugin_logger_factory():
return logging.getLogger(get_module_name(2))


@injectable_factory(logging.Logger, qualifier="logger")
def logger_factory():
return logging.getLogger(_get_module_name())


@injectable_factory(logging.Logger, qualifier="plugin_logger")
def plugin_logger_factory():
return logging.getLogger(_get_module_name(2))
return logging.getLogger(get_module_name(4))


if __name__ == '__main__':
Expand Down
23 changes: 23 additions & 0 deletions smib/common/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import inspect
import logging
import pickle
from pathlib import Path
Expand All @@ -16,6 +17,13 @@ def is_pickleable(obj):
except (pickle.PicklingError, AttributeError, TypeError):
return False

def is_json_encodable(value):
try:
json.dumps(value)
return True
except TypeError:
return False


def to_path(x):
path = Path(f"/{x.lstrip('/')}").as_posix().lstrip('/')
Expand Down Expand Up @@ -65,3 +73,18 @@ def wrapper(*args, **kwargs):
return BoltResponse(status=response[0], body=json.dumps(response[1]))

return wrapper


def get_module_name(stack_num: int = 4):
stack = inspect.stack()
frame = stack[stack_num]
module = inspect.getmodule(frame[0])
module_name = module.__name__
return module_name


def get_module_file(stack_num: int = 4) -> Path:
stack = inspect.stack()
frame = stack[stack_num]
file = inspect.getfile(frame[0])
return Path(file)
3 changes: 3 additions & 0 deletions smib/logging.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
"tzlocal": {
"level": "WARNING",
"propagate": false
},
"pymongo": {
"level": "WARNING"
}
}
}
39 changes: 39 additions & 0 deletions smib/slack/db/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import functools
import inspect
from pathlib import Path

from injectable import inject
from mogo import Model, Field, connect

from smib.slack.plugin import PluginManager
from smib.common.utils import get_module_file
from smib.common.config import MONGO_DB_URL, MONGO_DB_CONNECT_TIMEOUT_SECONDS


def get_current_plugin_id() -> str:
plugin_manager: PluginManager = inject("PluginManager")
plugin_name = plugin_manager.get_plugin_from_file(get_module_file(2)).id
return plugin_name


def database(database_name: str = None):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
db_name = database_name

# If no database_name parameter name passed in, get current plugin id and use that
if db_name is None:
plugin_file = Path(inspect.getfile(func))
plugin_manager: PluginManager = inject("PluginManager")
db_name = plugin_manager.get_plugin_from_file(plugin_file).id

inject("logger").debug(f"Database name: {db_name}")

# Connect to DB and close it afterward
with connect(db_name, uri=MONGO_DB_URL, timeoutMs=1000*MONGO_DB_CONNECT_TIMEOUT_SECONDS):
return func(*args, **kwargs)

return wrapper

return decorator
5 changes: 3 additions & 2 deletions smib/slack/logging_injector.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from injectable import inject
from smib.common.logging_.setup import plugin_logger_factory



def inject_logger_to_slack_context(context, next):
context['logger'] = inject("plugin_logger", lazy=True)
next()
next()
2 changes: 1 addition & 1 deletion smib/slack/plugin/loaders/abstract_plugin_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def load_plugin(self, plugin_path: Path) -> Plugin:
self.unload_plugin(plugin)

if plugin.error:
logger.error(plugin.error)
logger.error(f"Plugin {plugin.id} failed to load: {plugin.error}")

return returned_plugin

Expand Down
22 changes: 22 additions & 0 deletions smib/slack/plugins/space/openclose/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
__author__ = "Sam Cork"

from injectable import inject

from smib.common.utils import http_bolt_response, is_json_encodable
from smib.slack.custom_app import CustomApp
from slack_sdk import WebClient
from slack_sdk.models.views import View
from slack_sdk.models.blocks import ActionsBlock, PlainTextObject, HeaderBlock, ButtonElement

from .models import Space

from smib.common.config import SPACE_OPEN_ANNOUNCE_CHANNEL_ID
from smib.slack.db import database

app: CustomApp = inject("SlackApp")

Expand Down Expand Up @@ -45,17 +50,34 @@ def app_home_opened(client: WebClient, event: dict):

@app.action('space_open')
@app.event('http_put_space_open')
@database()
def space_open(say, context, ack):

ack()
context['logger'].debug("Space Open!")
say(text='Space Open!', channel=SPACE_OPEN_ANNOUNCE_CHANNEL_ID)

space: Space = Space.single()
space.set_open()


@app.action('space_closed')
@app.event('http_put_space_closed')
@database()
def space_closed(say, context, ack):

ack()
context['logger'].debug("Space Closed!")

say(text='Space Closed!', channel=SPACE_OPEN_ANNOUNCE_CHANNEL_ID)

space: Space = Space.single()
space.set_closed()


@app.event("http_get_space_state")
@http_bolt_response
@database()
def get_space_state():
space = Space.single()
return {k: v for k, v in space.copy().items() if is_json_encodable(v)}
17 changes: 17 additions & 0 deletions smib/slack/plugins/space/openclose/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from smib.slack.db import Model, Field


class Space(Model):
open: bool = Field[bool](default=None)

@classmethod
def single(cls):
return cls.find_one() or cls()

def set_open(self):
self.open = True
self.save()

def set_closed(self):
self.open = False
self.save()
Loading