major styling pass, artist list, and import processing improvements
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
# Ignore test data
|
||||
images/
|
||||
import/
|
||||
|
||||
# Ignore Python cache
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
|
||||
# Ignore environment files
|
||||
.env
|
||||
.env.*
|
||||
|
||||
# Ignore virtual environments
|
||||
env/
|
||||
venv/
|
||||
|
||||
# Ignore Git files
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Ignore DB files
|
||||
app.db
|
||||
|
||||
# Ignore Docker related files
|
||||
.dockerignore
|
||||
docker-compose.yml
|
||||
+1
-1
@@ -45,7 +45,7 @@ def login():
|
||||
user = User.query.filter_by(email=form.email.data).first()
|
||||
if user and check_password_hash(user.password_hash, form.password.data):
|
||||
login_user(user)
|
||||
return redirect(url_for('main.index'))
|
||||
return redirect(url_for('main.gallery'))
|
||||
else:
|
||||
flash('Login failed. Check username/password.', 'danger')
|
||||
return render_template('login.html', form=form)
|
||||
|
||||
+41
-9
@@ -14,7 +14,18 @@ main = Blueprint('main', __name__)
|
||||
|
||||
@main.route('/')
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
from random import choice
|
||||
from app.models import ImageRecord
|
||||
|
||||
image = ImageRecord.query.order_by(func.random()).first()
|
||||
background_url = None
|
||||
|
||||
if image:
|
||||
filename = image.filepath.replace('/images/', '')
|
||||
background_url = url_for('main.serve_image', filename=filename)
|
||||
|
||||
return render_template('index.html', background_url=background_url)
|
||||
|
||||
|
||||
@main.route('/gallery')
|
||||
@login_required
|
||||
@@ -25,11 +36,12 @@ def gallery():
|
||||
).limit(recent_images).all()
|
||||
return render_template('gallery.html', images=images)
|
||||
|
||||
@main.route('/static/images/<path:filename>')
|
||||
@main.route('/images/<path:filename>')
|
||||
def serve_image(filename):
|
||||
return send_from_directory('/images', filename)
|
||||
|
||||
@main.route('/artist/<artist_name>')
|
||||
@login_required
|
||||
def artist_gallery(artist_name):
|
||||
per_page=49
|
||||
page = request.args.get('page', 1, type=int)
|
||||
@@ -38,6 +50,30 @@ def artist_gallery(artist_name):
|
||||
).paginate(page=page, per_page=per_page)
|
||||
return render_template('artist_gallery.html', images=images, artist=artist_name)
|
||||
|
||||
@main.route('/artists')
|
||||
@login_required
|
||||
def artist_list():
|
||||
from app.models import ImageRecord
|
||||
from sqlalchemy import func
|
||||
|
||||
# Define how many images to show per artist card
|
||||
images_per_artist = 3 # Change this value to tweak
|
||||
|
||||
# Get all unique artist names
|
||||
artists = db.session.query(ImageRecord.artist).distinct().all()
|
||||
artist_data = []
|
||||
|
||||
for (artist,) in artists:
|
||||
images = (
|
||||
ImageRecord.query
|
||||
.filter_by(artist=artist)
|
||||
.order_by(ImageRecord.taken_at.desc().nullslast(), ImageRecord.imported_at.desc())
|
||||
.limit(images_per_artist)
|
||||
.all()
|
||||
)
|
||||
artist_data.append({'name': artist, 'images': images})
|
||||
|
||||
return render_template('artist_list.html', artist_data=artist_data, images_per_artist=images_per_artist)
|
||||
|
||||
@main.route('/settings')
|
||||
@login_required
|
||||
@@ -49,13 +85,9 @@ def settings():
|
||||
@main.route('/import-images')
|
||||
@login_required
|
||||
def trigger_image_import():
|
||||
if not current_user.is_admin:
|
||||
abort(403)
|
||||
source_dir = '/import'
|
||||
dest_dir = '/images'
|
||||
|
||||
result = import_images_task(source_dir, dest_dir)
|
||||
flash(f"Image import completed: {result}")
|
||||
with open('/import/trigger.flag', 'w') as f:
|
||||
f.write('start')
|
||||
flash("Image import triggered.")
|
||||
return redirect(url_for('main.settings'))
|
||||
|
||||
@main.route('/reset-db', methods=['POST'])
|
||||
|
||||
+204
-1
@@ -223,4 +223,207 @@ body {
|
||||
transform: none;
|
||||
font-size: 1.5rem;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Artist Styling */
|
||||
.artist-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 1.5rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.artist-card {
|
||||
background-color: #1c1c1c;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.artist-card h3 {
|
||||
margin-bottom: 0.5rem;
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.artist-preview {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.artist-thumb {
|
||||
flex: 1;
|
||||
height: 300px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.artist-thumb:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.artist-card-link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.artist-card-link:hover .artist-card {
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 4px 10px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.settings-container {
|
||||
max-width: 600px;
|
||||
margin: 2rem auto;
|
||||
padding: 1.5rem;
|
||||
background: #f8f8f8;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.settings-section h2 {
|
||||
margin-bottom: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.settings-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.primary-btn {
|
||||
background-color: #0066cc;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.primary-btn:hover {
|
||||
background-color: #004d99;
|
||||
}
|
||||
|
||||
.danger-btn {
|
||||
background-color: #cc0033;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.danger-btn:hover {
|
||||
background-color: #990024;
|
||||
}
|
||||
|
||||
.settings-info {
|
||||
margin-top: 2rem;
|
||||
font-size: 0.95rem;
|
||||
color: #444;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.index-hero {
|
||||
position: relative;
|
||||
height: 100vh;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
filter: blur(0px); /* Required to initialize */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.index-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(20, 20, 20, 0.5);
|
||||
backdrop-filter: blur(16px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.index-message {
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
padding: 2rem;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.index-message h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.index-message p {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.index-message .btn {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* Form Styling */
|
||||
.form-container {
|
||||
max-width: 400px;
|
||||
margin: 2rem auto;
|
||||
padding: 2rem;
|
||||
background-color: #ffffffdd;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.form-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
padding: 0.6rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.form-button {
|
||||
padding: 0.75rem;
|
||||
background-color: #5a67d8;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.form-button:hover {
|
||||
background-color: #434190;
|
||||
}
|
||||
|
||||
.form-footer-text {
|
||||
margin-top: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% block title %}{{ artist }}'s Gallery{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 style="text-align:center; margin-top: 1rem;">{{ artist }}</h2>
|
||||
<h1 style="text-align:center; margin-top: 1rem;">{{ artist }}</h1>
|
||||
|
||||
<!-- Pagination Controls -->
|
||||
<div class="pagination-container">
|
||||
@@ -21,23 +21,21 @@
|
||||
</div>
|
||||
|
||||
<!-- Gallery Grid -->
|
||||
<div class="gallery-grid">
|
||||
{% for image in images %}
|
||||
<div class="gallery-grid">
|
||||
{% for image in images.items %}
|
||||
<div class="gallery-item">
|
||||
<div class="gallery-thumb img-clickable"
|
||||
data-full="{{ url_for('static', filename=image.filepath) }}"
|
||||
style="background-image: url('{{ url_for('static', filename=image.filepath) }}');"
|
||||
data-full="{{ url_for('static', filename=image.filepath) }}"
|
||||
data-filename="{{ image.filename }}">
|
||||
</div>
|
||||
<a href="{{ url_for('main.artist_gallery', artist_name=image.artist) }}">{{ image.artist }}</a>
|
||||
<span class="image-date">
|
||||
{{ image.taken_at or image.imported_at | datetimeformat }}
|
||||
</span>
|
||||
<div class="gallery-thumb img-clickable"
|
||||
data-full="{{ url_for('main.serve_image', filename=image.filepath | replace('/images/', '')) }}"
|
||||
style="background-image: url('{{ url_for('main.serve_image', filename=image.filepath | replace('/images/', '')) }}');"
|
||||
data-filename="{{ image.filename }}">
|
||||
</div>
|
||||
<a href="{{ url_for('main.artist_gallery', artist_name=image.artist) }}">{{ image.artist }}</a>
|
||||
<span class="image-date">
|
||||
{{ (image.taken_at or image.imported_at) | datetimeformat }}
|
||||
</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Pagination Controls -->
|
||||
<div class="pagination-container">
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block title %}Artists{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 style="text-align:center; margin-top: 1rem;">Artists</h1>
|
||||
|
||||
<div class="artist-grid">
|
||||
{% for artist in artist_data %}
|
||||
<a href="{{ url_for('main.artist_gallery', artist_name=artist.name) }}" class="artist-card-link">
|
||||
<div class="artist-card">
|
||||
<h3>{{ artist.name }}</h3>
|
||||
<div class="artist-preview">
|
||||
{% for image in artist.images %}
|
||||
<div class="artist-thumb"
|
||||
style="background-image: url('{{ url_for('main.serve_image', filename=image.filepath | replace('/images/', '')) }}');">
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -8,18 +8,15 @@
|
||||
{% for image in images %}
|
||||
<div class="gallery-item">
|
||||
<div class="gallery-thumb img-clickable"
|
||||
data-full="{{ url_for('static', filename=image.filepath) }}"
|
||||
style="background-image: url('{{ url_for('static', filename=image.filepath) }}');"
|
||||
data-full="{{ url_for('static', filename=image.filepath) }}"
|
||||
data-full="{{ url_for('main.serve_image', filename=image.filepath | replace('/images/', '')) }}"
|
||||
style="background-image: url('{{ url_for('main.serve_image', filename=image.filepath | replace('/images/', '')) }}');"
|
||||
data-filename="{{ image.filename }}">
|
||||
</div>
|
||||
<a href="{{ url_for('main.artist_gallery', artist_name=image.artist) }}">{{ image.artist }}</a>
|
||||
<span class="image-date">
|
||||
{{ image.taken_at or image.imported_at | datetimeformat }}
|
||||
{{ (image.taken_at or image.imported_at) | datetimeformat }}
|
||||
</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block title %}Home{% endblock %}
|
||||
{% block title %}Welcome{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Welcome to the Flask Template App!</h1>
|
||||
<p>This is the index page.</p>
|
||||
<div class="index-hero" style="background-image: url('{{ background_url }}');">
|
||||
<div class="index-overlay">
|
||||
<div class="index-message">
|
||||
<h1>Private Image Repository</h1>
|
||||
<p>This is a private image collection. You must register for access to browse and contribute.</p>
|
||||
<a href="{{ url_for('auth.register') }}" class="btn primary-btn">Register</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Flask Template App</title>
|
||||
<title>ImageRepo - {% block title %}{% endblock %}</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
@@ -18,6 +18,7 @@
|
||||
{% endif %}
|
||||
|
||||
{% if current_user.is_authenticated %}
|
||||
<a class="nav-button" href="{{ url_for('main.artist_list') }}">Artists</a>
|
||||
<a class="nav-button" href="{{ url_for('auth.logout') }}">Logout</a>
|
||||
{% else %}
|
||||
<a class="nav-button" href="{{ url_for('auth.login') }}">Login</a>
|
||||
|
||||
@@ -1,10 +1,30 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block title %}Login{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Login</h2>
|
||||
<form method="POST">
|
||||
<div class="form-container">
|
||||
<h2>Login</h2>
|
||||
<form method="POST" class="form-card">
|
||||
{{ form.hidden_tag() }}
|
||||
{{ form.email(size=32, placeholder=form.email.label.text) }}<br>
|
||||
{{ form.password(size=32, placeholder=form.password.label.text) }}<br>
|
||||
{{ form.submit() }}
|
||||
</form>
|
||||
|
||||
<div class="form-group">
|
||||
{{ form.email.label(class="form-label") }}
|
||||
{{ form.email(class="form-input", placeholder=form.email.label.text) }}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
{{ form.password.label(class="form-label") }}
|
||||
{{ form.password(class="form-input", placeholder=form.password.label.text) }}
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
{{ form.submit(class="form-button") }}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p class="form-footer-text">
|
||||
Don’t have an account? <a href="{{ url_for('auth.register') }}">Register here</a>.
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
+43
-30
@@ -1,39 +1,52 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block title %}Register{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Register</h2>
|
||||
<form method="POST">
|
||||
<div class="form-container">
|
||||
<h2>Register</h2>
|
||||
<form method="POST" class="form-card">
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
<p>
|
||||
{{ form.username(size=32, placeholder=form.username.label.text) }}
|
||||
{% for error in form.username.errors %}
|
||||
<span class="error">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</p>
|
||||
<div class="form-group">
|
||||
{{ form.username.label(class="form-label") }}
|
||||
{{ form.username(class="form-input", placeholder=form.username.label.text) }}
|
||||
{% for error in form.username.errors %}
|
||||
<span class="error">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<p>
|
||||
{{ form.email(size=32, placeholder=form.email.label.text) }}
|
||||
{% for error in form.email.errors %}
|
||||
<span class="error">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</p>
|
||||
<div class="form-group">
|
||||
{{ form.email.label(class="form-label") }}
|
||||
{{ form.email(class="form-input", placeholder=form.email.label.text) }}
|
||||
{% for error in form.email.errors %}
|
||||
<span class="error">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<p>
|
||||
{{ form.password(size=32, placeholder=form.password.label.text) }}
|
||||
{% for error in form.password.errors %}
|
||||
<span class="error">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</p>
|
||||
<div class="form-group">
|
||||
{{ form.password.label(class="form-label") }}
|
||||
{{ form.password(class="form-input", placeholder=form.password.label.text) }}
|
||||
{% for error in form.password.errors %}
|
||||
<span class="error">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<p>
|
||||
{{ form.confirm_password(size=32, placeholder=form.confirm_password.label.text) }}
|
||||
{% for error in form.confirm_password.errors %}
|
||||
<span class="error">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</p>
|
||||
<div class="form-group">
|
||||
{{ form.confirm_password.label(class="form-label") }}
|
||||
{{ form.confirm_password(class="form-input", placeholder=form.confirm_password.label.text) }}
|
||||
{% for error in form.confirm_password.errors %}
|
||||
<span class="error">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<p>
|
||||
{{ form.submit() }}
|
||||
</p>
|
||||
</form>
|
||||
<div class="form-actions">
|
||||
{{ form.submit(class="form-button") }}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p class="form-footer-text">
|
||||
Already have an account? <a href="{{ url_for('auth.login') }}">Login here</a>.
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
+15
-15
@@ -1,25 +1,25 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block title %}Settings{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h2>Image Management</h2>
|
||||
<p>Use the options below to manage image data.</p>
|
||||
<h1 style="text-align:center; margin-top: 1rem;">Settings</h1>
|
||||
|
||||
<div class="mb-3">
|
||||
<a href="{{ url_for('main.trigger_image_import') }}" class="btn btn-primary">
|
||||
📥 Import Images
|
||||
</a>
|
||||
</div>
|
||||
<div class="settings-container">
|
||||
<div class="settings-section">
|
||||
<h2>Admin Actions</h2>
|
||||
<div class="settings-actions">
|
||||
<form action="{{ url_for('main.trigger_image_import') }}" method="get">
|
||||
<button type="submit" class="btn primary-btn">Trigger Image Import</button>
|
||||
</form>
|
||||
|
||||
<div class="mb-3">
|
||||
<form method="POST" action="{{ url_for('main.reset_db') }}"
|
||||
onsubmit="return confirm('Are you sure you want to delete all image records?');">
|
||||
<button type="submit" class="btn btn-danger">
|
||||
🗑️ Reset Database
|
||||
</button>
|
||||
<form action="{{ url_for('main.reset_db') }}" method="post" onsubmit="return confirm('Are you sure you want to delete all image records? This action cannot be undone.');">
|
||||
<button type="submit" class="btn danger-btn">Reset Image Database</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-info">
|
||||
<p>Use these tools to manually trigger an import or reset the image database. Resetting the database does not delete the actual image files.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# app/utils/image_importer.py
|
||||
|
||||
from datetime import datetime
|
||||
import os, shutil, hashlib
|
||||
import os, shutil, hashlib, time
|
||||
from PIL import Image
|
||||
import exifread
|
||||
import mimetypes
|
||||
@@ -60,6 +60,9 @@ def import_images_task(source_dir, dest_dir):
|
||||
db.session.add(record)
|
||||
imported.append(filename)
|
||||
|
||||
# Add delay between each image
|
||||
time.sleep(1)
|
||||
|
||||
db.session.commit()
|
||||
return f"Imported {len(imported)} images."
|
||||
|
||||
|
||||
@@ -5,26 +5,26 @@ class Config:
|
||||
DB_TYPE = os.environ.get('DB_TYPE', 'sqlite').lower() # 'sqlite' or 'postgresql'
|
||||
|
||||
if DB_TYPE == 'postgresql':
|
||||
DB_USER = os.environ.get('DB_USER', 'postgres')
|
||||
DB_USER = os.environ.get('DB_USER', 'imagerepo')
|
||||
DB_PASS = os.environ.get('DB_PASS', 'postgres')
|
||||
DB_HOST = os.environ.get('DB_HOST', 'localhost')
|
||||
DB_HOST = os.environ.get('DB_HOST', 'postgres')
|
||||
DB_PORT = os.environ.get('DB_PORT', '5432')
|
||||
DB_NAME = os.environ.get('DB_NAME', 'postgres')
|
||||
DB_NAME = os.environ.get('DB_NAME', 'imagerepo')
|
||||
|
||||
SQLALCHEMY_DATABASE_URI = (
|
||||
f'postgresql://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}'
|
||||
)
|
||||
|
||||
elif DB_TYPE == 'sqlite':
|
||||
BASEDIR = os.path.abspath(os.path.dirname(__file__))
|
||||
DB_DIR = '/db'
|
||||
os.makedirs(DB_DIR, exist_ok=True) # only works if /db is writable
|
||||
DB_NAME = os.environ.get('DB_NAME', 'app.db')
|
||||
SQLALCHEMY_DATABASE_URI = f'sqlite:///{os.path.join(BASEDIR, DB_NAME)}'
|
||||
SQLALCHEMY_DATABASE_URI = f'sqlite:///{os.path.join(DB_DIR, DB_NAME)}'
|
||||
|
||||
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unsupported DB_TYPE '{DB_TYPE}'. Use 'sqlite' or 'postgresql'.")
|
||||
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
|
||||
CELERY_BROKER_URL = 'redis://localhost:6379/0'
|
||||
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
|
||||
|
||||
|
||||
+4
-3
@@ -4,7 +4,8 @@ services:
|
||||
ports:
|
||||
- "5000:5000"
|
||||
volumes:
|
||||
- ./:/app
|
||||
- ./import:/import
|
||||
- ./images:/images
|
||||
- ./imagerepo/db/:/db # SQLite db storage
|
||||
- ./imagerepo/images:/images # where the app will store it's imported images
|
||||
- ./import:/import # where it will import images from
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ echo "Applying database migrations..."
|
||||
flask db upgrade
|
||||
|
||||
# Start periodic image import in background
|
||||
echo "Starting 2h import loop"
|
||||
echo "import worker waiting for flag"
|
||||
python image_import_worker.py &
|
||||
|
||||
# Start the Flask app
|
||||
|
||||
+28
-10
@@ -1,20 +1,38 @@
|
||||
# image_import_loop.py
|
||||
import time
|
||||
import os
|
||||
from app import create_app
|
||||
from app.utils.image_importer import import_images_task
|
||||
|
||||
TRIGGER_FLAG = "/import/trigger.flag"
|
||||
SOURCE_DIR = "/import"
|
||||
DEST_DIR = "/images"
|
||||
CHECK_INTERVAL = 10 # seconds
|
||||
|
||||
app = create_app()
|
||||
|
||||
def looped_import(source_dir='/import', dest_dir='/images', delay_seconds=7200):
|
||||
def monitor_and_import():
|
||||
with app.app_context():
|
||||
print("[Image Importer] Starting import task...")
|
||||
try:
|
||||
result = import_images_task(source_dir, dest_dir)
|
||||
print(f"[Image Importer] {result}")
|
||||
except Exception as e:
|
||||
print(f"[Image Importer] Error: {e}")
|
||||
print(f"[Image Importer] Sleeping for {delay_seconds / 3600:.1f} hours...\n")
|
||||
time.sleep(delay_seconds)
|
||||
print("[Image Importer] Monitoring for trigger flag...")
|
||||
|
||||
while True:
|
||||
if os.path.exists(TRIGGER_FLAG):
|
||||
print("[Image Importer] Trigger flag found. Starting import...")
|
||||
try:
|
||||
result = import_images_task(SOURCE_DIR, DEST_DIR)
|
||||
print(f"[Image Importer] {result}")
|
||||
except Exception as e:
|
||||
print(f"[Image Importer] Error during import: {e}")
|
||||
finally:
|
||||
try:
|
||||
os.remove(TRIGGER_FLAG)
|
||||
print("[Image Importer] Trigger flag cleared.")
|
||||
except Exception as e:
|
||||
print(f"[Image Importer] Failed to remove trigger flag: {e}")
|
||||
else:
|
||||
print("[Image Importer] No trigger flag. Sleeping...")
|
||||
|
||||
time.sleep(CHECK_INTERVAL)
|
||||
|
||||
if __name__ == '__main__':
|
||||
looped_import()
|
||||
monitor_and_import()
|
||||
|
||||
Reference in New Issue
Block a user