Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ bundles
style.*.css
.vagrant
node_modules
muckrock/assets/dist
.settings.sh
webpack-stats.json
npm-debug.log
Expand All @@ -38,3 +39,4 @@ celerybeat-schedule
CLAUDE.md
/data
.env
.claude
2 changes: 1 addition & 1 deletion Procfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
web: bin/start-nginx gunicorn -c config/gunicorn.conf muckrock.wsgi:application
scheduler: celery -A muckrock.core.celery worker -E -B --loglevel=INFO
worker: celery -A muckrock.core.celery worker -E -Q celery,phaxio --loglevel=INFO
release: python manage.py compress --settings=muckrock.settings.compress_production && python manage.py migrate --no-input && python manage.py collectstatic --no-input
release: python manage.py migrate --no-input && python manage.py collectstatic --no-input
10 changes: 10 additions & 0 deletions compose/local/vite.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM node:22 AS base

# Set working directory
WORKDIR /app

# Install node modules
COPY package*.json ./
RUN npm install --include=dev --legacy-peer-deps --ignore-scripts

# Note: vite.config.js and src code will be mounted via volumes
24 changes: 24 additions & 0 deletions local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ services:
env_file:
- ./.envs/.local/.django
- ./.envs/.local/.postgres
ports:
- "8000:80"
command: /start
networks:
default:
Expand Down Expand Up @@ -76,6 +78,28 @@ services:
squarelet_default:
aliases: []

muckrock_vite:
build:
context: .
dockerfile: ./compose/local/vite.Dockerfile
image: muckrock_local_vite
restart: always
ports:
- "4201:4200"
command: "npm run dev"
volumes:
- ".:/app"
- "/app/node_modules"
networks:
default:
aliases:
- internal.dev.muckrock.com
- internal.dev.foiamachine.org
squarelet_default:
aliases:
- internal.dev.muckrock.com
- internal.dev.foiamachine.org

networks:
squarelet_default:
external: true
13 changes: 11 additions & 2 deletions muckrock/assets/entry.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
import 'core-js/stable';
import './js/muckrock.js'
// Vite module preload polyfill
import "vite/modulepreload-polyfill";

// Make jQuery available globally for legacy code FIRST
import jQuery from "jquery";
window.$ = window.jQuery = jQuery;

// Import styles (doesn't depend on jQuery)
import './scss/style.scss'

// Use dynamic import to ensure jQuery is set up before muckrock.js loads
import('./js/muckrock.js')
4 changes: 2 additions & 2 deletions muckrock/assets/js/muckrock.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import '../vendor/formset';
import '../vendor/loupe';
import '../vendor/quicksearch';
import '../vendor/stacktable';
import qq from '../vendor/fine-uploader';
window.qq = qq;
import '../vendor/fine-uploader'; // UMD module, attaches to window.qq
// Note: autocomplete_light scripts are loaded via Django static files in templates

import './account';
import './checkout';
Expand Down
22 changes: 8 additions & 14 deletions muckrock/core/storage.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""
Cache classes that extend S3, for asset compression
Storage classes that extend S3
"""

# Django
from django.conf import settings
from django.core.files.storage import get_storage_class
from django.contrib.staticfiles.storage import StaticFilesStorage

# Third Party
from storages.backends.s3boto3 import S3Boto3Storage
Expand All @@ -14,32 +14,26 @@

class CachedS3Boto3Storage(S3Boto3Storage):
"""
S3 storage backend for static files that saves the files locally, too.
S3 storage backend for static files that also saves files locally.
Files inherit the bucket's public access policy rather than setting per-object ACLs.
This works with modern AWS S3 security settings that block public ACLs.
"""

bucket_name = settings.AWS_STORAGE_BUCKET_NAME

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.local_storage = get_storage_class(
"compressor.storage.CompressorFileStorage"
)()
self.local_storage = StaticFilesStorage()

def save(self, name, content, max_length=None):
# Save to local storage first
# pylint: disable=protected-access
self.local_storage._save(name, content)
# Then save to S3
super().save(name, self.local_storage._open(name), max_length)
return name


class OfflineManifestFileStorage(CachedS3Boto3Storage):
"""Store into the COMPRESS_OUTPUT_DIR"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.location = settings.COMPRESS_OUTPUT_DIR


