Basic api stuff and bot exec
This commit is contained in:
parent
d6fa090a56
commit
fb9744a93a
@ -1,4 +0,0 @@
|
|||||||
# General
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
USE_DOCKER=yes
|
|
||||||
IPYTHONDIR=/app/.ipython
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
# PostgreSQL
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
POSTGRES_HOST=postgres
|
|
||||||
POSTGRES_PORT=5432
|
|
||||||
POSTGRES_DB=geeksbot_v2
|
|
||||||
POSTGRES_USER=eJujPHAXiaktuvRsJQlNbdhzHbkEGdgs
|
|
||||||
POSTGRES_PASSWORD=hrjQlc8cfRVa0pgeJ1OWcsh4ZbRpTS1bJbMP2Atx3zKivMDJWKO71Dly0jFmRUWq
|
|
||||||
42
.idea/geeksbot_v2.iml
generated
42
.idea/geeksbot_v2.iml
generated
@ -1,42 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="PYTHON_MODULE" version="4">
|
|
||||||
<component name="FacetManager">
|
|
||||||
<facet type="django" name="Django">
|
|
||||||
<configuration>
|
|
||||||
<option name="rootFolder" value="$MODULE_DIR$" />
|
|
||||||
<option name="settingsModule" value="config/settings/local.py" />
|
|
||||||
<option name="manageScript" value="$MODULE_DIR$/manage.py" />
|
|
||||||
<option name="environment" value="<map/>" />
|
|
||||||
<option name="doNotUseTestRunner" value="false" />
|
|
||||||
<option name="trackFilePattern" value="migrations" />
|
|
||||||
</configuration>
|
|
||||||
</facet>
|
|
||||||
</component>
|
|
||||||
<component name="NewModuleRootManager">
|
|
||||||
<content url="file://$MODULE_DIR$" />
|
|
||||||
<orderEntry type="jdk" jdkName="Python 3.7 (geeksbot)" jdkType="Python SDK" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
<component name="PackageRequirementsSettings">
|
|
||||||
<option name="requirementsPath" value="$MODULE_DIR$/requirements/local.txt" />
|
|
||||||
</component>
|
|
||||||
<component name="PyDocumentationSettings">
|
|
||||||
<option name="renderExternalDocumentation" value="true" />
|
|
||||||
</component>
|
|
||||||
<component name="ReSTService">
|
|
||||||
<option name="workdir" value="$MODULE_DIR$/docs" />
|
|
||||||
<option name="DOC_DIR" value="$MODULE_DIR$/docs" />
|
|
||||||
</component>
|
|
||||||
<component name="TemplatesService">
|
|
||||||
<option name="TEMPLATE_CONFIGURATION" value="Django" />
|
|
||||||
<option name="TEMPLATE_FOLDERS">
|
|
||||||
<list>
|
|
||||||
<option value="$MODULE_DIR$/geeksbot_v2/templates" />
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
<component name="TestRunnerService">
|
|
||||||
<option name="projectConfiguration" value="pytest" />
|
|
||||||
<option name="PROJECT_TEST_RUNNER" value="pytest" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
7
.idea/misc.xml
generated
7
.idea/misc.xml
generated
@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="JavaScriptSettings">
|
|
||||||
<option name="languageLevel" value="ES6" />
|
|
||||||
</component>
|
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (geeksbot)" project-jdk-type="Python SDK" />
|
|
||||||
</project>
|
|
||||||
8
.idea/modules.xml
generated
8
.idea/modules.xml
generated
@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/geeksbot_v2.iml" filepath="$PROJECT_DIR$/.idea/geeksbot_v2.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="merge_production_dotenvs_in_dotenv" type="PythonConfigurationType" factoryName="Python" singleton="true">
|
|
||||||
<module name="geeksbot_v2" />
|
|
||||||
<option name="INTERPRETER_OPTIONS" value="" />
|
|
||||||
<option name="PARENT_ENVS" value="true" />
|
|
||||||
<envs>
|
|
||||||
<env name="PYTHONUNBUFFERED" value="1" />
|
|
||||||
</envs>
|
|
||||||
<option name="SDK_HOME" value="" />
|
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
|
||||||
<option name="IS_MODULE_SDK" value="true" />
|
|
||||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
|
||||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
|
||||||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
|
|
||||||
<option name="SCRIPT_NAME" value="merge_production_dotenvs_in_dotenv.py" />
|
|
||||||
<option name="PARAMETERS" value="" />
|
|
||||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
|
||||||
<option name="EMULATE_TERMINAL" value="false" />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
32
.idea/runConfigurations/migrate.xml
generated
32
.idea/runConfigurations/migrate.xml
generated
@ -1,32 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="migrate" type="Python.DjangoServer" factoryName="Django server" singleton="true">
|
|
||||||
<module name="geeksbot_v2" />
|
|
||||||
<option name="INTERPRETER_OPTIONS" value="" />
|
|
||||||
<option name="PARENT_ENVS" value="true" />
|
|
||||||
<envs>
|
|
||||||
<env name="PYTHONUNBUFFERED" value="1" />
|
|
||||||
<env name="DJANGO_SETTINGS_MODULE" value="config.settings.local" />
|
|
||||||
</envs>
|
|
||||||
<option name="SDK_HOME" value="" />
|
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
|
||||||
<option name="IS_MODULE_SDK" value="true" />
|
|
||||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
|
||||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
|
||||||
<PathMappingSettings>
|
|
||||||
<option name="pathMappings">
|
|
||||||
<list>
|
|
||||||
<mapping local-root="$PROJECT_DIR$" remote-root="/app" />
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
</PathMappingSettings>
|
|
||||||
<option name="launchJavascriptDebuger" value="false" />
|
|
||||||
<option name="host" value="" />
|
|
||||||
<option name="additionalOptions" value="" />
|
|
||||||
<option name="browserUrl" value="" />
|
|
||||||
<option name="runTestServer" value="false" />
|
|
||||||
<option name="runNoReload" value="false" />
|
|
||||||
<option name="useCustomRunCommand" value="true" />
|
|
||||||
<option name="customRunCommand" value="migrate" />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
25
.idea/runConfigurations/pytest___.xml
generated
25
.idea/runConfigurations/pytest___.xml
generated
@ -1,25 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="pytest: ." type="tests" factoryName="py.test" singleton="true">
|
|
||||||
<module name="geeksbot_v2" />
|
|
||||||
<option name="INTERPRETER_OPTIONS" value="" />
|
|
||||||
<option name="PARENT_ENVS" value="true" />
|
|
||||||
<option name="SDK_HOME" value="" />
|
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
|
||||||
<option name="IS_MODULE_SDK" value="true" />
|
|
||||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
|
||||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
|
||||||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
|
||||||
<PathMappingSettings>
|
|
||||||
<option name="pathMappings">
|
|
||||||
<list>
|
|
||||||
<mapping local-root="$PROJECT_DIR$" remote-root="/app" />
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
</PathMappingSettings>
|
|
||||||
<option name="_new_keywords" value="""" />
|
|
||||||
<option name="_new_additionalArguments" value="""" />
|
|
||||||
<option name="_new_target" value=""."" />
|
|
||||||
<option name="_new_targetType" value=""PATH"" />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
25
.idea/runConfigurations/pytest__users.xml
generated
25
.idea/runConfigurations/pytest__users.xml
generated
@ -1,25 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="pytest: users" type="tests" factoryName="py.test" singleton="true">
|
|
||||||
<module name="geeksbot_v2" />
|
|
||||||
<option name="INTERPRETER_OPTIONS" value="" />
|
|
||||||
<option name="PARENT_ENVS" value="true" />
|
|
||||||
<option name="SDK_HOME" value="" />
|
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
|
||||||
<option name="IS_MODULE_SDK" value="true" />
|
|
||||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
|
||||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
|
||||||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
|
||||||
<PathMappingSettings>
|
|
||||||
<option name="pathMappings">
|
|
||||||
<list>
|
|
||||||
<mapping local-root="$PROJECT_DIR$" remote-root="/app" />
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
</PathMappingSettings>
|
|
||||||
<option name="_new_keywords" value="""" />
|
|
||||||
<option name="_new_additionalArguments" value="""" />
|
|
||||||
<option name="_new_target" value=""./geeksbot_v2/users/"" />
|
|
||||||
<option name="_new_targetType" value=""PATH"" />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
33
.idea/runConfigurations/runserver.xml
generated
33
.idea/runConfigurations/runserver.xml
generated
@ -1,33 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="runserver" type="Python.DjangoServer" factoryName="Django server" singleton="true">
|
|
||||||
<module name="geeksbot_v2" />
|
|
||||||
<option name="INTERPRETER_OPTIONS" value="" />
|
|
||||||
<option name="PARENT_ENVS" value="true" />
|
|
||||||
<envs>
|
|
||||||
<env name="PYTHONUNBUFFERED" value="1" />
|
|
||||||
<env name="DJANGO_SETTINGS_MODULE" value="config.settings.local" />
|
|
||||||
</envs>
|
|
||||||
<option name="SDK_HOME" value="" />
|
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
|
||||||
<option name="IS_MODULE_SDK" value="true" />
|
|
||||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
|
||||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
|
||||||
<PathMappingSettings>
|
|
||||||
<option name="pathMappings">
|
|
||||||
<list>
|
|
||||||
<mapping local-root="$PROJECT_DIR$" remote-root="/app" />
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
</PathMappingSettings>
|
|
||||||
<option name="launchJavascriptDebuger" value="false" />
|
|
||||||
<option name="port" value="8000" />
|
|
||||||
<option name="host" value="0.0.0.0" />
|
|
||||||
<option name="additionalOptions" value="" />
|
|
||||||
<option name="browserUrl" value="" />
|
|
||||||
<option name="runTestServer" value="false" />
|
|
||||||
<option name="runNoReload" value="false" />
|
|
||||||
<option name="useCustomRunCommand" value="false" />
|
|
||||||
<option name="customRunCommand" value="" />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
33
.idea/runConfigurations/runserver_plus.xml
generated
33
.idea/runConfigurations/runserver_plus.xml
generated
@ -1,33 +0,0 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
|
||||||
<configuration default="false" name="runserver_plus" type="Python.DjangoServer" factoryName="Django server" singleton="true">
|
|
||||||
<module name="geeksbot_v2" />
|
|
||||||
<option name="INTERPRETER_OPTIONS" value="" />
|
|
||||||
<option name="PARENT_ENVS" value="true" />
|
|
||||||
<envs>
|
|
||||||
<env name="PYTHONUNBUFFERED" value="1" />
|
|
||||||
<env name="DJANGO_SETTINGS_MODULE" value="config.settings.local" />
|
|
||||||
</envs>
|
|
||||||
<option name="SDK_HOME" value="" />
|
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
|
||||||
<option name="IS_MODULE_SDK" value="true" />
|
|
||||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
|
||||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
|
||||||
<PathMappingSettings>
|
|
||||||
<option name="pathMappings">
|
|
||||||
<list>
|
|
||||||
<mapping local-root="$PROJECT_DIR$" remote-root="/app" />
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
</PathMappingSettings>
|
|
||||||
<option name="launchJavascriptDebuger" value="false" />
|
|
||||||
<option name="port" value="8000" />
|
|
||||||
<option name="host" value="0.0.0.0" />
|
|
||||||
<option name="additionalOptions" value="" />
|
|
||||||
<option name="browserUrl" value="" />
|
|
||||||
<option name="runTestServer" value="false" />
|
|
||||||
<option name="runNoReload" value="false" />
|
|
||||||
<option name="useCustomRunCommand" value="true" />
|
|
||||||
<option name="customRunCommand" value="runserver_plus" />
|
|
||||||
<method />
|
|
||||||
</configuration>
|
|
||||||
</component>
|
|
||||||
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
14
.idea/webResources.xml
generated
14
.idea/webResources.xml
generated
@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="WebResourcesPaths">
|
|
||||||
<contentEntries>
|
|
||||||
<entry url="file://$PROJECT_DIR$">
|
|
||||||
<entryData>
|
|
||||||
<resourceRoots>
|
|
||||||
<path value="file://$PROJECT_DIR$/geeksbot_v2/static" />
|
|
||||||
</resourceRoots>
|
|
||||||
</entryData>
|
|
||||||
</entry>
|
|
||||||
</contentEntries>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@ -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"]
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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}."
|
|
||||||
|
|
||||||
@ -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."
|
|
||||||
|
|
||||||
@ -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"]
|
|
||||||
@ -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 "$@"
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
|
|
||||||
BACKUP_DIR_PATH='/backups'
|
|
||||||
BACKUP_FILE_PREFIX='backup'
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
@ -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: ${@}"
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
}
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
|
|
||||||
### Create a database backup.
|
|
||||||
###
|
|
||||||
### Usage:
|
|
||||||
### $ docker-compose -f <environment>.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}'."
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
|
|
||||||
### View backups.
|
|
||||||
###
|
|
||||||
### Usage:
|
|
||||||
### $ docker-compose -f <environment>.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}"
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
|
|
||||||
### Restore database from a backup.
|
|
||||||
###
|
|
||||||
### Parameters:
|
|
||||||
### <1> filename of an existing backup.
|
|
||||||
###
|
|
||||||
### Usage:
|
|
||||||
### $ docker-compose -f <environment>.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."
|
|
||||||
@ -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
|
|
||||||
@ -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"
|
|
||||||
@ -36,8 +36,6 @@ services:
|
|||||||
- DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
|
- DATABASE_URL=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
|
||||||
- REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}/${REDIS_DB}
|
- REDIS_URL=redis://${REDIS_HOST}:${REDIS_PORT}/${REDIS_DB}
|
||||||
volumes:
|
volumes:
|
||||||
- ${PWD}/config:/code/config
|
|
||||||
- ${PWD}/geeksbot_web:/code/geeksbot_web
|
|
||||||
- ${PWD}/geeksbot_v2:/code/geeksbot_v2
|
- ${PWD}/geeksbot_v2:/code/geeksbot_v2
|
||||||
geeksbot:
|
geeksbot:
|
||||||
build:
|
build:
|
||||||
@ -49,6 +47,8 @@ services:
|
|||||||
- redis
|
- redis
|
||||||
- base
|
- base
|
||||||
- web
|
- web
|
||||||
|
links:
|
||||||
|
- web:geeksbot.app
|
||||||
volumes:
|
volumes:
|
||||||
- ${PWD}/geeksbot:/code/geeksbot
|
- ${PWD}/geeksbot:/code/geeksbot
|
||||||
- ~/.ssh/id_rsa:/root/.ssh/id_rsa
|
- ~/.ssh/id_rsa:/root/.ssh/id_rsa
|
||||||
|
|||||||
@ -53,8 +53,6 @@ logger.info(f'Process Libs Import Complete - Took {(datetime.utcnow() - start).t
|
|||||||
start = datetime.utcnow()
|
start = datetime.utcnow()
|
||||||
import re # noqa: E402
|
import re # noqa: E402
|
||||||
logger.info('re Imported')
|
logger.info('re Imported')
|
||||||
from typing import Dict # noqa: E402
|
|
||||||
logger.info('Typing Dict Imported')
|
|
||||||
import json # noqa: E402
|
import json # noqa: E402
|
||||||
logger.info('JSON Imported')
|
logger.info('JSON Imported')
|
||||||
import aiohttp # noqa: E402
|
import aiohttp # noqa: E402
|
||||||
@ -98,7 +96,7 @@ class Geeksbot(commands.Bot):
|
|||||||
|
|
||||||
async def unload_ext(self, mod):
|
async def unload_ext(self, mod):
|
||||||
self.unload_extension(f'geeksbot.{self.extension_dir}.{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):
|
def load_default_extensions(self):
|
||||||
for load_item in self.bot_config['load_list']:
|
for load_item in self.bot_config['load_list']:
|
||||||
|
|||||||
221
geeksbot/exts/exec.py
Normal file
221
geeksbot/exts/exec.py
Normal file
@ -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, '<repl session>', 'eval')
|
||||||
|
except SyntaxError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
executor = eval
|
||||||
|
if executor is exec:
|
||||||
|
try:
|
||||||
|
code = compile(cleaned, '<repl session>', '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))
|
||||||
@ -3,6 +3,69 @@ import asyncio
|
|||||||
import typing
|
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
|
# noinspection PyShadowingNames
|
||||||
class Paginator:
|
class Paginator:
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import environ
|
|||||||
ROOT_DIR = (
|
ROOT_DIR = (
|
||||||
environ.Path(__file__) - 3
|
environ.Path(__file__) - 3
|
||||||
) # (geeksbot_v2/config/settings/base.py - 3 = geeksbot_v2/)
|
) # (geeksbot_v2/config/settings/base.py - 3 = geeksbot_v2/)
|
||||||
APPS_DIR = ROOT_DIR.path("geeksbot_v2")
|
APPS_DIR = ROOT_DIR
|
||||||
|
|
||||||
env = environ.Env()
|
env = environ.Env()
|
||||||
|
|
||||||
@ -68,11 +68,17 @@ THIRD_PARTY_APPS = [
|
|||||||
"allauth",
|
"allauth",
|
||||||
"allauth.account",
|
"allauth.account",
|
||||||
"allauth.socialaccount",
|
"allauth.socialaccount",
|
||||||
|
"allauth.socialaccount.providers.discord",
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
|
"rest_framework.authtoken",
|
||||||
]
|
]
|
||||||
|
|
||||||
LOCAL_APPS = [
|
LOCAL_APPS = [
|
||||||
"geeksbot_v2.users.apps.UsersConfig",
|
"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
|
# Your stuff: custom apps go here
|
||||||
]
|
]
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
||||||
@ -243,16 +249,16 @@ LOGGING = {
|
|||||||
"root": {"level": "INFO", "handlers": ["console"]},
|
"root": {"level": "INFO", "handlers": ["console"]},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# django-allauth
|
# 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
|
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
||||||
ACCOUNT_AUTHENTICATION_METHOD = "username"
|
ACCOUNT_AUTHENTICATION_METHOD = "username"
|
||||||
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
# 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
|
# 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
|
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
||||||
ACCOUNT_ADAPTER = "geeksbot_v2.users.adapters.AccountAdapter"
|
ACCOUNT_ADAPTER = "geeksbot_v2.users.adapters.AccountAdapter"
|
||||||
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
||||||
@ -261,3 +267,14 @@ SOCIALACCOUNT_ADAPTER = "geeksbot_v2.users.adapters.SocialAccountAdapter"
|
|||||||
|
|
||||||
# Your stuff...
|
# 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,
|
||||||
|
}
|
||||||
@ -16,6 +16,8 @@ urlpatterns = [
|
|||||||
path("users/", include("geeksbot_v2.users.urls", namespace="users")),
|
path("users/", include("geeksbot_v2.users.urls", namespace="users")),
|
||||||
path("accounts/", include("allauth.urls")),
|
path("accounts/", include("allauth.urls")),
|
||||||
# Your stuff: custom urls includes go here
|
# 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)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
10
geeksbot_v2/dmessages/admin.py
Normal file
10
geeksbot_v2/dmessages/admin.py
Normal file
@ -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)
|
||||||
7
geeksbot_v2/dmessages/apps.py
Normal file
7
geeksbot_v2/dmessages/apps.py
Normal file
@ -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")
|
||||||
64
geeksbot_v2/dmessages/migrations/0001_initial.py
Normal file
64
geeksbot_v2/dmessages/migrations/0001_initial.py
Normal file
@ -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')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
0
geeksbot_v2/dmessages/migrations/__init__.py
Normal file
0
geeksbot_v2/dmessages/migrations/__init__.py
Normal file
58
geeksbot_v2/dmessages/models.py
Normal file
58
geeksbot_v2/dmessages/models.py
Normal file
@ -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}"
|
||||||
22
geeksbot_v2/dmessages/serializers.py
Normal file
22
geeksbot_v2/dmessages/serializers.py
Normal file
@ -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__"
|
||||||
3
geeksbot_v2/dmessages/tests.py
Normal file
3
geeksbot_v2/dmessages/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
3
geeksbot_v2/dmessages/views.py
Normal file
3
geeksbot_v2/dmessages/views.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
3
entrypoint → geeksbot_v2/entrypoint
Normal file → Executable file
3
entrypoint → geeksbot_v2/entrypoint
Normal file → Executable file
@ -4,9 +4,6 @@ set -o errexit
|
|||||||
set -o pipefail
|
set -o pipefail
|
||||||
set -o nounset
|
set -o nounset
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if [ -z "${POSTGRES_USER}" ]; then
|
if [ -z "${POSTGRES_USER}" ]; then
|
||||||
base_postgres_image_default_user='postgres'
|
base_postgres_image_default_user='postgres'
|
||||||
export POSTGRES_USER="${base_postgres_image_default_user}"
|
export POSTGRES_USER="${base_postgres_image_default_user}"
|
||||||
0
geeksbot_v2/guilds/__init__.py
Normal file
0
geeksbot_v2/guilds/__init__.py
Normal file
8
geeksbot_v2/guilds/admin.py
Normal file
8
geeksbot_v2/guilds/admin.py
Normal file
@ -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)
|
||||||
8
geeksbot_v2/guilds/api_urls.py
Normal file
8
geeksbot_v2/guilds/api_urls.py
Normal file
@ -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")
|
||||||
|
]
|
||||||
7
geeksbot_v2/guilds/apps.py
Normal file
7
geeksbot_v2/guilds/apps.py
Normal file
@ -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")
|
||||||
35
geeksbot_v2/guilds/migrations/0001_initial.py
Normal file
35
geeksbot_v2/guilds/migrations/0001_initial.py
Normal file
@ -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')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
28
geeksbot_v2/guilds/migrations/0002_auto_20190917_0508.py
Normal file
28
geeksbot_v2/guilds/migrations/0002_auto_20190917_0508.py
Normal file
@ -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),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
geeksbot_v2/guilds/migrations/__init__.py
Normal file
0
geeksbot_v2/guilds/migrations/__init__.py
Normal file
25
geeksbot_v2/guilds/models.py
Normal file
25
geeksbot_v2/guilds/models.py
Normal file
@ -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}"
|
||||||
16
geeksbot_v2/guilds/serializers.py
Normal file
16
geeksbot_v2/guilds/serializers.py
Normal file
@ -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"]
|
||||||
3
geeksbot_v2/guilds/tests.py
Normal file
3
geeksbot_v2/guilds/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
0
geeksbot_v2/guilds/urls.py
Normal file
0
geeksbot_v2/guilds/urls.py
Normal file
51
geeksbot_v2/guilds/views.py
Normal file
51
geeksbot_v2/guilds/views.py
Normal file
@ -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)
|
||||||
@ -3,7 +3,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "geeksbot_v2.config.settings.local")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from django.core.management import execute_from_command_line
|
from django.core.management import execute_from_command_line
|
||||||
0
geeksbot_v2/patreon/__init__.py
Normal file
0
geeksbot_v2/patreon/__init__.py
Normal file
9
geeksbot_v2/patreon/admin.py
Normal file
9
geeksbot_v2/patreon/admin.py
Normal file
@ -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)
|
||||||
7
geeksbot_v2/patreon/apps.py
Normal file
7
geeksbot_v2/patreon/apps.py
Normal file
@ -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")
|
||||||
37
geeksbot_v2/patreon/migrations/0001_initial.py
Normal file
37
geeksbot_v2/patreon/migrations/0001_initial.py
Normal file
@ -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')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
0
geeksbot_v2/patreon/migrations/__init__.py
Normal file
0
geeksbot_v2/patreon/migrations/__init__.py
Normal file
27
geeksbot_v2/patreon/models.py
Normal file
27
geeksbot_v2/patreon/models.py
Normal file
@ -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}"
|
||||||
16
geeksbot_v2/patreon/serializers.py
Normal file
16
geeksbot_v2/patreon/serializers.py
Normal file
@ -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__"
|
||||||
3
geeksbot_v2/patreon/tests.py
Normal file
3
geeksbot_v2/patreon/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
3
geeksbot_v2/patreon/views.py
Normal file
3
geeksbot_v2/patreon/views.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
0
geeksbot_v2/rcon/__init__.py
Normal file
0
geeksbot_v2/rcon/__init__.py
Normal file
6
geeksbot_v2/rcon/admin.py
Normal file
6
geeksbot_v2/rcon/admin.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import RconServer
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
admin.site.register(RconServer)
|
||||||
7
geeksbot_v2/rcon/apps.py
Normal file
7
geeksbot_v2/rcon/apps.py
Normal file
@ -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")
|
||||||
37
geeksbot_v2/rcon/migrations/0001_initial.py
Normal file
37
geeksbot_v2/rcon/migrations/0001_initial.py
Normal file
@ -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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
0
geeksbot_v2/rcon/migrations/__init__.py
Normal file
0
geeksbot_v2/rcon/migrations/__init__.py
Normal file
29
geeksbot_v2/rcon/models.py
Normal file
29
geeksbot_v2/rcon/models.py
Normal file
@ -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}"
|
||||||
9
geeksbot_v2/rcon/serializers.py
Normal file
9
geeksbot_v2/rcon/serializers.py
Normal file
@ -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__"
|
||||||
3
geeksbot_v2/rcon/tests.py
Normal file
3
geeksbot_v2/rcon/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
3
geeksbot_v2/rcon/views.py
Normal file
3
geeksbot_v2/rcon/views.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
@ -59,10 +59,6 @@
|
|||||||
<a class="nav-link" href="{% url 'account_logout' %}">{% trans "Sign Out" %}</a>
|
<a class="nav-link" href="{% url 'account_logout' %}">{% trans "Sign Out" %}</a>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="nav-item">
|
|
||||||
{# URL provided by django-allauth/account/urls.py #}
|
|
||||||
<a id="sign-up-link" class="nav-link" href="{% url 'account_signup' %}">{% trans "Sign Up" %}</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
{# URL provided by django-allauth/account/urls.py #}
|
{# URL provided by django-allauth/account/urls.py #}
|
||||||
<a id="log-in-link" class="nav-link" href="{% url 'account_login' %}">{% trans "Sign In" %}</a>
|
<a id="log-in-link" class="nav-link" href="{% url 'account_login' %}">{% trans "Sign In" %}</a>
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
{% if object.name %}
|
{% if object.name %}
|
||||||
<p>{{ object.name }}</p>
|
<p>{{ object.name }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{{ user.id }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -2,15 +2,34 @@ from typing import Any
|
|||||||
|
|
||||||
from allauth.account.adapter import DefaultAccountAdapter
|
from allauth.account.adapter import DefaultAccountAdapter
|
||||||
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
|
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.conf import settings
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
|
|
||||||
|
|
||||||
class AccountAdapter(DefaultAccountAdapter):
|
class AccountAdapter(DefaultAccountAdapter):
|
||||||
def is_open_for_signup(self, request: HttpRequest):
|
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):
|
class SocialAccountAdapter(DefaultSocialAccountAdapter):
|
||||||
def is_open_for_signup(self, request: HttpRequest, sociallogin: Any):
|
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
|
||||||
|
|||||||
@ -1,17 +1,13 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth import admin as auth_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
|
from .forms import UserChangeForm
|
||||||
|
from .models import User
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(User)
|
|
||||||
class UserAdmin(auth_admin.UserAdmin):
|
class UserAdmin(auth_admin.UserAdmin):
|
||||||
|
model = User
|
||||||
form = UserChangeForm
|
form = UserChangeForm
|
||||||
add_form = UserCreationForm
|
|
||||||
fieldsets = (("User", {"fields": ("name",)}),) + auth_admin.UserAdmin.fieldsets
|
|
||||||
list_display = ["username", "name", "is_superuser"]
|
admin.site.register(User, UserAdmin)
|
||||||
search_fields = ["name"]
|
|
||||||
|
|||||||
8
geeksbot_v2/users/api_urls.py
Normal file
8
geeksbot_v2/users/api_urls.py
Normal file
@ -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")
|
||||||
|
]
|
||||||
@ -1,30 +1,8 @@
|
|||||||
from django.contrib.auth import get_user_model, forms
|
from django.contrib.auth import forms
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
User = get_user_model()
|
from .models import User
|
||||||
|
|
||||||
|
|
||||||
class UserChangeForm(forms.UserChangeForm):
|
class UserChangeForm(forms.UserChangeForm):
|
||||||
class Meta(forms.UserChangeForm.Meta):
|
class Meta(forms.UserChangeForm.Meta):
|
||||||
model = User
|
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"])
|
|
||||||
|
|||||||
@ -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())],
|
|
||||||
)
|
|
||||||
]
|
|
||||||
@ -2,6 +2,20 @@ from django.contrib.auth.models import AbstractUser
|
|||||||
from django.db.models import CharField
|
from django.db.models import CharField
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import ugettext_lazy as _
|
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):
|
class User(AbstractUser):
|
||||||
@ -9,6 +23,28 @@ class User(AbstractUser):
|
|||||||
# First Name and Last Name do not cover name patterns
|
# First Name and Last Name do not cover name patterns
|
||||||
# around the globe.
|
# around the globe.
|
||||||
name = CharField(_("Name of User"), blank=True, max_length=255)
|
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):
|
def get_absolute_url(self):
|
||||||
return reverse("users:detail", kwargs={"username": self.username})
|
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}"
|
||||||
|
|||||||
16
geeksbot_v2/users/serializers.py
Normal file
16
geeksbot_v2/users/serializers.py
Normal file
@ -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__"
|
||||||
@ -4,6 +4,14 @@ from django.urls import reverse
|
|||||||
from django.views.generic import DetailView, RedirectView, UpdateView
|
from django.views.generic import DetailView, RedirectView, UpdateView
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.utils.translation import ugettext_lazy as _
|
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()
|
User = get_user_model()
|
||||||
|
|
||||||
@ -14,6 +22,11 @@ class UserDetailView(LoginRequiredMixin, DetailView):
|
|||||||
slug_field = "username"
|
slug_field = "username"
|
||||||
slug_url_kwarg = "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()
|
user_detail_view = UserDetailView.as_view()
|
||||||
|
|
||||||
@ -48,3 +61,49 @@ class UserRedirectView(LoginRequiredMixin, RedirectView):
|
|||||||
|
|
||||||
|
|
||||||
user_redirect_view = UserRedirectView.as_view()
|
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)
|
||||||
|
|||||||
33
geeksbot_v2/utils/api_utils.py
Normal file
33
geeksbot_v2/utils/api_utils.py
Normal file
@ -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)
|
||||||
@ -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 ./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_cert_chain.crt /etc/ssl/geeksbot_app_cert_chain.crt
|
||||||
COPY ./ssl_certs/geeksbot_app/geeksbot.app.key /etc/ssl/geeksbot.app.key
|
COPY ./ssl_certs/geeksbot_app/geeksbot.app.key /etc/ssl/geeksbot.app.key
|
||||||
|
COPY ./.env /code/
|
||||||
|
|
||||||
RUN rm -rf /tmp/*
|
RUN rm -rf /tmp/*
|
||||||
|
|
||||||
RUN mkdir -p /tmp/logs/nginx
|
RUN mkdir -p /tmp/logs/nginx
|
||||||
RUN mkdir -p /tmp/logs/geeksbot
|
RUN mkdir -p /tmp/logs/geeksbot
|
||||||
|
|
||||||
COPY manage.py .
|
WORKDIR /code/geeksbot_v2
|
||||||
copy entrypoint .
|
|
||||||
RUN sed -i 's/\r$//g' /code/entrypoint
|
# RUN sed -i 's/\r$//g' ./entrypoint
|
||||||
RUN chmod +x /code/entrypoint
|
# RUN chmod +x ./entrypoint
|
||||||
|
|
||||||
EXPOSE 80 8000 443
|
EXPOSE 80 8000 443
|
||||||
|
|
||||||
ENTRYPOINT [ "/code/entrypoint" ]
|
ENTRYPOINT [ "./entrypoint" ]
|
||||||
|
|||||||
@ -13,11 +13,11 @@ server {
|
|||||||
error_log /tmp/logs/geeksbot/error.log;
|
error_log /tmp/logs/geeksbot/error.log;
|
||||||
|
|
||||||
location /static/ {
|
location /static/ {
|
||||||
alias /code/staticfiles/;
|
alias /code/geeksbot_v2/staticfiles/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /error/ {
|
location /error/ {
|
||||||
alias /code/staticfiles/errors/;
|
alias /code/geeksbot_v2/staticfiles/errors/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[program:geeksbot]
|
[program:geeksbot]
|
||||||
command=/usr/local/bin/gunicorn config.wsgi:application -c /etc/gunicorn.conf
|
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
|
stdout_logfile=/tmp/logs/geeksbot/gunicorn.log
|
||||||
autostart=true
|
autostart=true
|
||||||
autorestart=true
|
autorestart=true
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user