Compare commits
160 Commits
fda5655533
...
0.9
| Author | SHA1 | Date | |
|---|---|---|---|
| 0da5d9305d | |||
| 9fc09be7de | |||
| 3f8517ec7d | |||
| e85d91d1f4 | |||
| bb139a2b95 | |||
| c06dd6c578 | |||
| 3d4034d6b1 | |||
| f825bab894 | |||
| c9d1d7416b | |||
| e25c7660b0 | |||
| 843af814fd | |||
| cb19b8b21c | |||
| 95456651a6 | |||
| 57aebb8c9e | |||
| e486b8a83d | |||
| 2f6de65e5c | |||
| 7092167001 | |||
| efdb6d50c3 | |||
| 04448e34c2 | |||
| da75b4cd50 | |||
| 4e9a3fe139 | |||
| e469db9ba6 | |||
| 64569c3505 | |||
| 15f69f533a | |||
| 5d28bf31dd | |||
| 3f3dba8759 | |||
| 8fde46c157 | |||
| d87b3e5b02 | |||
| 6b87fd6fc1 | |||
| 68940e87f9 | |||
| f0115a70f9 | |||
| a801eb1eeb | |||
| 83c94acbac | |||
| 5c2c514825 | |||
| 04689797f7 | |||
| 468235662b | |||
| f5e6076123 | |||
| 0f1dc51949 | |||
| 583f1c9d32 | |||
| af4ffb8559 | |||
| a9130bbe61 | |||
| 7015a46f94 | |||
| b96d5e4487 | |||
| 5bb0667060 | |||
| 580289d3a1 | |||
| 5c3cce1556 | |||
| f2361b94ba | |||
| f71b461e29 | |||
| 326bd1bd72 | |||
| 8f76832f69 | |||
| 309b03956f | |||
| 176ab4a194 | |||
| e43718894b | |||
| c31f1bb59d | |||
| 2014c326b1 | |||
| 112a99ffcb | |||
| 7aa96119a9 | |||
| 53ac07a9ee | |||
| 3288824383 | |||
| 9194b48eb8 | |||
| 301a83b295 | |||
| 2521c319a0 | |||
| 522ea2d976 | |||
| 91469735d2 | |||
| 7a8005c263 | |||
| fc05fda666 | |||
| fd34dd20ca | |||
| a917822fb8 | |||
| 30142f83df | |||
| 1cbfab0c2f | |||
| 46b56369c2 | |||
| 2db476ce09 | |||
| c5c1f35c08 | |||
| 3e5285225d | |||
| e58bec3da0 | |||
| cde3cba527 | |||
| c0346efcc7 | |||
| acececf899 | |||
| 8509b0567b | |||
| d619283d09 | |||
| e4238d9fdb | |||
| d7c1305dae | |||
| f6abdb5c63 | |||
| 51cea567ca | |||
| 85b769f7dd | |||
| 996f7dca16 | |||
| eb2946510a | |||
| 99a76c540f | |||
| 56177b2811 | |||
| 57fa221d47 | |||
| f65265b4a5 | |||
| 0047cfbcd1 | |||
| 33f6e0386b | |||
| 164e8373a4 | |||
| 5834aec885 | |||
| ca32ee0de4 | |||
| ee5b2d9fd9 | |||
| d4ae0fe2d8 | |||
| 39cbff2234 | |||
| 6273866324 | |||
| a78f3c0786 | |||
| 97fde3388b | |||
| 6e5229c8ba | |||
| b9df790d1f | |||
| 71213b87a0 | |||
| 5746600340 | |||
| 905a056c87 | |||
| 41cdd5ec7f | |||
| 88c3bc1b5b | |||
| 0f9f9d1b73 | |||
| 3dc897518e | |||
| 79fa32d1dd | |||
| 6ae1ee3365 | |||
| add00d488c | |||
| 0a471792e1 | |||
| e948a9e55f | |||
| 7f97d90f04 | |||
| b580bb2db3 | |||
| 9dd4ac5863 | |||
| 27d4922ce8 | |||
| c1d4fe1c9a | |||
| 02e7710676 | |||
| cd16d34fe5 | |||
| 4d38c8715e | |||
| 8edd96b671 | |||
| ea841e4d54 | |||
| 5c6c3f436e | |||
| 4dbaa27cba | |||
| c95a1c456b | |||
| 66ac834ab0 | |||
| 81ee935150 | |||
| 765c07316a | |||
| 694c8df364 | |||
| 220d892fa0 | |||
| 75127394c7 | |||
| 11745f2eb8 | |||
| fdef0c5f66 | |||
| 5a9b6be79d | |||
| 38e24a690a | |||
| 7d08a57c85 | |||
| 17e0781b14 | |||
| b06a282160 | |||
| e8d79cca19 | |||
| 047ad6ef10 | |||
| 06772ed48c | |||
| b9233136a7 | |||
| 85bfd0f3ae | |||
| 2800da1859 | |||
| 3a768146c1 | |||
| ea118a37c5 | |||
| aeefd17b10 | |||
| c0a97a1714 | |||
| b55a919944 | |||
| 3e7f7ff636 | |||
| e1390a8adc | |||
| 1c74706736 | |||
| 58c23a6380 | |||
| 779e81346b | |||
| 08a11c240d | |||
| c452a920b1 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -27,3 +27,6 @@ logs/
|
|||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
coverage/
|
coverage/
|
||||||
|
|
||||||
|
# Python cache
|
||||||
|
__pycache__/
|
||||||
39
Dockerfile
39
Dockerfile
@@ -1,33 +1,38 @@
|
|||||||
FROM python:3.11-slim
|
FROM python:3.11-slim
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Install system dependencies
|
# Install system dependencies
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
gcc \
|
build-essential \
|
||||||
|
libpq-dev \
|
||||||
postgresql-client \
|
postgresql-client \
|
||||||
|
curl \
|
||||||
netcat-traditional \
|
netcat-traditional \
|
||||||
|
dos2unix \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Create a non-root user
|
||||||
|
RUN useradd -m -u 1000 celery
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy requirements first to leverage Docker cache
|
# Copy requirements first to leverage Docker cache
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
# Copy the rest of the application
|
# Copy application code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Create migrations directory if it doesn't exist
|
# Convert line endings and set permissions
|
||||||
RUN mkdir -p migrations/versions
|
RUN dos2unix /app/entrypoint.sh && \
|
||||||
|
chmod +x /app/entrypoint.sh && \
|
||||||
|
mkdir -p /app/uploads/rooms /app/uploads/profile_pics /app/static/uploads && \
|
||||||
|
chown -R celery:celery /app && \
|
||||||
|
chmod -R 755 /app/uploads
|
||||||
|
|
||||||
# Make entrypoint script executable
|
# Switch to non-root user
|
||||||
RUN chmod +x entrypoint.sh
|
USER celery
|
||||||
|
|
||||||
# Set environment variables
|
# Set entrypoint
|
||||||
ENV FLASK_APP=app.py
|
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||||
ENV FLASK_ENV=production
|
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
|
||||||
|
|
||||||
# Expose the port the app runs on
|
|
||||||
EXPOSE 5000
|
|
||||||
|
|
||||||
# Use the entrypoint script
|
|
||||||
ENTRYPOINT ["./entrypoint.sh"]
|
|
||||||
274
NGINX_swagger.json
Normal file
274
NGINX_swagger.json
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"info": {
|
||||||
|
"title": "Nginx Proxy Manager API",
|
||||||
|
"version": "2.x.x"
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "http://127.0.0.1:81/api"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"components": {
|
||||||
|
"securitySchemes": {
|
||||||
|
"bearerAuth": {
|
||||||
|
"type": "http",
|
||||||
|
"scheme": "bearer",
|
||||||
|
"bearerFormat": "JWT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"/": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/get.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/audit-log": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/audit-log/get.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/access-lists": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/nginx/access-lists/get.json"
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/nginx/access-lists/post.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/access-lists/{listID}": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/nginx/access-lists/listID/get.json"
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"$ref": "./paths/nginx/access-lists/listID/put.json"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"$ref": "./paths/nginx/access-lists/listID/delete.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/certificates": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/nginx/certificates/get.json"
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/nginx/certificates/post.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/certificates/validate": {
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/nginx/certificates/validate/post.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/certificates/test-http": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/nginx/certificates/test-http/get.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/certificates/{certID}": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/nginx/certificates/certID/get.json"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"$ref": "./paths/nginx/certificates/certID/delete.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/certificates/{certID}/download": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/nginx/certificates/certID/download/get.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/certificates/{certID}/renew": {
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/nginx/certificates/certID/renew/post.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/certificates/{certID}/upload": {
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/nginx/certificates/certID/upload/post.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/proxy-hosts": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/nginx/proxy-hosts/get.json"
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/nginx/proxy-hosts/post.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/proxy-hosts/{hostID}": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/nginx/proxy-hosts/hostID/get.json"
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"$ref": "./paths/nginx/proxy-hosts/hostID/put.json"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"$ref": "./paths/nginx/proxy-hosts/hostID/delete.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/proxy-hosts/{hostID}/enable": {
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/nginx/proxy-hosts/hostID/enable/post.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/proxy-hosts/{hostID}/disable": {
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/nginx/proxy-hosts/hostID/disable/post.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/redirection-hosts": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/nginx/redirection-hosts/get.json"
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/nginx/redirection-hosts/post.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/redirection-hosts/{hostID}": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/nginx/redirection-hosts/hostID/get.json"
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"$ref": "./paths/nginx/redirection-hosts/hostID/put.json"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"$ref": "./paths/nginx/redirection-hosts/hostID/delete.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/redirection-hosts/{hostID}/enable": {
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/nginx/redirection-hosts/hostID/enable/post.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/redirection-hosts/{hostID}/disable": {
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/nginx/redirection-hosts/hostID/disable/post.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/dead-hosts": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/nginx/dead-hosts/get.json"
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/nginx/dead-hosts/post.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/dead-hosts/{hostID}": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/nginx/dead-hosts/hostID/get.json"
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"$ref": "./paths/nginx/dead-hosts/hostID/put.json"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"$ref": "./paths/nginx/dead-hosts/hostID/delete.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/dead-hosts/{hostID}/enable": {
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/nginx/dead-hosts/hostID/enable/post.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/dead-hosts/{hostID}/disable": {
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/nginx/dead-hosts/hostID/disable/post.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/streams": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/nginx/streams/get.json"
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/nginx/streams/post.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/streams/{streamID}": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/nginx/streams/streamID/get.json"
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"$ref": "./paths/nginx/streams/streamID/put.json"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"$ref": "./paths/nginx/streams/streamID/delete.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/streams/{streamID}/enable": {
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/nginx/streams/streamID/enable/post.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/nginx/streams/{streamID}/disable": {
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/nginx/streams/streamID/disable/post.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/reports/hosts": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/reports/hosts/get.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/schema": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/schema/get.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/settings": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/settings/get.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/settings/{settingID}": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/settings/settingID/get.json"
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"$ref": "./paths/settings/settingID/put.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/tokens": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/tokens/get.json"
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/tokens/post.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/users": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/users/get.json"
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/users/post.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/users/{userID}": {
|
||||||
|
"get": {
|
||||||
|
"$ref": "./paths/users/userID/get.json"
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"$ref": "./paths/users/userID/put.json"
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"$ref": "./paths/users/userID/delete.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/users/{userID}/auth": {
|
||||||
|
"put": {
|
||||||
|
"$ref": "./paths/users/userID/auth/put.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/users/{userID}/permissions": {
|
||||||
|
"put": {
|
||||||
|
"$ref": "./paths/users/userID/permissions/put.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/users/{userID}/login": {
|
||||||
|
"post": {
|
||||||
|
"$ref": "./paths/users/userID/login/post.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
__pycache__/app.cpython-311.pyc
Normal file
BIN
__pycache__/app.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
__pycache__/celery_worker.cpython-311.pyc
Normal file
BIN
__pycache__/celery_worker.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/celery_worker.cpython-313.pyc
Normal file
BIN
__pycache__/celery_worker.cpython-313.pyc
Normal file
Binary file not shown.
BIN
__pycache__/create_default_templates.cpython-313.pyc
Normal file
BIN
__pycache__/create_default_templates.cpython-313.pyc
Normal file
Binary file not shown.
BIN
__pycache__/extensions.cpython-311.pyc
Normal file
BIN
__pycache__/extensions.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
__pycache__/forms.cpython-311.pyc
Normal file
BIN
__pycache__/forms.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
__pycache__/models.cpython-311.pyc
Normal file
BIN
__pycache__/models.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
__pycache__/tasks.cpython-311.pyc
Normal file
BIN
__pycache__/tasks.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
148
app.py
148
app.py
@@ -1,20 +1,29 @@
|
|||||||
from flask import Flask, send_from_directory
|
import random
|
||||||
|
from flask import Flask, send_from_directory, jsonify
|
||||||
from flask_migrate import Migrate
|
from flask_migrate import Migrate
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import os
|
import os
|
||||||
from models import User
|
from models import User, SiteSettings
|
||||||
from flask_wtf.csrf import generate_csrf
|
from flask_wtf.csrf import generate_csrf
|
||||||
from routes.room_files import room_files_bp
|
from routes.room_files import room_files_bp
|
||||||
from routes.user import user_bp
|
|
||||||
from routes.room_members import room_members_bp
|
from routes.room_members import room_members_bp
|
||||||
from routes.trash import trash_bp
|
from routes.trash import trash_bp
|
||||||
|
from routes.admin_api import admin_api
|
||||||
|
from routes.launch_api import launch_api
|
||||||
from tasks import cleanup_trash
|
from tasks import cleanup_trash
|
||||||
import click
|
import click
|
||||||
from utils import timeago
|
from utils import timeago
|
||||||
from extensions import db, login_manager, csrf
|
from extensions import db, login_manager, csrf
|
||||||
|
from utils.email_templates import create_default_templates
|
||||||
|
from datetime import datetime
|
||||||
|
from sqlalchemy import text
|
||||||
|
from utils.asset_utils import get_asset_version
|
||||||
|
|
||||||
# Load environment variables
|
# Load environment variables
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
print("Environment variables after loading .env:")
|
||||||
|
print(f"MASTER: {os.getenv('MASTER')}")
|
||||||
|
print(f"ISMASTER: {os.getenv('ISMASTER')}")
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
@@ -24,7 +33,9 @@ def create_app():
|
|||||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||||
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'your-secure-secret-key-here')
|
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'your-secure-secret-key-here')
|
||||||
app.config['UPLOAD_FOLDER'] = os.path.join(app.root_path, 'static', 'uploads')
|
app.config['UPLOAD_FOLDER'] = os.path.join(app.root_path, 'static', 'uploads')
|
||||||
app.config['CSS_VERSION'] = os.getenv('CSS_VERSION', '1.0.0') # Add CSS version for cache busting
|
app.config['CSS_VERSION'] = os.getenv('CSS_VERSION', '1.0.3') # Add CSS version for cache busting
|
||||||
|
app.config['SERVER_NAME'] = os.getenv('SERVER_NAME', '127.0.0.1:5000')
|
||||||
|
app.config['PREFERRED_URL_SCHEME'] = os.getenv('PREFERRED_URL_SCHEME', 'http')
|
||||||
|
|
||||||
# Initialize extensions
|
# Initialize extensions
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
@@ -39,20 +50,61 @@ def create_app():
|
|||||||
|
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
def inject_config():
|
def inject_config():
|
||||||
return dict(config=app.config)
|
site_settings = SiteSettings.query.first()
|
||||||
|
if not site_settings:
|
||||||
|
site_settings = SiteSettings()
|
||||||
|
db.session.add(site_settings)
|
||||||
|
db.session.commit()
|
||||||
|
return dict(config=app.config, site_settings=site_settings)
|
||||||
|
|
||||||
|
@app.context_processor
|
||||||
|
def inject_unread_notifications():
|
||||||
|
from flask_login import current_user
|
||||||
|
from utils import get_unread_count
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
unread_count = get_unread_count(current_user.id)
|
||||||
|
return {'unread_notifications': unread_count}
|
||||||
|
return {'unread_notifications': 0}
|
||||||
|
|
||||||
|
@app.template_filter('asset_version')
|
||||||
|
def asset_version_filter(filename):
|
||||||
|
"""Template filter to get version hash for static assets"""
|
||||||
|
return get_asset_version(filename) or ''
|
||||||
|
|
||||||
# User loader for Flask-Login
|
# User loader for Flask-Login
|
||||||
@login_manager.user_loader
|
@login_manager.user_loader
|
||||||
def load_user(user_id):
|
def load_user(user_id):
|
||||||
return User.query.get(int(user_id))
|
return User.query.get(int(user_id))
|
||||||
|
|
||||||
|
# Health check endpoint
|
||||||
|
@app.route('/health')
|
||||||
|
def health_check():
|
||||||
|
try:
|
||||||
|
# Check database connection with a timeout
|
||||||
|
db.session.execute(text('SELECT 1'))
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'status': 'healthy',
|
||||||
|
'database': 'connected',
|
||||||
|
'timestamp': datetime.utcnow().isoformat()
|
||||||
|
}), 200
|
||||||
|
except Exception as e:
|
||||||
|
app.logger.error(f"Health check failed: {str(e)}")
|
||||||
|
return jsonify({
|
||||||
|
'status': 'unhealthy',
|
||||||
|
'error': str(e),
|
||||||
|
'timestamp': datetime.utcnow().isoformat()
|
||||||
|
}), 500
|
||||||
|
|
||||||
# Initialize routes
|
# Initialize routes
|
||||||
from routes import init_app
|
from routes import init_app
|
||||||
init_app(app)
|
init_app(app)
|
||||||
app.register_blueprint(room_files_bp, url_prefix='/api/rooms')
|
app.register_blueprint(room_files_bp, url_prefix='/api/rooms')
|
||||||
app.register_blueprint(room_members_bp, url_prefix='/api/rooms')
|
app.register_blueprint(room_members_bp, url_prefix='/api/rooms')
|
||||||
app.register_blueprint(trash_bp, url_prefix='/api/rooms')
|
app.register_blueprint(trash_bp, url_prefix='/api/trash')
|
||||||
app.register_blueprint(user_bp)
|
app.register_blueprint(admin_api, url_prefix='/api/admin')
|
||||||
|
app.register_blueprint(launch_api, url_prefix='/api/admin')
|
||||||
|
|
||||||
@app.cli.command("cleanup-trash")
|
@app.cli.command("cleanup-trash")
|
||||||
def cleanup_trash_command():
|
def cleanup_trash_command():
|
||||||
@@ -60,6 +112,13 @@ def create_app():
|
|||||||
cleanup_trash()
|
cleanup_trash()
|
||||||
click.echo("Trash cleanup completed.")
|
click.echo("Trash cleanup completed.")
|
||||||
|
|
||||||
|
@app.cli.command("cleanup-tokens")
|
||||||
|
def cleanup_tokens_command():
|
||||||
|
"""Clean up expired password reset and setup tokens."""
|
||||||
|
from tasks import cleanup_expired_tokens
|
||||||
|
cleanup_expired_tokens()
|
||||||
|
click.echo("Token cleanup completed.")
|
||||||
|
|
||||||
@app.cli.command("create-admin")
|
@app.cli.command("create-admin")
|
||||||
def create_admin():
|
def create_admin():
|
||||||
"""Create the default administrator user."""
|
"""Create the default administrator user."""
|
||||||
@@ -71,26 +130,91 @@ def create_app():
|
|||||||
admin = User(
|
admin = User(
|
||||||
username='administrator',
|
username='administrator',
|
||||||
email='administrator@docupulse.com',
|
email='administrator@docupulse.com',
|
||||||
last_name='None',
|
last_name='Administrator',
|
||||||
company='docupulse',
|
company='DocuPulse',
|
||||||
|
position='System Administrator',
|
||||||
is_admin=True,
|
is_admin=True,
|
||||||
is_active=True
|
is_active=True,
|
||||||
|
preferred_view='grid'
|
||||||
)
|
)
|
||||||
admin.set_password('q]H488h[8?.A')
|
admin.set_password('changeme')
|
||||||
db.session.add(admin)
|
db.session.add(admin)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
click.echo("Default administrator user created successfully.")
|
click.echo("Default administrator user created successfully.")
|
||||||
|
click.echo("Admin credentials:")
|
||||||
|
click.echo("Email: administrator@docupulse.com")
|
||||||
|
click.echo("Password: changeme")
|
||||||
|
|
||||||
# Register custom filters
|
# Register custom filters
|
||||||
app.jinja_env.filters['timeago'] = timeago
|
app.jinja_env.filters['timeago'] = timeago
|
||||||
|
|
||||||
|
# Create default email templates if they don't exist
|
||||||
|
with app.app_context():
|
||||||
|
try:
|
||||||
|
# Ensure database tables exist
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
# Create admin user first
|
||||||
|
admin = User.query.filter_by(email='administrator@docupulse.com').first()
|
||||||
|
if not admin:
|
||||||
|
admin = User(
|
||||||
|
username='administrator',
|
||||||
|
email='administrator@docupulse.com',
|
||||||
|
last_name='Administrator',
|
||||||
|
company='DocuPulse',
|
||||||
|
position='System Administrator',
|
||||||
|
is_admin=True,
|
||||||
|
is_active=True,
|
||||||
|
preferred_view='grid'
|
||||||
|
)
|
||||||
|
admin.set_password('changeme')
|
||||||
|
db.session.add(admin)
|
||||||
|
db.session.commit()
|
||||||
|
print("Default administrator user created successfully.")
|
||||||
|
print("Admin credentials:")
|
||||||
|
print("Email: administrator@docupulse.com")
|
||||||
|
print("Password: changeme")
|
||||||
|
|
||||||
|
# Then create default templates
|
||||||
|
create_default_templates()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Could not create default templates: {e}")
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
app = create_app()
|
app = create_app()
|
||||||
|
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def page_not_found(e):
|
||||||
|
from flask import render_template
|
||||||
|
return render_template('common/404.html'), 404
|
||||||
|
|
||||||
|
@app.errorhandler(403)
|
||||||
|
def forbidden(e):
|
||||||
|
from flask import render_template
|
||||||
|
return render_template('common/403.html'), 403
|
||||||
|
|
||||||
|
@app.errorhandler(401)
|
||||||
|
def unauthorized(e):
|
||||||
|
from flask import render_template
|
||||||
|
return render_template('common/401.html'), 401
|
||||||
|
|
||||||
|
@app.errorhandler(400)
|
||||||
|
def bad_request(e):
|
||||||
|
from flask import render_template
|
||||||
|
return render_template('common/400.html'), 400
|
||||||
|
|
||||||
|
@app.errorhandler(500)
|
||||||
|
def internal_server_error(e):
|
||||||
|
from flask import render_template
|
||||||
|
import traceback
|
||||||
|
error_details = f"{str(e)}\n\n{traceback.format_exc()}"
|
||||||
|
app.logger.error(f"500 error: {error_details}")
|
||||||
|
return render_template('common/500.html', error=error_details), 500
|
||||||
|
|
||||||
@app.route('/uploads/profile_pics/<filename>')
|
@app.route('/uploads/profile_pics/<filename>')
|
||||||
def profile_pic(filename):
|
def profile_pic(filename):
|
||||||
return send_from_directory(os.path.join(os.getcwd(), 'uploads', 'profile_pics'), filename)
|
return send_from_directory('/app/uploads/profile_pics', filename)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(debug=True)
|
app.run(debug=True)
|
||||||
@@ -1,40 +1,64 @@
|
|||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
|
networks:
|
||||||
|
docupulse_network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
build: .
|
build:
|
||||||
|
# context: .
|
||||||
|
# dockerfile: Dockerfile
|
||||||
|
context: https://git.kobeamerijckx.com/Kobe/docupulse.git
|
||||||
|
dockerfile: Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- "10335:5000"
|
- "${PORT:-10335}:5000"
|
||||||
environment:
|
environment:
|
||||||
- FLASK_APP=app.py
|
- FLASK_APP=app.py
|
||||||
- FLASK_ENV=production
|
- FLASK_ENV=production
|
||||||
- DATABASE_URL=postgresql://postgres:postgres@db:5432/docupulse
|
- DATABASE_URL=postgresql://docupulse_${PORT:-10335}:docupulse_${PORT:-10335}@db:5432/docupulse_${PORT:-10335}
|
||||||
- POSTGRES_USER=postgres
|
- POSTGRES_USER=docupulse_${PORT:-10335}
|
||||||
- POSTGRES_PASSWORD=postgres
|
- POSTGRES_PASSWORD=docupulse_${PORT:-10335}
|
||||||
- POSTGRES_DB=docupulse
|
- POSTGRES_DB=docupulse_${PORT:-10335}
|
||||||
|
- MASTER=${ISMASTER:-false}
|
||||||
volumes:
|
volumes:
|
||||||
- uploads:/app/uploads
|
- docupulse_uploads:/app/uploads
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
|
||||||
|
interval: 60s
|
||||||
|
timeout: 30s
|
||||||
|
retries: 3
|
||||||
|
start_period: 120s
|
||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
cpus: '1'
|
cpus: '1'
|
||||||
memory: 1G
|
memory: 1G
|
||||||
|
networks:
|
||||||
|
- docupulse_network
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: postgres:13
|
image: postgres:13
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=postgres
|
- POSTGRES_USER=docupulse_${PORT:-10335}
|
||||||
- POSTGRES_PASSWORD=postgres
|
- POSTGRES_PASSWORD=docupulse_${PORT:-10335}
|
||||||
- POSTGRES_DB=docupulse
|
- POSTGRES_DB=docupulse_${PORT:-10335}
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- docupulse_postgres_data:/var/lib/postgresql/data
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U docupulse_${PORT:-10335}"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
networks:
|
||||||
|
- docupulse_network
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
docupulse_postgres_data:
|
||||||
name: ${COMPOSE_PROJECT_NAME:-default}_postgres_data
|
name: docupulse_${PORT:-10335}_postgres_data
|
||||||
uploads:
|
docupulse_uploads:
|
||||||
name: ${COMPOSE_PROJECT_NAME:-default}_uploads
|
name: docupulse_${PORT:-10335}_uploads
|
||||||
130
entrypoint.sh
130
entrypoint.sh
@@ -7,12 +7,28 @@ echo "POSTGRES_PASSWORD: $POSTGRES_PASSWORD"
|
|||||||
echo "POSTGRES_DB: $POSTGRES_DB"
|
echo "POSTGRES_DB: $POSTGRES_DB"
|
||||||
echo "DATABASE_URL: $DATABASE_URL"
|
echo "DATABASE_URL: $DATABASE_URL"
|
||||||
|
|
||||||
# Wait for the database to be ready
|
# Function to wait for database
|
||||||
echo "Waiting for database to be ready..."
|
wait_for_db() {
|
||||||
while ! nc -z db 5432; do
|
echo "Waiting for database..."
|
||||||
sleep 0.1
|
while ! nc -z db 5432; do
|
||||||
done
|
sleep 1
|
||||||
echo "Database is ready!"
|
done
|
||||||
|
echo "Database is ready!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to create database if it doesn't exist
|
||||||
|
create_database() {
|
||||||
|
echo "Creating database if it doesn't exist..."
|
||||||
|
PGPASSWORD=$POSTGRES_PASSWORD psql -h db -U $POSTGRES_USER -tc "SELECT 1 FROM pg_database WHERE datname = '$POSTGRES_DB'" | grep -q 1 || \
|
||||||
|
PGPASSWORD=$POSTGRES_PASSWORD psql -h db -U $POSTGRES_USER -c "CREATE DATABASE $POSTGRES_DB"
|
||||||
|
echo "Database check/creation complete!"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Wait for database to be ready
|
||||||
|
wait_for_db
|
||||||
|
|
||||||
|
# Create database if it doesn't exist
|
||||||
|
create_database
|
||||||
|
|
||||||
# Wait for PostgreSQL to be ready to accept connections
|
# Wait for PostgreSQL to be ready to accept connections
|
||||||
echo "Waiting for PostgreSQL to accept connections..."
|
echo "Waiting for PostgreSQL to accept connections..."
|
||||||
@@ -22,46 +38,84 @@ until PGPASSWORD=$POSTGRES_PASSWORD psql -h db -U $POSTGRES_USER -d $POSTGRES_DB
|
|||||||
done
|
done
|
||||||
echo "PostgreSQL is up - executing command"
|
echo "PostgreSQL is up - executing command"
|
||||||
|
|
||||||
# Clean up existing migrations and initialize fresh
|
# Run all initialization in a single Python script to avoid multiple Flask instances
|
||||||
echo "Cleaning up and initializing fresh migrations..."
|
echo "Running initialization..."
|
||||||
rm -rf migrations/versions/*
|
|
||||||
flask db init
|
|
||||||
flask db migrate -m "Initial migration"
|
|
||||||
flask db upgrade
|
|
||||||
|
|
||||||
# Create events table
|
|
||||||
echo "Creating events table..."
|
|
||||||
python3 -c "
|
python3 -c "
|
||||||
from migrations.add_events_table import upgrade
|
import sys
|
||||||
from app import create_app
|
from app import create_app
|
||||||
|
from models import SiteSettings, db, User
|
||||||
|
from utils.email_templates import create_default_templates
|
||||||
|
|
||||||
|
def log_error(message, error=None):
|
||||||
|
print(f'ERROR: {message}', file=sys.stderr)
|
||||||
|
if error:
|
||||||
|
print(f'Error details: {str(error)}', file=sys.stderr)
|
||||||
|
|
||||||
app = create_app()
|
app = create_app()
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
try:
|
try:
|
||||||
|
# Run migrations
|
||||||
|
print('Running database migrations...')
|
||||||
|
from flask_migrate import upgrade
|
||||||
upgrade()
|
upgrade()
|
||||||
print('Events table created successfully')
|
print('Database migrations completed successfully')
|
||||||
except Exception as e:
|
|
||||||
print(f'Error creating events table: {e}')
|
|
||||||
"
|
|
||||||
|
|
||||||
# Create default site settings if they don't exist
|
# Create default site settings
|
||||||
echo "Creating default site settings..."
|
print('Creating default site settings...')
|
||||||
python3 -c "
|
try:
|
||||||
from app import create_app
|
settings = SiteSettings.get_settings()
|
||||||
from models import SiteSettings, db
|
print('Default site settings created successfully')
|
||||||
app = create_app()
|
except Exception as e:
|
||||||
with app.app_context():
|
log_error('Error creating site settings', e)
|
||||||
try:
|
|
||||||
settings = SiteSettings.get_settings()
|
|
||||||
print('Default site settings created successfully')
|
|
||||||
except Exception as e:
|
|
||||||
print(f'Error creating site settings: {e}')
|
|
||||||
"
|
|
||||||
|
|
||||||
# Initialize admin user
|
# Create admin user if it doesn't exist
|
||||||
echo "Initializing admin user..."
|
print('Creating admin user...')
|
||||||
python3 -c "
|
try:
|
||||||
from init_admin import init_admin
|
admin = User.query.filter_by(email='administrator@docupulse.com').first()
|
||||||
init_admin()
|
if not admin:
|
||||||
|
print('Admin user not found, creating new admin user...')
|
||||||
|
admin = User(
|
||||||
|
username='administrator',
|
||||||
|
email='administrator@docupulse.com',
|
||||||
|
last_name='Administrator',
|
||||||
|
company='DocuPulse',
|
||||||
|
position='System Administrator',
|
||||||
|
is_admin=True,
|
||||||
|
is_active=True,
|
||||||
|
preferred_view='grid'
|
||||||
|
)
|
||||||
|
admin.set_password('changeme')
|
||||||
|
print('Admin user object created, attempting to add to database...')
|
||||||
|
db.session.add(admin)
|
||||||
|
try:
|
||||||
|
db.session.commit()
|
||||||
|
print('Default administrator user created successfully.')
|
||||||
|
print('Admin credentials:')
|
||||||
|
print('Email: administrator@docupulse.com')
|
||||||
|
print('Password: changeme')
|
||||||
|
except Exception as e:
|
||||||
|
db.session.rollback()
|
||||||
|
log_error('Failed to commit admin user creation', e)
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
print('Admin user already exists.')
|
||||||
|
print('Admin credentials:')
|
||||||
|
print('Email: administrator@docupulse.com')
|
||||||
|
print('Password: changeme')
|
||||||
|
except Exception as e:
|
||||||
|
log_error('Error during admin user creation/check', e)
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Create default templates
|
||||||
|
print('Creating default templates...')
|
||||||
|
try:
|
||||||
|
create_default_templates()
|
||||||
|
print('Default templates created successfully')
|
||||||
|
except Exception as e:
|
||||||
|
log_error('Error creating default templates', e)
|
||||||
|
except Exception as e:
|
||||||
|
log_error('Fatal error during initialization', e)
|
||||||
|
sys.exit(1)
|
||||||
"
|
"
|
||||||
|
|
||||||
# Start the application
|
# Start the application
|
||||||
|
|||||||
13
forms.py
13
forms.py
@@ -1,5 +1,5 @@
|
|||||||
from flask_wtf import FlaskForm
|
from flask_wtf import FlaskForm
|
||||||
from wtforms import StringField, TextAreaField, BooleanField, SubmitField, PasswordField, SelectMultipleField
|
from wtforms import StringField, TextAreaField, BooleanField, SubmitField, PasswordField, SelectMultipleField, SelectField
|
||||||
from wtforms.validators import DataRequired, Email, Length, Optional, ValidationError
|
from wtforms.validators import DataRequired, Email, Length, Optional, ValidationError
|
||||||
from models import User
|
from models import User
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
@@ -13,7 +13,11 @@ class UserForm(FlaskForm):
|
|||||||
company = StringField('Company (Optional)', validators=[Optional(), Length(max=100)])
|
company = StringField('Company (Optional)', validators=[Optional(), Length(max=100)])
|
||||||
position = StringField('Position (Optional)', validators=[Optional(), Length(max=100)])
|
position = StringField('Position (Optional)', validators=[Optional(), Length(max=100)])
|
||||||
notes = TextAreaField('Notes (Optional)', validators=[Optional()])
|
notes = TextAreaField('Notes (Optional)', validators=[Optional()])
|
||||||
is_admin = BooleanField('Admin Role', default=False)
|
role = SelectField('Role', choices=[
|
||||||
|
('user', 'Standard User'),
|
||||||
|
('manager', 'Manager'),
|
||||||
|
('admin', 'Administrator')
|
||||||
|
], validators=[DataRequired()])
|
||||||
new_password = PasswordField('New Password (Optional)')
|
new_password = PasswordField('New Password (Optional)')
|
||||||
confirm_password = PasswordField('Confirm Password (Optional)')
|
confirm_password = PasswordField('Confirm Password (Optional)')
|
||||||
profile_picture = FileField('Profile Picture (Optional)', validators=[FileAllowed(['jpg', 'jpeg', 'png', 'gif'], 'Images only!')])
|
profile_picture = FileField('Profile Picture (Optional)', validators=[FileAllowed(['jpg', 'jpeg', 'png', 'gif'], 'Images only!')])
|
||||||
@@ -30,6 +34,11 @@ class UserForm(FlaskForm):
|
|||||||
if total_admins <= 1:
|
if total_admins <= 1:
|
||||||
raise ValidationError('There must be at least one admin user in the system.')
|
raise ValidationError('There must be at least one admin user in the system.')
|
||||||
|
|
||||||
|
def validate_is_manager(self, field):
|
||||||
|
# Prevent setting both admin and manager roles
|
||||||
|
if field.data and self.is_admin.data:
|
||||||
|
raise ValidationError('A user cannot be both an admin and a manager.')
|
||||||
|
|
||||||
def validate(self, extra_validators=None):
|
def validate(self, extra_validators=None):
|
||||||
rv = super().validate(extra_validators=extra_validators)
|
rv = super().validate(extra_validators=extra_validators)
|
||||||
if not rv:
|
if not rv:
|
||||||
|
|||||||
BIN
migrations/__pycache__/env.cpython-311.pyc
Normal file
BIN
migrations/__pycache__/env.cpython-311.pyc
Normal file
Binary file not shown.
@@ -1,61 +0,0 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Add the parent directory to Python path so we can import from root
|
|
||||||
sys.path.append(str(Path(__file__).parent.parent))
|
|
||||||
|
|
||||||
from flask import Flask
|
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
|
||||||
from extensions import db
|
|
||||||
from sqlalchemy import text
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# Create events table
|
|
||||||
with db.engine.connect() as conn:
|
|
||||||
conn.execute(text('''
|
|
||||||
CREATE TABLE IF NOT EXISTS events (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
event_type VARCHAR(50) NOT NULL,
|
|
||||||
user_id INTEGER NOT NULL REFERENCES "user" (id),
|
|
||||||
timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
details JSONB,
|
|
||||||
ip_address VARCHAR(45),
|
|
||||||
user_agent VARCHAR(255)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Create index on event_type for faster filtering
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_events_event_type ON events(event_type);
|
|
||||||
|
|
||||||
-- Create index on timestamp for faster date-based queries
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
|
|
||||||
|
|
||||||
-- Create index on user_id for faster user-based queries
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_events_user_id ON events(user_id);
|
|
||||||
'''))
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# Drop events table and its indexes
|
|
||||||
with db.engine.connect() as conn:
|
|
||||||
conn.execute(text('''
|
|
||||||
DROP INDEX IF EXISTS idx_events_event_type;
|
|
||||||
DROP INDEX IF EXISTS idx_events_timestamp;
|
|
||||||
DROP INDEX IF EXISTS idx_events_user_id;
|
|
||||||
DROP TABLE IF EXISTS events;
|
|
||||||
'''))
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
# Use the same database configuration as in app.py
|
|
||||||
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', 'postgresql://postgres:1253@localhost:5432/docupulse')
|
|
||||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
|
||||||
|
|
||||||
print("Connecting to database...")
|
|
||||||
|
|
||||||
db.init_app(app)
|
|
||||||
|
|
||||||
with app.app_context():
|
|
||||||
upgrade()
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Add the parent directory to Python path so we can import from root
|
|
||||||
sys.path.append(str(Path(__file__).parent.parent))
|
|
||||||
|
|
||||||
from flask import Flask
|
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
|
||||||
from extensions import db
|
|
||||||
from sqlalchemy import text
|
|
||||||
|
|
||||||
def upgrade():
|
|
||||||
# Create notifs table
|
|
||||||
with db.engine.connect() as conn:
|
|
||||||
conn.execute(text('''
|
|
||||||
CREATE TABLE IF NOT EXISTS notifs (
|
|
||||||
id SERIAL PRIMARY KEY,
|
|
||||||
notif_type VARCHAR(50) NOT NULL,
|
|
||||||
user_id INTEGER NOT NULL REFERENCES "user" (id),
|
|
||||||
sender_id INTEGER REFERENCES "user" (id),
|
|
||||||
timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
read BOOLEAN NOT NULL DEFAULT FALSE,
|
|
||||||
details JSONB
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Create indexes for faster queries
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_notifs_notif_type ON notifs(notif_type);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_notifs_timestamp ON notifs(timestamp);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_notifs_user_id ON notifs(user_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_notifs_sender_id ON notifs(sender_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_notifs_read ON notifs(read);
|
|
||||||
'''))
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
def downgrade():
|
|
||||||
# Drop notifs table and its indexes
|
|
||||||
with db.engine.connect() as conn:
|
|
||||||
conn.execute(text('''
|
|
||||||
DROP INDEX IF EXISTS idx_notifs_notif_type;
|
|
||||||
DROP INDEX IF EXISTS idx_notifs_timestamp;
|
|
||||||
DROP INDEX IF EXISTS idx_notifs_user_id;
|
|
||||||
DROP INDEX IF EXISTS idx_notifs_sender_id;
|
|
||||||
DROP INDEX IF EXISTS idx_notifs_read;
|
|
||||||
DROP TABLE IF EXISTS notifs;
|
|
||||||
'''))
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
# Use the same database configuration as in app.py
|
|
||||||
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL', 'postgresql://postgres:1253@localhost:5432/docupulse')
|
|
||||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
|
||||||
|
|
||||||
print("Connecting to database...")
|
|
||||||
|
|
||||||
db.init_app(app)
|
|
||||||
|
|
||||||
with app.app_context():
|
|
||||||
upgrade()
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
"""add key value settings table
|
||||||
|
|
||||||
|
Revision ID: 0a8006bd1732
|
||||||
|
Revises: 20519a2437c2
|
||||||
|
Create Date: 2025-06-02 14:10:54.033943
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '0a8006bd1732'
|
||||||
|
down_revision = '20519a2437c2'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
conn = op.get_bind()
|
||||||
|
inspector = inspect(conn)
|
||||||
|
tables = inspector.get_table_names()
|
||||||
|
|
||||||
|
if 'key_value_settings' not in tables:
|
||||||
|
op.create_table('key_value_settings',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('key', sa.String(length=100), nullable=False),
|
||||||
|
sa.Column('value', sa.Text(), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('key')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('key_value_settings')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -7,6 +7,7 @@ Create Date: 2025-05-26 14:00:05.521776
|
|||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
@@ -18,23 +19,41 @@ depends_on = None
|
|||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
conn = op.get_bind()
|
||||||
|
inspector = inspect(conn)
|
||||||
|
columns = [col['name'] for col in inspector.get_columns('message')]
|
||||||
|
|
||||||
with op.batch_alter_table('message', schema=None) as batch_op:
|
with op.batch_alter_table('message', schema=None) as batch_op:
|
||||||
batch_op.add_column(sa.Column('has_attachment', sa.Boolean(), nullable=True))
|
if 'has_attachment' not in columns:
|
||||||
batch_op.add_column(sa.Column('attachment_name', sa.String(length=255), nullable=True))
|
batch_op.add_column(sa.Column('has_attachment', sa.Boolean(), nullable=True))
|
||||||
batch_op.add_column(sa.Column('attachment_path', sa.String(length=512), nullable=True))
|
if 'attachment_name' not in columns:
|
||||||
batch_op.add_column(sa.Column('attachment_type', sa.String(length=100), nullable=True))
|
batch_op.add_column(sa.Column('attachment_name', sa.String(length=255), nullable=True))
|
||||||
batch_op.add_column(sa.Column('attachment_size', sa.Integer(), nullable=True))
|
if 'attachment_path' not in columns:
|
||||||
|
batch_op.add_column(sa.Column('attachment_path', sa.String(length=512), nullable=True))
|
||||||
|
if 'attachment_type' not in columns:
|
||||||
|
batch_op.add_column(sa.Column('attachment_type', sa.String(length=100), nullable=True))
|
||||||
|
if 'attachment_size' not in columns:
|
||||||
|
batch_op.add_column(sa.Column('attachment_size', sa.Integer(), nullable=True))
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
conn = op.get_bind()
|
||||||
|
inspector = inspect(conn)
|
||||||
|
columns = [col['name'] for col in inspector.get_columns('message')]
|
||||||
|
|
||||||
with op.batch_alter_table('message', schema=None) as batch_op:
|
with op.batch_alter_table('message', schema=None) as batch_op:
|
||||||
batch_op.drop_column('attachment_size')
|
if 'attachment_size' in columns:
|
||||||
batch_op.drop_column('attachment_type')
|
batch_op.drop_column('attachment_size')
|
||||||
batch_op.drop_column('attachment_path')
|
if 'attachment_type' in columns:
|
||||||
batch_op.drop_column('attachment_name')
|
batch_op.drop_column('attachment_type')
|
||||||
batch_op.drop_column('has_attachment')
|
if 'attachment_path' in columns:
|
||||||
|
batch_op.drop_column('attachment_path')
|
||||||
|
if 'attachment_name' in columns:
|
||||||
|
batch_op.drop_column('attachment_name')
|
||||||
|
if 'has_attachment' in columns:
|
||||||
|
batch_op.drop_column('has_attachment')
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
|
|
||||||
Revision ID: 1c297825e3a9
|
Revision ID: 1c297825e3a9
|
||||||
Revises:
|
Revises:
|
||||||
Create Date: 2025-05-23 08:39:40.494853
|
Create Date: 2025-06-02 13:26:30.353000
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
@@ -17,17 +18,24 @@ depends_on = None
|
|||||||
|
|
||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# Check if the table exists before creating it
|
||||||
op.create_table('user',
|
conn = op.get_bind()
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
inspector = sa.inspect(conn)
|
||||||
sa.Column('username', sa.String(length=150), nullable=False),
|
if 'user' not in inspector.get_table_names():
|
||||||
sa.Column('email', sa.String(length=150), nullable=False),
|
conn = op.get_bind()
|
||||||
sa.Column('password_hash', sa.String(length=128), nullable=True),
|
inspector = inspect(conn)
|
||||||
sa.PrimaryKeyConstraint('id'),
|
tables = inspector.get_table_names()
|
||||||
sa.UniqueConstraint('email'),
|
|
||||||
sa.UniqueConstraint('username')
|
if 'user' not in tables:
|
||||||
)
|
op.create_table('user',
|
||||||
# ### end Alembic commands ###
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('username', sa.String(length=150), nullable=False),
|
||||||
|
sa.Column('email', sa.String(length=150), nullable=False),
|
||||||
|
sa.Column('password_hash', sa.String(length=128), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('email'),
|
||||||
|
sa.UniqueConstraint('username')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
|
|||||||
47
migrations/versions/20519a2437c2_add_mails_table.py
Normal file
47
migrations/versions/20519a2437c2_add_mails_table.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
"""add_mails_table
|
||||||
|
|
||||||
|
Revision ID: 20519a2437c2
|
||||||
|
Revises: 444d76da74ba
|
||||||
|
Create Date: 2025-06-02 09:04:39.972021
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '20519a2437c2'
|
||||||
|
down_revision = '444d76da74ba'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
conn = op.get_bind()
|
||||||
|
inspector = inspect(conn)
|
||||||
|
tables = inspector.get_table_names()
|
||||||
|
|
||||||
|
if 'mails' not in tables:
|
||||||
|
op.create_table('mails',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('recipient', sa.String(length=150), nullable=False),
|
||||||
|
sa.Column('subject', sa.String(length=200), nullable=False),
|
||||||
|
sa.Column('body', sa.Text(), nullable=False),
|
||||||
|
sa.Column('status', sa.String(length=20), nullable=False),
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('sent_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('template_id', sa.Integer(), nullable=True),
|
||||||
|
sa.Column('notif_id', sa.Integer(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['notif_id'], ['notifs.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['template_id'], ['email_templates.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('mails')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -7,6 +7,7 @@ Create Date: 2025-05-23 16:10:53.731035
|
|||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
@@ -18,15 +19,25 @@ depends_on = None
|
|||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
conn = op.get_bind()
|
||||||
|
inspector = inspect(conn)
|
||||||
|
columns = [col['name'] for col in inspector.get_columns('contact')]
|
||||||
|
|
||||||
with op.batch_alter_table('contact', schema=None) as batch_op:
|
with op.batch_alter_table('contact', schema=None) as batch_op:
|
||||||
batch_op.add_column(sa.Column('is_admin', sa.Boolean(), nullable=True))
|
if 'is_admin' not in columns:
|
||||||
|
batch_op.add_column(sa.Column('is_admin', sa.Boolean(), nullable=True))
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
conn = op.get_bind()
|
||||||
|
inspector = inspect(conn)
|
||||||
|
columns = [col['name'] for col in inspector.get_columns('contact')]
|
||||||
|
|
||||||
with op.batch_alter_table('contact', schema=None) as batch_op:
|
with op.batch_alter_table('contact', schema=None) as batch_op:
|
||||||
batch_op.drop_column('is_admin')
|
if 'is_admin' in columns:
|
||||||
|
batch_op.drop_column('is_admin')
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Create Date: 2025-05-23 21:44:58.832286
|
|||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
@@ -18,17 +19,22 @@ depends_on = None
|
|||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table('room_member_permissions',
|
conn = op.get_bind()
|
||||||
sa.Column('room_id', sa.Integer(), nullable=False),
|
inspector = inspect(conn)
|
||||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
tables = inspector.get_table_names()
|
||||||
sa.Column('can_view', sa.Boolean(), nullable=False),
|
|
||||||
sa.Column('can_upload', sa.Boolean(), nullable=False),
|
if 'room_member_permissions' not in tables:
|
||||||
sa.Column('can_delete', sa.Boolean(), nullable=False),
|
op.create_table('room_member_permissions',
|
||||||
sa.Column('can_share', sa.Boolean(), nullable=False),
|
sa.Column('room_id', sa.Integer(), nullable=False),
|
||||||
sa.ForeignKeyConstraint(['room_id'], ['room.id'], ),
|
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
sa.Column('can_view', sa.Boolean(), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('room_id', 'user_id')
|
sa.Column('can_upload', sa.Boolean(), nullable=False),
|
||||||
)
|
sa.Column('can_delete', sa.Boolean(), nullable=False),
|
||||||
|
sa.Column('can_share', sa.Boolean(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['room_id'], ['room.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('room_id', 'user_id')
|
||||||
|
)
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Create Date: 2025-05-23 21:27:17.497481
|
|||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
@@ -18,15 +19,24 @@ depends_on = None
|
|||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table('room_members',
|
conn = op.get_bind()
|
||||||
sa.Column('room_id', sa.Integer(), nullable=False),
|
inspector = inspect(conn)
|
||||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
tables = inspector.get_table_names()
|
||||||
sa.ForeignKeyConstraint(['room_id'], ['room.id'], ),
|
|
||||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
if 'room_members' not in tables:
|
||||||
sa.PrimaryKeyConstraint('room_id', 'user_id')
|
op.create_table('room_members',
|
||||||
)
|
sa.Column('room_id', sa.Integer(), nullable=False),
|
||||||
with op.batch_alter_table('room', schema=None) as batch_op:
|
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||||
batch_op.drop_column('is_private')
|
sa.ForeignKeyConstraint(['room_id'], ['room.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('room_id', 'user_id')
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if is_private column exists before dropping it
|
||||||
|
columns = [col['name'] for col in inspector.get_columns('room')]
|
||||||
|
if 'is_private' in columns:
|
||||||
|
with op.batch_alter_table('room', schema=None) as batch_op:
|
||||||
|
batch_op.drop_column('is_private')
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Create Date: 2025-05-23 21:25:27.880150
|
|||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
@@ -18,16 +19,21 @@ depends_on = None
|
|||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table('room',
|
conn = op.get_bind()
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
inspector = inspect(conn)
|
||||||
sa.Column('name', sa.String(length=100), nullable=False),
|
tables = inspector.get_table_names()
|
||||||
sa.Column('description', sa.Text(), nullable=True),
|
|
||||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
if 'room' not in tables:
|
||||||
sa.Column('created_by', sa.Integer(), nullable=False),
|
op.create_table('room',
|
||||||
sa.Column('is_private', sa.Boolean(), nullable=True),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.ForeignKeyConstraint(['created_by'], ['user.id'], ),
|
sa.Column('name', sa.String(length=100), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.Column('description', sa.Text(), nullable=True),
|
||||||
)
|
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('created_by', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('is_private', sa.Boolean(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['created_by'], ['user.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Create Date: 2025-05-23 09:24:23.926302
|
|||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
@@ -18,12 +19,21 @@ depends_on = None
|
|||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
conn = op.get_bind()
|
||||||
|
inspector = inspect(conn)
|
||||||
|
columns = [col['name'] for col in inspector.get_columns('user')]
|
||||||
|
|
||||||
with op.batch_alter_table('user', schema=None) as batch_op:
|
with op.batch_alter_table('user', schema=None) as batch_op:
|
||||||
batch_op.add_column(sa.Column('phone', sa.String(length=20), nullable=True))
|
if 'phone' not in columns:
|
||||||
batch_op.add_column(sa.Column('company', sa.String(length=100), nullable=True))
|
batch_op.add_column(sa.Column('phone', sa.String(length=20), nullable=True))
|
||||||
batch_op.add_column(sa.Column('position', sa.String(length=100), nullable=True))
|
if 'company' not in columns:
|
||||||
batch_op.add_column(sa.Column('notes', sa.Text(), nullable=True))
|
batch_op.add_column(sa.Column('company', sa.String(length=100), nullable=True))
|
||||||
batch_op.add_column(sa.Column('is_active', sa.Boolean(), nullable=True))
|
if 'position' not in columns:
|
||||||
|
batch_op.add_column(sa.Column('position', sa.String(length=100), nullable=True))
|
||||||
|
if 'notes' not in columns:
|
||||||
|
batch_op.add_column(sa.Column('notes', sa.Text(), nullable=True))
|
||||||
|
if 'is_active' not in columns:
|
||||||
|
batch_op.add_column(sa.Column('is_active', sa.Boolean(), nullable=True))
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|||||||
63
migrations/versions/444d76da74ba_add_notifications_table.py
Normal file
63
migrations/versions/444d76da74ba_add_notifications_table.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
"""add_notifications_table
|
||||||
|
|
||||||
|
Revision ID: 444d76da74ba
|
||||||
|
Revises: c770e08966b4
|
||||||
|
Create Date: 2025-06-02 08:25:48.241102
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '444d76da74ba'
|
||||||
|
down_revision = 'c770e08966b4'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
conn = op.get_bind()
|
||||||
|
inspector = inspect(conn)
|
||||||
|
tables = inspector.get_table_names()
|
||||||
|
|
||||||
|
if 'template_variables' in tables:
|
||||||
|
op.drop_table('template_variables')
|
||||||
|
|
||||||
|
op.create_table('notification',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('title', sa.String(length=200), nullable=False),
|
||||||
|
sa.Column('message', sa.Text(), nullable=False),
|
||||||
|
sa.Column('type', sa.String(length=50), nullable=False),
|
||||||
|
sa.Column('read', sa.Boolean(), nullable=False, server_default='false'),
|
||||||
|
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
conn = op.get_bind()
|
||||||
|
inspector = inspect(conn)
|
||||||
|
tables = inspector.get_table_names()
|
||||||
|
|
||||||
|
if 'notification' in tables:
|
||||||
|
op.drop_table('notification')
|
||||||
|
|
||||||
|
if 'template_variables' not in tables:
|
||||||
|
op.create_table('template_variables',
|
||||||
|
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('notification_type', sa.VARCHAR(length=50), autoincrement=False, nullable=False),
|
||||||
|
sa.Column('variable_name', sa.VARCHAR(length=50), autoincrement=False, nullable=False),
|
||||||
|
sa.Column('description', sa.VARCHAR(length=200), autoincrement=False, nullable=False),
|
||||||
|
sa.Column('example_value', sa.VARCHAR(length=200), autoincrement=False, nullable=True),
|
||||||
|
sa.Column('created_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id', name=op.f('template_variables_pkey'))
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
24
migrations/versions/4ee23cb29001_merge_heads.py
Normal file
24
migrations/versions/4ee23cb29001_merge_heads.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
"""merge heads
|
||||||
|
|
||||||
|
Revision ID: 4ee23cb29001
|
||||||
|
Revises: 72ab6c4c6a5f, add_status_details
|
||||||
|
Create Date: 2025-06-09 10:04:48.708415
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '4ee23cb29001'
|
||||||
|
down_revision = ('72ab6c4c6a5f', 'add_status_details')
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
pass
|
||||||
@@ -7,6 +7,7 @@ Create Date: 2025-05-24 10:07:02.159730
|
|||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
@@ -18,22 +19,31 @@ depends_on = None
|
|||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table('room_file',
|
conn = op.get_bind()
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
inspector = inspect(conn)
|
||||||
sa.Column('room_id', sa.Integer(), nullable=False),
|
tables = inspector.get_table_names()
|
||||||
sa.Column('name', sa.String(length=255), nullable=False),
|
|
||||||
sa.Column('path', sa.String(length=1024), nullable=False),
|
if 'room_file' not in tables:
|
||||||
sa.Column('type', sa.String(length=10), nullable=False),
|
op.create_table('room_file',
|
||||||
sa.Column('size', sa.BigInteger(), nullable=True),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('modified', sa.Float(), nullable=True),
|
sa.Column('room_id', sa.Integer(), nullable=False),
|
||||||
sa.Column('uploaded_by', sa.Integer(), nullable=False),
|
sa.Column('name', sa.String(length=255), nullable=False),
|
||||||
sa.Column('uploaded_at', sa.DateTime(), nullable=False),
|
sa.Column('path', sa.String(length=1024), nullable=False),
|
||||||
sa.ForeignKeyConstraint(['room_id'], ['room.id'], ),
|
sa.Column('type', sa.String(length=10), nullable=False),
|
||||||
sa.ForeignKeyConstraint(['uploaded_by'], ['user.id'], ),
|
sa.Column('size', sa.BigInteger(), nullable=True),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.Column('modified', sa.Float(), nullable=True),
|
||||||
)
|
sa.Column('uploaded_by', sa.Integer(), nullable=False),
|
||||||
with op.batch_alter_table('room_member_permissions', schema=None) as batch_op:
|
sa.Column('uploaded_at', sa.DateTime(), nullable=False),
|
||||||
batch_op.drop_column('preferred_view')
|
sa.ForeignKeyConstraint(['room_id'], ['room.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['uploaded_by'], ['user.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if preferred_view column exists before trying to drop it
|
||||||
|
columns = [col['name'] for col in inspector.get_columns('room_member_permissions')]
|
||||||
|
if 'preferred_view' in columns:
|
||||||
|
with op.batch_alter_table('room_member_permissions', schema=None) as batch_op:
|
||||||
|
batch_op.drop_column('preferred_view')
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Create Date: 2025-05-24 18:14:38.320999
|
|||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
from sqlalchemy.dialects import postgresql
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
@@ -18,43 +19,63 @@ depends_on = None
|
|||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
conn = op.get_bind()
|
||||||
|
inspector = inspect(conn)
|
||||||
|
columns = [col['name'] for col in inspector.get_columns('room_file')]
|
||||||
|
|
||||||
with op.batch_alter_table('room_file', schema=None) as batch_op:
|
with op.batch_alter_table('room_file', schema=None) as batch_op:
|
||||||
batch_op.add_column(sa.Column('starred', sa.Boolean(), nullable=True))
|
if 'starred' not in columns:
|
||||||
batch_op.alter_column('path',
|
batch_op.add_column(sa.Column('starred', sa.Boolean(), nullable=True))
|
||||||
existing_type=sa.VARCHAR(length=1024),
|
|
||||||
type_=sa.String(length=255),
|
# Only alter columns if they exist
|
||||||
existing_nullable=False)
|
if 'path' in columns:
|
||||||
batch_op.alter_column('size',
|
batch_op.alter_column('path',
|
||||||
existing_type=sa.BIGINT(),
|
existing_type=sa.VARCHAR(length=1024),
|
||||||
type_=sa.Integer(),
|
type_=sa.String(length=255),
|
||||||
existing_nullable=True)
|
existing_nullable=False)
|
||||||
batch_op.alter_column('uploaded_by',
|
if 'size' in columns:
|
||||||
existing_type=sa.INTEGER(),
|
batch_op.alter_column('size',
|
||||||
nullable=True)
|
existing_type=sa.BIGINT(),
|
||||||
batch_op.alter_column('uploaded_at',
|
type_=sa.Integer(),
|
||||||
existing_type=postgresql.TIMESTAMP(),
|
existing_nullable=True)
|
||||||
nullable=True)
|
if 'uploaded_by' in columns:
|
||||||
|
batch_op.alter_column('uploaded_by',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
nullable=True)
|
||||||
|
if 'uploaded_at' in columns:
|
||||||
|
batch_op.alter_column('uploaded_at',
|
||||||
|
existing_type=postgresql.TIMESTAMP(),
|
||||||
|
nullable=True)
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
conn = op.get_bind()
|
||||||
|
inspector = inspect(conn)
|
||||||
|
columns = [col['name'] for col in inspector.get_columns('room_file')]
|
||||||
|
|
||||||
with op.batch_alter_table('room_file', schema=None) as batch_op:
|
with op.batch_alter_table('room_file', schema=None) as batch_op:
|
||||||
batch_op.alter_column('uploaded_at',
|
if 'uploaded_at' in columns:
|
||||||
existing_type=postgresql.TIMESTAMP(),
|
batch_op.alter_column('uploaded_at',
|
||||||
nullable=False)
|
existing_type=postgresql.TIMESTAMP(),
|
||||||
batch_op.alter_column('uploaded_by',
|
nullable=False)
|
||||||
existing_type=sa.INTEGER(),
|
if 'uploaded_by' in columns:
|
||||||
nullable=False)
|
batch_op.alter_column('uploaded_by',
|
||||||
batch_op.alter_column('size',
|
existing_type=sa.INTEGER(),
|
||||||
existing_type=sa.Integer(),
|
nullable=False)
|
||||||
type_=sa.BIGINT(),
|
if 'size' in columns:
|
||||||
existing_nullable=True)
|
batch_op.alter_column('size',
|
||||||
batch_op.alter_column('path',
|
existing_type=sa.Integer(),
|
||||||
existing_type=sa.String(length=255),
|
type_=sa.BIGINT(),
|
||||||
type_=sa.VARCHAR(length=1024),
|
existing_nullable=True)
|
||||||
existing_nullable=False)
|
if 'path' in columns:
|
||||||
batch_op.drop_column('starred')
|
batch_op.alter_column('path',
|
||||||
|
existing_type=sa.String(length=255),
|
||||||
|
type_=sa.VARCHAR(length=1024),
|
||||||
|
existing_nullable=False)
|
||||||
|
if 'starred' in columns:
|
||||||
|
batch_op.drop_column('starred')
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|||||||
32
migrations/versions/72ab6c4c6a5f_merge_heads.py
Normal file
32
migrations/versions/72ab6c4c6a5f_merge_heads.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
"""merge heads
|
||||||
|
|
||||||
|
Revision ID: 72ab6c4c6a5f
|
||||||
|
Revises: 0a8006bd1732, add_docupulse_settings, add_manager_role, make_events_user_id_nullable
|
||||||
|
Create Date: 2025-06-05 14:21:46.046125
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '72ab6c4c6a5f'
|
||||||
|
down_revision = ('0a8006bd1732', 'add_docupulse_settings', 'add_manager_role', 'make_events_user_id_nullable')
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# Ensure is_manager column exists
|
||||||
|
conn = op.get_bind()
|
||||||
|
inspector = inspect(conn)
|
||||||
|
columns = [col['name'] for col in inspector.get_columns('user')]
|
||||||
|
|
||||||
|
with op.batch_alter_table('user', schema=None) as batch_op:
|
||||||
|
if 'is_manager' not in columns:
|
||||||
|
batch_op.add_column(sa.Column('is_manager', sa.Boolean(), nullable=True, server_default='false'))
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
pass
|
||||||
@@ -7,6 +7,7 @@ Create Date: 2024-03-19 10:05:00.000000
|
|||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
from sqlalchemy.sql import text
|
from sqlalchemy.sql import text
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
24
migrations/versions/761908f0cacf_merge_heads.py
Normal file
24
migrations/versions/761908f0cacf_merge_heads.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
"""merge heads
|
||||||
|
|
||||||
|
Revision ID: 761908f0cacf
|
||||||
|
Revises: 4ee23cb29001, add_connection_token
|
||||||
|
Create Date: 2025-06-09 13:57:17.650231
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '761908f0cacf'
|
||||||
|
down_revision = ('4ee23cb29001', 'add_connection_token')
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
pass
|
||||||
@@ -7,6 +7,7 @@ Create Date: 2025-05-25 10:03:03.423064
|
|||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Create Date: 2025-05-26 10:42:17.287566
|
|||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
@@ -18,17 +19,31 @@ depends_on = None
|
|||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
conn = op.get_bind()
|
||||||
|
inspector = inspect(conn)
|
||||||
|
columns = [col['name'] for col in inspector.get_columns('site_settings')]
|
||||||
|
|
||||||
with op.batch_alter_table('site_settings', schema=None) as batch_op:
|
with op.batch_alter_table('site_settings', schema=None) as batch_op:
|
||||||
batch_op.add_column(sa.Column('company_website', sa.String(length=200), nullable=True))
|
if 'company_website' not in columns:
|
||||||
batch_op.add_column(sa.Column('company_email', sa.String(length=100), nullable=True))
|
batch_op.add_column(sa.Column('company_website', sa.String(length=200), nullable=True))
|
||||||
batch_op.add_column(sa.Column('company_phone', sa.String(length=20), nullable=True))
|
if 'company_email' not in columns:
|
||||||
batch_op.add_column(sa.Column('company_address', sa.String(length=200), nullable=True))
|
batch_op.add_column(sa.Column('company_email', sa.String(length=100), nullable=True))
|
||||||
batch_op.add_column(sa.Column('company_city', sa.String(length=100), nullable=True))
|
if 'company_phone' not in columns:
|
||||||
batch_op.add_column(sa.Column('company_state', sa.String(length=100), nullable=True))
|
batch_op.add_column(sa.Column('company_phone', sa.String(length=20), nullable=True))
|
||||||
batch_op.add_column(sa.Column('company_zip', sa.String(length=20), nullable=True))
|
if 'company_address' not in columns:
|
||||||
batch_op.add_column(sa.Column('company_country', sa.String(length=100), nullable=True))
|
batch_op.add_column(sa.Column('company_address', sa.String(length=200), nullable=True))
|
||||||
batch_op.add_column(sa.Column('company_description', sa.Text(), nullable=True))
|
if 'company_city' not in columns:
|
||||||
batch_op.add_column(sa.Column('company_industry', sa.String(length=100), nullable=True))
|
batch_op.add_column(sa.Column('company_city', sa.String(length=100), nullable=True))
|
||||||
|
if 'company_state' not in columns:
|
||||||
|
batch_op.add_column(sa.Column('company_state', sa.String(length=100), nullable=True))
|
||||||
|
if 'company_zip' not in columns:
|
||||||
|
batch_op.add_column(sa.Column('company_zip', sa.String(length=20), nullable=True))
|
||||||
|
if 'company_country' not in columns:
|
||||||
|
batch_op.add_column(sa.Column('company_country', sa.String(length=100), nullable=True))
|
||||||
|
if 'company_description' not in columns:
|
||||||
|
batch_op.add_column(sa.Column('company_description', sa.Text(), nullable=True))
|
||||||
|
if 'company_industry' not in columns:
|
||||||
|
batch_op.add_column(sa.Column('company_industry', sa.String(length=100), nullable=True))
|
||||||
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Create Date: 2024-03-19 10:15:00.000000
|
|||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
from sqlalchemy.sql import text
|
from sqlalchemy.sql import text
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ Create Date: 2025-05-25 21:16:39.683736
|
|||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import inspect
|
||||||
from sqlalchemy.dialects import postgresql
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = '9faab7ef6036'
|
revision = '9faab7ef6036'
|
||||||
down_revision = 'ca9026520dad'
|
down_revision = 'ca9026520dad'
|
||||||
@@ -18,25 +20,35 @@ depends_on = None
|
|||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table('site_settings',
|
conn = op.get_bind()
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
inspector = inspect(conn)
|
||||||
sa.Column('primary_color', sa.String(length=7), nullable=True),
|
tables = inspector.get_table_names()
|
||||||
sa.Column('secondary_color', sa.String(length=7), nullable=True),
|
|
||||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
if 'site_settings' not in tables:
|
||||||
sa.PrimaryKeyConstraint('id')
|
op.create_table('site_settings',
|
||||||
)
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('primary_color', sa.String(length=7), nullable=True),
|
||||||
|
sa.Column('secondary_color', sa.String(length=7), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
op.drop_table('color_settings')
|
op.drop_table('color_settings')
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table('color_settings',
|
conn = op.get_bind()
|
||||||
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
|
inspector = inspect(conn)
|
||||||
sa.Column('primary_color', sa.VARCHAR(length=7), autoincrement=False, nullable=True),
|
tables = inspector.get_table_names()
|
||||||
sa.Column('secondary_color', sa.VARCHAR(length=7), autoincrement=False, nullable=True),
|
|
||||||
sa.Column('updated_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
|
if 'color_settings' not in tables:
|
||||||
sa.PrimaryKeyConstraint('id', name=op.f('color_settings_pkey'))
|
op.create_table('color_settings',
|
||||||
)
|
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
|
||||||
|
sa.Column('primary_color', sa.VARCHAR(length=7), autoincrement=False, nullable=True),
|
||||||
|
sa.Column('secondary_color', sa.VARCHAR(length=7), autoincrement=False, nullable=True),
|
||||||
|
sa.Column('updated_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id', name=op.f('color_settings_pkey'))
|
||||||
|
)
|
||||||
op.drop_table('site_settings')
|
op.drop_table('site_settings')
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user