mirror of
https://github.com/wahyd4/links.git
synced 2026-08-08 21:04:53 +10:00
Rebase
This commit is contained in:
+83
-9
@@ -16,18 +16,92 @@ class CoreConfig(AppConfig):
|
||||
Initialize APScheduler when Django starts
|
||||
"""
|
||||
from core.scheduler import scheduler, start_scheduler
|
||||
from links.tasks import schedule_pending_pages
|
||||
from links.tasks import (
|
||||
schedule_pending_pages, schedule_pending_screenshots,
|
||||
retry_stuck_image_imports, flush_click_buffer,
|
||||
)
|
||||
from apscheduler.triggers.interval import IntervalTrigger
|
||||
|
||||
|
||||
# Start the scheduler
|
||||
start_scheduler()
|
||||
|
||||
|
||||
# Read intervals from SiteSettings (fall back to 120s if DB not ready)
|
||||
try:
|
||||
from links.models import SiteSettings
|
||||
ss = SiteSettings.get()
|
||||
pages_interval = ss.schedule_pending_pages_interval or 120
|
||||
screenshots_interval = ss.schedule_pending_screenshots_interval or 120
|
||||
except Exception:
|
||||
pages_interval = 120
|
||||
screenshots_interval = 120
|
||||
|
||||
# Add periodic job for checking pending pages
|
||||
if not scheduler.get_job('schedule_pending_pages'):
|
||||
scheduler.add_job(
|
||||
schedule_pending_pages,
|
||||
trigger=IntervalTrigger(seconds=pages_interval),
|
||||
id='schedule_pending_pages',
|
||||
replace_existing=True,
|
||||
)
|
||||
logger.info(f"Scheduled periodic task: schedule_pending_pages (every {pages_interval}s)")
|
||||
|
||||
# Add periodic job for recovering stuck screenshots
|
||||
scheduler.add_job(
|
||||
schedule_pending_screenshots,
|
||||
trigger=IntervalTrigger(seconds=screenshots_interval),
|
||||
id='schedule_pending_screenshots',
|
||||
replace_existing=True,
|
||||
)
|
||||
logger.info(f"Scheduled periodic task: schedule_pending_screenshots (every {screenshots_interval}s)")
|
||||
|
||||
# Add periodic job for retrying stuck image imports (every 5 minutes)
|
||||
scheduler.add_job(
|
||||
retry_stuck_image_imports,
|
||||
trigger=IntervalTrigger(seconds=300),
|
||||
id='retry_stuck_image_imports',
|
||||
replace_existing=True,
|
||||
)
|
||||
logger.info("Scheduled periodic task: retry_stuck_image_imports (every 300s)")
|
||||
|
||||
# Flush Redis-buffered click counts to the database (every 60 seconds)
|
||||
scheduler.add_job(
|
||||
flush_click_buffer,
|
||||
trigger=IntervalTrigger(seconds=60),
|
||||
id='flush_click_buffer',
|
||||
replace_existing=True,
|
||||
)
|
||||
logger.info("Scheduled periodic task: flush_click_buffer (every 60s)")
|
||||
|
||||
# ── nginxmon: monthly geo DB refresh ──────────────────────────────────
|
||||
try:
|
||||
from nginxmon.geo import download_geo_db as refresh_geo_db
|
||||
scheduler.add_job(
|
||||
schedule_pending_pages,
|
||||
trigger=IntervalTrigger(seconds=120),
|
||||
id='schedule_pending_pages',
|
||||
replace_existing=True
|
||||
refresh_geo_db,
|
||||
trigger=IntervalTrigger(days=30),
|
||||
id='refresh_geo_db',
|
||||
replace_existing=True,
|
||||
)
|
||||
logger.info("Scheduled periodic task: schedule_pending_pages")
|
||||
logger.info('nginxmon: scheduled geo DB refresh (every 30 days)')
|
||||
except Exception as exc:
|
||||
logger.warning('nginxmon: geo DB scheduler setup failed: %s', exc)
|
||||
|
||||
# ── routermon ──────────────────────────────────────────────────────────
|
||||
try:
|
||||
from routermon.tasks import cleanup_old_queries
|
||||
from routermon.models import RouterMonSettings
|
||||
from routermon.receiver import start_receiver
|
||||
|
||||
rm_settings = RouterMonSettings.get()
|
||||
if rm_settings.enabled:
|
||||
start_receiver(rm_settings.syslog_port)
|
||||
|
||||
scheduler.add_job(
|
||||
cleanup_old_queries,
|
||||
trigger=IntervalTrigger(hours=6),
|
||||
id='routermon_cleanup',
|
||||
replace_existing=True,
|
||||
)
|
||||
logger.info("routermon: scheduled cleanup job (every 6h)")
|
||||
except Exception as exc:
|
||||
import sys
|
||||
print(f'routermon: startup error (non-fatal): {exc}', file=sys.stderr, flush=True)
|
||||
logger.warning("routermon: startup error (non-fatal): %s", exc)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def build_info(request):
|
||||
return {
|
||||
'BUILD_TIME': getattr(settings, 'BUILD_TIME', ''),
|
||||
'BUILD_VERSION': getattr(settings, 'BUILD_VERSION', ''),
|
||||
}
|
||||
+41
-39
@@ -21,22 +21,13 @@ INSTALLED_APPS = [
|
||||
'simplemde',
|
||||
'markdown', # 只需要基本的markdown包
|
||||
'invest',
|
||||
'netscan',
|
||||
'nginxmon',
|
||||
'routermon',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'core.urls'
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'core.middleware.CustomLocaleMiddleware', # 替换原来的 LocaleMiddleware
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'whitenoise.middleware.WhiteNoiseMiddleware',
|
||||
]
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
@@ -48,6 +39,7 @@ TEMPLATES = [
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'core.context_processors.build_info',
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -57,7 +49,8 @@ TEMPLATES = [
|
||||
|
||||
# 添加以下基本设置(如果尚未存在)
|
||||
SECRET_KEY = 'your-secret-key-here' # 请更改为一个安全的随机值
|
||||
DEBUG = True
|
||||
|
||||
DEBUG = os.environ.get('DEBUG', 'True').lower() in ('true', '1', 'yes')
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
# 数据库设置(使用默认的SQLite配置)
|
||||
@@ -114,6 +107,24 @@ STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
|
||||
|
||||
CSRF_TRUSTED_ORIGINS = os.environ.get('CSRF_TRUSTED_ORIGINS', 'http://localhost:8000').split(',')
|
||||
|
||||
# Redis cache
|
||||
REDIS_URL = os.environ.get('REDIS_URL', 'redis://192.168.1.2:6379/0')
|
||||
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'django_redis.cache.RedisCache',
|
||||
'LOCATION': REDIS_URL,
|
||||
'OPTIONS': {
|
||||
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
|
||||
},
|
||||
'TIMEOUT': 300,
|
||||
}
|
||||
}
|
||||
|
||||
# Store sessions in Redis instead of the database
|
||||
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
|
||||
SESSION_CACHE_ALIAS = 'default'
|
||||
|
||||
# SimpleMDE 配置
|
||||
SIMPLEMDE_OPTIONS = {
|
||||
'placeholder': 'Type here...',
|
||||
@@ -129,6 +140,12 @@ MEDIA_ROOT = os.path.join(BASE_DIR, 'data', 'media')
|
||||
|
||||
MUSIC_ROOT = os.path.join(BASE_DIR, 'data', 'music')
|
||||
|
||||
# File uploads folder — override via FILE_UPLOADS_FOLDER env var.
|
||||
# Defaults to ~/Downloads locally; set to /uploads on k8s.
|
||||
FILE_UPLOADS_FOLDER = os.environ.get('FILE_UPLOADS_FOLDER', os.path.expanduser('~/Downloads'))
|
||||
IMAGES_FOLDER = os.environ.get('IMAGES_FOLDER', os.path.expanduser('~/Pictures'))
|
||||
|
||||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
@@ -150,15 +167,6 @@ LOGGING = {
|
||||
},
|
||||
}
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_RENDERER_CLASSES': [
|
||||
'rest_framework.renderers.JSONRenderer',
|
||||
],
|
||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
||||
'PAGE_SIZE': 10,
|
||||
'UNAUTHENTICATED_USER': None, # 添加这行
|
||||
}
|
||||
|
||||
LANGUAGE_URL_MAP = {
|
||||
'en': 'en',
|
||||
'zh-hans': 'zh',
|
||||
@@ -178,6 +186,7 @@ LOCALE_INDEPENDENT_PATHS = [
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'whitenoise.middleware.WhiteNoiseMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'core.middleware.CustomLocaleMiddleware', # 使用自定义中间件
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
@@ -191,6 +200,8 @@ REST_FRAMEWORK = {
|
||||
'DEFAULT_RENDERER_CLASSES': [
|
||||
'rest_framework.renderers.JSONRenderer',
|
||||
],
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': [],
|
||||
'DEFAULT_PERMISSION_CLASSES': [],
|
||||
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
||||
'PAGE_SIZE': 10,
|
||||
'UNAUTHENTICATED_USER': None,
|
||||
@@ -218,21 +229,12 @@ R2_CUSTOM_DOMAIN = os.environ.get('R2_CUSTOM_DOMAIN')
|
||||
CRAWL4AI_API_URL = os.environ.get('CRAWL4AI_API_URL', 'https://crawl-api.junv.cc')
|
||||
CRAWL4AI_ENABLED = os.environ.get('CRAWL4AI_ENABLED', 'True').lower() in ('true', '1', 'yes')
|
||||
|
||||
# For debugging
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'handlers': {
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'links': {
|
||||
'handlers': ['console'],
|
||||
'level': 'DEBUG',
|
||||
'propagate': True,
|
||||
},
|
||||
},
|
||||
# Build metadata (injected at Docker image build time)
|
||||
BUILD_TIME = os.environ.get('BUILD_TIME', '')
|
||||
BUILD_VERSION = os.environ.get('BUILD_VERSION', '')
|
||||
|
||||
LOGGING['loggers']['links'] = {
|
||||
'handlers': ['console'],
|
||||
'level': 'DEBUG',
|
||||
'propagate': False,
|
||||
}
|
||||
|
||||
+39
-1
@@ -3,15 +3,35 @@ from django.urls import path, include
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from django.views.static import serve
|
||||
from django.views.generic import TemplateView
|
||||
from django.http import FileResponse, Http404
|
||||
from links.views import LinkDetailView, LinkUpdateView, CustomLinkView
|
||||
from links.file_views import PublicFileView, import_image_view
|
||||
from django.urls import path, include, re_path
|
||||
from django.conf.urls.i18n import i18n_patterns
|
||||
import os
|
||||
|
||||
|
||||
def _serve_static_file(filename, content_type):
|
||||
"""Serve a file directly from the committed static/ directory."""
|
||||
def view(request):
|
||||
path = os.path.join(settings.BASE_DIR, 'static', filename)
|
||||
try:
|
||||
return FileResponse(open(path, 'rb'), content_type=content_type)
|
||||
except FileNotFoundError:
|
||||
raise Http404
|
||||
return view
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
# LLM-friendly discovery endpoints — must be FIRST before any catch-all routes
|
||||
path('llms.txt', _serve_static_file('llms.txt', 'text/plain; charset=utf-8'), name='llms-txt'),
|
||||
path('.well-known/ai-plugin.json', _serve_static_file('.well-known/ai-plugin.json', 'application/json'), name='ai-plugin-json'),
|
||||
|
||||
path('admin/', admin.site.urls),
|
||||
# Add API URLs before locale URLs
|
||||
path('api/', include('links.api_urls')), # New line for API routes
|
||||
path('api/invest/', include('invest.urls', namespace='invest-api')),
|
||||
path('api/invest/', include('invest.api_urls')),
|
||||
# Media files
|
||||
path('media/<path:path>', serve, {
|
||||
'document_root': settings.MEDIA_ROOT,
|
||||
@@ -28,6 +48,24 @@ urlpatterns = [
|
||||
path('custom/<slug:alias>/edit/', LinkUpdateView.as_view(), name='custom_link_update'),
|
||||
|
||||
path('invest/', include('invest.urls')),
|
||||
|
||||
# Include netscan and files BEFORE links.urls to prevent the alias catch-all from intercepting them
|
||||
path('ui/netscan/', include('netscan.urls')),
|
||||
path('ui/nginxmon/', include('nginxmon.urls')),
|
||||
path('ui/routermon/', include('routermon.urls')),
|
||||
path('ui/files/', include('links.file_urls')),
|
||||
|
||||
# Import external image by URL — /import/images/<path:image_url> (also plural alias)
|
||||
path('import/images/<path:image_url>', import_image_view, name='import-image'),
|
||||
path('imports/images/<path:image_url>', import_image_view, name='imports-image'),
|
||||
|
||||
# Public file access — /public/files/{uuid}-{filename}
|
||||
re_path(
|
||||
r'^public/files/(?P<pk>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})-(?P<filename>.+)$',
|
||||
PublicFileView.as_view(),
|
||||
name='public-file',
|
||||
),
|
||||
|
||||
# Include main app URLs with locale
|
||||
path('', include('links.urls')),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user