Browse Source

add responsive UI

main
Christoph Marzell 1 month ago
parent
commit
c25b44c175
  1. 2
      Gemfile
  2. 18
      Gemfile.lock
  3. 21
      app/controllers/admin/application_controller.rb
  4. 46
      app/controllers/admin/entries_controller.rb
  5. 46
      app/controllers/admin/users_controller.rb
  6. 10
      app/controllers/entries_controller.rb
  7. 81
      app/dashboards/entry_dashboard.rb
  8. 90
      app/dashboards/user_dashboard.rb
  9. 22
      app/models/entry.rb
  10. 63
      app/views/entries/_form.html.erb
  11. 165
      app/views/entries/index.html.erb
  12. 53
      app/views/layouts/application.html.erb
  13. 18
      config/environments/production.rb
  14. 12
      config/routes.rb
  15. 2
      docker-compose.yml

2
Gemfile

@ -35,7 +35,7 @@ gem "bootsnap", require: false
# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
# gem "image_processing", "~> 1.2"
gem 'administrate'
group :development do
# Use console on exceptions pages [https://github.com/rails/web-console]

18
Gemfile.lock

@ -80,6 +80,11 @@ GEM
mutex_m
securerandom (>= 0.3)
tzinfo (~> 2.0)
administrate (1.0.0)
actionpack (>= 6.0, < 9.0)
actionview (>= 6.0, < 9.0)
activerecord (>= 6.0, < 9.0)
kaminari (~> 1.2.2)
base64 (0.3.0)
bcrypt (3.1.20)
benchmark (0.5.0)
@ -115,6 +120,18 @@ GEM
jbuilder (2.14.1)
actionview (>= 7.0.0)
activesupport (>= 7.0.0)
kaminari (1.2.2)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.2)
kaminari-activerecord (= 1.2.2)
kaminari-core (= 1.2.2)
kaminari-actionview (1.2.2)
actionview
kaminari-core (= 1.2.2)
kaminari-activerecord (1.2.2)
activerecord
kaminari-core (= 1.2.2)
kaminari-core (1.2.2)
logger (1.7.0)
loofah (2.24.1)
crass (~> 1.0.2)
@ -243,6 +260,7 @@ PLATFORMS
x86_64-linux
DEPENDENCIES
administrate
bootsnap
devise
jbuilder

21
app/controllers/admin/application_controller.rb

@ -0,0 +1,21 @@
# All Administrate controllers inherit from this
# `Administrate::ApplicationController`, making it the ideal place to put
# authentication logic or other before_actions.
#
# If you want to add pagination or other controller-level concerns,
# you're free to overwrite the RESTful controller actions.
module Admin
class ApplicationController < Administrate::ApplicationController
before_action :authenticate_admin
def authenticate_admin
redirect_to root_path, alert: "Kein Zugriff!" unless current_user.email =="christoph@marzell.net"
end
# Override this value to specify the number of elements to display at a time
# on index pages. Defaults to 20.
# def records_per_page
# params[:per_page] || 20
# end
end
end

46
app/controllers/admin/entries_controller.rb

@ -0,0 +1,46 @@
module Admin
class EntriesController < Admin::ApplicationController
# Overwrite any of the RESTful controller actions to implement custom behavior
# For example, you may want to send an email after a foo is updated.
#
# def update
# super
# send_foo_updated_email(requested_resource)
# end
# Override this method to specify custom lookup behavior.
# This will be used to set the resource for the `show`, `edit`, and `update`
# actions.
#
# def find_resource(param)
# Foo.find_by!(slug: param)
# end
# The result of this lookup will be available as `requested_resource`
# Override this if you have certain roles that require a subset
# this will be used to set the records shown on the `index` action.
#
# def scoped_resource
# if current_user.super_admin?
# resource_class
# else
# resource_class.with_less_stuff
# end
# end
# Override `resource_params` if you want to transform the submitted
# data before it's persisted. For example, the following would turn all
# empty values into nil values. It uses other APIs such as `resource_class`
# and `dashboard`:
#
# def resource_params
# params.require(resource_class.model_name.param_key).
# permit(dashboard.permitted_attributes(action_name)).
# transform_values { |value| value == "" ? nil : value }
# end
# See https://administrate-demo.herokuapp.com/customizing_controller_actions
# for more information
end
end