class MediaRootS3BotoStorage(S3Boto3Storage):
"""
S3 storage backend for user-uploaded media files.
Expand Down
16 changes: 5 additions & 11 deletions muckrock/foiamachine/templates/foiamachine/base/base.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{% load hosts %}
{% load compress %}
{% load static %}
{% load render_bundle from webpack_loader %}
{% load django_vite %}

<!doctype html>
<html>
Expand All @@ -13,14 +12,12 @@
<meta name="description" content="{% block description %}{% endblock %}">
<meta name="author" content="{% block content_author %}FOIA Machine{% endblock %}">
<title>{% block title %}FOIA Machine{% endblock %}</title>
{% compress css %}
<link href="{% static 'vendor/select2/dist/css/select2.css' %}" type="text/css" media="screen" rel="stylesheet" />
<link href="{% static 'admin/css/autocomplete.css' %}" type="text/css" media="screen" rel="stylesheet" />
<link href="{% static 'autocomplete_light/select2.css' %}" type="text/css" media="screen" rel="stylesheet" />
{% vite_asset 'muckrock/foiamachine/assets/entry.js' %}
{% block styles %}
<link href="{% static 'vendor/select2/dist/css/select2.css' %}" type="text/css" media="screen" rel="stylesheet" />
<link href="{% static 'admin/css/autocomplete.css' %}" type="text/css" media="screen" rel="stylesheet" />
<link href="{% static 'autocomplete_light/select2.css' %}" type="text/css" media="screen" rel="stylesheet" />
{% render_bundle 'foiamachine' 'css' %}
{% endblock %}
{% endcompress %}
<link rel="shortcut icon" href="{% static 'foiamachine/icons/favicon.ico' %}" />
<link rel="apple-touch-icon-precomposed" href="{% static 'foiamachine/icons/favicon-152.png' %}">
<link rel="mask-icon" href="{% static 'foiamachine/icons/favicon.svg' %}" color="#fb5238" />
Expand Down Expand Up @@ -77,14 +74,11 @@
</nav>
<!-- Scripts -->

{% compress js %}
{% block scripts %}
{% render_bundle 'foiamachine' 'js' %}
<script type="text/javascript" src="{% static 'vendor/autocomplete_light/select2.full.js' %}"></script>
<script type="text/javascript" src="{% static 'autocomplete_light/autocomplete_light.js' %}"></script>
<script type="text/javascript" src="{% static 'vendor/autocomplete_light/select2.js' %}"></script>
{% endblock %}
{% endcompress %}
{% if request.session.session_state %}
<iframe src="{% url "acct-rp-iframe" %}" frameborder="0" width="0" height="0"></iframe>
{% endif %}
Expand Down
40 changes: 10 additions & 30 deletions muckrock/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,27 +90,12 @@ def boolcheck(setting):
STATIC_ROOT = os.path.join(SITE_ROOT, "static")
MEDIA_ROOT = os.path.join(STATIC_ROOT, "media")
ASSETS_ROOT = os.path.join(SITE_ROOT, "assets")
COMPRESS_ROOT = ASSETS_ROOT

STATICFILES_DIRS = (os.path.join(SITE_ROOT, "assets"),)

WEBPACK_LOADER = {
"DEFAULT": {
"BUNDLE_DIR_NAME": "bundles/",
"STATS_FILE": os.path.join(SITE_ROOT, "assets/webpack-stats.json"),
}
}
# DJANGO_VITE configuration is defined in environment-specific settings
# (local.py, production.py, etc.) to properly reference DEBUG

COMPRESS_OFFLINE = True

COMPRESS_CSS_FILTERS = [
"compressor.filters.css_default.CssAbsoluteFilter",
"compressor.filters.cssmin.CSSMinFilter",
]
# Don't do any JS compression here
# 1. It can cause bugs which means the resulting JS has a syntax error
# 2. We compress the javascript in the webpack config when built for production
COMPRESS_JS_FILTERS = []

THUMBNAIL_CACHE_DIMENSIONS = True

Expand All @@ -122,17 +107,9 @@ def boolcheck(setting):
"staticfiles": {
"BACKEND": "muckrock.core.storage.CachedS3Boto3Storage",
},
"compressor": {
"BACKEND": "muckrock.core.storage.CachedS3Boto3Storage",
},
"compressor-offline": {
"BACKEND": "muckrock.core.storage.OfflineManifestFileStorage",
},
}
THUMBNAIL_DEFAULT_STORAGE = STORAGES["default"]["BACKEND"]
THUMBNAIL_STORAGE = STORAGES["default"]["BACKEND"]
COMPRESS_STORAGE = STORAGES["compressor"]["BACKEND"]
COMPRESS_OFFLINE_MANIFEST_STORAGE = STORAGES["compressor-offline"]["BACKEND"]
CLEAN_S3_ON_FOIA_DELETE = True

# Settings for static bucket storage
Expand All @@ -147,15 +124,20 @@ def boolcheck(setting):
if AWS_S3_CUSTOM_DOMAIN
else f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/"
)
COMPRESS_URL = STATIC_URL
COMPRESS_ENABLED = True
AWS_QUERYSTRING_AUTH = False
AWS_S3_SECURE_URLS = True
AWS_HEADERS = {
"Expires": "Thu, 31 Dec 2099 20:00:00 GMT",
"Cache-Control": "max-age=94608000",
}
# Note: AWS_DEFAULT_ACL is kept for backwards compatibility with other storage classes
# but CachedS3Boto3Storage overrides this to None to work with modern S3 security
AWS_DEFAULT_ACL = os.environ.get("AWS_STORAGE_DEFAULT_ACL", "public-read")
# Modern way to set object parameters for S3
# Works with AWS S3 buckets that have "Block Public Access" enabled
AWS_S3_OBJECT_PARAMETERS = {
"CacheControl": "max-age=94608000",
}
AWS_S3_MAX_MEMORY_SIZE = int(os.environ.get("AWS_S3_MAX_MEMORY_SIZE", 16 * 1024 * 1024))
AWS_S3_MIN_PART_SIZE = int(os.environ.get("AWS_S3_MIN_PART_SIZE", 16 * 1024 * 1024))

Expand Down Expand Up @@ -185,7 +167,6 @@ def boolcheck(setting):
STATICFILES_FINDERS = (
"django.contrib.staticfiles.finders.FileSystemFinder",
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
"compressor.finders.CompressorFinder",
)

TEMPLATES = [
Expand Down Expand Up @@ -274,7 +255,6 @@ def boolcheck(setting):
"django.contrib.humanize",
"django.contrib.staticfiles",
"django.forms",
"compressor",
"corsheaders",
"debug_toolbar",
"django_premailer",
Expand All @@ -293,7 +273,7 @@ def boolcheck(setting):
"storages",
"taggit",
"watson",
"webpack_loader",
"django_vite",
"django_hosts",
"hijack",
"django_filters",
Expand Down
9 changes: 6 additions & 3 deletions muckrock/settings/compress_production.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""
Settings for compressing production assets
NOTE: Django Compressor has been removed and replaced with Vite.
This file is kept for reference but is no longer used.
"""

