aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docker-compose.yml2
-rwxr-xr-xmanage.py74
-rw-r--r--postgres/init.sql73
3 files changed, 109 insertions, 40 deletions
diff --git a/docker-compose.yml b/docker-compose.yml
index 05867a46..37678949 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -18,8 +18,6 @@ services:
POSTGRES_DB: pysite
POSTGRES_PASSWORD: pysite
POSTGRES_USER: pysite
- volumes:
- - ./postgres/init.sql:/docker-entrypoint-initdb.d/init.sql
web:
build:
diff --git a/manage.py b/manage.py
index 66ad26f4..1e613e93 100755
--- a/manage.py
+++ b/manage.py
@@ -1,10 +1,9 @@
#!/usr/bin/env python
import os
-import re
import socket
import sys
import time
-from typing import List
+from urllib.parse import SplitResult, urlsplit
import django
from django.contrib.auth import get_user_model
@@ -42,7 +41,7 @@ class SiteManager:
--verbose Sets verbose console output.
"""
- def __init__(self, args: List[str]):
+ def __init__(self, args: list[str]):
self.debug = "--debug" in args
self.silent = "--silent" in args
@@ -56,6 +55,22 @@ class SiteManager:
print("Starting in debug mode.")
@staticmethod
+ def parse_db_url(db_url: str) -> SplitResult:
+ """Validate and split the given databse url."""
+ db_url_parts = urlsplit(db_url)
+ if not all((
+ db_url_parts.hostname,
+ db_url_parts.port,
+ db_url_parts.username,
+ db_url_parts.password,
+ db_url_parts.path
+ )):
+ raise ValueError(
+ "The DATABASE_URL environment variable is not a valid PostgreSQL database URL."
+ )
+ return db_url_parts
+
+ @staticmethod
def create_superuser() -> None:
"""Create a default django admin super user in development environments."""
print("Creating a superuser.")
@@ -90,12 +105,9 @@ class SiteManager:
print("Waiting for PostgreSQL database.")
# Get database URL based on environmental variable passed in compose
- database_url = os.environ["DATABASE_URL"]
- match = re.search(r"@([\w.]+):(\d+)/", database_url)
- if not match:
- raise OSError("Valid DATABASE_URL environmental variable not found.")
- domain = match.group(1)
- port = int(match.group(2))
+ database_url_parts = SiteManager.parse_db_url(os.environ["DATABASE_URL"])
+ domain = database_url_parts.hostname
+ port = database_url_parts.port
# Attempt to connect to the database socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -129,12 +141,45 @@ class SiteManager:
name="pythondiscord.local:8000"
)
+ @staticmethod
+ def run_metricity_init() -> None:
+ """
+ Initialise metricity relations and populate with some testing data.
+
+ This is done at run time since other projects, like Python bot,
+ rely on the site initialising it's own db, since they do not have
+ access to the init.sql file to mount a docker-compose volume.
+ """
+ import psycopg2
+ from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT
+
+ print("Initialising metricity.")
+
+ db_url_parts = SiteManager.parse_db_url(os.environ["DATABASE_URL"])
+ conn = psycopg2.connect(
+ host=db_url_parts.hostname,
+ port=db_url_parts.port,
+ user=db_url_parts.username,
+ password=db_url_parts.password,
+ database=db_url_parts.path[1:]
+ )
+ # Required to create a db from `cursor.execute()`
+ conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)
+
+ with conn.cursor() as cursor, open("postgres/init.sql", encoding="utf-8") as f:
+ cursor.execute(
+ f.read(),
+ ("metricity", db_url_parts.username, db_url_parts.password)
+ )
+ conn.close()
+
def prepare_server(self) -> None:
"""Perform preparation tasks before running the server."""
- django.setup()
-
+ self.wait_for_postgres()
if self.debug:
- self.wait_for_postgres()
+ self.run_metricity_init()
+
+ django.setup()
print("Applying migrations.")
call_command("migrate", verbosity=self.verbosity)
@@ -188,6 +233,11 @@ class SiteManager:
def main() -> None:
"""Entry point for Django management script."""
+ # Always run metricity init when in CI, indicated by the CI env var
+ if os.environ.get("CI", "false").lower() == "true":
+ SiteManager.wait_for_postgres()
+ SiteManager.run_metricity_init()
+
# Use the custom site manager for launching the server
if len(sys.argv) > 1 and sys.argv[1] == "run":
SiteManager(sys.argv).run_server()
diff --git a/postgres/init.sql b/postgres/init.sql
index 190a705c..55bb468f 100644
--- a/postgres/init.sql
+++ b/postgres/init.sql
@@ -1,8 +1,29 @@
-CREATE DATABASE metricity;
-
-\c metricity;
-
-CREATE TABLE users (
+-- The following function is from Stack Overflow
+-- https://stackoverflow.com/questions/18389124/simulate-create-database-if-not-exists-for-postgresql/36218838#36218838
+-- User frankhommers (https://stackoverflow.com/users/971229/frankhommers)
+
+DO
+$do$
+DECLARE
+ _db TEXT := %s;
+ _user TEXT := %s;
+ _password TEXT := %s;
+BEGIN
+ CREATE EXTENSION IF NOT EXISTS dblink;
+ IF EXISTS (SELECT 1 FROM pg_database WHERE datname = _db) THEN
+ RAISE NOTICE 'Database already exists';
+ ELSE
+ PERFORM dblink_connect(
+ 'host=localhost user=' || _user ||
+ ' password=' || _password ||
+ ' dbname=' || current_database()
+ );
+ PERFORM dblink_exec('CREATE DATABASE ' || _db);
+ END IF;
+END
+$do$;
+
+CREATE TABLE IF NOT EXISTS users (
id varchar,
joined_at timestamp,
primary key(id)
@@ -11,14 +32,14 @@ CREATE TABLE users (
INSERT INTO users VALUES (
0,
current_timestamp
-);
+) ON CONFLICT (id) DO NOTHING;
INSERT INTO users VALUES (
1,
current_timestamp
-);
+) ON CONFLICT (id) DO NOTHING;
-CREATE TABLE channels (
+CREATE TABLE IF NOT EXISTS channels (
id varchar,
name varchar,
primary key(id)
@@ -27,44 +48,44 @@ CREATE TABLE channels (
INSERT INTO channels VALUES(
'267659945086812160',
'python-general'
-);
+) ON CONFLICT (id) DO NOTHING;
INSERT INTO channels VALUES(
'11',
'help-apple'
-);
+) ON CONFLICT (id) DO NOTHING;
INSERT INTO channels VALUES(
'12',
'help-cherry'
-);
+) ON CONFLICT (id) DO NOTHING;
INSERT INTO channels VALUES(
'21',
'ot0-hello'
-);
+) ON CONFLICT (id) DO NOTHING;
INSERT INTO channels VALUES(
'22',
'ot1-world'
-);
+) ON CONFLICT (id) DO NOTHING;
INSERT INTO channels VALUES(
'31',
'voice-chat-0'
-);
+) ON CONFLICT (id) DO NOTHING;
INSERT INTO channels VALUES(
'32',
'code-help-voice-0'
-);
+) ON CONFLICT (id) DO NOTHING;
INSERT INTO channels VALUES(
'1234',
'zebra'
-);
+) ON CONFLICT (id) DO NOTHING;
-CREATE TABLE messages (
+CREATE TABLE IF NOT EXISTS messages (
id varchar,
author_id varchar references users(id),
is_deleted boolean,
@@ -79,7 +100,7 @@ INSERT INTO messages VALUES(
false,
now(),
'267659945086812160'
-);
+) ON CONFLICT (id) DO NOTHING;
INSERT INTO messages VALUES(
1,
@@ -87,7 +108,7 @@ INSERT INTO messages VALUES(
false,
now() + INTERVAL '10 minutes,',
'1234'
-);
+) ON CONFLICT (id) DO NOTHING;
INSERT INTO messages VALUES(
2,
@@ -95,7 +116,7 @@ INSERT INTO messages VALUES(
false,
now(),
'11'
-);
+) ON CONFLICT (id) DO NOTHING;
INSERT INTO messages VALUES(
3,
@@ -103,7 +124,7 @@ INSERT INTO messages VALUES(
false,
now(),
'12'
-);
+) ON CONFLICT (id) DO NOTHING;
INSERT INTO messages VALUES(
4,
@@ -111,7 +132,7 @@ INSERT INTO messages VALUES(
false,
now(),
'21'
-);
+) ON CONFLICT (id) DO NOTHING;
INSERT INTO messages VALUES(
5,
@@ -119,7 +140,7 @@ INSERT INTO messages VALUES(
false,
now(),
'22'
-);
+) ON CONFLICT (id) DO NOTHING;
INSERT INTO messages VALUES(
6,
@@ -127,7 +148,7 @@ INSERT INTO messages VALUES(
false,
now(),
'31'
-);
+) ON CONFLICT (id) DO NOTHING;
INSERT INTO messages VALUES(
7,
@@ -135,7 +156,7 @@ INSERT INTO messages VALUES(
false,
now(),
'32'
-);
+) ON CONFLICT (id) DO NOTHING;
INSERT INTO messages VALUES(
8,
@@ -143,4 +164,4 @@ INSERT INTO messages VALUES(
true,
now(),
'32'
-);
+) ON CONFLICT (id) DO NOTHING;