46
app/controllers/admin/users_controller.rb

@ -0,0 +1,46 @@
module Admin
class UsersController < Admin::ApplicationController
# Overwrite any of the RESTful controller actions to implement custom behavior
# For example, you may want to send an email after a foo is updated.
#
# def update
# super
# send_foo_updated_email(requested_resource)
# end
# Override this method to specify custom lookup behavior.
# This will be used to set the resource for the `show`, `edit`, and `update`
# actions.
#
# def find_resource(param)
# Foo.find_by!(slug: param)
# end
# The result of this lookup will be available as `requested_resource`
# Override this if you have certain roles that require a subset
# this will be used to set the records shown on the `index` action.
#
# def scoped_resource
# if current_user.super_admin?
# resource_class
# else
# resource_class.with_less_stuff
# end
# end
# Override `resource_params` if you want to transform the submitted
# data before it's persisted. For example, the following would turn all
# empty values into nil values. It uses other APIs such as `resource_class`
# and `dashboard`:
#
# def resource_params
# params.require(resource_class.model_name.param_key).
# permit(dashboard.permitted_attributes(action_name)).
# transform_values { |value| value == "" ? nil : value }
# end
# See https://administrate-demo.herokuapp.com/customizing_controller_actions
# for more information
end
end

10
app/controllers/entries_controller.rb

@ -85,6 +85,14 @@ class EntriesController < ApplicationController
redirect_to entries_path, notice: "Eintrag gelöscht"
end
def export_csv
@entries = current_user.entries.order(date: :desc)
respond_to do |format|
format.csv { send_data @entries.to_csv, filename: "eintraege-#{Date.today}.csv" }
end
end
private
def set_entry
@ -101,5 +109,7 @@ class EntriesController < ApplicationController
:distance_km
)
end
end

81
app/dashboards/entry_dashboard.rb

@ -0,0 +1,81 @@
require "administrate/base_dashboard"
class EntryDashboard < Administrate::BaseDashboard
# ATTRIBUTE_TYPES
# a hash that describes the type of each of the model's fields.
#
# Each different type represents an Administrate::Field object,
# which determines how the attribute is displayed
# on pages throughout the dashboard.
ATTRIBUTE_TYPES = {
id: Field::Number,
date: Field::Date,
distance_km: Field::Number,
entry_art: Field::String,
hours: Field::Number,
minutes: Field::Number,
praktikums_typ: Field::String,
user: Field::BelongsTo,
created_at: Field::DateTime,
updated_at: Field::DateTime,
}.freeze
# COLLECTION_ATTRIBUTES
# an array of attributes that will be displayed on the model's index page.
#
# By default, it's limited to four items to reduce clutter on index pages.
# Feel free to add, remove, or rearrange items.
COLLECTION_ATTRIBUTES = %i[
id
date
distance_km
entry_art
].freeze
# SHOW_PAGE_ATTRIBUTES
# an array of attributes that will be displayed on the model's show page.
SHOW_PAGE_ATTRIBUTES = %i[
id
date
distance_km
entry_art
hours
minutes
praktikums_typ
user
created_at
updated_at
].freeze
# FORM_ATTRIBUTES
# an array of attributes that will be displayed
# on the model's form (`new` and `edit`) pages.
FORM_ATTRIBUTES = %i[
date
distance_km
entry_art
hours
minutes
praktikums_typ
user
].freeze
# COLLECTION_FILTERS
# a hash that defines filters that can be used while searching via the search
# field of the dashboard.
#
# For example to add an option to search for open resources by typing "open:"
# in the search field:
#
# COLLECTION_FILTERS = {
# open: ->(resources) { resources.where(open: true) }
# }.freeze
COLLECTION_FILTERS = {}.freeze
# Overwrite this method to customize how entries are displayed
# across all pages of the admin dashboard.
#
# def display_resource(entry)
# "Entry ##{entry.id}"
# end
end

90
app/dashboards/user_dashboard.rb