# pylint: disable=wildcard-import
Expand All @@ -11,7 +13,8 @@
STORAGES["staticfiles"][
"BACKEND"
] = "django.contrib.staticfiles.storage.StaticFilesStorage"
COMPRESS_STORAGE = STORAGES["compressor"]["BACKEND"]
# Django Compressor settings removed - no longer used
# COMPRESS_STORAGE = STORAGES["compressor"]["BACKEND"]
STATIC_URL = "https://cdn.muckrock.com/"
COMPRESS_URL = STATIC_URL
COMPRESS_ENABLED = True
# COMPRESS_URL = STATIC_URL
# COMPRESS_ENABLED = True
8 changes: 8 additions & 0 deletions muckrock/settings/heroku.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@
# MuckRock
from muckrock.settings.base import *

# Vite configuration for production
DJANGO_VITE = {
"default": {
"dev_mode": False,
"manifest_path": os.path.join(SITE_ROOT, "assets/dist/manifest.json"),
}
}

INSTALLED_APPS = ("scout_apm.django",) + INSTALLED_APPS
USE_SCOUT = True

Expand Down
11 changes: 11 additions & 0 deletions muckrock/settings/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@

DEBUG = True

# Vite configuration for local development
DJANGO_VITE = {
"default": {
"dev_mode": DEBUG,
"dev_server_protocol": "http",
"dev_server_host": "localhost",
"dev_server_port": 4201,
"manifest_path": os.path.join(SITE_ROOT, "assets/dist/manifest.json"),
}
}

# Loads static files locally
STORAGES["staticfiles"][
"BACKEND"
Expand Down
8 changes: 8 additions & 0 deletions muckrock/settings/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@
COMPRESS_ENABLED = False
CACHES["default"]["BACKEND"] = "django.core.cache.backends.dummy.DummyCache"

# Vite configuration for tests - use built manifest
DJANGO_VITE = {
"default": {
"dev_mode": False,
"manifest_path": os.path.join(SITE_ROOT, "assets/dist/manifest.json"),
}
}

PASSWORD_HASHERS = ("django.contrib.auth.hashers.MD5PasswordHasher",)

SITE_ID = 1
Expand Down
Loading