Skip to content

Commit ebdac37

Browse files
authored
Merge pull request #1131 from Freika/dev
0.25.10
2 parents 339a4d6 + e1f3d18 commit ebdac37

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+942
-440
lines changed

.app_version

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.25.9
1+
0.25.10

.github/workflows/build_and_push.yml

+7
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ jobs:
3636
- name: Install dependencies
3737
run: npm install
3838

39+
- name: Docker meta
40+
id: meta
41+
uses: docker/metadata-action@v5
42+
with:
43+
images: freikin/dawarich
44+
3945
- name: Login to Docker Hub
4046
uses: docker/login-action@v3.1.0
4147
with:
@@ -67,6 +73,7 @@ jobs:
6773
file: ./docker/Dockerfile.dev
6874
push: true
6975
tags: ${{ steps.docker_meta.outputs.tags }}
76+
labels: ${{ steps.meta.outputs.labels }}
7077
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
7178
cache-from: type=local,src=/tmp/.buildx-cache
7279
cache-to: type=local,dest=/tmp/.buildx-cache

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,7 @@
6868

6969
/config/credentials/production.key
7070
/config/credentials/production.yml.enc
71+
/config/credentials/staging.key
72+
/config/credentials/staging.yml.enc
7173

7274
Makefile

CHANGELOG.md

+18
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/)
55
and this project adheres to [Semantic Versioning](http://semver.org/).
66

7+
# 0.25.10 - 2025-05-02
8+
9+
## Added
10+
11+
- Vector maps are supported in non-self-hosted mode.
12+
- Credentials for Sidekiq UI are now being set via environment variables: `SIDEKIQ_USERNAME` and `SIDEKIQ_PASSWORD`. Default credentials are `sidekiq` and `password`. If you don't set them, in self-hosted mode, Sidekiq UI will not be protected by basic auth.
13+
- New import page now shows progress of the upload.
14+
15+
## Changed
16+
17+
- Datetime is now being displayed with seconds in the Points page. #1088
18+
- Imported files are now being uploaded via direct uploads.
19+
- `/api/v1/points` endpoint now creates accepted points synchronously.
20+
21+
## Removed
22+
23+
- Sample points are no longer being imported automatically for new users.
24+
725
# 0.25.9 - 2025-04-29
826

927
## Fixed

Gemfile.lock

+1-1
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ GEM
426426
stimulus-rails (1.3.4)
427427
railties (>= 6.0.0)
428428
stringio (3.1.7)
429-
strong_migrations (2.2.0)
429+
strong_migrations (2.3.0)
430430
activerecord (>= 7)
431431
super_diff (0.15.0)
432432
attr_extras (>= 6.2.4)

Procfile

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
web: bundle exec puma -C config/puma.rb
2-
3-
release: bundle exec rails db:migrate
2+
worker: bundle exec sidekiq -C config/sidekiq.yml

app.json

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
{
2+
"name": "dawarich",
3+
"description": "Dawarich",
4+
"buildpacks": [
5+
{ "url": "https://github.com/heroku/heroku-buildpack-nodejs.git" },
6+
{ "url": "https://github.com/heroku/heroku-buildpack-ruby.git" }
7+
],
28
"formation": {
39
"web": {
410
"quantity": 1
511
},
612
"worker": {
7-
"quantity": 0
13+
"quantity": 1
14+
}
15+
},
16+
"scripts": {
17+
"dokku": {
18+
"predeploy": "bundle exec rails db:migrate"
819
}
920
}
1021
}

app/assets/builds/tailwind.css

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controllers/api/v1/points_controller.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ def index
2424
end
2525

2626
def create
27-
Points::CreateJob.perform_later(batch_params, current_api_user.id)
27+
points = Points::Create.new(current_api_user, batch_params).call
2828

29-
render json: { message: 'Points are being processed' }
29+
render json: { data: points }
3030
end
3131

3232
def update

app/controllers/api/v1/subscriptions_controller.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ def callback
1010

1111
render json: { message: 'Subscription updated successfully' }
1212
rescue JWT::DecodeError => e
13-
Sentry.capture_exception(e)
13+
ExceptionReporter.call(e)
1414
render json: { message: 'Failed to verify subscription update.' }, status: :unauthorized
1515
rescue ArgumentError => e
16-
Sentry.capture_exception(e)
16+
ExceptionReporter.call(e)
1717
render json: { message: 'Invalid subscription data received.' }, status: :unprocessable_entity
1818
end
1919
end

app/controllers/exports_controller.rb

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ def create
2525
rescue StandardError => e
2626
export&.destroy
2727

28+
ExceptionReporter.call(e)
29+
2830
redirect_to exports_url, alert: "Export failed to initiate: #{e.message}", status: :unprocessable_entity
2931
end
3032

app/controllers/imports_controller.rb

+46-12
Original file line numberDiff line numberDiff line change
@@ -31,26 +31,43 @@ def update
3131
end
3232

3333
def create
34-
files = import_params[:files].reject(&:blank?)
34+
files_params = params.dig(:import, :files)
35+
raw_files = Array(files_params).reject(&:blank?)
3536

36-
files.each do |file|
37-
import = current_user.imports.build(
38-
name: file.original_filename,
39-
source: params[:import][:source]
40-
)
37+
if raw_files.empty?
38+
redirect_to new_import_path, alert: 'No files were selected for upload', status: :unprocessable_entity
39+
return
40+
end
41+
42+
created_imports = []
4143

42-
import.file.attach(io: file, filename: file.original_filename, content_type: file.content_type)
44+
raw_files.each do |item|
45+
next if item.is_a?(ActionDispatch::Http::UploadedFile)
4346

44-
import.save!
47+
import = create_import_from_signed_id(item)
48+
created_imports << import if import.present?
4549
end
4650

47-
redirect_to imports_url, notice: "#{files.size} files are queued to be imported in background", status: :see_other
51+
if created_imports.any?
52+
redirect_to imports_url,
53+
notice: "#{created_imports.size} files are queued to be imported in background",
54+
status: :see_other
55+
else
56+
redirect_to new_import_path,
57+
alert: 'No valid file references were found. Please upload files using the file selector.',
58+
status: :unprocessable_entity
59+
end
4860
rescue StandardError => e
49-
Import.where(user: current_user, name: files.map(&:original_filename)).destroy_all
61+
if created_imports.present?
62+
import_ids = created_imports.map(&:id).compact
63+
Import.where(id: import_ids).destroy_all if import_ids.any?
64+
end
5065

51-
flash.now[:error] = e.message
66+
Rails.logger.error "Import error: #{e.message}"
67+
Rails.logger.error e.backtrace.join("\n")
68+
ExceptionReporter.call(e)
5269

53-
redirect_to new_import_path, notice: e.message, status: :unprocessable_entity
70+
redirect_to new_import_path, alert: e.message, status: :unprocessable_entity
5471
end
5572

5673
def destroy
@@ -68,4 +85,21 @@ def set_import
6885
def import_params
6986
params.require(:import).permit(:source, files: [])
7087
end
88+
89+
def create_import_from_signed_id(signed_id)
90+
Rails.logger.debug "Creating import from signed ID: #{signed_id[0..20]}..."
91+
92+
blob = ActiveStorage::Blob.find_signed(signed_id)
93+
94+
import = current_user.imports.build(
95+
name: blob.filename.to_s,
96+
source: params[:import][:source]
97+
)
98+
99+
import.file.attach(blob)
100+
101+
import.save!
102+
103+
import
104+
end
71105
end

app/helpers/application_helper.rb

+11
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,17 @@ def human_datetime(datetime)
126126
)
127127
end
128128