@ -0,0 +1,90 @@
require "administrate/base_dashboard"
class UserDashboard < Administrate::BaseDashboard
# ATTRIBUTE_TYPES
# a hash that describes the type of each of the model's fields.
#
# Each different type represents an Administrate::Field object,
# which determines how the attribute is displayed
# on pages throughout the dashboard.
ATTRIBUTE_TYPES = {
id: Field::Number,
email: Field::String,
encrypted_password: Field::String,
entries: Field::HasMany,
remember_created_at: Field::DateTime,
required_hours_matrix: Field::String.with_options(searchable: false),
reset_password_sent_at: Field::DateTime,
reset_password_token: Field::String,
total_required_hours: Field::Number,
weekly_target_hours: Field::Number,
weekly_target_matrix: Field::String.with_options(searchable: false),
created_at: Field::DateTime,
updated_at: Field::DateTime,
}.freeze
# COLLECTION_ATTRIBUTES
# an array of attributes that will be displayed on the model's index page.
#
# By default, it's limited to four items to reduce clutter on index pages.
# Feel free to add, remove, or rearrange items.
COLLECTION_ATTRIBUTES = %i[
id
email
encrypted_password
entries
].freeze
# SHOW_PAGE_ATTRIBUTES
# an array of attributes that will be displayed on the model's show page.
SHOW_PAGE_ATTRIBUTES = %i[
id
email
encrypted_password
entries
remember_created_at
required_hours_matrix
reset_password_sent_at
reset_password_token
total_required_hours
weekly_target_hours
weekly_target_matrix
created_at
updated_at
].freeze
# FORM_ATTRIBUTES
# an array of attributes that will be displayed
# on the model's form (`new` and `edit`) pages.
FORM_ATTRIBUTES = %i[
email
encrypted_password
entries
remember_created_at
required_hours_matrix
reset_password_sent_at
reset_password_token
total_required_hours
weekly_target_hours
weekly_target_matrix
].freeze
# COLLECTION_FILTERS
# a hash that defines filters that can be used while searching via the search
# field of the dashboard.
#
# For example to add an option to search for open resources by typing "open:"
# in the search field:
#
# COLLECTION_FILTERS = {
# open: ->(resources) { resources.where(open: true) }
# }.freeze
COLLECTION_FILTERS = {}.freeze
# Overwrite this method to customize how users are displayed
# across all pages of the admin dashboard.
#
# def display_resource(user)
# "User ##{user.id}"
# end
end

22
app/models/entry.rb

@ -1,4 +1,8 @@
class Entry < ApplicationRecord
require 'csv'
belongs_to :user
validates :date, :hours, :minutes, presence: true
@ -26,7 +30,23 @@ class Entry < ApplicationRecord
def total_minutes
hours * 60 + minutes
end
def self.to_csv
CSV.generate(headers: true, col_sep: ";") do |csv|
csv << %w[Datum Stunden Minuten Typ Art Kilometer User]
all.each do |entry|
csv << [
entry.date,
entry.hours,
entry.minutes,
entry.praktikums_typ,
entry.entry_art,
entry.distance_km,
entry.user.email
]
end
end
end
private
def normalize_time

63
app/views/entries/_form.html.erb

