diff --git a/.envs/.local/.django b/.envs/.local/.django
deleted file mode 100644
index bcde257..0000000
--- a/.envs/.local/.django
+++ /dev/null
@@ -1,4 +0,0 @@
-# General
-# ------------------------------------------------------------------------------
-USE_DOCKER=yes
-IPYTHONDIR=/app/.ipython
diff --git a/.envs/.local/.postgres b/.envs/.local/.postgres
deleted file mode 100644
index 76130c4..0000000
--- a/.envs/.local/.postgres
+++ /dev/null
@@ -1,7 +0,0 @@
-# PostgreSQL
-# ------------------------------------------------------------------------------
-POSTGRES_HOST=postgres
-POSTGRES_PORT=5432
-POSTGRES_DB=geeksbot_v2
-POSTGRES_USER=eJujPHAXiaktuvRsJQlNbdhzHbkEGdgs
-POSTGRES_PASSWORD=hrjQlc8cfRVa0pgeJ1OWcsh4ZbRpTS1bJbMP2Atx3zKivMDJWKO71Dly0jFmRUWq
diff --git a/.idea/geeksbot_v2.iml b/.idea/geeksbot_v2.iml
deleted file mode 100644
index b21232c..0000000
--- a/.idea/geeksbot_v2.iml
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index cb27393..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 54abf96..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/.idea/runConfigurations/merge_production_dotenvs_in_dotenv.xml b/.idea/runConfigurations/merge_production_dotenvs_in_dotenv.xml
deleted file mode 100644
index 56e1755..0000000
--- a/.idea/runConfigurations/merge_production_dotenvs_in_dotenv.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.idea/runConfigurations/migrate.xml b/.idea/runConfigurations/migrate.xml
deleted file mode 100644
index 22f8d2e..0000000
--- a/.idea/runConfigurations/migrate.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.idea/runConfigurations/pytest___.xml b/.idea/runConfigurations/pytest___.xml
deleted file mode 100644
index 88bf101..0000000
--- a/.idea/runConfigurations/pytest___.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/runConfigurations/pytest__users.xml b/.idea/runConfigurations/pytest__users.xml
deleted file mode 100644
index 401c728..0000000
--- a/.idea/runConfigurations/pytest__users.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/runConfigurations/runserver.xml b/.idea/runConfigurations/runserver.xml
deleted file mode 100644
index a4781f6..0000000
--- a/.idea/runConfigurations/runserver.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.idea/runConfigurations/runserver_plus.xml b/.idea/runConfigurations/runserver_plus.xml
deleted file mode 100644
index 055b235..0000000
--- a/.idea/runConfigurations/runserver_plus.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 5ace414..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/.idea/webResources.xml b/.idea/webResources.xml
deleted file mode 100644
index eab2601..0000000
--- a/.idea/webResources.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/compose/local/django/Dockerfile b/compose/local/django/Dockerfile
deleted file mode 100644
index d3e1b32..0000000
--- a/compose/local/django/Dockerfile
+++ /dev/null
@@ -1,32 +0,0 @@
-FROM python:3.6-alpine
-
-ENV PYTHONUNBUFFERED 1
-
-RUN apk update \
- # psycopg2 dependencies
- && apk add --virtual build-deps gcc python3-dev musl-dev \
- && apk add postgresql-dev \
- # Pillow dependencies
- && apk add jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev tiff-dev tk-dev tcl-dev \
- # CFFI dependencies
- && apk add libffi-dev py-cffi \
- # Translations dependencies
- && apk add gettext \
- # https://docs.djangoproject.com/en/dev/ref/django-admin/#dbshell
- && apk add postgresql-client
-
-# Requirements are installed here to ensure they will be cached.
-COPY ./requirements /requirements
-RUN pip install -r /requirements/local.txt
-
-COPY ./compose/production/django/entrypoint /entrypoint
-RUN sed -i 's/\r$//g' /entrypoint
-RUN chmod +x /entrypoint
-
-COPY ./compose/local/django/start /start
-RUN sed -i 's/\r$//g' /start
-RUN chmod +x /start
-
-WORKDIR /app
-
-ENTRYPOINT ["/entrypoint"]
diff --git a/compose/local/django/start b/compose/local/django/start
deleted file mode 100644
index 921604d..0000000
--- a/compose/local/django/start
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/sh
-
-set -o errexit
-set -o pipefail
-set -o nounset
-
-
-python manage.py migrate
-python manage.py runserver_plus 0.0.0.0:8000
diff --git a/compose/production/aws/Dockerfile b/compose/production/aws/Dockerfile
deleted file mode 100644
index 8282047..0000000
--- a/compose/production/aws/Dockerfile
+++ /dev/null
@@ -1,9 +0,0 @@
-FROM garland/aws-cli-docker:1.15.47
-
-COPY ./compose/production/aws/maintenance /usr/local/bin/maintenance
-COPY ./compose/production/postgres/maintenance/_sourced /usr/local/bin/maintenance/_sourced
-
-RUN chmod +x /usr/local/bin/maintenance/*
-
-RUN mv /usr/local/bin/maintenance/* /usr/local/bin \
- && rmdir /usr/local/bin/maintenance
diff --git a/compose/production/aws/maintenance/download b/compose/production/aws/maintenance/download
deleted file mode 100644
index 8d5ea09..0000000
--- a/compose/production/aws/maintenance/download
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/bin/sh
-
-### Download a file from your Amazon S3 bucket to the postgres /backups folder
-###
-### Usage:
-### $ docker-compose -f production.yml run --rm awscli <1>
-
-set -o errexit
-set -o pipefail
-set -o nounset
-
-working_dir="$(dirname ${0})"
-source "${working_dir}/_sourced/constants.sh"
-source "${working_dir}/_sourced/messages.sh"
-
-export AWS_ACCESS_KEY_ID="${DJANGO_AWS_ACCESS_KEY_ID}"
-export AWS_SECRET_ACCESS_KEY="${DJANGO_AWS_SECRET_ACCESS_KEY}"
-export AWS_STORAGE_BUCKET_NAME="${DJANGO_AWS_STORAGE_BUCKET_NAME}"
-
-
-aws s3 cp s3://${AWS_STORAGE_BUCKET_NAME}${BACKUP_DIR_PATH}/${1} ${BACKUP_DIR_PATH}/${1}
-
-message_success "Finished downloading ${1}."
-
diff --git a/compose/production/aws/maintenance/upload b/compose/production/aws/maintenance/upload
deleted file mode 100644
index 4a89dcb..0000000
--- a/compose/production/aws/maintenance/upload
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/sh
-
-### Upload the /backups folder to Amazon S3
-###
-### Usage:
-### $ docker-compose -f production.yml run --rm awscli upload
-
-set -o errexit
-set -o pipefail
-set -o nounset
-
-working_dir="$(dirname ${0})"
-source "${working_dir}/_sourced/constants.sh"
-source "${working_dir}/_sourced/messages.sh"
-
-export AWS_ACCESS_KEY_ID="${DJANGO_AWS_ACCESS_KEY_ID}"
-export AWS_SECRET_ACCESS_KEY="${DJANGO_AWS_SECRET_ACCESS_KEY}"
-export AWS_STORAGE_BUCKET_NAME="${DJANGO_AWS_STORAGE_BUCKET_NAME}"
-
-
-message_info "Upload the backups directory to S3 bucket {$AWS_STORAGE_BUCKET_NAME}"
-
-aws s3 cp ${BACKUP_DIR_PATH} s3://${AWS_STORAGE_BUCKET_NAME}${BACKUP_DIR_PATH} --recursive
-
-message_info "Cleaning the directory ${BACKUP_DIR_PATH}"
-
-rm -rf ${BACKUP_DIR_PATH}/*
-
-message_success "Finished uploading and cleaning."
-
diff --git a/compose/production/django/Dockerfile b/compose/production/django/Dockerfile
deleted file mode 100644
index 29e6d2e..0000000
--- a/compose/production/django/Dockerfile
+++ /dev/null
@@ -1,40 +0,0 @@
-
-FROM python:3.6-alpine
-
-ENV PYTHONUNBUFFERED 1
-
-RUN apk update \
- # psycopg2 dependencies
- && apk add --virtual build-deps gcc python3-dev musl-dev \
- && apk add postgresql-dev \
- # Pillow dependencies
- && apk add jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev tiff-dev tk-dev tcl-dev \
- # CFFI dependencies
- && apk add libffi-dev py-cffi
-
-RUN addgroup -S django \
- && adduser -S -G django django
-
-# Requirements are installed here to ensure they will be cached.
-COPY ./requirements /requirements
-RUN pip install --no-cache-dir -r /requirements/production.txt \
- && rm -rf /requirements
-
-COPY ./compose/production/django/entrypoint /entrypoint
-RUN sed -i 's/\r$//g' /entrypoint
-RUN chmod +x /entrypoint
-RUN chown django /entrypoint
-
-COPY ./compose/production/django/start /start
-RUN sed -i 's/\r$//g' /start
-RUN chmod +x /start
-RUN chown django /start
-COPY . /app
-
-RUN chown -R django /app
-
-USER django
-
-WORKDIR /app
-
-ENTRYPOINT ["/entrypoint"]
diff --git a/compose/production/django/entrypoint b/compose/production/django/entrypoint
deleted file mode 100644
index 929744d..0000000
--- a/compose/production/django/entrypoint
+++ /dev/null
@@ -1,42 +0,0 @@
-#!/bin/sh
-
-set -o errexit
-set -o pipefail
-set -o nounset
-
-
-
-
-if [ -z "${POSTGRES_USER}" ]; then
- base_postgres_image_default_user='postgres'
- export POSTGRES_USER="${base_postgres_image_default_user}"
-fi
-export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
-
-postgres_ready() {
-python << END
-import sys
-
-import psycopg2
-
-try:
- psycopg2.connect(
- dbname="${POSTGRES_DB}",
- user="${POSTGRES_USER}",
- password="${POSTGRES_PASSWORD}",
- host="${POSTGRES_HOST}",
- port="${POSTGRES_PORT}",
- )
-except psycopg2.OperationalError:
- sys.exit(-1)
-sys.exit(0)
-
-END
-}
-until postgres_ready; do
- >&2 echo 'Waiting for PostgreSQL to become available...'
- sleep 1
-done
->&2 echo 'PostgreSQL is available'
-
-exec "$@"
diff --git a/compose/production/django/start b/compose/production/django/start
deleted file mode 100644
index 0ad39df..0000000
--- a/compose/production/django/start
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/sh
-
-set -o errexit
-set -o pipefail
-set -o nounset
-
-
-python /app/manage.py collectstatic --noinput
-/usr/local/bin/gunicorn config.wsgi --bind 0.0.0.0:5000 --chdir=/app
diff --git a/compose/production/postgres/Dockerfile b/compose/production/postgres/Dockerfile
deleted file mode 100644
index 7cf4173..0000000
--- a/compose/production/postgres/Dockerfile
+++ /dev/null
@@ -1,6 +0,0 @@
-FROM postgres:11.3
-
-COPY ./compose/production/postgres/maintenance /usr/local/bin/maintenance
-RUN chmod +x /usr/local/bin/maintenance/*
-RUN mv /usr/local/bin/maintenance/* /usr/local/bin \
- && rmdir /usr/local/bin/maintenance
diff --git a/compose/production/postgres/maintenance/_sourced/constants.sh b/compose/production/postgres/maintenance/_sourced/constants.sh
deleted file mode 100644
index 6ca4f0c..0000000
--- a/compose/production/postgres/maintenance/_sourced/constants.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/usr/bin/env bash
-
-
-BACKUP_DIR_PATH='/backups'
-BACKUP_FILE_PREFIX='backup'
diff --git a/compose/production/postgres/maintenance/_sourced/countdown.sh b/compose/production/postgres/maintenance/_sourced/countdown.sh
deleted file mode 100644
index e6cbfb6..0000000
--- a/compose/production/postgres/maintenance/_sourced/countdown.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/usr/bin/env bash
-
-
-countdown() {
- declare desc="A simple countdown. Source: https://superuser.com/a/611582"
- local seconds="${1}"
- local d=$(($(date +%s) + "${seconds}"))
- while [ "$d" -ge `date +%s` ]; do
- echo -ne "$(date -u --date @$(($d - `date +%s`)) +%H:%M:%S)\r";
- sleep 0.1
- done
-}
diff --git a/compose/production/postgres/maintenance/_sourced/messages.sh b/compose/production/postgres/maintenance/_sourced/messages.sh
deleted file mode 100644
index f6be756..0000000
--- a/compose/production/postgres/maintenance/_sourced/messages.sh
+++ /dev/null
@@ -1,41 +0,0 @@
-#!/usr/bin/env bash
-
-
-message_newline() {
- echo
-}
-
-message_debug()
-{
- echo -e "DEBUG: ${@}"
-}
-
-message_welcome()
-{
- echo -e "\e[1m${@}\e[0m"
-}
-
-message_warning()
-{
- echo -e "\e[33mWARNING\e[0m: ${@}"
-}
-
-message_error()
-{
- echo -e "\e[31mERROR\e[0m: ${@}"
-}
-
-message_info()
-{
- echo -e "\e[37mINFO\e[0m: ${@}"
-}
-
-message_suggestion()
-{
- echo -e "\e[33mSUGGESTION\e[0m: ${@}"
-}
-
-message_success()
-{
- echo -e "\e[32mSUCCESS\e[0m: ${@}"
-}
diff --git a/compose/production/postgres/maintenance/_sourced/yes_no.sh b/compose/production/postgres/maintenance/_sourced/yes_no.sh
deleted file mode 100644
index fd9cae1..0000000
--- a/compose/production/postgres/maintenance/_sourced/yes_no.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/usr/bin/env bash
-
-
-yes_no() {
- declare desc="Prompt for confirmation. \$\"\{1\}\": confirmation message."
- local arg1="${1}"
-
- local response=
- read -r -p "${arg1} (y/[n])? " response
- if [[ "${response}" =~ ^[Yy]$ ]]
- then
- exit 0
- else
- exit 1
- fi
-}
diff --git a/compose/production/postgres/maintenance/backup b/compose/production/postgres/maintenance/backup
deleted file mode 100644
index ee0c9d6..0000000
--- a/compose/production/postgres/maintenance/backup
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/usr/bin/env bash
-
-
-### Create a database backup.
-###
-### Usage:
-### $ docker-compose -f .yml (exec |run --rm) postgres backup
-
-
-set -o errexit
-set -o pipefail
-set -o nounset
-
-
-working_dir="$(dirname ${0})"
-source "${working_dir}/_sourced/constants.sh"
-source "${working_dir}/_sourced/messages.sh"
-
-
-message_welcome "Backing up the '${POSTGRES_DB}' database..."
-
-
-if [[ "${POSTGRES_USER}" == "postgres" ]]; then
- message_error "Backing up as 'postgres' user is not supported. Assign 'POSTGRES_USER' env with another one and try again."
- exit 1
-fi
-
-export PGHOST="${POSTGRES_HOST}"
-export PGPORT="${POSTGRES_PORT}"
-export PGUSER="${POSTGRES_USER}"
-export PGPASSWORD="${POSTGRES_PASSWORD}"
-export PGDATABASE="${POSTGRES_DB}"
-
-backup_filename="${BACKUP_FILE_PREFIX}_$(date +'%Y_%m_%dT%H_%M_%S').sql.gz"
-pg_dump | gzip > "${BACKUP_DIR_PATH}/${backup_filename}"
-
-
-message_success "'${POSTGRES_DB}' database backup '${backup_filename}' has been created and placed in '${BACKUP_DIR_PATH}'."
diff --git a/compose/production/postgres/maintenance/backups b/compose/production/postgres/maintenance/backups
deleted file mode 100644
index 0484ccf..0000000
--- a/compose/production/postgres/maintenance/backups
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env bash
-
-
-### View backups.
-###
-### Usage:
-### $ docker-compose -f .yml (exec |run --rm) postgres backups
-
-
-set -o errexit
-set -o pipefail
-set -o nounset
-
-
-working_dir="$(dirname ${0})"
-source "${working_dir}/_sourced/constants.sh"
-source "${working_dir}/_sourced/messages.sh"
-
-
-message_welcome "These are the backups you have got:"
-
-ls -lht "${BACKUP_DIR_PATH}"
diff --git a/compose/production/postgres/maintenance/restore b/compose/production/postgres/maintenance/restore
deleted file mode 100644
index 9661ca7..0000000
--- a/compose/production/postgres/maintenance/restore
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/usr/bin/env bash
-
-
-### Restore database from a backup.
-###
-### Parameters:
-### <1> filename of an existing backup.
-###
-### Usage:
-### $ docker-compose -f .yml (exec |run --rm) postgres restore <1>
-
-
-set -o errexit
-set -o pipefail
-set -o nounset
-
-
-working_dir="$(dirname ${0})"
-source "${working_dir}/_sourced/constants.sh"
-source "${working_dir}/_sourced/messages.sh"
-
-
-if [[ -z ${1+x} ]]; then
- message_error "Backup filename is not specified yet it is a required parameter. Make sure you provide one and try again."
- exit 1
-fi
-backup_filename="${BACKUP_DIR_PATH}/${1}"
-if [[ ! -f "${backup_filename}" ]]; then
- message_error "No backup with the specified filename found. Check out the 'backups' maintenance script output to see if there is one and try again."
- exit 1
-fi
-
-message_welcome "Restoring the '${POSTGRES_DB}' database from the '${backup_filename}' backup..."
-
-if [[ "${POSTGRES_USER}" == "postgres" ]]; then
- message_error "Restoring as 'postgres' user is not supported. Assign 'POSTGRES_USER' env with another one and try again."
- exit 1
-fi
-
-export PGHOST="${POSTGRES_HOST}"
-export PGPORT="${POSTGRES_PORT}"
-export PGUSER="${POSTGRES_USER}"
-export PGPASSWORD="${POSTGRES_PASSWORD}"
-export PGDATABASE="${POSTGRES_DB}"
-
-message_info "Dropping the database..."
-dropdb "${PGDATABASE}"
-
-message_info "Creating a new database..."
-createdb --owner="${POSTGRES_USER}"
-
-message_info "Applying the backup to the new database..."
-gunzip -c "${backup_filename}" | psql "${POSTGRES_DB}"
-
-message_success "The '${POSTGRES_DB}' database has been restored from the '${backup_filename}' backup."
diff --git a/compose/production/traefik/Dockerfile b/compose/production/traefik/Dockerfile
deleted file mode 100644
index 7088e6f..0000000
--- a/compose/production/traefik/Dockerfile
+++ /dev/null
@@ -1,5 +0,0 @@
-FROM traefik:alpine
-RUN mkdir -p /etc/traefik/acme
-RUN touch /etc/traefik/acme/acme.json
-RUN chmod 600 /etc/traefik/acme/acme.json
-COPY ./compose/production/traefik/traefik.toml /etc/traefik
diff --git a/compose/production/traefik/traefik.toml b/compose/production/traefik/traefik.toml
deleted file mode 100644
index a275c7d..0000000
--- a/compose/production/traefik/traefik.toml
+++ /dev/null
@@ -1,41 +0,0 @@
-logLevel = "INFO"
-defaultEntryPoints = ["http", "https"]
-
-# Entrypoints, http and https
-[entryPoints]
- # http should be redirected to https
- [entryPoints.http]
- address = ":80"
- [entryPoints.http.redirect]
- entryPoint = "https"
- # https is the default
- [entryPoints.https]
- address = ":443"
- [entryPoints.https.tls]
-
-# Enable ACME (Let's Encrypt): automatic SSL
-[acme]
-# Email address used for registration
-email = "dusty.p@geeksbot.app"
-storage = "/etc/traefik/acme/acme.json"
-entryPoint = "https"
-onDemand = false
-OnHostRule = true
- # Use a HTTP-01 acme challenge rather than TLS-SNI-01 challenge
- [acme.httpChallenge]
- entryPoint = "http"
-
-[file]
-[backends]
- [backends.django]
- [backends.django.servers.server1]
- url = "http://django:5000"
-
-[frontends]
- [frontends.django]
- backend = "django"
- passHostHeader = true
- [frontends.django.headers]
- HostsProxyHeaders = ['X-CSRFToken']
- [frontends.django.routes.dr1]
- rule = "Host:geeksbot.app"
diff --git a/docker-compose.yml b/docker-compose.yml
index 2ccf48c..ab4cee1 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -36,8 +36,6 @@ services:
- DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
- REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}/${REDIS_DB}
volumes:
- - ${PWD}/config:/code/config
- - ${PWD}/geeksbot_web:/code/geeksbot_web
- ${PWD}/geeksbot_v2:/code/geeksbot_v2
geeksbot:
build:
@@ -49,6 +47,8 @@ services:
- redis
- base
- web
+ links:
+ - web:geeksbot.app
volumes:
- ${PWD}/geeksbot:/code/geeksbot
- ~/.ssh/id_rsa:/root/.ssh/id_rsa
diff --git a/geeksbot/__main__.py b/geeksbot/__main__.py
index 4c5a9ff..07ce153 100644
--- a/geeksbot/__main__.py
+++ b/geeksbot/__main__.py
@@ -53,8 +53,6 @@ logger.info(f'Process Libs Import Complete - Took {(datetime.utcnow() - start).t
start = datetime.utcnow()
import re # noqa: E402
logger.info('re Imported')
-from typing import Dict # noqa: E402
-logger.info('Typing Dict Imported')
import json # noqa: E402
logger.info('JSON Imported')
import aiohttp # noqa: E402
@@ -98,7 +96,7 @@ class Geeksbot(commands.Bot):
async def unload_ext(self, mod):
self.unload_extension(f'geeksbot.{self.extension_dir}.{mod}')
- logger.info(f'Extension Loaded: {mod}')
+ logger.info(f'Extension Unloaded: {mod}')
def load_default_extensions(self):
for load_item in self.bot_config['load_list']:
diff --git a/geeksbot/exts/exec.py b/geeksbot/exts/exec.py
new file mode 100644
index 0000000..9ba442a
--- /dev/null
+++ b/geeksbot/exts/exec.py
@@ -0,0 +1,221 @@
+from discord.ext import commands
+import asyncio
+import traceback
+import discord
+import inspect
+import textwrap
+import time
+import os
+from datetime import datetime
+from contextlib import redirect_stdout
+import io
+from geeksbot.imports.utils import run_command, format_output, Paginator, Book
+import logging
+
+repl_log = logging.getLogger('repl')
+
+
+class Exec(commands.Cog):
+
+ def __init__(self, bot):
+ self.bot = bot
+ self._last_result = None
+ self.sessions = set()
+
+ @staticmethod
+ def cleanup_code(content):
+ """Automatically removes code blocks from the code."""
+ if content.startswith('```') and content.endswith('```'):
+ return '\n'.join(content.split('\n')[1:(- 1)])
+ return content.strip('` \n')
+
+ @staticmethod
+ def get_syntax_error(e):
+ if e.text is None:
+ return '```py\n{0.__class__.__name__}: {0}\n```'.format(e)
+ return '```py\n{0.text}{1:>{0.offset}}\n{2}: {0}```'.format(e, '^', type(e).__name__)
+
+ @commands.command(hidden=True, name='exec')
+ async def _eval(self, ctx, *, body: str):
+ if ctx.author.id != self.bot.owner_id:
+ return
+ pag = Paginator(self.bot)
+ env = {
+ 'bot': self.bot,
+ 'ctx': ctx,
+ 'channel': ctx.channel,
+ 'author': ctx.author,
+ 'server': ctx.guild,
+ 'message': ctx.message,
+ '_': self._last_result,
+ }
+ env.update(globals())
+ body = self.cleanup_code(body)
+ stdout = io.StringIO()
+ to_compile = 'async def func():\n%s' % textwrap.indent(body, ' ')
+ try:
+ exec(to_compile, env)
+ except SyntaxError as e:
+ return await ctx.send(self.get_syntax_error(e))
+ func = env['func']
+ # noinspection PyBroadException
+ try:
+ with redirect_stdout(stdout):
+ ret = await func()
+ except Exception:
+ pag.add(stdout.getvalue())
+ pag.add(traceback.format_exc())
+ for page in pag.pages():
+ await ctx.send(page)
+ else:
+ value = stdout.getvalue()
+ # noinspection PyBroadException
+ try:
+ await ctx.message.add_reaction('✅')
+ except Exception:
+ pass
+ value = format_output(value)
+ pag.add(value)
+ pag.add(f'\nReturned: {ret}')
+ self._last_result = ret
+ for page in pag.pages():
+ await ctx.send(page)
+
+ @commands.command(hidden=True)
+ async def repl(self, ctx):
+ if ctx.author.id != self.bot.owner_id:
+ return
+ msg = ctx.message
+ variables = {
+ 'ctx': ctx,
+ 'bot': self.bot,
+ 'message': msg,
+ 'server': msg.guild,
+ 'channel': msg.channel,
+ 'author': msg.author,
+ '_': None,
+ }
+ if msg.channel.id in self.sessions:
+ await ctx.send('Already running a REPL session in this channel. Exit it with `quit`.')
+ return
+ self.sessions.add(msg.channel.id)
+ await ctx.send('Enter code to execute or evaluate. `exit()` or `quit` to exit.')
+ while True:
+ response = await self.bot.wait_for('message', check=(lambda m: m.content.startswith('`')))
+ if response.author.id == self.bot.owner_id:
+ cleaned = self.cleanup_code(response.content)
+ if cleaned in ('quit', 'exit', 'exit()'):
+ await response.channel.send('Exiting.')
+ self.sessions.remove(msg.channel.id)
+ return
+ executor = exec
+ if cleaned.count('\n') == 0:
+ try:
+ code = compile(cleaned, '', 'eval')
+ except SyntaxError:
+ pass
+ else:
+ executor = eval
+ if executor is exec:
+ try:
+ code = compile(cleaned, '', 'exec')
+ except SyntaxError as e:
+ await response.channel.send(self.get_syntax_error(e))
+ continue
+ variables['message'] = response
+ fmt = None
+ stdout = io.StringIO()
+ # noinspection PyBroadException
+ try:
+ with redirect_stdout(stdout):
+ result = executor(code, variables)
+ if inspect.isawaitable(result):
+ result = await result
+ except Exception:
+ value = stdout.getvalue()
+ fmt = '{}{}'.format(value, traceback.format_exc())
+ else:
+ value = stdout.getvalue()
+ if result is not None:
+ fmt = '{}{}'.format(value, result)
+ variables['_'] = result
+ elif value:
+ fmt = '{}'.format(value)
+ try:
+ if fmt is not None:
+ pag = Paginator(self.bot)
+ pag.add(fmt)
+ for page in pag.pages():
+ await response.channel.send(page)
+ await ctx.send(response.channel)
+ except discord.Forbidden:
+ pass
+ except discord.HTTPException as e:
+ await msg.channel.send('Unexpected error: `{}`'.format(e))
+
+ @commands.command(hidden=True)
+ async def os(self, ctx, *, body: str):
+ if ctx.author.id != self.bot.owner_id:
+ return
+ try:
+ body = self.cleanup_code(body)
+ pag = Paginator(self.bot)
+ pag.add(await asyncio.wait_for(self.bot.loop.create_task(run_command(body)), 120))
+ for page in pag.pages():
+ await ctx.send(page)
+ await ctx.message.add_reaction('✅')
+ except asyncio.TimeoutError:
+ await ctx.send(f"Command did not complete in the time allowed.")
+ await ctx.message.add_reaction('❌')
+
+ @commands.command(name='haskell', aliases=['hs'])
+ async def haskell_compiler(self, ctx, *, body: str = None):
+ if ctx.author.id != self.bot.owner_id:
+ return
+
+ if body is None:
+ await ctx.send('Nothing to do.')
+ return
+
+ async with ctx.typing():
+ msg = await ctx.send('Warming up GHC... Please wait.')
+ try:
+ body = self.cleanup_code(body)
+ file_name = f'haskell_{datetime.utcnow().strftime("%Y%m%dT%H%M%S%f")}'
+ with open(f'{file_name}.hs', 'w') as f:
+ f.write(body)
+ pag = Paginator(self.bot)
+ compile_start = time.time()
+ pag.add(await asyncio.wait_for(
+ self.bot.loop.create_task(run_command(f'ghc -o {file_name} {file_name}.hs')), timeout=60))
+ compile_end = time.time()
+ compile_real = compile_end - compile_start
+ book = Book(pag, (msg, ctx.channel, ctx.bot, ctx.message))
+ await book.create_book()
+ pag = Paginator(self.bot)
+ if file_name in os.listdir():
+ run_start = time.time()
+ pag.add(await asyncio.wait_for(self.bot.loop.create_task(run_command(f'./{file_name}')),
+ timeout=600))
+ run_end = time.time()
+ run_real = run_end - run_start
+ total_real = run_real + compile_real
+ pag.add(f'\n\nCompile took {compile_real:.2f} seconds')
+ pag.add(f'Total Time {total_real:.2f} seconds')
+ book = Book(pag, (None, ctx.channel, ctx.bot, ctx.message))
+ await msg.delete()
+ await book.create_book()
+ os.remove(file_name)
+ os.remove(f'{file_name}.hs')
+ os.remove(f'{file_name}.o')
+ os.remove(f'{file_name}.hi')
+ except asyncio.TimeoutError:
+ await msg.delete()
+ await ctx.send(f"Command did not complete in the time allowed.")
+ await ctx.message.add_reaction('❌')
+ except FileNotFoundError as e:
+ repl_log.warning(e)
+
+
+def setup(bot):
+ bot.add_cog(Exec(bot))
diff --git a/geeksbot/imports/utils.py b/geeksbot/imports/utils.py
index a06c92d..7926b94 100644
--- a/geeksbot/imports/utils.py
+++ b/geeksbot/imports/utils.py
@@ -3,6 +3,69 @@ import asyncio
import typing
+# noinspection PyDefaultArgument
+def to_list_of_str(items, out: list = list(), level=1, recurse=0):
+ # noinspection PyShadowingNames
+ def rec_loop(item, key, out, level):
+ quote = '"'
+ if type(item) == list:
+ out.append(f'{" "*level}{quote+key+quote+": " if key else ""}[')
+ new_level = level + 1
+ out = to_list_of_str(item, out, new_level, 1)
+ out.append(f'{" "*level}]')
+ elif type(item) == dict:
+ out.append(f'{" "*level}{quote+key+quote+": " if key else ""}{{')
+ new_level = level + 1
+ out = to_list_of_str(item, out, new_level, 1)
+ out.append(f'{" "*level}}}')
+ else:
+ out.append(f'{" "*level}{quote+key+quote+": " if key else ""}{repr(item)},')
+
+ if type(items) == list:
+ if not recurse:
+ out = list()
+ out.append('[')
+ for item in items:
+ rec_loop(item, None, out, level)
+ if not recurse:
+ out.append(']')
+ elif type(items) == dict:
+ if not recurse:
+ out = list()
+ out.append('{')
+ for key in items:
+ rec_loop(items[key], key, out, level)
+ if not recurse:
+ out.append('}')
+
+ return out
+
+
+def format_output(text):
+ if type(text) == list:
+ text = to_list_of_str(text)
+ elif type(text) == dict:
+ text = to_list_of_str(text)
+ return text
+
+
+async def run_command(args):
+ # Create subprocess
+ process = await asyncio.create_subprocess_shell(
+ f'time -f "Process took %e seconds (%U user | %S system) and used %P of the CPU" {args}',
+ # stdout must a pipe to be accessible as process.stdout
+ stdout=asyncio.subprocess.PIPE,
+ stderr=asyncio.subprocess.PIPE)
+ # Wait for the subprocess to finish
+ stdout, stderr = await process.communicate()
+ # Return stdout
+ if stderr and stderr.strip() != '':
+ output = f'{stdout.decode().strip()}\n{stderr.decode().strip()}'
+ else:
+ output = stdout.decode().strip()
+ return output
+
+
# noinspection PyShadowingNames
class Paginator:
def __init__(self,
@@ -44,12 +107,12 @@ class Paginator:
self._embed_url = None
self._bot = bot
- def set_embed_meta(self, title: str=None,
- description: str=None,
- color: discord.Colour=None,
- thumbnail: str=None,
- footer: str='',
- url: str=None):
+ def set_embed_meta(self, title: str = None,
+ description: str = None,
+ color: discord.Colour = None,
+ thumbnail: str = None,
+ footer: str = '',
+ url: str = None):
if title and len(title) > self._max_field_name:
raise RuntimeError('Provided Title is too long')
else:
@@ -111,7 +174,7 @@ class Paginator:
_field_name = name
_field_value = self._prefix
- def close_field(next_name: str=None):
+ def close_field(next_name: str = None):
nonlocal _field_name, _field_value, _fields
_field_value += self._suffix
if _field_value != self._prefix + self._suffix:
@@ -188,10 +251,10 @@ class Paginator:
# noinspection PyProtectedMember
return self.__class__ == other.__class__ and self._parts == other._parts
- def add_page_break(self, *, to_beginning: bool=False) -> None:
+ def add_page_break(self, *, to_beginning: bool = False) -> None:
self.add(self._page_break, to_beginning=to_beginning)
- def add(self, item: typing.Any, *, to_beginning: bool=False, keep_intact: bool=False, truncate=False) -> None:
+ def add(self, item: typing.Any, *, to_beginning: bool = False, keep_intact: bool = False, truncate=False) -> None:
item = str(item)
i = 0
if not keep_intact and not item == self._page_break:
diff --git a/config/__init__.py b/geeksbot_v2/config/__init__.py
similarity index 100%
rename from config/__init__.py
rename to geeksbot_v2/config/__init__.py
diff --git a/config/settings/__init__.py b/geeksbot_v2/config/settings/__init__.py
similarity index 100%
rename from config/settings/__init__.py
rename to geeksbot_v2/config/settings/__init__.py
diff --git a/config/settings/base.py b/geeksbot_v2/config/settings/base.py
similarity index 93%
rename from config/settings/base.py
rename to geeksbot_v2/config/settings/base.py
index a03a820..d7c1cba 100644
--- a/config/settings/base.py
+++ b/geeksbot_v2/config/settings/base.py
@@ -7,7 +7,7 @@ import environ
ROOT_DIR = (
environ.Path(__file__) - 3
) # (geeksbot_v2/config/settings/base.py - 3 = geeksbot_v2/)
-APPS_DIR = ROOT_DIR.path("geeksbot_v2")
+APPS_DIR = ROOT_DIR
env = environ.Env()
@@ -68,11 +68,17 @@ THIRD_PARTY_APPS = [
"allauth",
"allauth.account",
"allauth.socialaccount",
+ "allauth.socialaccount.providers.discord",
"rest_framework",
+ "rest_framework.authtoken",
]
LOCAL_APPS = [
"geeksbot_v2.users.apps.UsersConfig",
+ "geeksbot_v2.guilds.apps.GuildsConfig",
+ "geeksbot_v2.dmessages.apps.MessagesConfig",
+ "geeksbot_v2.patreon.apps.PatreonConfig",
+ "geeksbot_v2.rcon.apps.RconConfig",
# Your stuff: custom apps go here
]
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
@@ -243,16 +249,16 @@ LOGGING = {
"root": {"level": "INFO", "handlers": ["console"]},
}
-
# django-allauth
# ------------------------------------------------------------------------------
-ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True)
+ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", False)
+SOCIAL_ACCOUNT_ALLOW_REGISTRATION = env.bool('DJANGO_SOCIAL_ACCOUNT_ALLOW_REGISTRATION', True)
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_AUTHENTICATION_METHOD = "username"
# https://django-allauth.readthedocs.io/en/latest/configuration.html
-ACCOUNT_EMAIL_REQUIRED = True
+ACCOUNT_EMAIL_REQUIRED = False
# https://django-allauth.readthedocs.io/en/latest/configuration.html
-ACCOUNT_EMAIL_VERIFICATION = "mandatory"
+ACCOUNT_EMAIL_VERIFICATION = "optional"
# https://django-allauth.readthedocs.io/en/latest/configuration.html
ACCOUNT_ADAPTER = "geeksbot_v2.users.adapters.AccountAdapter"
# https://django-allauth.readthedocs.io/en/latest/configuration.html
@@ -261,3 +267,14 @@ SOCIALACCOUNT_ADAPTER = "geeksbot_v2.users.adapters.SocialAccountAdapter"
# Your stuff...
# ------------------------------------------------------------------------------
+
+REST_FRAMEWORK = {
+ 'DEFAULT_AUTHENTICATION_CLASSES': [
+ 'rest_framework.authentication.TokenAuthentication',
+ ],
+ "DEFAULT_RENDERER_CLASSES": [
+ "rest_framework.renderers.JSONRenderer",
+ ],
+ "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
+ "PAGE_SIZE": 100,
+}
diff --git a/config/settings/local.py b/geeksbot_v2/config/settings/local.py
similarity index 100%
rename from config/settings/local.py
rename to geeksbot_v2/config/settings/local.py
diff --git a/config/settings/production.py b/geeksbot_v2/config/settings/production.py
similarity index 100%
rename from config/settings/production.py
rename to geeksbot_v2/config/settings/production.py
diff --git a/config/settings/test.py b/geeksbot_v2/config/settings/test.py
similarity index 100%
rename from config/settings/test.py
rename to geeksbot_v2/config/settings/test.py
diff --git a/config/urls.py b/geeksbot_v2/config/urls.py
similarity index 90%
rename from config/urls.py
rename to geeksbot_v2/config/urls.py
index 950fe09..3920996 100644
--- a/config/urls.py
+++ b/geeksbot_v2/config/urls.py
@@ -16,6 +16,8 @@ urlpatterns = [
path("users/", include("geeksbot_v2.users.urls", namespace="users")),
path("accounts/", include("allauth.urls")),
# Your stuff: custom urls includes go here
+ path("api/users", include("geeksbot_v2.users.api_urls", namespace="users_api")),
+ path("api/guilds", include("geeksbot_v2.guilds.api_urls", namespace="guilds_api"))
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if settings.DEBUG:
diff --git a/config/wsgi.py b/geeksbot_v2/config/wsgi.py
similarity index 100%
rename from config/wsgi.py
rename to geeksbot_v2/config/wsgi.py
diff --git a/geeksbot_v2/users/migrations/__init__.py b/geeksbot_v2/dmessages/__init__.py
similarity index 100%
rename from geeksbot_v2/users/migrations/__init__.py
rename to geeksbot_v2/dmessages/__init__.py
diff --git a/geeksbot_v2/dmessages/admin.py b/geeksbot_v2/dmessages/admin.py
new file mode 100644
index 0000000..e14fb8b
--- /dev/null
+++ b/geeksbot_v2/dmessages/admin.py
@@ -0,0 +1,10 @@
+from django.contrib import admin
+
+from .models import Message
+from .models import GuildInfo
+from .models import AdminRequest
+
+# Register your models here.
+admin.site.register(Message)
+admin.site.register(GuildInfo)
+admin.site.register(AdminRequest)
\ No newline at end of file
diff --git a/geeksbot_v2/dmessages/apps.py b/geeksbot_v2/dmessages/apps.py
new file mode 100644
index 0000000..1355c6c
--- /dev/null
+++ b/geeksbot_v2/dmessages/apps.py
@@ -0,0 +1,7 @@
+from django.apps import AppConfig
+from django.utils.translation import gettext_lazy as _
+
+
+class MessagesConfig(AppConfig):
+ name = 'geeksbot_v2.dmessages'
+ verbose_name = _("DMessages")
diff --git a/geeksbot_v2/dmessages/migrations/0001_initial.py b/geeksbot_v2/dmessages/migrations/0001_initial.py
new file mode 100644
index 0000000..a0b9602
--- /dev/null
+++ b/geeksbot_v2/dmessages/migrations/0001_initial.py
@@ -0,0 +1,64 @@
+# Generated by Django 2.2.4 on 2019-09-16 05:23
+
+from django.conf import settings
+import django.contrib.postgres.fields
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('guilds', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Message',
+ fields=[
+ ('id', models.CharField(max_length=30, primary_key=True, serialize=False)),
+ ('channel', models.CharField(max_length=30)),
+ ('created_at', models.DateTimeField()),
+ ('modified_at', models.DateTimeField(null=True)),
+ ('deleted_at', models.DateTimeField(null=True)),
+ ('content', models.CharField(max_length=2000)),
+ ('previous_content', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=2000), size=None)),
+ ('tagged_users', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=30), size=None)),
+ ('tagged_channels', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=30), size=None)),
+ ('tagged_roles', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=30), size=None)),
+ ('tagged_everyone', models.BooleanField()),
+ ('embeds', django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None)),
+ ('previous_embeds', django.contrib.postgres.fields.ArrayField(base_field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(), size=None), size=None)),
+ ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='GuildInfo',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('type', models.PositiveSmallIntegerField()),
+ ('text', models.TextField(max_length=1980)),
+ ('format', models.PositiveSmallIntegerField()),
+ ('channel', models.CharField(max_length=30)),
+ ('message_number', models.PositiveSmallIntegerField()),
+ ('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')),
+ ('message', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dmessages.Message')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='AdminRequest',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('completed', models.BooleanField()),
+ ('requested_at', models.DateTimeField()),
+ ('completed_at', models.DateTimeField()),
+ ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')),
+ ('message', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dmessages.Message')),
+ ],
+ ),
+ ]
diff --git a/geeksbot_v2/dmessages/migrations/__init__.py b/geeksbot_v2/dmessages/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/geeksbot_v2/dmessages/models.py b/geeksbot_v2/dmessages/models.py
new file mode 100644
index 0000000..6b23008
--- /dev/null
+++ b/geeksbot_v2/dmessages/models.py
@@ -0,0 +1,58 @@
+from django.db import models
+from django.contrib.postgres.fields import ArrayField
+
+from geeksbot_v2.guilds.models import Guild
+from geeksbot_v2.users.models import User
+
+# Create your models here.
+
+
+class Message(models.Model):
+ id = models.CharField(max_length=30, primary_key=True)
+ author = models.ForeignKey(User, on_delete=models.CASCADE)
+ guild = models.ForeignKey(Guild, on_delete=models.CASCADE)
+ channel = models.CharField(max_length=30)
+ created_at = models.DateTimeField()
+ modified_at = models.DateTimeField(null=True)
+ deleted_at = models.DateTimeField(null=True)
+ content = models.CharField(max_length=2000)
+ previous_content = ArrayField(models.CharField(max_length=2000))
+ tagged_users = ArrayField(models.CharField(max_length=30))
+ tagged_channels = ArrayField(models.CharField(max_length=30))
+ tagged_roles = ArrayField(models.CharField(max_length=30))
+ tagged_everyone = models.BooleanField()
+ embeds = ArrayField(models.TextField())
+ previous_embeds = ArrayField(ArrayField(models.TextField()))
+
+ def __str__(self):
+ return (f'{self.created_at} | '
+ f'{self.author.id}'
+ f'{" | Modified" if self.modified_at else ""}'
+ f'{" | Deleted" if self.deleted_at else ""}')
+
+
+class GuildInfo(models.Model):
+ message = models.ForeignKey(
+ Message, on_delete=models.CASCADE, blank=True, null=True
+ )
+ guild = models.ForeignKey(Guild, on_delete=models.CASCADE)
+ type = models.PositiveSmallIntegerField()
+ text = models.TextField(max_length=1980)
+ format = models.PositiveSmallIntegerField()
+ channel = models.CharField(max_length=30)
+ message_number = models.PositiveSmallIntegerField()
+
+ def __str__(self):
+ return f"{self.guild.id} | {self.text[:25]}"
+
+
+class AdminRequest(models.Model):
+ guild = models.ForeignKey(Guild, on_delete=models.CASCADE)
+ author = models.ForeignKey(User, on_delete=models.CASCADE)
+ message = models.ForeignKey(Message, on_delete=models.CASCADE)
+ completed = models.BooleanField()
+ requested_at = models.DateTimeField()
+ completed_at = models.DateTimeField()
+
+ def __str__(self):
+ return f"{self.guild.id} | {self.requested_at} | By {self.author.id}"
diff --git a/geeksbot_v2/dmessages/serializers.py b/geeksbot_v2/dmessages/serializers.py
new file mode 100644
index 0000000..52bd27b
--- /dev/null
+++ b/geeksbot_v2/dmessages/serializers.py
@@ -0,0 +1,22 @@
+from rest_framework import serializers
+
+from .models import Message
+from .models import GuildInfo
+from .models import AdminRequest
+
+
+class MessageSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Message
+ fields = "__all__"
+
+class GuildInfoSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = GuildInfo
+ fields = "__all__"
+
+
+class AdminRequestSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = AdminRequest
+ fields = "__all__"
diff --git a/geeksbot_v2/dmessages/tests.py b/geeksbot_v2/dmessages/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/geeksbot_v2/dmessages/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/geeksbot_v2/dmessages/views.py b/geeksbot_v2/dmessages/views.py
new file mode 100644
index 0000000..91ea44a
--- /dev/null
+++ b/geeksbot_v2/dmessages/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/entrypoint b/geeksbot_v2/entrypoint
old mode 100644
new mode 100755
similarity index 99%
rename from entrypoint
rename to geeksbot_v2/entrypoint
index 941bd8e..d75e631
--- a/entrypoint
+++ b/geeksbot_v2/entrypoint
@@ -4,9 +4,6 @@ set -o errexit
set -o pipefail
set -o nounset
-
-
-
if [ -z "${POSTGRES_USER}" ]; then
base_postgres_image_default_user='postgres'
export POSTGRES_USER="${base_postgres_image_default_user}"
diff --git a/geeksbot_v2/guilds/__init__.py b/geeksbot_v2/guilds/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/geeksbot_v2/guilds/admin.py b/geeksbot_v2/guilds/admin.py
new file mode 100644
index 0000000..a85e098
--- /dev/null
+++ b/geeksbot_v2/guilds/admin.py
@@ -0,0 +1,8 @@
+from django.contrib import admin
+
+from geeksbot_v2.guilds.models import Guild
+from geeksbot_v2.guilds.models import Role
+
+# Register your models here.
+admin.site.register(Guild)
+admin.site.register(Role)
\ No newline at end of file
diff --git a/geeksbot_v2/guilds/api_urls.py b/geeksbot_v2/guilds/api_urls.py
new file mode 100644
index 0000000..dc65d71
--- /dev/null
+++ b/geeksbot_v2/guilds/api_urls.py
@@ -0,0 +1,8 @@
+from django.urls import path
+
+from .views import GuildsAPI
+
+app_name = "users_api"
+urlpatterns = [
+ path("/", view=GuildsAPI.as_view(), name="list")
+]
diff --git a/geeksbot_v2/guilds/apps.py b/geeksbot_v2/guilds/apps.py
new file mode 100644
index 0000000..6789cc4
--- /dev/null
+++ b/geeksbot_v2/guilds/apps.py
@@ -0,0 +1,7 @@
+from django.apps import AppConfig
+from django.utils.translation import gettext_lazy as _
+
+
+class GuildsConfig(AppConfig):
+ name = 'geeksbot_v2.guilds'
+ verbose_name = _("Guilds")
diff --git a/geeksbot_v2/guilds/migrations/0001_initial.py b/geeksbot_v2/guilds/migrations/0001_initial.py
new file mode 100644
index 0000000..c66c8a4
--- /dev/null
+++ b/geeksbot_v2/guilds/migrations/0001_initial.py
@@ -0,0 +1,35 @@
+# Generated by Django 2.2.4 on 2019-09-16 05:23
+
+import django.contrib.postgres.fields
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Guild',
+ fields=[
+ ('id', models.CharField(max_length=30, primary_key=True, serialize=False)),
+ ('admin_chat', models.CharField(max_length=30)),
+ ('new_patron_message', models.TextField(blank=True, max_length=1000)),
+ ('default_channel', models.CharField(max_length=30)),
+ ('new_patron_channel', models.CharField(max_length=30)),
+ ('prefixes', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=10), size=None)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Role',
+ fields=[
+ ('id', models.CharField(max_length=30, primary_key=True, serialize=False)),
+ ('type', models.PositiveSmallIntegerField()),
+ ('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')),
+ ],
+ ),
+ ]
diff --git a/geeksbot_v2/guilds/migrations/0002_auto_20190917_0508.py b/geeksbot_v2/guilds/migrations/0002_auto_20190917_0508.py
new file mode 100644
index 0000000..60272ae
--- /dev/null
+++ b/geeksbot_v2/guilds/migrations/0002_auto_20190917_0508.py
@@ -0,0 +1,28 @@
+# Generated by Django 2.2.4 on 2019-09-17 05:08
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('guilds', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='guild',
+ name='admin_chat',
+ field=models.CharField(blank=True, max_length=30, null=True),
+ ),
+ migrations.AlterField(
+ model_name='guild',
+ name='new_patron_channel',
+ field=models.CharField(blank=True, max_length=30, null=True),
+ ),
+ migrations.AlterField(
+ model_name='guild',
+ name='new_patron_message',
+ field=models.TextField(blank=True, max_length=1000, null=True),
+ ),
+ ]
diff --git a/geeksbot_v2/guilds/migrations/__init__.py b/geeksbot_v2/guilds/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/geeksbot_v2/guilds/models.py b/geeksbot_v2/guilds/models.py
new file mode 100644
index 0000000..b954330
--- /dev/null
+++ b/geeksbot_v2/guilds/models.py
@@ -0,0 +1,25 @@
+from django.db import models
+from django.contrib.postgres.fields import ArrayField
+
+# Create your models here.
+
+
+class Guild(models.Model):
+ id = models.CharField(max_length=30, primary_key=True)
+ admin_chat = models.CharField(max_length=30, blank=True, null=True)
+ new_patron_message = models.TextField(max_length=1000, blank=True, null=True)
+ default_channel = models.CharField(max_length=30)
+ new_patron_channel = models.CharField(max_length=30, blank=True, null=True)
+ prefixes = ArrayField(models.CharField(max_length=10))
+
+ def __str__(self):
+ return self.id
+
+
+class Role(models.Model):
+ id = models.CharField(max_length=30, primary_key=True)
+ guild = models.ForeignKey(Guild, on_delete=models.CASCADE, null=False)
+ type = models.PositiveSmallIntegerField()
+
+ def __str__(self):
+ return f"{self.guild.id} | {self.id}"
diff --git a/geeksbot_v2/guilds/serializers.py b/geeksbot_v2/guilds/serializers.py
new file mode 100644
index 0000000..8dc1548
--- /dev/null
+++ b/geeksbot_v2/guilds/serializers.py
@@ -0,0 +1,16 @@
+from rest_framework import serializers
+
+from geeksbot_v2.guilds.models import Guild
+from geeksbot_v2.guilds.models import Role
+
+
+class GuildSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Guild
+ fields = "__all__"
+
+
+class RoleSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Role
+ fields = ["id", "guild", "type"]
diff --git a/geeksbot_v2/guilds/tests.py b/geeksbot_v2/guilds/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/geeksbot_v2/guilds/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/geeksbot_v2/guilds/urls.py b/geeksbot_v2/guilds/urls.py
new file mode 100644
index 0000000..e69de29
diff --git a/geeksbot_v2/guilds/views.py b/geeksbot_v2/guilds/views.py
new file mode 100644
index 0000000..fc08748
--- /dev/null
+++ b/geeksbot_v2/guilds/views.py
@@ -0,0 +1,51 @@
+import os
+
+from django.shortcuts import render
+from rest_framework.response import Response
+from rest_framework.views import APIView
+from rest_framework import status
+
+from geeksbot_v2.utils.api_utils import PaginatedAPIView
+from .models import Guild
+from .serializers import GuildSerializer
+
+# Create your views here.
+
+# API Views
+
+
+class GuildsAPI(PaginatedAPIView):
+ def get(self, request, format=None):
+ users = Guild.objects.all()
+ page = self.paginate_queryset(users)
+ if page is not None:
+ serialized_users = GuildSerializer(users, many=True)
+ return self.get_paginated_response(serialized_users.data)
+
+ serialized_users = GuildSerializer(users, many=True)
+ return Response(serialized_users.data)
+
+ def post(self, request, format=None):
+ data = dict(request.data)
+ print(data)
+ id = data.get('id')
+ default_channel = data.get('default_channel')
+ if not (id and default_channel):
+ return Response({'msg': 'id and default_channel are required'}, status=status.HTTP_400_BAD_REQUEST)
+
+ admin_chat = data.get('admin_chat')
+ new_patron_message = data.get('new_patron_message')
+ default_prefix = os.environ['DISCORD_DEFAULT_PREFIX']
+ prefixes = data.get('prefixes', [default_prefix, ])
+ print(prefixes)
+
+ guild = Guild(
+ id=id[0] if isinstance(id, list) else id,
+ default_channel=default_channel[0] if isinstance(default_channel, list) else default_channel,
+ prefixes=prefixes,
+ admin_chat=admin_chat[0] if isinstance(admin_chat, list) else admin_chat,
+ new_patron_message=new_patron_message[0] if isinstance(new_patron_message, list) else new_patron_message
+ )
+ guild.save()
+
+ return Response(GuildSerializer(guild).data, status=status.HTTP_201_CREATED)
diff --git a/manage.py b/geeksbot_v2/manage.py
similarity index 91%
rename from manage.py
rename to geeksbot_v2/manage.py
index f482f42..31df531 100755
--- a/manage.py
+++ b/geeksbot_v2/manage.py
@@ -3,7 +3,7 @@ import os
import sys
if __name__ == "__main__":
- os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "geeksbot_v2.config.settings.local")
try:
from django.core.management import execute_from_command_line
diff --git a/geeksbot_v2/patreon/__init__.py b/geeksbot_v2/patreon/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/geeksbot_v2/patreon/admin.py b/geeksbot_v2/patreon/admin.py
new file mode 100644
index 0000000..6e73988
--- /dev/null
+++ b/geeksbot_v2/patreon/admin.py
@@ -0,0 +1,9 @@
+from django.contrib import admin
+
+
+from .models import PatreonCreator
+from .models import PatreonTier
+
+# Register your models here.
+admin.site.register(PatreonCreator)
+admin.site.register(PatreonTier)
\ No newline at end of file
diff --git a/geeksbot_v2/patreon/apps.py b/geeksbot_v2/patreon/apps.py
new file mode 100644
index 0000000..5544439
--- /dev/null
+++ b/geeksbot_v2/patreon/apps.py
@@ -0,0 +1,7 @@
+from django.apps import AppConfig
+from django.utils.translation import gettext_lazy as _
+
+
+class PatreonConfig(AppConfig):
+ name = 'geeksbot_v2.patreon'
+ verbose_name = _("Patreon")
diff --git a/geeksbot_v2/patreon/migrations/0001_initial.py b/geeksbot_v2/patreon/migrations/0001_initial.py
new file mode 100644
index 0000000..b74b38f
--- /dev/null
+++ b/geeksbot_v2/patreon/migrations/0001_initial.py
@@ -0,0 +1,37 @@
+# Generated by Django 2.2.4 on 2019-09-16 05:23
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('guilds', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='PatreonCreator',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('creator', models.CharField(max_length=50)),
+ ('link', models.CharField(max_length=100)),
+ ('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='PatreonTier',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=50)),
+ ('description', models.TextField()),
+ ('amount', models.IntegerField(null=True)),
+ ('creator', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='patreon.PatreonCreator')),
+ ('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')),
+ ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Role')),
+ ],
+ ),
+ ]
diff --git a/geeksbot_v2/patreon/migrations/__init__.py b/geeksbot_v2/patreon/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/geeksbot_v2/patreon/models.py b/geeksbot_v2/patreon/models.py
new file mode 100644
index 0000000..1035fa8
--- /dev/null
+++ b/geeksbot_v2/patreon/models.py
@@ -0,0 +1,27 @@
+from django.db import models
+
+from geeksbot_v2.guilds.models import Guild
+from geeksbot_v2.guilds.models import Role
+
+# Create your models here.
+
+
+class PatreonCreator(models.Model):
+ guild = models.ForeignKey(Guild, on_delete=models.CASCADE, null=False)
+ creator = models.CharField(max_length=50, null=False)
+ link = models.CharField(max_length=100, null=False)
+
+ def __str__(self):
+ return f"{self.guild.id} | {self.creator}"
+
+
+class PatreonTier(models.Model):
+ creator = models.ForeignKey(PatreonCreator, on_delete=models.CASCADE)
+ guild = models.ForeignKey(Guild, on_delete=models.CASCADE)
+ name = models.CharField(max_length=50)
+ description = models.TextField()
+ role = models.ForeignKey(Role, on_delete=models.CASCADE)
+ amount = models.IntegerField(null=True)
+
+ def __str__(self):
+ return f"{self.guild.id} | {self.creator.creator} | {self.name}"
diff --git a/geeksbot_v2/patreon/serializers.py b/geeksbot_v2/patreon/serializers.py
new file mode 100644
index 0000000..818c942
--- /dev/null
+++ b/geeksbot_v2/patreon/serializers.py
@@ -0,0 +1,16 @@
+from rest_framework import serializers
+
+from geeksbot_v2.patreon.models import PatreonCreator
+from geeksbot_v2.patreon.models import PatreonTier
+
+
+class PatreonCreatorSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = PatreonCreator
+ fields = "__all__"
+
+
+class PatreonTierSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = PatreonTier
+ fields = "__all__"
diff --git a/geeksbot_v2/patreon/tests.py b/geeksbot_v2/patreon/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/geeksbot_v2/patreon/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/geeksbot_v2/patreon/views.py b/geeksbot_v2/patreon/views.py
new file mode 100644
index 0000000..91ea44a
--- /dev/null
+++ b/geeksbot_v2/patreon/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/geeksbot_v2/rcon/__init__.py b/geeksbot_v2/rcon/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/geeksbot_v2/rcon/admin.py b/geeksbot_v2/rcon/admin.py
new file mode 100644
index 0000000..7209c51
--- /dev/null
+++ b/geeksbot_v2/rcon/admin.py
@@ -0,0 +1,6 @@
+from django.contrib import admin
+
+from .models import RconServer
+
+# Register your models here.
+admin.site.register(RconServer)
\ No newline at end of file
diff --git a/geeksbot_v2/rcon/apps.py b/geeksbot_v2/rcon/apps.py
new file mode 100644
index 0000000..c6d1290
--- /dev/null
+++ b/geeksbot_v2/rcon/apps.py
@@ -0,0 +1,7 @@
+from django.apps import AppConfig
+from django.utils.translation import gettext_lazy as _
+
+
+class RconConfig(AppConfig):
+ name = 'geeksbot_v2.rcon'
+ verbose_name = _("Rcon")
diff --git a/geeksbot_v2/rcon/migrations/0001_initial.py b/geeksbot_v2/rcon/migrations/0001_initial.py
new file mode 100644
index 0000000..56e2bd6
--- /dev/null
+++ b/geeksbot_v2/rcon/migrations/0001_initial.py
@@ -0,0 +1,37 @@
+# Generated by Django 2.2.4 on 2019-09-16 05:23
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('guilds', '0001_initial'),
+ ('dmessages', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='RconServer',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=50)),
+ ('ip', models.GenericIPAddressField()),
+ ('port', models.PositiveIntegerField()),
+ ('password', models.CharField(max_length=50)),
+ ('monitor_chat', models.BooleanField()),
+ ('monitor_chat_channel', models.CharField(blank=True, max_length=30)),
+ ('alerts_channel', models.CharField(blank=True, max_length=30)),
+ ('info_channel', models.CharField(blank=True, max_length=30)),
+ ('guild', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='guilds.Guild')),
+ ('info_message', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='dmessages.Message')),
+ ('settings_message', models.ForeignKey(blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='dmessages.Message')),
+ ('whitelist', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/geeksbot_v2/rcon/migrations/__init__.py b/geeksbot_v2/rcon/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/geeksbot_v2/rcon/models.py b/geeksbot_v2/rcon/models.py
new file mode 100644
index 0000000..b341b7b
--- /dev/null
+++ b/geeksbot_v2/rcon/models.py
@@ -0,0 +1,29 @@
+from django.db import models
+
+from geeksbot_v2.guilds.models import Guild
+from geeksbot_v2.dmessages.models import Message
+from geeksbot_v2.users.models import User
+
+# Create your models here.
+
+
+class RconServer(models.Model):
+ guild = models.ForeignKey(Guild, on_delete=models.CASCADE)
+ name = models.CharField(max_length=50)
+ ip = models.GenericIPAddressField()
+ port = models.PositiveIntegerField()
+ password = models.CharField(max_length=50)
+ monitor_chat = models.BooleanField()
+ monitor_chat_channel = models.CharField(max_length=30, blank=True)
+ alerts_channel = models.CharField(max_length=30, blank=True)
+ info_channel = models.CharField(max_length=30, blank=True)
+ info_message = models.ForeignKey(
+ Message, on_delete=models.CASCADE, related_name="+", blank=True
+ )
+ settings_message = models.ForeignKey(
+ Message, on_delete=models.CASCADE, related_name="+", blank=True
+ )
+ whitelist = models.ManyToManyField(User, blank=True)
+
+ def __str__(self):
+ return f"{self.guild.id} | {self.name}"
diff --git a/geeksbot_v2/rcon/serializers.py b/geeksbot_v2/rcon/serializers.py
new file mode 100644
index 0000000..18184e3
--- /dev/null
+++ b/geeksbot_v2/rcon/serializers.py
@@ -0,0 +1,9 @@
+from rest_framework import serializers
+
+from geeksbot_v2.rcon.models import RconServer
+
+
+class RconServerSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = RconServer
+ fields = "__all__"
diff --git a/geeksbot_v2/rcon/tests.py b/geeksbot_v2/rcon/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/geeksbot_v2/rcon/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/geeksbot_v2/rcon/views.py b/geeksbot_v2/rcon/views.py
new file mode 100644
index 0000000..91ea44a
--- /dev/null
+++ b/geeksbot_v2/rcon/views.py
@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.
diff --git a/geeksbot_v2/templates/base.html b/geeksbot_v2/templates/base.html
index c8ddef7..547b0c1 100644
--- a/geeksbot_v2/templates/base.html
+++ b/geeksbot_v2/templates/base.html
@@ -59,10 +59,6 @@
{% trans "Sign Out" %}
{% else %}
-
- {# URL provided by django-allauth/account/urls.py #}
- {% trans "Sign Up" %}
-
{# URL provided by django-allauth/account/urls.py #}
{% trans "Sign In" %}
diff --git a/geeksbot_v2/templates/users/user_detail.html b/geeksbot_v2/templates/users/user_detail.html
index e86eda1..9190a5f 100644
--- a/geeksbot_v2/templates/users/user_detail.html
+++ b/geeksbot_v2/templates/users/user_detail.html
@@ -13,6 +13,7 @@
{% if object.name %}
{{ object.name }}
{% endif %}
+ {{ user.id }}
diff --git a/geeksbot_v2/users/adapters.py b/geeksbot_v2/users/adapters.py
index 0d206fa..41ee20e 100644
--- a/geeksbot_v2/users/adapters.py
+++ b/geeksbot_v2/users/adapters.py
@@ -2,15 +2,34 @@ from typing import Any
from allauth.account.adapter import DefaultAccountAdapter
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
+from allauth.account.utils import user_email, user_username, user_field
+from allauth.utils import valid_email_or_none
from django.conf import settings
from django.http import HttpRequest
class AccountAdapter(DefaultAccountAdapter):
def is_open_for_signup(self, request: HttpRequest):
- return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
+ return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", False)
class SocialAccountAdapter(DefaultSocialAccountAdapter):
def is_open_for_signup(self, request: HttpRequest, sociallogin: Any):
- return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
+ return getattr(settings, "SOCIAL_ACCOUNT_ALLOW_REGISTRATION", True)
+
+ def populate_user(self, request, sociallogin, data):
+# print(sociallogin.account.extra_data)
+ first_name = data.get('first_name')
+ last_name = data.get('last_name')
+ name = data.get('name')
+ id = sociallogin.account.extra_data.get('id')
+ user = sociallogin.user
+ user_username(user, data.get('username', ''))
+ user_email(user, valid_email_or_none(data.get('email')) or '')
+ name_parts = (name or '').partition(' ')
+ user_field(user, 'first_name', first_name or name_parts[0])
+ user_field(user, 'last_name', last_name or name_parts[2])
+ user_field(user, 'id', id or '')
+ user_field(user, 'avatar', sociallogin.account.extra_data.get('avatar', ''))
+ user_field(user, 'discriminator', sociallogin.account.extra_data.get('discriminator', ''))
+ return user
diff --git a/geeksbot_v2/users/admin.py b/geeksbot_v2/users/admin.py
index 60b5980..60a20f6 100644
--- a/geeksbot_v2/users/admin.py
+++ b/geeksbot_v2/users/admin.py
@@ -1,17 +1,13 @@
from django.contrib import admin
from django.contrib.auth import admin as auth_admin
-from django.contrib.auth import get_user_model
-from geeksbot_v2.users.forms import UserChangeForm, UserCreationForm
-
-User = get_user_model()
+from .forms import UserChangeForm
+from .models import User
-@admin.register(User)
class UserAdmin(auth_admin.UserAdmin):
-
+ model = User
form = UserChangeForm
- add_form = UserCreationForm
- fieldsets = (("User", {"fields": ("name",)}),) + auth_admin.UserAdmin.fieldsets
- list_display = ["username", "name", "is_superuser"]
- search_fields = ["name"]
+
+
+admin.site.register(User, UserAdmin)
diff --git a/geeksbot_v2/users/api_urls.py b/geeksbot_v2/users/api_urls.py
new file mode 100644
index 0000000..6d500cb
--- /dev/null
+++ b/geeksbot_v2/users/api_urls.py
@@ -0,0 +1,8 @@
+from django.urls import path
+
+from geeksbot_v2.users.views import UsersAPI
+
+app_name = "users_api"
+urlpatterns = [
+ path("users/", view=UsersAPI.as_view(), name="list")
+]
diff --git a/geeksbot_v2/users/forms.py b/geeksbot_v2/users/forms.py
index 250cc90..8be2bc6 100644
--- a/geeksbot_v2/users/forms.py
+++ b/geeksbot_v2/users/forms.py
@@ -1,30 +1,8 @@
-from django.contrib.auth import get_user_model, forms
-from django.core.exceptions import ValidationError
-from django.utils.translation import ugettext_lazy as _
+from django.contrib.auth import forms
-User = get_user_model()
+from .models import User
class UserChangeForm(forms.UserChangeForm):
class Meta(forms.UserChangeForm.Meta):
model = User
-
-
-class UserCreationForm(forms.UserCreationForm):
-
- error_message = forms.UserCreationForm.error_messages.update(
- {"duplicate_username": _("This username has already been taken.")}
- )
-
- class Meta(forms.UserCreationForm.Meta):
- model = User
-
- def clean_username(self):
- username = self.cleaned_data["username"]
-
- try:
- User.objects.get(username=username)
- except User.DoesNotExist:
- return username
-
- raise ValidationError(self.error_messages["duplicate_username"])
diff --git a/geeksbot_v2/users/migrations/0001_initial.py b/geeksbot_v2/users/migrations/0001_initial.py
deleted file mode 100644
index c9d8905..0000000
--- a/geeksbot_v2/users/migrations/0001_initial.py
+++ /dev/null
@@ -1,132 +0,0 @@
-import django.contrib.auth.models
-import django.contrib.auth.validators
-from django.db import migrations, models
-import django.utils.timezone
-
-
-class Migration(migrations.Migration):
-
- initial = True
-
- dependencies = [("auth", "0008_alter_user_username_max_length")]
-
- operations = [
- migrations.CreateModel(
- name="User",
- fields=[
- (
- "id",
- models.AutoField(
- auto_created=True,
- primary_key=True,
- serialize=False,
- verbose_name="ID",
- ),
- ),
- ("password", models.CharField(max_length=128, verbose_name="password")),
- (
- "last_login",
- models.DateTimeField(
- blank=True, null=True, verbose_name="last login"
- ),
- ),
- (
- "is_superuser",
- models.BooleanField(
- default=False,
- help_text="Designates that this user has all permissions without explicitly assigning them.",
- verbose_name="superuser status",
- ),
- ),
- (
- "username",
- models.CharField(
- error_messages={
- "unique": "A user with that username already exists."
- },
- help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
- max_length=150,
- unique=True,
- validators=[
- django.contrib.auth.validators.UnicodeUsernameValidator()
- ],
- verbose_name="username",
- ),
- ),
- (
- "first_name",
- models.CharField(
- blank=True, max_length=30, verbose_name="first name"
- ),
- ),
- (
- "last_name",
- models.CharField(
- blank=True, max_length=150, verbose_name="last name"
- ),
- ),
- (
- "email",
- models.EmailField(
- blank=True, max_length=254, verbose_name="email address"
- ),
- ),
- (
- "is_staff",
- models.BooleanField(
- default=False,
- help_text="Designates whether the user can log into this admin site.",
- verbose_name="staff status",
- ),
- ),
- (
- "is_active",
- models.BooleanField(
- default=True,
- help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
- verbose_name="active",
- ),
- ),
- (
- "date_joined",
- models.DateTimeField(
- default=django.utils.timezone.now, verbose_name="date joined"
- ),
- ),
- (
- "name",
- models.CharField(
- blank=True, max_length=255, verbose_name="Name of User"
- ),
- ),
- (
- "groups",
- models.ManyToManyField(
- blank=True,
- help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
- related_name="user_set",
- related_query_name="user",
- to="auth.Group",
- verbose_name="groups",
- ),
- ),
- (
- "user_permissions",
- models.ManyToManyField(
- blank=True,
- help_text="Specific permissions for this user.",
- related_name="user_set",
- related_query_name="user",
- to="auth.Permission",
- verbose_name="user permissions",
- ),
- ),
- ],
- options={
- "verbose_name_plural": "users",
- "verbose_name": "user",
- "abstract": False,
- },
- managers=[("objects", django.contrib.auth.models.UserManager())],
- )
- ]
diff --git a/geeksbot_v2/users/models.py b/geeksbot_v2/users/models.py
index 8f07b15..3b893b2 100644
--- a/geeksbot_v2/users/models.py
+++ b/geeksbot_v2/users/models.py
@@ -2,6 +2,20 @@ from django.contrib.auth.models import AbstractUser
from django.db.models import CharField
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
+from django.db import models
+from django.contrib.postgres.fields import ArrayField
+from django.conf import settings
+from django.db.models.signals import post_save
+from django.dispatch import receiver
+from rest_framework.authtoken.models import Token
+
+from geeksbot_v2.guilds.models import Guild
+
+
+@receiver(post_save, sender=settings.AUTH_USER_MODEL)
+def create_auth_token(sender, instance=None, created=False, **kwargs):
+ if created:
+ Token.objects.create(user=instance)
class User(AbstractUser):
@@ -9,6 +23,28 @@ class User(AbstractUser):
# First Name and Last Name do not cover name patterns
# around the globe.
name = CharField(_("Name of User"), blank=True, max_length=255)
+ id = models.CharField(max_length=30, primary_key=True)
+ discord_username = models.CharField(max_length=100, null=True)
+ previous_discord_usernames = ArrayField(models.CharField(max_length=100), blank=True, null=True)
+ discriminator = models.IntegerField(null=True)
+ previous_discriminators = ArrayField(models.IntegerField(), blank=True, null=True)
+ guilds = models.ManyToManyField(Guild, blank=True, null=True)
+ steam_id = models.CharField(max_length=30, blank=True, null=True)
+ animated = models.BooleanField(blank=True, null=True)
+ avatar = models.CharField(max_length=100, blank=True, null=True)
+ bot = models.BooleanField(blank=True, null=True)
+ banned = models.BooleanField(default=False)
+ logging_enabled = models.BooleanField(default=True)
def get_absolute_url(self):
return reverse("users:detail", kwargs={"username": self.username})
+
+
+class UserLog(models.Model):
+ user = models.ForeignKey(User, on_delete=models.CASCADE)
+ time = models.DateTimeField()
+ action = models.IntegerField()
+ description = models.CharField(max_length=100)
+
+ def __str__(self):
+ return f"{self.time} | {self.user.id} | {self.action}"
diff --git a/geeksbot_v2/users/serializers.py b/geeksbot_v2/users/serializers.py
new file mode 100644
index 0000000..340aef1
--- /dev/null
+++ b/geeksbot_v2/users/serializers.py
@@ -0,0 +1,16 @@
+from rest_framework import serializers
+
+from geeksbot_v2.users.models import User
+from geeksbot_v2.users.models import UserLog
+
+
+class UserSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = User
+ fields = "__all__"
+
+
+class UserLogSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = UserLog
+ fields = "__all__"
diff --git a/geeksbot_v2/users/views.py b/geeksbot_v2/users/views.py
index 5c0d5b5..3758c2e 100644
--- a/geeksbot_v2/users/views.py
+++ b/geeksbot_v2/users/views.py
@@ -4,6 +4,14 @@ from django.urls import reverse
from django.views.generic import DetailView, RedirectView, UpdateView
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
+from rest_framework.response import Response
+from rest_framework.views import APIView
+
+
+from geeksbot_v2.users.serializers import UserSerializer
+from geeksbot_v2.users.serializers import UserLogSerializer
+from geeksbot_v2.users.models import UserLog
+from geeksbot_v2.utils.api_utils import PaginatedAPIView
User = get_user_model()
@@ -14,6 +22,11 @@ class UserDetailView(LoginRequiredMixin, DetailView):
slug_field = "username"
slug_url_kwarg = "username"
+ def get(self, request, *args, **kwargs):
+ self.object = self.get_object()
+ context = self.get_context_data(object=self.object, user=request.user)
+ return self.render_to_response(context)
+
user_detail_view = UserDetailView.as_view()
@@ -48,3 +61,49 @@ class UserRedirectView(LoginRequiredMixin, RedirectView):
user_redirect_view = UserRedirectView.as_view()
+
+# API Views
+
+
+class UsersAPI(PaginatedAPIView):
+ def get(self, request, guild, format=None):
+ users = User.objects.filter(guilds__id=guild)
+ page = self.paginate_queryset(users)
+ if page is not None:
+ serialized_users = UserSerializer(users, many=True)
+ return self.get_paginated_response(serialized_users.data)
+
+ serialized_users = UserSerializer(users, many=True)
+ return Response(serialized_users.data)
+
+
+class UserDetail(APIView):
+ def get(self, request, guild, id, format=None):
+ user = User.objects.filter(guilds__id=guild).get(id=id)
+ return Response(UserSerializer(user).data)
+
+
+class UserLogList(PaginatedAPIView):
+ def get(self, request, user, action=None, format=None):
+ if action:
+ user_logs = (
+ UserLog.objects.filter(user=user)
+ .filter(action=action)
+ .order_by("-time")
+ )
+ else:
+ user_logs = UserLog.objects.filter(user=user).order_by("-time")
+
+ page = self.paginate_queryset(user_logs)
+ if page is not None:
+ serialized_logs = UserLogSerializer(page, many=True)
+ return self.get_paginated_response(serialized_logs.data)
+
+ serialized_logs = UserLogSerializer(user_logs, many=True)
+ return Response(serialized_logs.data)
+
+
+class UserLogDetail(APIView):
+ def get(self, request, id, format=None):
+ user_log = UserLog.objects.get(id=id)
+ return Response(UserLogSerializer(user_log).data)
diff --git a/geeksbot_v2/utils/api_utils.py b/geeksbot_v2/utils/api_utils.py
new file mode 100644
index 0000000..62a1775
--- /dev/null
+++ b/geeksbot_v2/utils/api_utils.py
@@ -0,0 +1,33 @@
+from rest_framework.views import APIView
+from rest_framework.settings import api_settings
+
+
+class PaginatedAPIView(APIView):
+ pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
+
+ @property
+ def paginator(self):
+ """
+ The paginator instance associated with the view, or `None`.
+ """
+ if not hasattr(self, "_paginator"):
+ if self.pagination_class is None:
+ self._paginator = None
+ else:
+ self._paginator = self.pagination_class()
+ return self._paginator
+
+ def paginate_queryset(self, queryset):
+ """
+ Return a single page of results, or `None` if pagination is disabled.
+ """
+ if self.paginator is None:
+ return None
+ return self.paginator.paginate_queryset(queryset, self.request, view=self)
+
+ def get_paginated_response(self, data):
+ """
+ Return a paginated style `Response` object for the given output data.
+ """
+ assert self.paginator is not None
+ return self.paginator.get_paginated_response(data)
diff --git a/services/Dockerfile-web b/services/Dockerfile-web
index b4081a4..ecf1d56 100644
--- a/services/Dockerfile-web
+++ b/services/Dockerfile-web
@@ -20,17 +20,18 @@ COPY ./services/web/supervisord.conf /etc/supervisor/supervisord.conf
COPY ./services/web/supervisor_geeksbot.conf /etc/supervisor/conf.d/geeksbot.conf
COPY ./ssl_certs/geeksbot_app/geeksbot_app_cert_chain.crt /etc/ssl/geeksbot_app_cert_chain.crt
COPY ./ssl_certs/geeksbot_app/geeksbot.app.key /etc/ssl/geeksbot.app.key
+COPY ./.env /code/
RUN rm -rf /tmp/*
RUN mkdir -p /tmp/logs/nginx
RUN mkdir -p /tmp/logs/geeksbot
-COPY manage.py .
-copy entrypoint .
-RUN sed -i 's/\r$//g' /code/entrypoint
-RUN chmod +x /code/entrypoint
+WORKDIR /code/geeksbot_v2
+
+# RUN sed -i 's/\r$//g' ./entrypoint
+# RUN chmod +x ./entrypoint
EXPOSE 80 8000 443
-ENTRYPOINT [ "/code/entrypoint" ]
+ENTRYPOINT [ "./entrypoint" ]
diff --git a/services/web/geeksbot.conf b/services/web/geeksbot.conf
index fce9f90..1019f13 100644
--- a/services/web/geeksbot.conf
+++ b/services/web/geeksbot.conf
@@ -13,11 +13,11 @@ server {
error_log /tmp/logs/geeksbot/error.log;
location /static/ {
- alias /code/staticfiles/;
+ alias /code/geeksbot_v2/staticfiles/;
}
location /error/ {
- alias /code/staticfiles/errors/;
+ alias /code/geeksbot_v2/staticfiles/errors/;
}
location / {
diff --git a/services/web/supervisor_geeksbot.conf b/services/web/supervisor_geeksbot.conf
index d1a1d10..9197c4a 100644
--- a/services/web/supervisor_geeksbot.conf
+++ b/services/web/supervisor_geeksbot.conf
@@ -1,6 +1,6 @@
[program:geeksbot]
command=/usr/local/bin/gunicorn config.wsgi:application -c /etc/gunicorn.conf
-directory=/code/
+directory=/code/geeksbot_v2
stdout_logfile=/tmp/logs/geeksbot/gunicorn.log
autostart=true
autorestart=true