Skip to main content

max / makenotwork

Add version labels, remove mandatory version policy Add optional label field to versions (e.g. "macOS (arm)", "Linux (x86_64)") for distinguishing platform variants within a single item. Multiple current versions with labels = platform variants of the same release. Bundles = distinct items grouped together. Remove "version changes are mandatory" warning. Simplify table columns (drop Uploaded, Status; add Label). Migration 108. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-05-10 21:38 UTC
Commit: 46c53592b85b50265b257c777823aa66a3700752
Parent: b2ec2b1
10 files changed, +40 insertions, -24 deletions
@@ -0,0 +1,3 @@
1 + -- Add optional label to versions (e.g. "macOS (arm)", "Linux (x86_64)")
2 + -- for distinguishing platform variants within a single item.
3 + ALTER TABLE versions ADD COLUMN label TEXT;
@@ -211,6 +211,8 @@ pub struct DbVersion {
211 211 pub s3_key: Option<String>,
212 212 /// Malware scan status for uploaded files.
213 213 pub scan_status: super::super::FileScanStatus,
214 + /// Optional short label (e.g. "macOS (arm)", "Linux (x86_64)").
215 + pub label: Option<String>,
214 216 }
215 217
216 218 /// A chapter marker within an audio item.
@@ -20,6 +20,7 @@ pub async fn create_version(
20 20 file_url: Option<&str>,
21 21 file_size_bytes: Option<i64>,
22 22 file_name: Option<&str>,
23 + label: Option<&str>,
23 24 ) -> Result<DbVersion> {
24 25 let mut tx = pool.begin().await?;
25 26
@@ -32,8 +33,8 @@ pub async fn create_version(
32 33 // Create new version as current
33 34 let version = sqlx::query_as::<_, DbVersion>(
34 35 r#"
35 - INSERT INTO versions (item_id, version_number, changelog, file_url, file_size_bytes, file_name, is_current)
36 - VALUES ($1, $2, $3, $4, $5, $6, true)
36 + INSERT INTO versions (item_id, version_number, changelog, file_url, file_size_bytes, file_name, is_current, label)
37 + VALUES ($1, $2, $3, $4, $5, $6, true, $7)
37 38 RETURNING *
38 39 "#,
39 40 )
@@ -43,6 +44,7 @@ pub async fn create_version(
43 44 .bind(file_url)
44 45 .bind(file_size_bytes)
45 46 .bind(file_name)
47 + .bind(label)
46 48 .fetch_one(&mut *tx)
47 49 .await?;
48 50
@@ -202,6 +202,7 @@ pub(super) async fn confirm_upload(
202 202 Some(&req.s3_key),
203 203 Some(file_size_bytes),
204 204 file_name.as_deref(),
205 + None,
205 206 )
206 207 .await?;
207 208 db::scanning::update_version_scan_status(&state.db, version.id, status).await?;
@@ -26,6 +26,7 @@ pub struct CreateVersionRequest {
26 26 pub file_url: Option<String>,
27 27 pub file_size_bytes: Option<i64>,
28 28 pub file_name: Option<String>,
29 + pub label: Option<String>,
29 30 }
30 31
31 32 /// JSON response representing an item version.
@@ -65,6 +66,7 @@ pub(in crate::routes::api) async fn create_version(
65 66 req.file_url.as_deref(),
66 67 req.file_size_bytes,
67 68 req.file_name.as_deref(),
69 + req.label.as_deref(),
68 70 )
69 71 .await?;
70 72
@@ -447,6 +447,7 @@ impl Version {
447 447 is_current: v.is_current,
448 448 has_file,
449 449 file_name: v.file_name.clone(),
450 + label: v.label.clone(),
450 451 }
451 452 }
452 453 }
@@ -732,6 +732,7 @@ pub struct Version {
732 732 pub is_current: bool,
733 733 pub has_file: bool,
734 734 pub file_name: Option<String>,
735 + pub label: Option<String>,
735 736 }
736 737
737 738 /// Row data for displaying a license key in the creator dashboard.
@@ -169,12 +169,14 @@
169 169 if (!versionNumber) { showToast('Please enter a version number.'); return; }
170 170 if (!selectedFile) { showToast('Please select a file to upload.'); return; }
171 171
172 + var label = (document.getElementById('new-version-label') || {}).value || '';
172 173 fetch('/api/items/' + itemId + '/versions', {
173 174 method: 'POST',
174 175 headers: { 'Content-Type': 'application/json', ...csrfHeaders() },
175 176 body: JSON.stringify({
176 177 version_number: versionNumber,
177 - changelog: changelog || null
178 + changelog: changelog || null,
179 + label: label.trim() || null
178 180 })
179 181 })
180 182 .then(function(res) {
@@ -122,8 +122,8 @@
122 122 {% endblock %}
123 123
124 124 {% block scripts %}
125 - <script src="/static/upload.js?v=0514"></script>
126 - <script src="/static/media-picker.js?v=0514"></script>
127 - <script src="/static/item-details.js?v=0514"></script>
128 - <script src="/static/item-upload.js?v=0514"></script>
125 + <script src="/static/upload.js?v=0515"></script>
126 + <script src="/static/media-picker.js?v=0515"></script>
127 + <script src="/static/item-details.js?v=0515"></script>
128 + <script src="/static/item-upload.js?v=0515"></script>
129 129 {% endblock %}
@@ -1,22 +1,18 @@
1 1 <div class="version-upload" id="version-upload" data-item-id="{{ item.id }}">
2 - <div class="warning-box">
3 - Version changes are mandatory: Any published change requires a new version number.
4 - </div>
5 2
6 3 {% if versions.is_empty() %}
7 4 <div style="text-align: center; padding: 2rem 1rem; opacity: 0.6;">
8 - <p>No versions uploaded yet. Create your first version below to publish this item.</p>
5 + <p>No versions uploaded yet. Create your first version below.</p>
9 6 </div>
10 7 {% else %}
11 8 <table class="data-table">
12 9 <thead>
13 10 <tr>
14 11 <th>Version</th>
15 - <th>Uploaded</th>
16 - <th>Files</th>
12 + <th>Label</th>
13 + <th>File</th>
17 14 <th>Size</th>
18 15 <th>Downloads</th>
19 - <th>Status</th>
20 16 <th>Actions</th>
21 17 </tr>
22 18 </thead>
@@ -24,11 +20,10 @@
24 20 {% for version in versions %}
25 21 <tr>
26 22 <td><span class="badge{% if version.is_current %} current{% endif %}">v{{ version.number }}</span></td>
27 - <td>{{ version.uploaded_date }}</td>
28 - <td>{% if version.has_file %}{% match version.file_name %}{% when Some with (name) %}{{ name }}{% when None %}1 file{% endmatch %}{% else %}No file{% endif %}</td>
23 + <td style="font-size: 0.85rem;">{% if let Some(label) = version.label %}{{ label }}{% endif %}</td>
24 + <td style="font-size: 0.85rem;">{% if version.has_file %}{% match version.file_name %}{% when Some with (name) %}{{ name }}{% when None %}1 file{% endmatch %}{% else %}<span style="opacity: 0.5;">No file</span>{% endif %}</td>
29 25 <td>{{ version.size }}</td>
30 26 <td>{{ version.downloads }}</td>
31 - <td>{{ version.status }}</td>
32 27 <td>
33 28 {% if version.has_file %}
34 29 <button class="secondary download-version-btn" style="padding: 0.4rem 0.8rem; font-size: 0.85rem;"
@@ -48,19 +43,26 @@
48 43
49 44 <!-- New Version Form -->
50 45 <div id="new-version-form">
51 - <div class="form-group">
52 - <label for="new-version-number">New Version Number</label>
53 - <input type="text" id="new-version-number" placeholder="e.g., 1.3.0">
46 + <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;">
47 + <div class="form-group">
48 + <label for="new-version-number">Version Number</label>
49 + <input type="text" id="new-version-number" placeholder="e.g., 1.0">
50 + </div>
51 +
52 + <div class="form-group">
53 + <label for="new-version-label">Label (optional)</label>
54 + <input type="text" id="new-version-label" placeholder="e.g., macOS (arm), Linux (x86_64)">
55 + </div>
54 56 </div>
55 57
56 58 <div class="form-group">
57 - <label for="version-changelog">Version Notes / Changelog</label>
58 - <textarea id="version-changelog" placeholder="Describe what changed in this version..."></textarea>
59 + <label for="version-changelog">Notes (optional)</label>
60 + <textarea id="version-changelog" rows="2" placeholder="What changed in this version..."></textarea>
59 61 </div>
60 62
61 63 <div class="file-upload-area" id="version-dropzone">
62 64 <div class="upload-text">Drop file here or click to upload</div>
63 - <div class="upload-hint">Supported: ZIP, DMG, EXE, AppImage, DEB, tar.gz, CLAP, VST3 up to 500 MB</div>
65 + <div class="upload-hint">ZIP, DMG, EXE, AppImage, DEB, tar.gz, CLAP, VST3 up to 500 MB</div>
64 66 <input type="file" id="version-file-input" style="display: none;"
65 67 accept=".zip,.dmg,.exe,.appimage,.deb,.tar.gz,.clap,.vst3">
66 68 </div>
@@ -72,7 +74,7 @@
72 74 <div class="hidden" id="existing-version-upload">
73 75 <div class="file-upload-area" id="existing-version-dropzone">
74 76 <div class="upload-text">Drop file to upload for this version</div>
75 - <div class="upload-hint">Supported: ZIP, DMG, EXE, AppImage, DEB, tar.gz, CLAP, VST3</div>
77 + <div class="upload-hint">ZIP, DMG, EXE, AppImage, DEB, tar.gz, CLAP, VST3</div>
76 78 <input type="file" id="existing-version-file-input" style="display: none;"
77 79 accept=".zip,.dmg,.exe,.appimage,.deb,.tar.gz,.clap,.vst3">
78 80 </div>