@ -10,37 +10,52 @@
</div>
<% end %>
<div class="mb-3" style="width: 33%">
<%= form.label :date, 'Datum', class: 'form-label' %>
<%= form.text_field :date, class: 'form-control flatpickr', data: { enable_time: false } , value: (form.object.date || Date.today)%>
</div>
<div class="row g-3">
<div class="col-12 col-md-4">
<%= form.label :date, 'Datum', class: 'form-label' %>
<%= form.text_field :date,
class: 'form-control flatpickr',
data: { enable_time: false },
value: (form.object.date || Date.today) %>
</div>
<div class="mb-3" style="width: 33%">
<%= form.label :hours, 'Stunden', class: 'form-label' %>
<%= form.number_field :hours, class: 'form-control', min: 0 , value: form.object.minutes || 2%>
</div>
<div class="col-6 col-md-4">
<%= form.label :hours, 'Stunden', class: 'form-label' %>
<%= form.number_field :hours,
class: 'form-control',
min: 0,
value: form.object.hours || 0 %>
</div>
<div class="mb-3" style="width: 33%">
<%= form.label :minutes, 'Minuten', class: 'form-label' %>
<%= form.number_field :minutes, class: 'form-control', min: 0, max: 59, value: form.object.minutes || 0 %>
</div>
<div class="col-6 col-md-4">
<%= form.label :minutes, 'Minuten', class: 'form-label' %>
<%= form.number_field :minutes,
class: 'form-control',
min: 0, max: 59,
value: form.object.minutes || 0 %>
</div>
<div class="mb-3">
<%= form.label :praktikums_typ, 'Praktikumstyp', class: 'form-label' %>
<%= form.select :praktikums_typ, Entry::PRAKTIKUMSTYPEN, {}, class: 'form-control' %>
</div>
<div class="col-12 col-md-6">
<%= form.label :praktikums_typ, 'Praktikumstyp', class: 'form-label' %>
<%= form.select :praktikums_typ, Entry::PRAKTIKUMSTYPEN, {}, class: 'form-select' %>
</div>
<div class="mb-3">
<%= form.label :entry_art, 'Art', class: 'form-label' %>
<%= form.select :entry_art, Entry::ENTRY_ARTEN, {}, class: 'form-control' %>
</div>
<div class="col-12 col-md-6">
<%= form.label :entry_art, 'Art', class: 'form-label' %>
<%= form.select :entry_art, Entry::ENTRY_ARTEN, {}, class: 'form-select' %>
</div>
<div class="mb-3">
<%= form.label :distance_km, 'Entfernung (km) Gesamt', class: 'form-label' %>
<%= form.number_field :distance_km, class: 'form-control', min: 0 %>
<div class="col-12 col-md-6">
<%= form.label :distance_km, 'Entfernung (km) Gesamt', class: 'form-label' %>
<%= form.number_field :distance_km,
class: 'form-control',
min: 0,
value: form.object.distance_km || 0 %>
</div>
</div>
<div class="mb-3">
<div class="mt-4">
<%= form.submit 'Speichern', class: 'btn btn-primary' %>
<%= link_to 'Zurück', entries_path, class: 'btn btn-secondary ms-2' %>
</div>
<% end %>

165
app/views/entries/index.html.erb