129+
def human_datetime_with_seconds(datetime)
130+
return unless datetime
131+
132+
content_tag(
133+
:span,
134+
datetime.strftime('%e %b %Y, %H:%M:%S'),
135+
class: 'tooltip',
136+
data: { tip: datetime.iso8601 }
137+
)
138+
end
139+
129140
def speed_text_color(speed)
130141
return 'text-default' if speed.to_i >= 0
131142

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { Controller } from "@hotwired/stimulus"
2+
import { DirectUpload } from "@rails/activestorage"
3+
4+
export default class extends Controller {
5+
static targets = ["input", "progress", "progressBar", "submit", "form"]
6+
static values = {
7+
url: String
8+
}
9+
10+
connect() {
11+
this.inputTarget.addEventListener("change", this.upload.bind(this))
12+
13+
// Add form submission handler to disable the file input
14+
if (this.hasFormTarget) {
15+
this.formTarget.addEventListener("submit", this.onSubmit.bind(this))
16+
}
17+
}
18+
19+
onSubmit(event) {
20+
if (this.isUploading) {
21+
// If still uploading, prevent submission
22+
event.preventDefault()
23+
console.log("Form submission prevented during upload")
24+
return
25+
}
26+
27+
// Disable the file input to prevent it from being submitted with the form
28+
// This ensures only our hidden inputs with signed IDs are submitted
29+
this.inputTarget.disabled = true
30+
31+
// Check if we have any signed IDs
32+
const signedIds = this.element.querySelectorAll('input[name="import[files][]"][type="hidden"]')
33+
if (signedIds.length === 0) {
34+
event.preventDefault()
35+
console.log("No files uploaded yet")
36+
alert("Please select and upload files first")
37+
} else {
38+
console.log(`Submitting form with ${signedIds.length} uploaded files`)
39+
}
40+
}
41+
42+
upload() {
43+
const files = this.inputTarget.files
44+
if (files.length === 0) return
45+
46+
console.log(`Uploading ${files.length} files`)
47+
this.isUploading = true
48+
49+
// Disable submit button during upload
50+
this.submitTarget.disabled = true
51+
52+
// Always remove any existing progress bar to ensure we create a fresh one
53+
if (this.hasProgressTarget) {
54+
this.progressTarget.remove()
55+
}
56+
57+
// Create a wrapper div for better positioning and visibility
58+
const progressWrapper = document.createElement("div")
59+
progressWrapper.className = "mt-4 mb-6 border p-4 rounded-lg bg-gray-50"
60+
61+
// Add a label
62+
const progressLabel = document.createElement("div")
63+
progressLabel.className = "font-medium mb-2 text-gray-700"
64+
progressLabel.textContent = "Upload Progress"
65+
progressWrapper.appendChild(progressLabel)
66+
67+
// Create a new progress container
68+
const progressContainer = document.createElement("div")
69+
progressContainer.setAttribute("data-direct-upload-target", "progress")
70+
progressContainer.className = "w-full bg-gray-200 rounded-full h-4"
71+
72+
// Create the progress bar fill element
73+
const progressBarFill = document.createElement("div")
74+
progressBarFill.setAttribute("data-direct-upload-target", "progressBar")
75+
progressBarFill.className = "bg-blue-600 h-4 rounded-full transition-all duration-300"
76+
progressBarFill.style.width = "0%"
77+
78+
// Add the fill element to the container
79+
progressContainer.appendChild(progressBarFill)
80+
progressWrapper.appendChild(progressContainer)
81+
progressBarFill.dataset.percentageDisplay = "true"
82+
83+
// Add the progress wrapper AFTER the file input field but BEFORE the submit button
84+
this.submitTarget.parentNode.insertBefore(progressWrapper, this.submitTarget)
85+
86+
console.log("Progress bar created and inserted before submit button")
87+
88+
let uploadCount = 0
89+
const totalFiles = files.length
90+
91+
// Clear any existing hidden fields for files
92+
this.element.querySelectorAll('input[name="import[files][]"][type="hidden"]').forEach(el => {
93+
if (el !== this.inputTarget) {
94+
el.remove()
95+
}
96+
});
97+
98+
Array.from(files).forEach(file => {
99+
console.log(`Starting upload for ${file.name}`)
100+
const upload = new DirectUpload(file, this.urlValue, this)
101+
upload.create((error, blob) => {
102+
uploadCount++
103+
104+
if (error) {
105+
console.error("Error uploading file:", error)
106+
} else {
107+
console.log(`Successfully uploaded ${file.name} with ID: ${blob.signed_id}`)
108+
109+
// Create a hidden field with the correct name
110+
const hiddenField = document.createElement("input")
111+
hiddenField.setAttribute("type", "hidden")
112+
hiddenField.setAttribute("name", "import[files][]")
113+
hiddenField.setAttribute("value", blob.signed_id)
114+
this.element.appendChild(hiddenField)
115+
116+
console.log("Added hidden field with signed ID:", blob.signed_id)
117+
}
118+
119+
// Enable submit button when all uploads are complete
120+
if (uploadCount === totalFiles) {
121+
this.submitTarget.disabled = false
122+
this.isUploading = false
123+
console.log("All uploads completed")
124+
console.log(`Ready to submit with ${this.element.querySelectorAll('input[name="import[files][]"][type="hidden"]').length} files`)
125+
}
126+
})
127+
})
128+
}
129+
130+
directUploadWillStoreFileWithXHR(request) {
131+
request.upload.addEventListener("progress", event => {
132+
if (!this.hasProgressBarTarget) {
133+
console.warn("Progress bar target not found")
134+
return
135+
}
136+
137+
const progress = (event.loaded / event.total) * 100
138+
const progressPercentage = `${progress.toFixed(1)}%`
139+
console.log(`Upload progress: ${progressPercentage}`)
140+
this.progressBarTarget.style.width = progressPercentage
141+
142+
// Update text percentage if exists
143+
const percentageDisplay = this.element.querySelector('[data-percentage-display="true"]')
144+
if (percentageDisplay) {
145+
percentageDisplay.textContent = progressPercentage
146+
}
147+
})
148+
}
149+
}

0 commit comments

Comments
 (0)