@ -1,95 +1,100 @@
<h1>Meine Einträge</h1>
<div class="container my-4">
<% if notice %>
<p class="alert alert-success"><%= notice %></p>
<% end %>
<h1 class="mb-4">Meine Einträge</h1>
<!-- 🔢 Zusammenfassung -->
<div class="mb-3">
<h5>🚗 Fahrtkosten (Kilometergeld)</h5>
<% @total_kilometer_costs_by_year.each do |year, sum| %>
<p><strong><%= year %>:</strong> <%= number_to_currency(sum, unit: "€", separator: ",", delimiter: ".", precision: 2) %></p>
<% end %>
<p><strong>Gesamtzeit:</strong> <%= @total_minutes / 60 %> h <%= @total_minutes % 60 %> min</p>
<!-- 🔢 Zusammenfassung -->
<div class="mb-4">
<h5 class="mb-3">🚗 Fahrtkosten (Kilometergeld)</h5>
<% @total_kilometer_costs_by_year.each do |year, sum| %>
<p><strong><%= year %>:</strong>
<%= number_to_currency(sum, unit: "€", separator: ",", delimiter: ".", precision: 2) %>
</p>
<% end %>
<p><strong>Gesamtzeit:</strong> <%= @total_minutes / 60 %> h <%= @total_minutes % 60 %> min</p>
</div>
<hr>
<!-- 📊 Übersicht je Kombination -->
<div class="mb-4">
<h4>📊 Übersicht je Kombination</h4>
<div class="table-responsive">
<table class="table table-bordered table-sm table-striped">
<thead class="table-light">
<tr>
<th>Typ</th>
<th>Art</th>
<th>Verbleibend</th>
<th>Soll (h)</th>
<th>Wöchentlich</th>
<th>Vorauss. Ende</th>
</tr>
</thead>
<tbody>
<% ["propädeutikum", "fachspezifikum"].each do |typ| %>
<% ["Praktikum", "Selbsterfahrung", "Supervision"].each do |art| %>
<% remaining = @remaining_minutes_matrix.dig(typ, art) %>
<% soll = current_user.required_hours_matrix.dig(typ, art) %>
<% weekly = current_user.weekly_target_matrix.dig(typ, art) %>
<% ende = @estimated_end_by_typ_art.dig(typ, art) %>
<% if remaining.present? || soll.present? || weekly.present? %>
<tr>
<td><%= typ.capitalize %></td>
<td><%= art %></td>
<td><%= remaining ? "#{remaining / 60} h #{remaining % 60} min" : "—" %></td>
<td><%= soll || "—" %> h</td>
<td><%= weekly || "—" %> h/Woche</td>
<td><%= ende.present? ? ende.strftime("%d.%m.%Y") : "—" %></td>
</tr>
<% end %>
<% end %>
<% end %>
</tbody>
</table>
</div>
<h4 class="mt-4">📊 Übersicht je Kombination</h4>
<%= link_to '➕ Neuer Eintrag', new_entry_path, class: 'btn btn-light mt-3 w-100 w-md-auto' %>
<table class="table table-bordered table-sm table-striped">
<thead class="table-light">
<tr>
<th>Typ</th>
<th>Art</th>
<th>Verbleibend</th>
<th>Soll (h)</th>
<th>Wöchentlich</th>
<th>Vorauss. Ende</th>
</tr>
</thead>
<tbody>
<% ["propädeutikum", "fachspezifikum"].each do |typ| %>
<% ["Praktikum", "Selbsterfahrung", "Supervision"].each do |art| %>
<% remaining = @remaining_minutes_matrix.dig(typ, art) %>
<% soll = current_user.required_hours_matrix.dig(typ, art) %>
<% weekly = current_user.weekly_target_matrix.dig(typ, art) %>
<% ende = @estimated_end_by_typ_art.dig(typ, art) %>
</div>
<% if remaining.present? || soll.present? || weekly.present? %>
<!-- 📋 Tabelle aller Einträge -->
<div class="mb-5">
<h4 class="mb-3">📋 Einträge</h4>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Datum</th>
<th>Zeit</th>
<th>Typ</th>
<th>Art</th>
<th>Kilometer</th>
<th>Pauschale</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<% @entries.each do |entry| %>
<tr>
<td><%= typ.capitalize %></td>
<td><%= art %></td>
<td><%= remaining ? "#{remaining / 60} h #{remaining % 60} min" : "—" %></td>
<td><%= soll || "—" %> h</td>
<td><%= weekly || "—" %> h/Woche</td>
<td><%= ende.present? ? ende.strftime("%d.%m.%Y") : "—" %></td>
<td><%= entry.date.strftime('%d.%m.%Y') %></td>
<td><%= entry.hours.to_i %>h <%= entry.minutes.to_i %>min</td>
<td><%= entry.praktikums_typ %></td>
<td><%= entry.entry_art %></td>
<td><%= entry.distance_km.to_f %> km</td>
<td><%= number_to_currency(entry.kilometer_pauschale, unit: "€", separator: ",", delimiter: ".") %></td>
<td class="text-end">
<%= link_to 'Bearbeiten', edit_entry_path(entry), class: 'btn btn-sm btn-outline-primary' %>
<%= link_to 'Löschen', entry_path(entry), class: 'btn btn-sm btn-outline-danger open-delete-modal' %>
</td>
</tr>
<% end %>
<% end %>
<% end %>
</tbody>
</table>
<%= link_to '➕ Neuer Eintrag', new_entry_path, class: 'btn btn-light mt-3' %>
</tbody>
</table>
</div>
<%= link_to "Export als CSV", export_csv_entries_path(format: :csv), class: "btn btn-outline-secondary mt-3 w-100 w-md-auto" %>
</div>
</div>
<!-- 📋 Tabelle -->
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Datum</th>
<th>Zeit</th>
<th>Typ</th>
<th>Art</th>
<th>Kilometer</th>
<th>Pauschale</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
<% @entries.each do |entry| %>
<tr>
<td><%= entry.date.strftime('%d.%m.%Y') %></td>
<td><%= entry.hours.to_i %>h <%= entry.minutes.to_i %>min</td>
<td><%= entry.praktikums_typ %></td>
<td><%= entry.entry_art %></td>
<td><%= entry.distance_km.to_f %> km</td>
<td><%= number_to_currency(entry.kilometer_pauschale, unit: "€", separator: ",", delimiter: ".") %></td>
<td>
<%= link_to '✏️ Bearbeiten', edit_entry_path(entry), class: 'btn btn-sm btn-primary' %>
<%= link_to '🗑️ Löschen', entry_path(entry), class: 'btn btn-sm btn-danger open-delete-modal' %>
</td>
</tr>
<% end %>
</tbody>
</table>
<!-- 🔒 Modal zur Bestätigung -->
<div class="modal fade" id="deleteConfirmModal" tabindex="-1" aria-labelledby="deleteConfirmModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">

53
app/views/layouts/application.html.erb

@ -1,36 +1,53 @@
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/themes/material_blue.css">
<!-- Bootstrap CSS (optional, falls noch nicht eingebunden) -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Praktikumsuhr</title>
<!-- Bootstrap Bundle JS (inkl. Popper & Modal Support) -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" defer></script>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Flatpickr Theme -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/themes/material_blue.css">
<!-- Custom Styles (optional) -->
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<!-- JS: Bootstrap Bundle (inkl. Popper) -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" defer></script>
<!-- Flatpickr JS -->
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<!-- JS: Flatpickr Init -->
<script defer>
document.addEventListener("DOMContentLoaded", function () {
flatpickr(".flatpickr", {
altInput: true,
altFormat: "d.m.Y",
dateFormat: "Y-m-d"
});
});
</script>
<!-- JavaScript from Rails -->
<%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>
</head>
<%= javascript_include_tag "application.js", "data-turbo-track": "reload", defer: true %>
<body class="container mt-4">
<% if notice %><div class="alert alert-success"><%= notice %></div><% end %>
<% if alert %><div class="alert alert-danger"><%= alert %></div><% end %>
<!-- Flatpickr JS -->
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<% if user_signed_in? %>
<p class="text-end">
Eingeloggt als <%= current_user.email %> |
<%= link_to "Profil", edit_user_registration_path %> |
<%= link_to "Admin", admin_root_path %> |
<%= link_to "Logout", destroy_user_session_path, method: :delete, data: { turbo: false } %>
</p>
<% end %>
<%= yield %>
</body>
<script>
document.addEventListener("DOMContentLoaded", function() {
flatpickr(".flatpickr", {
altInput: true,
altFormat: "d.m.Y",
dateFormat: "Y-m-d"
});
});
</script>

18
config/environments/production.rb

@ -94,4 +94,22 @@ Rails.application.configure do
# ]
# Skip DNS rebinding protection for the default health check endpoint.
# config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
config.action_mailer.default_url_options = {
host: "praktikum.marzell.net",
protocol: "https"
}
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: "smtp.mailgun.org",
port: 587,
domain: "mg.marzell.net",
user_name: "postmaster@mg.marzell.net", # ⬅️ das bekommst du bei Mailgun
password: ENV["MAILGUN_SMTP_PASSWORD"], # ⬅️ sicher via ENV setzen
authentication: "plain",
enable_starttls_auto: true
}
end

12
config/routes.rb

@ -1,5 +1,15 @@
Rails.application.routes.draw do
resources :entries
namespace :admin do
resources :entries
resources :users
root to: "entries#index"
end
resources :entries do
collection do
get :export_csv
end
end
resource :user_goal, only: [:update]
root 'entries#index'
devise_for :users, controllers: {

2
docker-compose.yml

@ -22,7 +22,7 @@ services:
web:
build: .
restart: unless-stopped
command: bash -c "bundle install && rm -f tmp/pids/server.pid && bundle exec rails db:create db:migrate db:seed && bundle exec rails s -p 3000 -b '0.0.0.0'"
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -b 0.0.0.0"
volumes:
- .:/app
ports:

Loading…
Cancel
Save