Compare commits
No commits in common. "main" and "with-bulma" have entirely different histories.
main
...
with-bulma
73 changed files with 318 additions and 1911 deletions
6
Gemfile
6
Gemfile
|
|
@ -18,7 +18,7 @@ gem "stimulus-rails"
|
||||||
gem "jbuilder"
|
gem "jbuilder"
|
||||||
|
|
||||||
# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
|
# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
|
||||||
gem "bcrypt", "~> 3.1.7"
|
# gem "bcrypt", "~> 3.1.7"
|
||||||
|
|
||||||
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
||||||
gem "tzinfo-data", platforms: %i[ windows jruby ]
|
gem "tzinfo-data", platforms: %i[ windows jruby ]
|
||||||
|
|
@ -51,7 +51,6 @@ group :development, :test do
|
||||||
gem "rubocop-rails-omakase", require: false
|
gem "rubocop-rails-omakase", require: false
|
||||||
|
|
||||||
gem "rspec-rails", "~> 8.0.0"
|
gem "rspec-rails", "~> 8.0.0"
|
||||||
gem "factory_bot_rails"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
|
|
@ -59,7 +58,6 @@ group :development do
|
||||||
gem "web-console"
|
gem "web-console"
|
||||||
gem "solargraph", require: false
|
gem "solargraph", require: false
|
||||||
gem "solargraph-rails", require: false
|
gem "solargraph-rails", require: false
|
||||||
gem "letter_opener_web"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
|
|
@ -70,5 +68,3 @@ end
|
||||||
|
|
||||||
gem "cssbundling-rails", "~> 1.4"
|
gem "cssbundling-rails", "~> 1.4"
|
||||||
gem "rails-i18n"
|
gem "rails-i18n"
|
||||||
|
|
||||||
gem "haml"
|
|
||||||
|
|
|
||||||
349
Gemfile.lock
349
Gemfile.lock
|
|
@ -1,29 +1,29 @@
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (8.0.4)
|
actioncable (8.0.2)
|
||||||
actionpack (= 8.0.4)
|
actionpack (= 8.0.2)
|
||||||
activesupport (= 8.0.4)
|
activesupport (= 8.0.2)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
actionmailbox (8.0.4)
|
actionmailbox (8.0.2)
|
||||||
actionpack (= 8.0.4)
|
actionpack (= 8.0.2)
|
||||||
activejob (= 8.0.4)
|
activejob (= 8.0.2)
|
||||||
activerecord (= 8.0.4)
|
activerecord (= 8.0.2)
|
||||||
activestorage (= 8.0.4)
|
activestorage (= 8.0.2)
|
||||||
activesupport (= 8.0.4)
|
activesupport (= 8.0.2)
|
||||||
mail (>= 2.8.0)
|
mail (>= 2.8.0)
|
||||||
actionmailer (8.0.4)
|
actionmailer (8.0.2)
|
||||||
actionpack (= 8.0.4)
|
actionpack (= 8.0.2)
|
||||||
actionview (= 8.0.4)
|
actionview (= 8.0.2)
|
||||||
activejob (= 8.0.4)
|
activejob (= 8.0.2)
|
||||||
activesupport (= 8.0.4)
|
activesupport (= 8.0.2)
|
||||||
mail (>= 2.8.0)
|
mail (>= 2.8.0)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
actionpack (8.0.4)
|
actionpack (8.0.2)
|
||||||
actionview (= 8.0.4)
|
actionview (= 8.0.2)
|
||||||
activesupport (= 8.0.4)
|
activesupport (= 8.0.2)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
rack (>= 2.2.4)
|
rack (>= 2.2.4)
|
||||||
rack-session (>= 1.0.1)
|
rack-session (>= 1.0.1)
|
||||||
|
|
@ -31,35 +31,35 @@ GEM
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
useragent (~> 0.16)
|
useragent (~> 0.16)
|
||||||
actiontext (8.0.4)
|
actiontext (8.0.2)
|
||||||
actionpack (= 8.0.4)
|
actionpack (= 8.0.2)
|
||||||
activerecord (= 8.0.4)
|
activerecord (= 8.0.2)
|
||||||
activestorage (= 8.0.4)
|
activestorage (= 8.0.2)
|
||||||
activesupport (= 8.0.4)
|
activesupport (= 8.0.2)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (8.0.4)
|
actionview (8.0.2)
|
||||||
activesupport (= 8.0.4)
|
activesupport (= 8.0.2)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.11)
|
erubi (~> 1.11)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
activejob (8.0.4)
|
activejob (8.0.2)
|
||||||
activesupport (= 8.0.4)
|
activesupport (= 8.0.2)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (8.0.4)
|
activemodel (8.0.2)
|
||||||
activesupport (= 8.0.4)
|
activesupport (= 8.0.2)
|
||||||
activerecord (8.0.4)
|
activerecord (8.0.2)
|
||||||
activemodel (= 8.0.4)
|
activemodel (= 8.0.2)
|
||||||
activesupport (= 8.0.4)
|
activesupport (= 8.0.2)
|
||||||
timeout (>= 0.4.0)
|
timeout (>= 0.4.0)
|
||||||
activestorage (8.0.4)
|
activestorage (8.0.2)
|
||||||
actionpack (= 8.0.4)
|
actionpack (= 8.0.2)
|
||||||
activejob (= 8.0.4)
|
activejob (= 8.0.2)
|
||||||
activerecord (= 8.0.4)
|
activerecord (= 8.0.2)
|
||||||
activesupport (= 8.0.4)
|
activesupport (= 8.0.2)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
activesupport (8.0.4)
|
activesupport (8.0.2)
|
||||||
base64
|
base64
|
||||||
benchmark (>= 0.3)
|
benchmark (>= 0.3)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
|
|
@ -72,19 +72,18 @@ GEM
|
||||||
securerandom (>= 0.3)
|
securerandom (>= 0.3)
|
||||||
tzinfo (~> 2.0, >= 2.0.5)
|
tzinfo (~> 2.0, >= 2.0.5)
|
||||||
uri (>= 0.13.1)
|
uri (>= 0.13.1)
|
||||||
addressable (2.8.8)
|
addressable (2.8.7)
|
||||||
public_suffix (>= 2.0.2, < 8.0)
|
public_suffix (>= 2.0.2, < 7.0)
|
||||||
ast (2.4.3)
|
ast (2.4.3)
|
||||||
backport (1.2.0)
|
backport (1.2.0)
|
||||||
base64 (0.3.0)
|
base64 (0.3.0)
|
||||||
bcrypt (3.1.21)
|
bcrypt_pbkdf (1.1.1)
|
||||||
bcrypt_pbkdf (1.1.2)
|
benchmark (0.4.1)
|
||||||
benchmark (0.5.0)
|
bigdecimal (3.2.2)
|
||||||
bigdecimal (4.0.1)
|
|
||||||
bindex (0.8.1)
|
bindex (0.8.1)
|
||||||
bootsnap (1.23.0)
|
bootsnap (1.18.6)
|
||||||
msgpack (~> 1.2)
|
msgpack (~> 1.2)
|
||||||
brakeman (8.0.2)
|
brakeman (7.0.2)
|
||||||
racc
|
racc
|
||||||
builder (3.3.0)
|
builder (3.3.0)
|
||||||
capybara (3.40.0)
|
capybara (3.40.0)
|
||||||
|
|
@ -96,57 +95,45 @@ GEM
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
regexp_parser (>= 1.5, < 3.0)
|
regexp_parser (>= 1.5, < 3.0)
|
||||||
xpath (~> 3.2)
|
xpath (~> 3.2)
|
||||||
childprocess (5.1.0)
|
concurrent-ruby (1.3.5)
|
||||||
logger (~> 1.5)
|
connection_pool (2.5.3)
|
||||||
concurrent-ruby (1.3.6)
|
|
||||||
connection_pool (3.0.2)
|
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
cssbundling-rails (1.4.3)
|
cssbundling-rails (1.4.3)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
date (3.5.1)
|
date (3.4.1)
|
||||||
debug (1.11.1)
|
debug (1.11.0)
|
||||||
irb (~> 1.10)
|
irb (~> 1.10)
|
||||||
reline (>= 0.3.8)
|
reline (>= 0.3.8)
|
||||||
diff-lcs (1.6.2)
|
diff-lcs (1.6.2)
|
||||||
dotenv (3.2.0)
|
dotenv (3.1.8)
|
||||||
drb (2.2.3)
|
drb (2.2.3)
|
||||||
ed25519 (1.4.0)
|
ed25519 (1.4.0)
|
||||||
erb (6.0.1)
|
erb (5.0.1)
|
||||||
erubi (1.13.1)
|
erubi (1.13.1)
|
||||||
et-orbi (1.4.0)
|
et-orbi (1.2.11)
|
||||||
tzinfo
|
tzinfo
|
||||||
factory_bot (6.5.6)
|
fugit (1.11.1)
|
||||||
activesupport (>= 6.1.0)
|
et-orbi (~> 1, >= 1.2.11)
|
||||||
factory_bot_rails (6.5.1)
|
|
||||||
factory_bot (~> 6.5)
|
|
||||||
railties (>= 6.1.0)
|
|
||||||
fugit (1.12.1)
|
|
||||||
et-orbi (~> 1.4)
|
|
||||||
raabro (~> 1.4)
|
raabro (~> 1.4)
|
||||||
globalid (1.3.0)
|
globalid (1.2.1)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 6.1)
|
||||||
haml (7.2.0)
|
i18n (1.14.7)
|
||||||
temple (>= 0.8.2)
|
|
||||||
thor
|
|
||||||
tilt
|
|
||||||
i18n (1.14.8)
|
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
importmap-rails (2.2.3)
|
importmap-rails (2.1.0)
|
||||||
actionpack (>= 6.0.0)
|
actionpack (>= 6.0.0)
|
||||||
activesupport (>= 6.0.0)
|
activesupport (>= 6.0.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
io-console (0.8.2)
|
io-console (0.8.0)
|
||||||
irb (1.17.0)
|
irb (1.15.2)
|
||||||
pp (>= 0.6.0)
|
pp (>= 0.6.0)
|
||||||
prism (>= 1.3.0)
|
|
||||||
rdoc (>= 4.0.0)
|
rdoc (>= 4.0.0)
|
||||||
reline (>= 0.4.2)
|
reline (>= 0.4.2)
|
||||||
jaro_winkler (1.7.0)
|
jaro_winkler (1.6.1)
|
||||||
jbuilder (2.14.1)
|
jbuilder (2.13.0)
|
||||||
actionview (>= 7.0.0)
|
actionview (>= 5.0.0)
|
||||||
activesupport (>= 7.0.0)
|
activesupport (>= 5.0.0)
|
||||||
json (2.18.1)
|
json (2.12.2)
|
||||||
kamal (2.10.1)
|
kamal (2.7.0)
|
||||||
activesupport (>= 7.0)
|
activesupport (>= 7.0)
|
||||||
base64 (~> 0.2)
|
base64 (~> 0.2)
|
||||||
bcrypt_pbkdf (~> 1.0)
|
bcrypt_pbkdf (~> 1.0)
|
||||||
|
|
@ -157,40 +144,27 @@ GEM
|
||||||
sshkit (>= 1.23.0, < 2.0)
|
sshkit (>= 1.23.0, < 2.0)
|
||||||
thor (~> 1.3)
|
thor (~> 1.3)
|
||||||
zeitwerk (>= 2.6.18, < 3.0)
|
zeitwerk (>= 2.6.18, < 3.0)
|
||||||
kramdown (2.5.2)
|
kramdown (2.5.1)
|
||||||
rexml (>= 3.4.4)
|
rexml (>= 3.3.9)
|
||||||
kramdown-parser-gfm (1.1.0)
|
kramdown-parser-gfm (1.1.0)
|
||||||
kramdown (~> 2.0)
|
kramdown (~> 2.0)
|
||||||
language_server-protocol (3.17.0.5)
|
language_server-protocol (3.17.0.5)
|
||||||
launchy (3.1.1)
|
|
||||||
addressable (~> 2.8)
|
|
||||||
childprocess (~> 5.0)
|
|
||||||
logger (~> 1.6)
|
|
||||||
letter_opener (1.10.0)
|
|
||||||
launchy (>= 2.2, < 4)
|
|
||||||
letter_opener_web (3.0.0)
|
|
||||||
actionmailer (>= 6.1)
|
|
||||||
letter_opener (~> 1.9)
|
|
||||||
railties (>= 6.1)
|
|
||||||
rexml
|
|
||||||
lint_roller (1.1.0)
|
lint_roller (1.1.0)
|
||||||
logger (1.7.0)
|
logger (1.7.0)
|
||||||
loofah (2.25.0)
|
loofah (2.24.1)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.12.0)
|
nokogiri (>= 1.12.0)
|
||||||
mail (2.9.0)
|
mail (2.8.1)
|
||||||
logger
|
|
||||||
mini_mime (>= 0.1.1)
|
mini_mime (>= 0.1.1)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
marcel (1.1.0)
|
marcel (1.0.4)
|
||||||
matrix (0.4.3)
|
matrix (0.4.3)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
minitest (6.0.1)
|
minitest (5.25.5)
|
||||||
prism (~> 1.5)
|
|
||||||
msgpack (1.8.0)
|
msgpack (1.8.0)
|
||||||
net-imap (0.6.3)
|
net-imap (0.5.9)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
net-pop (0.1.2)
|
net-pop (0.1.2)
|
||||||
|
|
@ -204,64 +178,64 @@ GEM
|
||||||
net-smtp (0.5.1)
|
net-smtp (0.5.1)
|
||||||
net-protocol
|
net-protocol
|
||||||
net-ssh (7.3.0)
|
net-ssh (7.3.0)
|
||||||
nio4r (2.7.5)
|
nio4r (2.7.4)
|
||||||
nokogiri (1.19.1-aarch64-linux-gnu)
|
nokogiri (1.18.8-aarch64-linux-gnu)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.19.1-aarch64-linux-musl)
|
nokogiri (1.18.8-aarch64-linux-musl)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.19.1-arm-linux-gnu)
|
nokogiri (1.18.8-arm-linux-gnu)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.19.1-arm-linux-musl)
|
nokogiri (1.18.8-arm-linux-musl)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.19.1-x86_64-linux-gnu)
|
nokogiri (1.18.8-x86_64-linux-gnu)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nokogiri (1.19.1-x86_64-linux-musl)
|
nokogiri (1.18.8-x86_64-linux-musl)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
observer (0.1.2)
|
observer (0.1.2)
|
||||||
open3 (0.2.1)
|
ostruct (0.6.2)
|
||||||
ostruct (0.6.3)
|
|
||||||
parallel (1.27.0)
|
parallel (1.27.0)
|
||||||
parser (3.3.10.2)
|
parser (3.3.8.0)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
racc
|
racc
|
||||||
pp (0.6.3)
|
pp (0.6.2)
|
||||||
prettyprint
|
prettyprint
|
||||||
prettyprint (0.2.0)
|
prettyprint (0.2.0)
|
||||||
prism (1.9.0)
|
prism (1.4.0)
|
||||||
propshaft (1.3.1)
|
propshaft (1.1.0)
|
||||||
actionpack (>= 7.0.0)
|
actionpack (>= 7.0.0)
|
||||||
activesupport (>= 7.0.0)
|
activesupport (>= 7.0.0)
|
||||||
rack
|
rack
|
||||||
psych (5.3.1)
|
railties (>= 7.0.0)
|
||||||
|
psych (5.2.6)
|
||||||
date
|
date
|
||||||
stringio
|
stringio
|
||||||
public_suffix (7.0.2)
|
public_suffix (6.0.2)
|
||||||
puma (7.2.0)
|
puma (6.6.0)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.8.1)
|
racc (1.8.1)
|
||||||
rack (3.2.5)
|
rack (3.1.16)
|
||||||
rack-session (2.1.1)
|
rack-session (2.1.1)
|
||||||
base64 (>= 0.1.0)
|
base64 (>= 0.1.0)
|
||||||
rack (>= 3.0.0)
|
rack (>= 3.0.0)
|
||||||
rack-test (2.2.0)
|
rack-test (2.2.0)
|
||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rackup (2.3.1)
|
rackup (2.2.1)
|
||||||
rack (>= 3)
|
rack (>= 3)
|
||||||
rails (8.0.4)
|
rails (8.0.2)
|
||||||
actioncable (= 8.0.4)
|
actioncable (= 8.0.2)
|
||||||
actionmailbox (= 8.0.4)
|
actionmailbox (= 8.0.2)
|
||||||
actionmailer (= 8.0.4)
|
actionmailer (= 8.0.2)
|
||||||
actionpack (= 8.0.4)
|
actionpack (= 8.0.2)
|
||||||
actiontext (= 8.0.4)
|
actiontext (= 8.0.2)
|
||||||
actionview (= 8.0.4)
|
actionview (= 8.0.2)
|
||||||
activejob (= 8.0.4)
|
activejob (= 8.0.2)
|
||||||
activemodel (= 8.0.4)
|
activemodel (= 8.0.2)
|
||||||
activerecord (= 8.0.4)
|
activerecord (= 8.0.2)
|
||||||
activestorage (= 8.0.4)
|
activestorage (= 8.0.2)
|
||||||
activesupport (= 8.0.4)
|
activesupport (= 8.0.2)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 8.0.4)
|
railties (= 8.0.2)
|
||||||
rails-dom-testing (2.3.0)
|
rails-dom-testing (2.3.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
minitest
|
minitest
|
||||||
|
|
@ -272,39 +246,36 @@ GEM
|
||||||
rails-i18n (8.1.0)
|
rails-i18n (8.1.0)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
railties (>= 8.0.0, < 9)
|
railties (>= 8.0.0, < 9)
|
||||||
railties (8.0.4)
|
railties (8.0.2)
|
||||||
actionpack (= 8.0.4)
|
actionpack (= 8.0.2)
|
||||||
activesupport (= 8.0.4)
|
activesupport (= 8.0.2)
|
||||||
irb (~> 1.13)
|
irb (~> 1.13)
|
||||||
rackup (>= 1.0.0)
|
rackup (>= 1.0.0)
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
thor (~> 1.0, >= 1.2.2)
|
thor (~> 1.0, >= 1.2.2)
|
||||||
tsort (>= 0.2)
|
|
||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
rainbow (3.1.1)
|
rainbow (3.1.1)
|
||||||
rake (13.3.1)
|
rake (13.3.0)
|
||||||
rbs (3.10.3)
|
rbs (3.9.5)
|
||||||
logger
|
logger
|
||||||
tsort
|
rdoc (6.14.2)
|
||||||
rdoc (7.2.0)
|
|
||||||
erb
|
erb
|
||||||
psych (>= 4.0.0)
|
psych (>= 4.0.0)
|
||||||
tsort
|
regexp_parser (2.10.0)
|
||||||
regexp_parser (2.11.3)
|
reline (0.6.1)
|
||||||
reline (0.6.3)
|
|
||||||
io-console (~> 0.5)
|
io-console (~> 0.5)
|
||||||
reverse_markdown (3.0.2)
|
reverse_markdown (3.0.0)
|
||||||
nokogiri
|
nokogiri
|
||||||
rexml (3.4.4)
|
rexml (3.4.1)
|
||||||
rspec-core (3.13.6)
|
rspec-core (3.13.5)
|
||||||
rspec-support (~> 3.13.0)
|
rspec-support (~> 3.13.0)
|
||||||
rspec-expectations (3.13.5)
|
rspec-expectations (3.13.5)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.13.0)
|
rspec-support (~> 3.13.0)
|
||||||
rspec-mocks (3.13.7)
|
rspec-mocks (3.13.5)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.13.0)
|
rspec-support (~> 3.13.0)
|
||||||
rspec-rails (8.0.3)
|
rspec-rails (8.0.2)
|
||||||
actionpack (>= 7.2)
|
actionpack (>= 7.2)
|
||||||
activesupport (>= 7.2)
|
activesupport (>= 7.2)
|
||||||
railties (>= 7.2)
|
railties (>= 7.2)
|
||||||
|
|
@ -312,8 +283,8 @@ GEM
|
||||||
rspec-expectations (~> 3.13)
|
rspec-expectations (~> 3.13)
|
||||||
rspec-mocks (~> 3.13)
|
rspec-mocks (~> 3.13)
|
||||||
rspec-support (~> 3.13)
|
rspec-support (~> 3.13)
|
||||||
rspec-support (3.13.7)
|
rspec-support (3.13.6)
|
||||||
rubocop (1.84.2)
|
rubocop (1.77.0)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (~> 3.17.0.2)
|
language_server-protocol (~> 3.17.0.2)
|
||||||
lint_roller (~> 1.1.0)
|
lint_roller (~> 1.1.0)
|
||||||
|
|
@ -321,17 +292,17 @@ GEM
|
||||||
parser (>= 3.3.0.2)
|
parser (>= 3.3.0.2)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 2.9.3, < 3.0)
|
regexp_parser (>= 2.9.3, < 3.0)
|
||||||
rubocop-ast (>= 1.49.0, < 2.0)
|
rubocop-ast (>= 1.45.1, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 2.4.0, < 4.0)
|
unicode-display_width (>= 2.4.0, < 4.0)
|
||||||
rubocop-ast (1.49.0)
|
rubocop-ast (1.45.1)
|
||||||
parser (>= 3.3.7.2)
|
parser (>= 3.3.7.2)
|
||||||
prism (~> 1.7)
|
prism (~> 1.4)
|
||||||
rubocop-performance (1.26.1)
|
rubocop-performance (1.25.0)
|
||||||
lint_roller (~> 1.1)
|
lint_roller (~> 1.1)
|
||||||
rubocop (>= 1.75.0, < 2.0)
|
rubocop (>= 1.75.0, < 2.0)
|
||||||
rubocop-ast (>= 1.47.1, < 2.0)
|
rubocop-ast (>= 1.38.0, < 2.0)
|
||||||
rubocop-rails (2.34.3)
|
rubocop-rails (2.32.0)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
lint_roller (~> 1.1)
|
lint_roller (~> 1.1)
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
|
|
@ -342,26 +313,24 @@ GEM
|
||||||
rubocop-performance (>= 1.24)
|
rubocop-performance (>= 1.24)
|
||||||
rubocop-rails (>= 2.30)
|
rubocop-rails (>= 2.30)
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
rubyzip (3.2.2)
|
rubyzip (2.4.1)
|
||||||
securerandom (0.4.1)
|
securerandom (0.4.1)
|
||||||
selenium-webdriver (4.41.0)
|
selenium-webdriver (4.34.0)
|
||||||
base64 (~> 0.2)
|
base64 (~> 0.2)
|
||||||
logger (~> 1.4)
|
logger (~> 1.4)
|
||||||
rexml (~> 3.2, >= 3.2.5)
|
rexml (~> 3.2, >= 3.2.5)
|
||||||
rubyzip (>= 1.2.2, < 4.0)
|
rubyzip (>= 1.2.2, < 3.0)
|
||||||
websocket (~> 1.0)
|
websocket (~> 1.0)
|
||||||
solargraph (0.58.2)
|
solargraph (0.57.0)
|
||||||
ast (~> 2.4.3)
|
|
||||||
backport (~> 1.2)
|
backport (~> 1.2)
|
||||||
benchmark (~> 0.4)
|
benchmark (~> 0.4)
|
||||||
bundler (>= 2.0)
|
bundler (~> 2.0)
|
||||||
diff-lcs (~> 1.4)
|
diff-lcs (~> 1.4)
|
||||||
jaro_winkler (~> 1.6, >= 1.6.1)
|
jaro_winkler (~> 1.6, >= 1.6.1)
|
||||||
kramdown (~> 2.3)
|
kramdown (~> 2.3)
|
||||||
kramdown-parser-gfm (~> 1.1)
|
kramdown-parser-gfm (~> 1.1)
|
||||||
logger (~> 1.6)
|
logger (~> 1.6)
|
||||||
observer (~> 0.1)
|
observer (~> 0.1)
|
||||||
open3 (~> 0.2.1)
|
|
||||||
ostruct (~> 0.6)
|
ostruct (~> 0.6)
|
||||||
parser (~> 3.0)
|
parser (~> 3.0)
|
||||||
prism (~> 1.4)
|
prism (~> 1.4)
|
||||||
|
|
@ -373,32 +342,32 @@ GEM
|
||||||
yard (~> 0.9, >= 0.9.24)
|
yard (~> 0.9, >= 0.9.24)
|
||||||
yard-activesupport-concern (~> 0.0)
|
yard-activesupport-concern (~> 0.0)
|
||||||
yard-solargraph (~> 0.1)
|
yard-solargraph (~> 0.1)
|
||||||
solargraph-rails (1.3)
|
solargraph-rails (1.2.4)
|
||||||
activesupport
|
activesupport
|
||||||
solargraph (>= 0.48.0)
|
solargraph (>= 0.48.0, <= 0.57)
|
||||||
solid_cable (3.0.12)
|
solid_cable (3.0.11)
|
||||||
actioncable (>= 7.2)
|
actioncable (>= 7.2)
|
||||||
activejob (>= 7.2)
|
activejob (>= 7.2)
|
||||||
activerecord (>= 7.2)
|
activerecord (>= 7.2)
|
||||||
railties (>= 7.2)
|
railties (>= 7.2)
|
||||||
solid_cache (1.0.10)
|
solid_cache (1.0.7)
|
||||||
activejob (>= 7.2)
|
activejob (>= 7.2)
|
||||||
activerecord (>= 7.2)
|
activerecord (>= 7.2)
|
||||||
railties (>= 7.2)
|
railties (>= 7.2)
|
||||||
solid_queue (1.3.2)
|
solid_queue (1.1.5)
|
||||||
activejob (>= 7.1)
|
activejob (>= 7.1)
|
||||||
activerecord (>= 7.1)
|
activerecord (>= 7.1)
|
||||||
concurrent-ruby (>= 1.3.1)
|
concurrent-ruby (>= 1.3.1)
|
||||||
fugit (~> 1.11)
|
fugit (~> 1.11.0)
|
||||||
railties (>= 7.1)
|
railties (>= 7.1)
|
||||||
thor (>= 1.3.1)
|
thor (~> 1.3.1)
|
||||||
sqlite3 (2.9.0-aarch64-linux-gnu)
|
sqlite3 (2.7.2-aarch64-linux-gnu)
|
||||||
sqlite3 (2.9.0-aarch64-linux-musl)
|
sqlite3 (2.7.2-aarch64-linux-musl)
|
||||||
sqlite3 (2.9.0-arm-linux-gnu)
|
sqlite3 (2.7.2-arm-linux-gnu)
|
||||||
sqlite3 (2.9.0-arm-linux-musl)
|
sqlite3 (2.7.2-arm-linux-musl)
|
||||||
sqlite3 (2.9.0-x86_64-linux-gnu)
|
sqlite3 (2.7.2-x86_64-linux-gnu)
|
||||||
sqlite3 (2.9.0-x86_64-linux-musl)
|
sqlite3 (2.7.2-x86_64-linux-musl)
|
||||||
sshkit (1.25.0)
|
sshkit (1.24.0)
|
||||||
base64
|
base64
|
||||||
logger
|
logger
|
||||||
net-scp (>= 1.1.2)
|
net-scp (>= 1.1.2)
|
||||||
|
|
@ -407,24 +376,22 @@ GEM
|
||||||
ostruct
|
ostruct
|
||||||
stimulus-rails (1.3.4)
|
stimulus-rails (1.3.4)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
stringio (3.2.0)
|
stringio (3.1.7)
|
||||||
temple (0.10.4)
|
thor (1.3.2)
|
||||||
thor (1.5.0)
|
thruster (0.1.14)
|
||||||
thruster (0.1.18)
|
thruster (0.1.14-aarch64-linux)
|
||||||
thruster (0.1.18-aarch64-linux)
|
thruster (0.1.14-x86_64-linux)
|
||||||
thruster (0.1.18-x86_64-linux)
|
tilt (2.6.1)
|
||||||
tilt (2.7.0)
|
timeout (0.4.3)
|
||||||
timeout (0.6.0)
|
turbo-rails (2.0.16)
|
||||||
tsort (0.2.0)
|
|
||||||
turbo-rails (2.0.23)
|
|
||||||
actionpack (>= 7.1.0)
|
actionpack (>= 7.1.0)
|
||||||
railties (>= 7.1.0)
|
railties (>= 7.1.0)
|
||||||
tzinfo (2.0.6)
|
tzinfo (2.0.6)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
unicode-display_width (3.2.0)
|
unicode-display_width (3.1.4)
|
||||||
unicode-emoji (~> 4.1)
|
unicode-emoji (~> 4.0, >= 4.0.4)
|
||||||
unicode-emoji (4.2.0)
|
unicode-emoji (4.0.4)
|
||||||
uri (1.1.1)
|
uri (1.0.3)
|
||||||
useragent (0.16.11)
|
useragent (0.16.11)
|
||||||
web-console (4.2.1)
|
web-console (4.2.1)
|
||||||
actionview (>= 6.0.0)
|
actionview (>= 6.0.0)
|
||||||
|
|
@ -438,12 +405,12 @@ GEM
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
yard (0.9.38)
|
yard (0.9.37)
|
||||||
yard-activesupport-concern (0.0.1)
|
yard-activesupport-concern (0.0.1)
|
||||||
yard (>= 0.8)
|
yard (>= 0.8)
|
||||||
yard-solargraph (0.1.0)
|
yard-solargraph (0.1.0)
|
||||||
yard (~> 0.9)
|
yard (~> 0.9)
|
||||||
zeitwerk (2.7.5)
|
zeitwerk (2.7.3)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
aarch64-linux
|
aarch64-linux
|
||||||
|
|
@ -456,18 +423,14 @@ PLATFORMS
|
||||||
x86_64-linux-musl
|
x86_64-linux-musl
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
bcrypt (~> 3.1.7)
|
|
||||||
bootsnap
|
bootsnap
|
||||||
brakeman
|
brakeman
|
||||||
capybara
|
capybara
|
||||||
cssbundling-rails (~> 1.4)
|
cssbundling-rails (~> 1.4)
|
||||||
debug
|
debug
|
||||||
factory_bot_rails
|
|
||||||
haml
|
|
||||||
importmap-rails
|
importmap-rails
|
||||||
jbuilder
|
jbuilder
|
||||||
kamal
|
kamal
|
||||||
letter_opener_web
|
|
||||||
propshaft
|
propshaft
|
||||||
puma (>= 5.0)
|
puma (>= 5.0)
|
||||||
rails (~> 8.0.2)
|
rails (~> 8.0.2)
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
web: env RUBY_DEBUG_OPEN=true bin/rails server
|
web: env RUBY_DEBUG_OPEN=true bin/rails server
|
||||||
css: node_modules/yarn/bin/yarn build:css --watch
|
css: yarn build:css --watch
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 37 KiB |
|
|
@ -20,12 +20,6 @@ main {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
#navbar-top {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.tracker {
|
.tracker {
|
||||||
align-content: center;
|
align-content: center;
|
||||||
}
|
}
|
||||||
|
|
@ -33,16 +27,42 @@ main {
|
||||||
.logs {
|
.logs {
|
||||||
height: 60vh;
|
height: 60vh;
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.current-day {
|
.current-day {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 64px;
|
top: 0;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bar {
|
.bar-afond {
|
||||||
|
background: black;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-creatif {
|
||||||
|
background: red;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-frigo {
|
||||||
|
background: gray;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-croisiere {
|
||||||
|
background: green;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-en-charge {
|
||||||
|
background: orange;
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
}
|
}
|
||||||
|
|
@ -54,35 +74,24 @@ main {
|
||||||
height: 15px;
|
height: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.day.has-day-log {
|
.creatif {
|
||||||
position: relative;
|
background-color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
.day.has-day-log::after {
|
.en-charge {
|
||||||
content: '';
|
background-color: orange;
|
||||||
position: absolute;
|
|
||||||
bottom: 1px;
|
|
||||||
right: 1px;
|
|
||||||
width: 4px;
|
|
||||||
height: 4px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected-day {
|
.frigo-vide {
|
||||||
border-radius: 50%;
|
background-color: grey;
|
||||||
box-shadow: 0 0 0 3px white, 0 0 0 5px black;
|
|
||||||
margin: 4px;
|
|
||||||
width: 15px;
|
|
||||||
height: 15px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-day {
|
.croisiere {
|
||||||
border: 1px dashed #aaa;
|
background-color: green;
|
||||||
margin: 4px;
|
}
|
||||||
width: 15px;
|
|
||||||
height: 15px;
|
.afond {
|
||||||
background: transparent;
|
background-color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
class ApplicationController < ActionController::Base
|
class ApplicationController < ActionController::Base
|
||||||
include Authentication
|
|
||||||
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
|
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
|
||||||
# allow_browser versions: :modern
|
# allow_browser versions: :modern
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
module Authentication
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
included do
|
|
||||||
before_action :require_authentication
|
|
||||||
helper_method :authenticated?
|
|
||||||
helper_method :current_user
|
|
||||||
end
|
|
||||||
|
|
||||||
class_methods do
|
|
||||||
def allow_unauthenticated_access(**options)
|
|
||||||
skip_before_action :require_authentication, **options
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def authenticated?
|
|
||||||
resume_session
|
|
||||||
end
|
|
||||||
|
|
||||||
def current_user
|
|
||||||
authenticated?&.user
|
|
||||||
end
|
|
||||||
|
|
||||||
def require_authentication
|
|
||||||
resume_session || request_authentication
|
|
||||||
end
|
|
||||||
|
|
||||||
def resume_session
|
|
||||||
Current.session ||= find_session_by_cookie
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_session_by_cookie
|
|
||||||
Session.find_by(id: cookies.signed[:session_id]) if cookies.signed[:session_id]
|
|
||||||
end
|
|
||||||
|
|
||||||
def request_authentication
|
|
||||||
session[:return_to_after_authenticating] = request.url
|
|
||||||
redirect_to new_session_path
|
|
||||||
end
|
|
||||||
|
|
||||||
def after_authentication_url
|
|
||||||
session.delete(:return_to_after_authenticating) || root_url
|
|
||||||
end
|
|
||||||
|
|
||||||
def start_new_session_for(user)
|
|
||||||
user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|
|
|
||||||
Current.session = session
|
|
||||||
cookies.signed.permanent[:session_id] = { value: session.id, httponly: true, same_site: :lax }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def terminate_session
|
|
||||||
Current.session.destroy
|
|
||||||
cookies.delete(:session_id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
class DayLogsController < ApplicationController
|
|
||||||
before_action :set_day_log, only: [ :update ]
|
|
||||||
|
|
||||||
def edit
|
|
||||||
@day_log = Current.user.day_logs.find_or_initialize_by(day: params[:day])
|
|
||||||
@mood = Current.user.moods.find_by(recorded_at: params[:day].to_date.beginning_of_day..params[:day].to_date.end_of_day)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
@day_log = Current.user.day_logs.build(day_log_params)
|
|
||||||
|
|
||||||
if @day_log.save
|
|
||||||
handle_mood(@day_log.day, params[:day_log][:mode_id])
|
|
||||||
redirect_to dashboard_path(day: @day_log.day)
|
|
||||||
else
|
|
||||||
render :new, status: :unprocessable_content
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
if @day_log.update(day_log_params)
|
|
||||||
handle_mood(@day_log.day, params[:day_log][:mode_id])
|
|
||||||
redirect_to dashboard_path(day: @day_log.day)
|
|
||||||
else
|
|
||||||
render :edit, status: :unprocessable_content
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_day_log
|
|
||||||
@day_log = Current.user.day_logs.find(params[:id])
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_mood(day, mode_id)
|
|
||||||
return if mode_id.blank?
|
|
||||||
|
|
||||||
mood = Current.user.moods.find_by(recorded_at: day.beginning_of_day..day.end_of_day)
|
|
||||||
|
|
||||||
if mood
|
|
||||||
mood.update(mode_id: mode_id)
|
|
||||||
else
|
|
||||||
Current.user.moods.create(mode_id: mode_id, recorded_at: day.to_datetime)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def day_log_params
|
|
||||||
params.expect(day_log: [ :day, :info ])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
class HomeController < ApplicationController
|
|
||||||
allow_unauthenticated_access
|
|
||||||
|
|
||||||
def index
|
|
||||||
@user = User.find_by(username: request.subdomain)
|
|
||||||
if @user&.public?
|
|
||||||
render "moods/index"
|
|
||||||
else
|
|
||||||
redirect_to dashboard_path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
class InvitationsController < ApplicationController
|
|
||||||
before_action :set_user_by_token, only: %i[ edit update ]
|
|
||||||
allow_unauthenticated_access
|
|
||||||
|
|
||||||
def edit
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
password_params = params.expect(user: [ :password, :password_confirmation ])
|
|
||||||
|
|
||||||
if @user.update(password_params.merge(invitation_accepted_at: Time.current))
|
|
||||||
redirect_to root_path, notice: "Account activated!"
|
|
||||||
else
|
|
||||||
redirect_to invitation_path(params[:token]), alert: "Passwords did not match."
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_user_by_token
|
|
||||||
@user = User.find_by_invitation_token(params[:token])
|
|
||||||
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
|
||||||
redirect_to root_path, alert: "Invitation link is invalid or has expired."
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
class MoodsController < ApplicationController
|
class MoodsController < ApplicationController
|
||||||
def index
|
def index
|
||||||
@user = Current.user
|
@mode = Mood.last&.mode || "croisiere"
|
||||||
redirect_to dashboard_path(day: Date.today) unless params[:day]
|
@mood_log = Mood.history_for_a_year || []
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
class PasswordsController < ApplicationController
|
|
||||||
allow_unauthenticated_access
|
|
||||||
before_action :set_user_by_token, only: %i[ edit update ]
|
|
||||||
|
|
||||||
def new
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
if user = User.find_by(email_address: params[:email_address])
|
|
||||||
PasswordsMailer.reset(user).deliver_later
|
|
||||||
end
|
|
||||||
|
|
||||||
redirect_to new_session_path, notice: "Password reset instructions sent (if user with that email address exists)."
|
|
||||||
end
|
|
||||||
|
|
||||||
def edit
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
if @user.update(params.permit(:password, :password_confirmation))
|
|
||||||
redirect_to new_session_path, notice: "Password has been reset."
|
|
||||||
else
|
|
||||||
redirect_to edit_password_path(params[:token]), alert: "Passwords did not match."
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def set_user_by_token
|
|
||||||
@user = User.find_by_password_reset_token!(params[:token])
|
|
||||||
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
|
||||||
redirect_to new_password_path, alert: "Password reset link is invalid or has expired."
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
class RfidTagsController < ApplicationController
|
class RfidTagsController < ApplicationController
|
||||||
skip_forgery_protection
|
skip_forgery_protection
|
||||||
allow_unauthenticated_access
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
rfid_tag_params = params.expect(rfid_tag: [ :chip_id, :identifier ])
|
identifier = params.expect(:identifier)
|
||||||
rfid_tag = RfidTag.find_or_initialize_by(chip_id: rfid_tag_params[:chip_id], identifier: rfid_tag_params[:identifier])
|
rfid_tag = RfidTag.find_or_initialize_by(identifier:)
|
||||||
if rfid_tag.new_record?
|
if rfid_tag.new_record?
|
||||||
register rfid_tag
|
register rfid_tag
|
||||||
return
|
return
|
||||||
|
|
@ -16,14 +15,13 @@ class RfidTagsController < ApplicationController
|
||||||
private
|
private
|
||||||
|
|
||||||
def register(rfid_tag)
|
def register(rfid_tag)
|
||||||
rfid_tag.user = RfidTag.where(chip_id: rfid_tag.chip_id).first&.user
|
|
||||||
rfid_tag.save!
|
rfid_tag.save!
|
||||||
head :created, code: :registered
|
head :created, code: :registered
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_mood(rfid_tag)
|
def create_mood(rfid_tag)
|
||||||
if rfid_tag.mode
|
if rfid_tag.mode
|
||||||
Mood.create!(recorded_at: DateTime.now, mode: rfid_tag.mode, user: rfid_tag.user)
|
Mood.create!(recorded_at: DateTime.now, mode: rfid_tag.mode)
|
||||||
head :created, code: :recorded
|
head :created, code: :recorded
|
||||||
else
|
else
|
||||||
head :unprocessable_entity
|
head :unprocessable_entity
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
class SessionsController < ApplicationController
|
|
||||||
allow_unauthenticated_access only: %i[ new create ]
|
|
||||||
rate_limit to: 10, within: 3.minutes, only: :create, with: -> { redirect_to new_session_url, alert: "Try again later." }
|
|
||||||
|
|
||||||
def new
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
if user = User.authenticate_by(params.permit(:email_address, :password))
|
|
||||||
start_new_session_for user
|
|
||||||
redirect_to after_authentication_url
|
|
||||||
else
|
|
||||||
redirect_to new_session_path, alert: "Try another email address or password."
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
terminate_session
|
|
||||||
redirect_to new_session_path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,39 +1,2 @@
|
||||||
module MoodsHelper
|
module MoodsHelper
|
||||||
def day_status(mood, user)
|
|
||||||
return :filled if mood[:mode].present?
|
|
||||||
return :guessed if mood[:guess].present? && user.guess?
|
|
||||||
return :unknown if mood[:guess].present? && !user.guess?
|
|
||||||
:empty
|
|
||||||
end
|
|
||||||
|
|
||||||
def mode_for(mood, user)
|
|
||||||
case day_status(mood, user)
|
|
||||||
when :filled then mood[:mode]
|
|
||||||
when :guessed then mood[:guess]
|
|
||||||
when :unknown, :empty then Mode.new(label: "unknown", color: "white")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
=begin
|
|
||||||
def mode_for(mood, user)
|
|
||||||
if user.guess?
|
|
||||||
mood[:mode] || mood[:guess] || { label: "unknown", color: "white", image_url: "unknown.jpg" }
|
|
||||||
else
|
|
||||||
mood[:mode] || { label: "unknown", color: "white", image_url: "unknown.jpg" }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
=end
|
|
||||||
|
|
||||||
def style_for_mode(mode, status)
|
|
||||||
case status
|
|
||||||
when :empty then ""
|
|
||||||
when :unknown then "background-color: #{mode.color}; border: 2px double grey;"
|
|
||||||
else "background-color: #{mode.color};"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def css_class_for_day(status, day_log)
|
|
||||||
css_class = status == :empty ? "day empty-day" : "day"
|
|
||||||
css_class += " has-day-log" if day_log
|
|
||||||
css_class
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
import { Controller } from '@hotwired/stimulus'
|
|
||||||
|
|
||||||
export default class extends Controller {
|
|
||||||
static targets = [ 'burger', 'navbar' ];
|
|
||||||
|
|
||||||
toggle(event) {
|
|
||||||
[ this.burgerTarget, this.navbarTarget ].forEach((target) => {
|
|
||||||
target.classList.toggle("is-active");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +1,15 @@
|
||||||
import { Controller } from '@hotwired/stimulus'
|
import { Controller } from '@hotwired/stimulus'
|
||||||
|
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
static targets = [ "image", "modeDay", "dayLogLink", "dayLogInfo" ];
|
static targets = [ "image", "modeDay", "modeDayMobile" ]
|
||||||
|
|
||||||
connect() {
|
|
||||||
this.lastTarget = null;
|
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
const selectedDay = params.get("day");
|
|
||||||
|
|
||||||
if (selectedDay) {
|
|
||||||
const dayEl = document.querySelector(`[data-day="${selectedDay}"]`);
|
|
||||||
if (dayEl) {
|
|
||||||
dayEl.click();
|
|
||||||
dayEl.scrollIntoView({ block: "center" });
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const logs = document.querySelector(".logs");
|
|
||||||
if (logs) logs.scrollTop = logs.scrollHeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDayInfo(event) {
|
updateDayInfo(event) {
|
||||||
const image = this.imageTarget;
|
const image = this.imageTarget;
|
||||||
const modeDay = this.modeDayTarget;
|
const modeDay = this.modeDayTarget;
|
||||||
const day = event.target.dataset.day;
|
const modeDayMobile = this.modeDayMobileTarget;
|
||||||
const formattedDay = new Date(day).toLocaleDateString('fr-FR');
|
const modeDayContent = event.target.dataset.day + ' : ' + event.target.dataset.mode;
|
||||||
const modeDayContent = formattedDay + ' : ' + event.target.dataset.mode;
|
|
||||||
image.src = event.target.dataset.image;
|
image.src = event.target.dataset.image;
|
||||||
|
modeDayMobile.textContent = modeDayContent;
|
||||||
modeDay.textContent = modeDayContent;
|
modeDay.textContent = modeDayContent;
|
||||||
this.dayLogLinkTarget.href = `/day_logs/edit?day=${day}`;
|
|
||||||
this.dayLogInfoTarget.textContent = event.target.dataset.info || '';
|
|
||||||
event.target.className = "selected-day " + event.target.dataset.mode;
|
|
||||||
|
|
||||||
history.pushState({}, '', `?day=${day}`);
|
|
||||||
|
|
||||||
if (this.lastTarget) {
|
|
||||||
this.lastTarget.className = "day " + this.lastTarget.dataset.mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastTarget = event.target;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
class PasswordsMailer < ApplicationMailer
|
|
||||||
def reset(user)
|
|
||||||
@user = user
|
|
||||||
mail subject: "Reset your password", to: user.email_address
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
class UserMailer < ApplicationMailer
|
|
||||||
def invitation_email(user, token)
|
|
||||||
@user = user
|
|
||||||
@invite_url = edit_invitation_url(token: token)
|
|
||||||
|
|
||||||
mail(to: @user.email_address, subject: "Vous êtes invité à rejoindre la kluk")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
class Current < ActiveSupport::CurrentAttributes
|
|
||||||
attribute :session
|
|
||||||
delegate :user, to: :session, allow_nil: true
|
|
||||||
end
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
class DayLog < ApplicationRecord
|
|
||||||
belongs_to :user
|
|
||||||
|
|
||||||
validates :day, presence: true
|
|
||||||
validates :info, presence: true
|
|
||||||
end
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
class Mode < ApplicationRecord
|
|
||||||
belongs_to :user
|
|
||||||
has_one_attached :image
|
|
||||||
|
|
||||||
def image_url
|
|
||||||
image.attached? ? Rails.application.routes.url_helpers.rails_blob_path(image, only_path: true) : ActionController::Base.helpers.asset_path("unknown.jpg")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,19 +1,45 @@
|
||||||
class Mood < ApplicationRecord
|
class Mood < ApplicationRecord
|
||||||
belongs_to :user
|
class << self
|
||||||
belongs_to :mode
|
def history_for_a_year
|
||||||
|
history(Date.today - 1.year, Date.today)
|
||||||
validates :recorded_at, presence: true
|
|
||||||
validate :unique_per_day_and_user
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def unique_per_day_and_user
|
|
||||||
return unless recorded_at && user
|
|
||||||
|
|
||||||
existing = user.moods.where.not(id: id).any? do |mood|
|
|
||||||
mood.recorded_at.to_date == recorded_at.to_date
|
|
||||||
end
|
end
|
||||||
|
|
||||||
errors.add(:recorded_at, :taken) if existing
|
private
|
||||||
|
def history(from, to)
|
||||||
|
return [] if Mood.count < 1
|
||||||
|
|
||||||
|
first_monday = monday_of_the_week(first_mood_date(from))
|
||||||
|
|
||||||
|
current_date = first_monday
|
||||||
|
current_mode = last_mode_before(current_date)
|
||||||
|
log_mood = []
|
||||||
|
mood_range = Mood.order(:recorded_at).where(recorded_at: (first_monday..to))
|
||||||
|
mood_range.each do |mood|
|
||||||
|
while current_date < mood.recorded_at.to_date do
|
||||||
|
log_mood << [ current_date.to_s, current_mode ]
|
||||||
|
current_date += 1
|
||||||
|
end
|
||||||
|
current_mode = mood.mode
|
||||||
|
end
|
||||||
|
|
||||||
|
while current_date <= to.to_date do
|
||||||
|
log_mood << [ current_date.to_s, current_mode ]
|
||||||
|
current_date += 1
|
||||||
|
end
|
||||||
|
log_mood.each_slice(7).to_a
|
||||||
|
end
|
||||||
|
|
||||||
|
def first_mood_date(from)
|
||||||
|
Mood.order(recorded_at: :asc).where("recorded_at >= ?", from).first.recorded_at.to_date
|
||||||
|
end
|
||||||
|
|
||||||
|
def monday_of_the_week(date)
|
||||||
|
date - date.wday + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def last_mode_before(date)
|
||||||
|
mood = Mood.where("recorded_at < ?", date).last
|
||||||
|
mood&.mode || "croisiere"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,2 @@
|
||||||
class RfidTag < ApplicationRecord
|
class RfidTag < ApplicationRecord
|
||||||
belongs_to :user, required: false
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
class Session < ApplicationRecord
|
|
||||||
belongs_to :user
|
|
||||||
end
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
class User < ApplicationRecord
|
|
||||||
has_secure_password
|
|
||||||
has_many :sessions, dependent: :destroy
|
|
||||||
has_many :moods, -> { order "recorded_at" }
|
|
||||||
has_many :modes
|
|
||||||
has_many :day_logs
|
|
||||||
|
|
||||||
normalizes :email_address, with: ->(e) { e.strip.downcase }
|
|
||||||
|
|
||||||
generates_token_for :invitation, expires_in: 5.days do
|
|
||||||
invitation_accepted_at
|
|
||||||
end
|
|
||||||
|
|
||||||
def invite_user!(email)
|
|
||||||
invited_user = User.create!(
|
|
||||||
email_address: email,
|
|
||||||
password: SecureRandom.hex(16) # temporary password
|
|
||||||
)
|
|
||||||
|
|
||||||
# Send invitation email using the generated token
|
|
||||||
token = invited_user.generate_token_for(:invitation)
|
|
||||||
UserMailer.invitation_email(invited_user, token).deliver_later
|
|
||||||
|
|
||||||
invited_user
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.find_by_invitation_token(token)
|
|
||||||
find_by_token_for(:invitation, token)
|
|
||||||
end
|
|
||||||
|
|
||||||
def invitation_token
|
|
||||||
generate_token_for(:invitation)
|
|
||||||
end
|
|
||||||
|
|
||||||
def history
|
|
||||||
MoodCalendarService.generate_calendar(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
def current_mode
|
|
||||||
self.moods.last&.mode || "croisiere"
|
|
||||||
end
|
|
||||||
|
|
||||||
def current_day_log
|
|
||||||
self.day_logs.find_by(day: Date.today)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
class MoodCalendarService
|
|
||||||
def self.generate_calendar(user, start_date: nil, end_date: nil)
|
|
||||||
data = user.moods.includes(:mode)
|
|
||||||
.order(:recorded_at)
|
|
||||||
.map { |mood| { mode: mood.mode, recorded_at: mood.recorded_at } }
|
|
||||||
|
|
||||||
day_logs_by_date = user.day_logs.index_by(&:day)
|
|
||||||
|
|
||||||
if data.empty?
|
|
||||||
start_date = Date.current
|
|
||||||
else
|
|
||||||
start_date ||= data.first[:recorded_at].to_date
|
|
||||||
end
|
|
||||||
|
|
||||||
start_date = start_date.to_date
|
|
||||||
end_date = end_date ? end_date.to_date : [ Date.current, start_date + 5.months ].max
|
|
||||||
=begin
|
|
||||||
if end_date.nil?
|
|
||||||
end_date = start_date + 5.months
|
|
||||||
else
|
|
||||||
end_date = end_date.to_date
|
|
||||||
end
|
|
||||||
=end
|
|
||||||
|
|
||||||
data_by_date = data.group_by { |d| d[:recorded_at].to_date }
|
|
||||||
.transform_values { |entries| entries.max_by { |e| e[:recorded_at] } }
|
|
||||||
|
|
||||||
last_mode = data.select { |d| d[:recorded_at].to_date < start_date }
|
|
||||||
.max_by { |d| d[:recorded_at] }
|
|
||||||
&.[](:mode)
|
|
||||||
|
|
||||||
complete_data = (start_date..end_date).map do |date|
|
|
||||||
day_log = day_logs_by_date[date]
|
|
||||||
if data_by_date[date]
|
|
||||||
last_mode = data_by_date[date][:mode]
|
|
||||||
data_by_date[date].merge(day_log: day_log)
|
|
||||||
else
|
|
||||||
{ mode: nil, recorded_at: date.to_datetime, guess: last_mode, day_log: day_log }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
complete_data.group_by { |d| d[:recorded_at].to_date.beginning_of_month }
|
|
||||||
.map do |month_start, _|
|
|
||||||
first_monday = month_start
|
|
||||||
first_monday = first_monday.next_occurring(:monday) unless first_monday.monday?
|
|
||||||
|
|
||||||
next_month_start = month_start.next_month.beginning_of_month
|
|
||||||
next_first_monday = next_month_start
|
|
||||||
next_first_monday = next_first_monday.next_occurring(:monday) unless next_first_monday.monday?
|
|
||||||
|
|
||||||
month_end = next_first_monday - 1.day
|
|
||||||
|
|
||||||
data_hash = complete_data.index_by { |d| d[:recorded_at].to_date }
|
|
||||||
|
|
||||||
all_days = (first_monday..month_end).map do |date|
|
|
||||||
data_hash[date] || { mode: nil, recorded_at: date.to_datetime, guess: nil, day_log: nil }
|
|
||||||
end
|
|
||||||
|
|
||||||
weeks = all_days.group_by { |d| d[:recorded_at].to_date.beginning_of_week(:monday) }
|
|
||||||
.sort_by { |week_start, _| week_start }
|
|
||||||
.map { |_, week_moods| week_moods }
|
|
||||||
|
|
||||||
{
|
|
||||||
month: month_start,
|
|
||||||
weeks: weeks
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
<nav id="navbar-top" class="navbar has-background-primary" role="navigation" aria-label="main navigation" data-controller="menu">
|
|
||||||
<div class="navbar-brand">
|
|
||||||
<div class="navbar-item is-size-5">Comment ça </div>
|
|
||||||
<div class="navbar-item is-size-3"><strong>KLUK</strong></div>
|
|
||||||
<div class="navbar-item is-size-5">aujourd'hui ?</div>
|
|
||||||
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-menu-target="burger" data-action="menu#toggle">
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="navbar-menu" data-menu-target="navbar">
|
|
||||||
<div class="navbar-end">
|
|
||||||
<% if authenticated? %>
|
|
||||||
<div class="navbar-item">
|
|
||||||
<%= button_to "Se déconnecter", session_path, method: :delete, class: "button is-light" %>
|
|
||||||
</div>
|
|
||||||
<% else %>
|
|
||||||
<div class="navbar-item">
|
|
||||||
<%= link_to "Se connecter", new_session_path, class: "button is-light" %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
= form_with model: day_log do |f|
|
|
||||||
- @day_log.errors.full_messages.each do |msg|
|
|
||||||
.notification.is-danger= msg
|
|
||||||
|
|
||||||
.field
|
|
||||||
= f.label :info, "Journal", class: "label"
|
|
||||||
.control
|
|
||||||
= f.text_area :info, class: "textarea", rows: 5
|
|
||||||
|
|
||||||
.field
|
|
||||||
= label_tag :mode_id, "Mode", class: "label"
|
|
||||||
.control
|
|
||||||
.select.is-fullwidth
|
|
||||||
= select_tag "day_log[mode_id]",
|
|
||||||
options_from_collection_for_select(Current.user.modes, :id, :label, @mood&.mode_id),
|
|
||||||
include_blank: true
|
|
||||||
|
|
||||||
.field.is-hidden
|
|
||||||
= f.date_field :day
|
|
||||||
|
|
||||||
.field.is-grouped
|
|
||||||
.control
|
|
||||||
= f.submit "Enregistrer", class: "button is-primary"
|
|
||||||
.control
|
|
||||||
= link_to "Annuler", dashboard_path(day: @day_log.day), class: "button is-light"
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
%main.columns.m-auto
|
|
||||||
.current-day.column.is-hidden-mobile
|
|
||||||
%section.m-4
|
|
||||||
%figure.image.has-ratio
|
|
||||||
= image_tag @mood ? @mood.mode.image_url : "unknown.jpg"
|
|
||||||
|
|
||||||
%section.section.column.has-background-primary-light
|
|
||||||
%h1.title.is-4
|
|
||||||
%span.icon
|
|
||||||
%i.fa-regular.fa-calendar
|
|
||||||
%span= l @day_log.day, format: :long
|
|
||||||
|
|
||||||
= render 'form', day_log: @day_log
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
%h1 Nouveau journal
|
|
||||||
|
|
||||||
= render 'form', day_log: @day_log
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
%h1 Comment ça KLUK ?
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
<div>
|
|
||||||
<h1>Set up your password</h1>
|
|
||||||
<p>Please set your password to activate your account.</p>
|
|
||||||
|
|
||||||
<%= form_with model: @user, url: invitation_path(token: params[:token]), method: :patch do |form| %>
|
|
||||||
<div>
|
|
||||||
<%= form.password_field :password,
|
|
||||||
required: true,
|
|
||||||
autocomplete: "new-password",
|
|
||||||
placeholder: "Enter new password" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<%= form.password_field :password_confirmation,
|
|
||||||
required: true,
|
|
||||||
autocomplete: "new-password",
|
|
||||||
placeholder: "Repeat new password" %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<%= form.submit "Activate Account" %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
|
|
@ -22,15 +22,12 @@
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Delius&family=Sour+Gummy:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Delius&family=Sour+Gummy:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
<script src="https://kit.fontawesome.com/079b06296f.js" crossorigin="anonymous"></script>
|
|
||||||
|
|
||||||
<%# Includes all stylesheet files in app/assets/stylesheets %>
|
<%# Includes all stylesheet files in app/assets/stylesheets %>
|
||||||
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
|
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
|
||||||
<%= javascript_importmap_tags %>
|
<%= javascript_importmap_tags %>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<%= render 'menu' %>
|
|
||||||
<%= yield %>
|
<%= yield %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,59 +1,60 @@
|
||||||
<main data-controller="mood" class="columns m-auto">
|
<main data-controller="mood" class="columns m-auto">
|
||||||
<div class="current-day column is-hidden-mobile">
|
<div class="current-day column">
|
||||||
<section class="m-4">
|
<section class="m-4">
|
||||||
<figure class="image has-ratio">
|
<h1 class="title is-3 is-spaced">Comment ça KLUK ?</h1>
|
||||||
<%= image_tag(@user.current_mode.image_url, "data-mood-target": "image") %>
|
<div class="is-hidden-tablet mb-4">
|
||||||
|
<div class="" data-mood-target="modeDayMobile">Aujourd'hui</div>
|
||||||
|
</div>
|
||||||
|
<figure class="image is-hidden-mobile has-ratio ">
|
||||||
|
<%= image_tag(@mode + ".jpg", "data-mood-target": "image") %>
|
||||||
</figure>
|
</figure>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<section class="section column has-background-primary-light is-flex is-flex-direction-column is-justify-content-space-around">
|
<section class="section column has-background-primary-light is-flex is-flex-direction-column is-justify-content-space-around">
|
||||||
|
<div class="is-hidden-mobile mb-4">
|
||||||
|
<div class="title is-3" data-mood-target="modeDay">Aujourd'hui</div>
|
||||||
|
</div>
|
||||||
<div class="tracker">
|
<div class="tracker">
|
||||||
<div class="legend is-flex is-flex-wrap-wrap mb-5">
|
<div class="legend is-flex is-flex-wrap-wrap mb-5">
|
||||||
<div class="is-flex is-align-items-center mr-4">
|
<div class="is-flex is-align-items-center mr-4">
|
||||||
<div class=""><strong>Légende</strong></div>
|
<div class="bar-frigo mr-1"></div>
|
||||||
|
<div class="">Triste</div>
|
||||||
</div>
|
</div>
|
||||||
<% @user.modes.each do |mode| %>
|
<div class="is-flex is-align-items-center mr-4">
|
||||||
<div class="is-flex is-align-items-center mr-4">
|
<div class="bar-en-charge mr-1"></div>
|
||||||
<div class="bar mr-1" style="<%= style_for_mode(mode, :filled) %>"></div>
|
<div class="">En charge</div>
|
||||||
<div class=""><%= mode.label %></div>
|
</div>
|
||||||
</div>
|
<div class="is-flex is-align-items-center mr-4">
|
||||||
<% end %>
|
<div class="bar-croisiere mr-1"></div>
|
||||||
</div>
|
<div class="">Croisiere</div>
|
||||||
<div class="container mb-4 p-3 has-background-white day-info">
|
</div>
|
||||||
<div class="title is-4">
|
<div class="is-flex is-align-items-center mr-4">
|
||||||
<span class="icon"><i class="fa-regular fa-calendar"></i></span>
|
<div class="bar-creatif mr-1"></div>
|
||||||
<span data-mood-target="modeDay">Aujourd'hui : <%= @user.current_mode.label %></span>
|
<div>Creatif</div>
|
||||||
<%= link_to edit_day_log_for_day_path(day: Date.today), "data-mood-target": "dayLogLink" do %>
|
</div>
|
||||||
<span class="icon ml-2"><i class="has-text-grey fa-solid fa-pencil fa-xs"></i></span>
|
<div class="is-flex is-align-items-center mr-4">
|
||||||
<% end %>
|
<div class="bar-afond mr-1"></div>
|
||||||
|
<div class="">A fond</div>
|
||||||
</div>
|
</div>
|
||||||
<p data-mood-target="dayLogInfo"><%= @user.current_day_log&.info %></p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="logs">
|
<div class="logs">
|
||||||
<div class="is-flex is-flex-direction-row is-flex-wrap-wrap mb-3">
|
<div class="is-flex is-flex-wrap-wrap">
|
||||||
<% @user.history.each do |month| %>
|
<% @mood_log.each do |week| %>
|
||||||
<div class="mr-3">
|
<div class="is-flex is-flex-direction-column is-flex-wrap-wrap mb-3">
|
||||||
<div> <%= I18n.l(month[:month], format: "%b %y").capitalize %></div>
|
<div class="is-size-7 day">
|
||||||
<div class="is-flex is-flex-wrap-wrap">
|
<% day = Date.parse(week.first[0]) %>
|
||||||
<% month[:weeks].each do |week| %>
|
<% if day.day < 8 && day.monday? %>
|
||||||
<div class="is-flex is-flex-direction-column is-flex-wrap-wrap mb-3">
|
<%= l(day, format: "%b") %>
|
||||||
<% week.each do |mood| %>
|
|
||||||
<% status = day_status(mood, @user) %>
|
|
||||||
<% mode = mode_for(mood, @user) %>
|
|
||||||
<div data-image="<%= mode.image_url %>"
|
|
||||||
data-mode="<%= mode.label %>"
|
|
||||||
data-day="<%= mood[:recorded_at].to_date.iso8601 %>"
|
|
||||||
data-info="<%= mood[:day_log]&.info %>"
|
|
||||||
data-action="click->mood#updateDayInfo"
|
|
||||||
title="<%= l(mood[:recorded_at].to_date) %> : <%= mode.label %>"
|
|
||||||
class="<%= css_class_for_day(status, mood[:day_log]) %>"
|
|
||||||
style="<%= style_for_mode(mode, status) %>">
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
<% week.each do |d| %>
|
||||||
|
<% day = Date.parse(d[0]) %>
|
||||||
|
<% if d[1] %>
|
||||||
|
<div data-image="<%= asset_path(d[1] + ".jpg") %>" data-mode="<%= d[1] %>" data-day="<%= d[0] %>" data-action="click->mood#updateDayInfo mouseover->mood#updateDayInfo mouseleave->mood#updateDayInfo" title="<%= d[0] %> : <%= d[1] %>" class="day <%= d[1] %>"></div>
|
||||||
|
<% else %>
|
||||||
|
<div class="day"></div>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
<h1>Update your password</h1>
|
|
||||||
|
|
||||||
<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %>
|
|
||||||
|
|
||||||
<%= form_with url: password_path(params[:token]), method: :put do |form| %>
|
|
||||||
<%= form.password_field :password, required: true, autocomplete: "new-password", placeholder: "Enter new password", maxlength: 72 %><br>
|
|
||||||
<%= form.password_field :password_confirmation, required: true, autocomplete: "new-password", placeholder: "Repeat new password", maxlength: 72 %><br>
|
|
||||||
<%= form.submit "Save" %>
|
|
||||||
<% end %>
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
<h1>Forgot your password?</h1>
|
|
||||||
|
|
||||||
<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %>
|
|
||||||
|
|
||||||
<%= form_with url: passwords_path do |form| %>
|
|
||||||
<%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address] %><br>
|
|
||||||
<%= form.submit "Email reset instructions" %>
|
|
||||||
<% end %>
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
<p>
|
|
||||||
You can reset your password within the next 15 minutes on
|
|
||||||
<%= link_to "this password reset page", edit_password_url(@user.password_reset_token) %>.
|
|
||||||
</p>
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
You can reset your password within the next 15 minutes on this password reset page:
|
|
||||||
<%= edit_password_url(@user.password_reset_token) %>
|
|
||||||
2
app/views/rfid_tags/post.html.erb
Normal file
2
app/views/rfid_tags/post.html.erb
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
<h1>RfidTags#post</h1>
|
||||||
|
<p>Find me in app/views/rfid_tags/post.html.erb</p>
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
%main.columns.m-auto{"data-controller" => "mood"}
|
|
||||||
.column.is-hidden-mobile
|
|
||||||
%section.m-4
|
|
||||||
%figure.image.has-ratio
|
|
||||||
= image_tag("croisiere.jpg")
|
|
||||||
%section.section.column.has-background-primary-light.is-flex.is-flex-direction-column.is-justify-content-center
|
|
||||||
%h1.title Connexion au tableau de bord de la KLUK
|
|
||||||
.flash.mb-2
|
|
||||||
.has-text-danger= flash[:alert] if flash[:alert]
|
|
||||||
.has-text-info= flash[:notice] if flash[:notice]
|
|
||||||
= form_with url: session_path do |form|
|
|
||||||
.field
|
|
||||||
= form.label :email_address, "Email"
|
|
||||||
.control
|
|
||||||
= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Entrez votre email", value: params[:email_address], class: "input"
|
|
||||||
.field
|
|
||||||
= form.label :password, "Mot de passe"
|
|
||||||
.control
|
|
||||||
= form.password_field :password, required: true, autocomplete: "current-password", placeholder: "Entrez votre mot de passe", maxlength: 72, class: "input"
|
|
||||||
.field
|
|
||||||
.control
|
|
||||||
%button.button.is-link Se connecter
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
<h1>You're invited!</h1>
|
|
||||||
<p>Click here to set up your account:</p>
|
|
||||||
<%= link_to "Set up account", @invite_url %>
|
|
||||||
|
|
@ -34,9 +34,6 @@ Rails.application.configure do
|
||||||
# Don't care if the mailer can't send.
|
# Don't care if the mailer can't send.
|
||||||
config.action_mailer.raise_delivery_errors = false
|
config.action_mailer.raise_delivery_errors = false
|
||||||
|
|
||||||
config.action_mailer.delivery_method = :letter_opener
|
|
||||||
config.action_mailer.perform_deliveries = true
|
|
||||||
|
|
||||||
# Make template changes take effect immediately.
|
# Make template changes take effect immediately.
|
||||||
config.action_mailer.perform_caching = false
|
config.action_mailer.perform_caching = false
|
||||||
|
|
||||||
|
|
@ -72,8 +69,4 @@ Rails.application.configure do
|
||||||
|
|
||||||
# Apply autocorrection by RuboCop to files generated by `bin/rails generate`.
|
# Apply autocorrection by RuboCop to files generated by `bin/rails generate`.
|
||||||
# config.generators.apply_rubocop_autocorrect_after_generate!
|
# config.generators.apply_rubocop_autocorrect_after_generate!
|
||||||
#
|
|
||||||
config.hosts << "localhost.localdomain:3000"
|
|
||||||
config.hosts << "robi.localhost.localdomain:3000"
|
|
||||||
config.hosts << "marie.localhost.localdomain:3000"
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -80,10 +80,10 @@ Rails.application.configure do
|
||||||
config.active_record.attributes_for_inspect = [ :id ]
|
config.active_record.attributes_for_inspect = [ :id ]
|
||||||
|
|
||||||
# Enable DNS rebinding protection and other `Host` header attacks.
|
# Enable DNS rebinding protection and other `Host` header attacks.
|
||||||
config.hosts = [
|
# config.hosts = [
|
||||||
"kluk.fr",
|
# "example.com", # Allow requests from example.com
|
||||||
/.*\.kluk\.fr/
|
# /.*\.example\.com/ # Allow requests from subdomains like `www.example.com`
|
||||||
]
|
# ]
|
||||||
#
|
#
|
||||||
# Skip DNS rebinding protection for the default health check endpoint.
|
# Skip DNS rebinding protection for the default health check endpoint.
|
||||||
# config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
|
# config.host_authorization = { exclude: ->(request) { request.path == "/up" } }
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
Rails.application.routes.draw do
|
Rails.application.routes.draw do
|
||||||
resource :session
|
post '/rfid_tags' => 'rfid_tags#create'
|
||||||
resources :passwords, param: :token
|
|
||||||
post "/rfid_tags" => "rfid_tags#create"
|
|
||||||
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
|
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
|
||||||
|
|
||||||
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
|
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.
|
||||||
|
|
@ -12,16 +10,6 @@ Rails.application.routes.draw do
|
||||||
# get "manifest" => "rails/pwa#manifest", as: :pwa_manifest
|
# get "manifest" => "rails/pwa#manifest", as: :pwa_manifest
|
||||||
# get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker
|
# get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker
|
||||||
|
|
||||||
mount LetterOpenerWeb::Engine, at: "/letter_opener" if Rails.env.development?
|
|
||||||
|
|
||||||
# Defines the root path route ("/")
|
# Defines the root path route ("/")
|
||||||
root "home#index"
|
root "moods#index"
|
||||||
|
|
||||||
get "/invite/:token", to: "invitations#edit", as: :edit_invitation
|
|
||||||
patch "/invite/:token", to: "invitations#update", as: :invitation
|
|
||||||
|
|
||||||
get "/moods", to: "moods#index", as: :dashboard
|
|
||||||
|
|
||||||
get "/day_logs/edit", to: "day_logs#edit", as: :edit_day_log_for_day
|
|
||||||
resources :day_logs, only: [ :create, :update ]
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
class CreateUsers < ActiveRecord::Migration[8.0]
|
|
||||||
def change
|
|
||||||
create_table :users do |t|
|
|
||||||
t.string :email_address, null: false
|
|
||||||
t.string :password_digest, null: false
|
|
||||||
|
|
||||||
t.timestamps
|
|
||||||
end
|
|
||||||
add_index :users, :email_address, unique: true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
class CreateSessions < ActiveRecord::Migration[8.0]
|
|
||||||
def change
|
|
||||||
create_table :sessions do |t|
|
|
||||||
t.references :user, null: false, foreign_key: true
|
|
||||||
t.string :ip_address
|
|
||||||
t.string :user_agent
|
|
||||||
|
|
||||||
t.timestamps
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
class AddInvitationAcceptedAtToUsers < ActiveRecord::Migration[8.0]
|
|
||||||
def change
|
|
||||||
add_column :users, :invitation_accepted_at, :datetime
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
class AddChipIdAndUsersToRfidTags < ActiveRecord::Migration[8.0]
|
|
||||||
def change
|
|
||||||
add_column :rfid_tags, :chip_id, :string
|
|
||||||
add_reference :rfid_tags, :user, foreign_key: true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
class AddUserToMoods < ActiveRecord::Migration[8.0]
|
|
||||||
def change
|
|
||||||
add_reference :moods, :user, foreign_key: true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
class AddUsernameToUsers < ActiveRecord::Migration[8.0]
|
|
||||||
def up
|
|
||||||
add_column :users, :username, :string
|
|
||||||
User.update_all(username: "temporary")
|
|
||||||
change_column_null :users, :username, false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
class AddPublicToUsers < ActiveRecord::Migration[8.0]
|
|
||||||
def change
|
|
||||||
add_column :users, :public, :boolean, default: false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
class AddGuessToUsers < ActiveRecord::Migration[8.0]
|
|
||||||
def change
|
|
||||||
add_column :users, :guess, :boolean, default: false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
class CreateModes < ActiveRecord::Migration[8.0]
|
|
||||||
def change
|
|
||||||
create_table :modes do |t|
|
|
||||||
t.string :label
|
|
||||||
t.string :color
|
|
||||||
t.references :user, null: false, foreign_key: true
|
|
||||||
|
|
||||||
t.timestamps
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
class ReplaceMoodModeStringWithModeReference < ActiveRecord::Migration[8.0]
|
|
||||||
def up
|
|
||||||
rename_column :moods, :mode, :mode_string
|
|
||||||
|
|
||||||
# 1. Ajouter la colonne de référence
|
|
||||||
add_reference :moods, :mode, foreign_key: true
|
|
||||||
|
|
||||||
# 2. Pour chaque mood, trouver ou créer le Mode correspondant
|
|
||||||
Mood.find_each do |mood|
|
|
||||||
next if mood.mode_string.blank?
|
|
||||||
|
|
||||||
mode_record = Mode.find_or_create_by!(label: mood.mode_string, user_id: mood.user_id) do |m|
|
|
||||||
m.color = "#000000" # couleur par défaut
|
|
||||||
end
|
|
||||||
|
|
||||||
mood.update_column(:mode_id, mode_record.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
# 3. Supprimer l'ancienne colonne string
|
|
||||||
remove_column :moods, :mode_string, :string
|
|
||||||
end
|
|
||||||
|
|
||||||
def down
|
|
||||||
add_column :moods, :mode_string, :string
|
|
||||||
|
|
||||||
Mood.find_each do |mood|
|
|
||||||
mood.update_column(:mode_string, mood.mode&.label)
|
|
||||||
end
|
|
||||||
|
|
||||||
remove_reference :moods, :mode, foreign_key: true
|
|
||||||
|
|
||||||
rename_column :moods, :mode_string, :mode
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
# This migration comes from active_storage (originally 20170806125915)
|
|
||||||
class CreateActiveStorageTables < ActiveRecord::Migration[7.0]
|
|
||||||
def change
|
|
||||||
# Use Active Record's configured type for primary and foreign keys
|
|
||||||
primary_key_type, foreign_key_type = primary_and_foreign_key_types
|
|
||||||
|
|
||||||
create_table :active_storage_blobs, id: primary_key_type do |t|
|
|
||||||
t.string :key, null: false
|
|
||||||
t.string :filename, null: false
|
|
||||||
t.string :content_type
|
|
||||||
t.text :metadata
|
|
||||||
t.string :service_name, null: false
|
|
||||||
t.bigint :byte_size, null: false
|
|
||||||
t.string :checksum
|
|
||||||
|
|
||||||
if connection.supports_datetime_with_precision?
|
|
||||||
t.datetime :created_at, precision: 6, null: false
|
|
||||||
else
|
|
||||||
t.datetime :created_at, null: false
|
|
||||||
end
|
|
||||||
|
|
||||||
t.index [ :key ], unique: true
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table :active_storage_attachments, id: primary_key_type do |t|
|
|
||||||
t.string :name, null: false
|
|
||||||
t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
|
|
||||||
t.references :blob, null: false, type: foreign_key_type
|
|
||||||
|
|
||||||
if connection.supports_datetime_with_precision?
|
|
||||||
t.datetime :created_at, precision: 6, null: false
|
|
||||||
else
|
|
||||||
t.datetime :created_at, null: false
|
|
||||||
end
|
|
||||||
|
|
||||||
t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true
|
|
||||||
t.foreign_key :active_storage_blobs, column: :blob_id
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table :active_storage_variant_records, id: primary_key_type do |t|
|
|
||||||
t.belongs_to :blob, null: false, index: false, type: foreign_key_type
|
|
||||||
t.string :variation_digest, null: false
|
|
||||||
|
|
||||||
t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true
|
|
||||||
t.foreign_key :active_storage_blobs, column: :blob_id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def primary_and_foreign_key_types
|
|
||||||
config = Rails.configuration.generators
|
|
||||||
setting = config.options[config.orm][:primary_key_type]
|
|
||||||
primary_key_type = setting || :primary_key
|
|
||||||
foreign_key_type = setting || :bigint
|
|
||||||
[ primary_key_type, foreign_key_type ]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
class CreateDayLogs < ActiveRecord::Migration[8.0]
|
|
||||||
def change
|
|
||||||
create_table :day_logs do |t|
|
|
||||||
t.date :day, null: false
|
|
||||||
t.text :info, null: false
|
|
||||||
t.references :user, null: false, foreign_key: true
|
|
||||||
|
|
||||||
t.index :day, unique: true
|
|
||||||
|
|
||||||
t.timestamps
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
91
db/schema.rb
generated
91
db/schema.rb
generated
|
|
@ -10,102 +10,19 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[8.0].define(version: 2026_03_19_181607) do
|
ActiveRecord::Schema[8.0].define(version: 2025_08_06_151318) do
|
||||||
create_table "active_storage_attachments", force: :cascade do |t|
|
|
||||||
t.string "name", null: false
|
|
||||||
t.string "record_type", null: false
|
|
||||||
t.bigint "record_id", null: false
|
|
||||||
t.bigint "blob_id", null: false
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
|
|
||||||
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "active_storage_blobs", force: :cascade do |t|
|
|
||||||
t.string "key", null: false
|
|
||||||
t.string "filename", null: false
|
|
||||||
t.string "content_type"
|
|
||||||
t.text "metadata"
|
|
||||||
t.string "service_name", null: false
|
|
||||||
t.bigint "byte_size", null: false
|
|
||||||
t.string "checksum"
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "active_storage_variant_records", force: :cascade do |t|
|
|
||||||
t.bigint "blob_id", null: false
|
|
||||||
t.string "variation_digest", null: false
|
|
||||||
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "day_logs", force: :cascade do |t|
|
|
||||||
t.date "day", null: false
|
|
||||||
t.text "info", null: false
|
|
||||||
t.integer "user_id", null: false
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.datetime "updated_at", null: false
|
|
||||||
t.index ["day"], name: "index_day_logs_on_day", unique: true
|
|
||||||
t.index ["user_id"], name: "index_day_logs_on_user_id"
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "modes", force: :cascade do |t|
|
|
||||||
t.string "label"
|
|
||||||
t.string "color"
|
|
||||||
t.integer "user_id", null: false
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.datetime "updated_at", null: false
|
|
||||||
t.index ["user_id"], name: "index_modes_on_user_id"
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "moods", force: :cascade do |t|
|
create_table "moods", force: :cascade do |t|
|
||||||
|
t.string "mode"
|
||||||
t.datetime "recorded_at"
|
t.datetime "recorded_at"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.integer "user_id"
|
|
||||||
t.integer "mode_id"
|
|
||||||
t.index ["mode_id"], name: "index_moods_on_mode_id"
|
|
||||||
t.index ["user_id"], name: "index_moods_on_user_id"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "rfid_tags", force: :cascade do |t|
|
create_table "rfid_tags", force: :cascade do |t|
|
||||||
t.string "identifier"
|
t.string "identifier"
|
||||||
t.string "mode"
|
t.string "mode"
|
||||||
t.datetime "created_at", default: -> { "CURRENT_DATE" }, null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", default: -> { "CURRENT_DATE" }, null: false
|
t.datetime "updated_at", null: false
|
||||||
t.string "chip_id"
|
|
||||||
t.integer "user_id"
|
|
||||||
t.index ["identifier"], name: "index_rfid_tags_on_identifier", unique: true
|
t.index ["identifier"], name: "index_rfid_tags_on_identifier", unique: true
|
||||||
t.index ["user_id"], name: "index_rfid_tags_on_user_id"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "sessions", force: :cascade do |t|
|
|
||||||
t.integer "user_id", null: false
|
|
||||||
t.string "ip_address"
|
|
||||||
t.string "user_agent"
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.datetime "updated_at", null: false
|
|
||||||
t.index ["user_id"], name: "index_sessions_on_user_id"
|
|
||||||
end
|
|
||||||
|
|
||||||
create_table "users", force: :cascade do |t|
|
|
||||||
t.string "email_address", null: false
|
|
||||||
t.string "password_digest", null: false
|
|
||||||
t.datetime "created_at", null: false
|
|
||||||
t.datetime "updated_at", null: false
|
|
||||||
t.datetime "invitation_accepted_at"
|
|
||||||
t.string "username", null: false
|
|
||||||
t.boolean "public", default: false
|
|
||||||
t.boolean "guess", default: false
|
|
||||||
t.index ["email_address"], name: "index_users_on_email_address", unique: true
|
|
||||||
end
|
|
||||||
|
|
||||||
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
|
|
||||||
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
|
|
||||||
add_foreign_key "day_logs", "users"
|
|
||||||
add_foreign_key "modes", "users"
|
|
||||||
add_foreign_key "moods", "modes"
|
|
||||||
add_foreign_key "moods", "users"
|
|
||||||
add_foreign_key "rfid_tags", "users"
|
|
||||||
add_foreign_key "sessions", "users"
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
FactoryBot.define do
|
|
||||||
factory :day_log do
|
|
||||||
day { "2026-03-19" }
|
|
||||||
info { "Arrivée à Paris" }
|
|
||||||
user { association :user }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
FactoryBot.define do
|
|
||||||
factory :mode do
|
|
||||||
label { "MyString" }
|
|
||||||
color { "MyString" }
|
|
||||||
user { nil }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
FactoryBot.define do
|
|
||||||
factory :mood do
|
|
||||||
recorded_at { DateTime.parse("2026-01-15 10:00:00") }
|
|
||||||
association :user
|
|
||||||
association :mode
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
FactoryBot.define do
|
|
||||||
factory :user do
|
|
||||||
sequence(:email_address) { |n| "user#{n}@example.com" }
|
|
||||||
sequence(:username) { |n| "user#{n}" }
|
|
||||||
password { "obanonalors" }
|
|
||||||
password_digest { BCrypt::Password.create(password) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
11
spec/fixtures/users.yml
vendored
11
spec/fixtures/users.yml
vendored
|
|
@ -1,11 +0,0 @@
|
||||||
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
|
|
||||||
|
|
||||||
<% password_digest = BCrypt::Password.create("password") %>
|
|
||||||
|
|
||||||
one:
|
|
||||||
email_address: one@example.com
|
|
||||||
password_digest: <%= password_digest %>
|
|
||||||
|
|
||||||
two:
|
|
||||||
email_address: two@example.com
|
|
||||||
password_digest: <%= password_digest %>
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
describe DayLog, type: :model do
|
|
||||||
subject { build(:day_log) }
|
|
||||||
|
|
||||||
it { is_expected.to be_valid }
|
|
||||||
|
|
||||||
describe "validations" do
|
|
||||||
it "is invalid without a day" do
|
|
||||||
subject.day = nil
|
|
||||||
expect(subject).not_to be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it "is invalid without info" do
|
|
||||||
subject.info = nil
|
|
||||||
expect(subject).not_to be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it "is invalid without a user" do
|
|
||||||
subject.user = nil
|
|
||||||
expect(subject).not_to be_valid
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe Mode, type: :model do
|
|
||||||
pending "add some examples to (or delete) #{__FILE__}"
|
|
||||||
end
|
|
||||||
|
|
@ -1,46 +1,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
describe Mood do
|
describe Mood, type: :model do
|
||||||
let(:user) { create(:user) }
|
it 'works' do
|
||||||
let(:mode) { create(:mode, user: user) }
|
expect(true)
|
||||||
|
|
||||||
subject { build(:mood, user: user, mode: mode, recorded_at: DateTime.parse("2026-01-15 10:00:00")) }
|
|
||||||
|
|
||||||
it { is_expected.to be_valid }
|
|
||||||
|
|
||||||
describe "validations" do
|
|
||||||
it "is invalid without a user" do
|
|
||||||
subject.user = nil
|
|
||||||
expect(subject).not_to be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it "is invalid without a mode" do
|
|
||||||
subject.mode = nil
|
|
||||||
expect(subject).not_to be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it "is invalid without a recorded_at" do
|
|
||||||
subject.recorded_at = nil
|
|
||||||
expect(subject).not_to be_valid
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "uniqueness per day and user" do
|
|
||||||
it "is invalid if a mood already exists for the same day and user" do
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: DateTime.parse("2026-01-15 18:00:00"))
|
|
||||||
expect(subject).not_to be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it "is valid if the same day exists but for a different user" do
|
|
||||||
other_user = create(:user)
|
|
||||||
other_mode = create(:mode, user: other_user)
|
|
||||||
create(:mood, user: other_user, mode: other_mode, recorded_at: DateTime.parse("2026-01-15 10:00:00"))
|
|
||||||
expect(subject).to be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it "is valid if the same user has a mood on a different day" do
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: DateTime.parse("2026-01-14 10:00:00"))
|
|
||||||
expect(subject).to be_valid
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe User, type: :model do
|
|
||||||
pending "add some examples to (or delete) #{__FILE__}"
|
|
||||||
end
|
|
||||||
|
|
@ -9,23 +9,21 @@ abort("The Rails environment is running in production mode!") if Rails.env.produ
|
||||||
# return unless Rails.env.test?
|
# return unless Rails.env.test?
|
||||||
require 'rspec/rails'
|
require 'rspec/rails'
|
||||||
# Add additional requires below this line. Rails is not loaded until this point!
|
# Add additional requires below this line. Rails is not loaded until this point!
|
||||||
require 'factory_bot_rails'
|
|
||||||
|
|
||||||
# Requires supporting ruby files with custom matchers and macros, etc, in
|
# Requires supporting ruby files with custom matchers and macros, etc, in
|
||||||
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
|
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
|
||||||
# run as spec files by default. This means that files in spec/support that end
|
# run as spec files by default. This means that files in spec/support that end
|
||||||
# in _spec.rb will both be required and run as specs, causing the specs to be
|
# in _spec.rb will both be required and run as specs, causing the specs to be
|
||||||
# run twice. It is recommended that you do not name files matching this glob to
|
# run twice. It is recommended that you do not name files matching this glob to
|
||||||
# end with _spec.rb. You can configure this pattern with the --pattern
|
# end with _spec.rb. You can configure this pattern with the --pattern
|
||||||
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
|
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
|
||||||
#
|
#
|
||||||
# The following line is provided for convenience purposes. It has the downside
|
# The following line is provided for convenience purposes. It has the downside
|
||||||
# of increasing the boot-up time by auto-requiring all files in the support
|
# of increasing the boot-up time by auto-requiring all files in the support
|
||||||
# directory. Alternatively, in the individual `*_spec.rb` files, manually
|
# directory. Alternatively, in the individual `*_spec.rb` files, manually
|
||||||
# require only the support files necessary.
|
# require only the support files necessary.
|
||||||
#
|
#
|
||||||
# Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).each { |f| require f }
|
# Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).each { |f| require f }
|
||||||
Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).each { |f| require f }
|
|
||||||
|
|
||||||
# Ensures that the test database schema matches the current schema file.
|
# Ensures that the test database schema matches the current schema file.
|
||||||
# If there are pending migrations it will invoke `db:test:prepare` to
|
# If there are pending migrations it will invoke `db:test:prepare` to
|
||||||
|
|
|
||||||
|
|
@ -1,251 +0,0 @@
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
describe "DayLogs", type: :request do
|
|
||||||
let(:user) { create(:user) }
|
|
||||||
let(:mode) { create(:mode, user: user) }
|
|
||||||
let(:day) { Date.parse("2026-01-15") }
|
|
||||||
|
|
||||||
describe "GET /day_logs/edit" do
|
|
||||||
context "when not authenticated" do
|
|
||||||
it "redirects to the login page" do
|
|
||||||
get edit_day_log_for_day_path(day: day)
|
|
||||||
expect(response).to redirect_to(new_session_path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when authenticated" do
|
|
||||||
before { login_as(user) }
|
|
||||||
|
|
||||||
context "with an existing day_log for the day" do
|
|
||||||
let!(:day_log) { create(:day_log, user: user, day: day) }
|
|
||||||
|
|
||||||
it "returns http success" do
|
|
||||||
get edit_day_log_for_day_path(day: day)
|
|
||||||
expect(response).to have_http_status(:success)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "without an existing day_log for the day" do
|
|
||||||
it "returns http success" do
|
|
||||||
get edit_day_log_for_day_path(day: day)
|
|
||||||
expect(response).to have_http_status(:success)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "does not persist a day_log" do
|
|
||||||
expect {
|
|
||||||
get edit_day_log_for_day_path(day: day)
|
|
||||||
}.not_to change(DayLog, :count)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "POST /day_logs" do
|
|
||||||
context "when not authenticated" do
|
|
||||||
it "redirects to the login page" do
|
|
||||||
post day_logs_path, params: { day_log: { day: day, info: "Une info" } }
|
|
||||||
expect(response).to redirect_to(new_session_path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when authenticated" do
|
|
||||||
before { login_as(user) }
|
|
||||||
|
|
||||||
context "with valid params" do
|
|
||||||
context "without mode" do
|
|
||||||
it "creates a day_log" do
|
|
||||||
expect {
|
|
||||||
post day_logs_path, params: { day_log: { day: day, info: "Une info" } }
|
|
||||||
}.to change(DayLog, :count).by(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "does not create a mood" do
|
|
||||||
expect {
|
|
||||||
post day_logs_path, params: { day_log: { day: day, info: "Une info" } }
|
|
||||||
}.not_to change(Mood, :count)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "with mode, no existing mood" do
|
|
||||||
it "creates a day_log" do
|
|
||||||
expect {
|
|
||||||
post day_logs_path, params: { day_log: { day: day, info: "Une info", mode_id: mode.id } }
|
|
||||||
}.to change(DayLog, :count).by(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "creates a mood" do
|
|
||||||
expect {
|
|
||||||
post day_logs_path, params: { day_log: { day: day, info: "Une info", mode_id: mode.id } }
|
|
||||||
}.to change(Mood, :count).by(1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "with mode, existing mood" do
|
|
||||||
let!(:existing_mood) { create(:mood, user: user, mode: mode, recorded_at: DateTime.parse("2026-01-15 08:00:00")) }
|
|
||||||
let(:other_mode) { create(:mode, user: user) }
|
|
||||||
|
|
||||||
it "creates a day_log" do
|
|
||||||
expect {
|
|
||||||
post day_logs_path, params: { day_log: { day: day, info: "Une info", mode_id: other_mode.id } }
|
|
||||||
}.to change(DayLog, :count).by(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "does not create a new mood" do
|
|
||||||
expect {
|
|
||||||
post day_logs_path, params: { day_log: { day: day, info: "Une info", mode_id: other_mode.id } }
|
|
||||||
}.not_to change(Mood, :count)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "updates the existing mood mode" do
|
|
||||||
post day_logs_path, params: { day_log: { day: day, info: "Une info", mode_id: other_mode.id } }
|
|
||||||
expect(existing_mood.reload.mode).to eq(other_mode)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it "redirects after creation" do
|
|
||||||
post day_logs_path, params: { day_log: { day: day, info: "Une info" } }
|
|
||||||
expect(response).to have_http_status(:redirect)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "with invalid params" do
|
|
||||||
it "does not create a day_log" do
|
|
||||||
expect {
|
|
||||||
post day_logs_path, params: { day_log: { day: nil, info: nil } }
|
|
||||||
}.not_to change(DayLog, :count)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns unprocessable content" do
|
|
||||||
post day_logs_path, params: { day_log: { day: nil, info: nil } }
|
|
||||||
expect(response).to have_http_status(:unprocessable_content)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "GET /day_logs/:id/edit" do
|
|
||||||
let!(:day_log) { create(:day_log, user: user, day: day) }
|
|
||||||
|
|
||||||
context "when not authenticated" do
|
|
||||||
it "redirects to the login page" do
|
|
||||||
get edit_day_log_path(day_log)
|
|
||||||
expect(response).to redirect_to(new_session_path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when authenticated" do
|
|
||||||
before { login_as(user) }
|
|
||||||
|
|
||||||
it "returns http success" do
|
|
||||||
get edit_day_log_path(day_log)
|
|
||||||
expect(response).to have_http_status(:success)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "GET /day_logs/edit" do
|
|
||||||
context "when not authenticated" do
|
|
||||||
it "redirects to the login page" do
|
|
||||||
get edit_day_log_for_day_path(day: day)
|
|
||||||
expect(response).to redirect_to(new_session_path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when authenticated" do
|
|
||||||
before { login_as(user) }
|
|
||||||
|
|
||||||
context "with an existing day_log for the day" do
|
|
||||||
let!(:day_log) { create(:day_log, user: user, day: day) }
|
|
||||||
|
|
||||||
it "returns http success" do
|
|
||||||
get edit_day_log_for_day_path(day: day)
|
|
||||||
expect(response).to have_http_status(:success)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "without an existing day_log for the day" do
|
|
||||||
it "returns http success" do
|
|
||||||
get edit_day_log_for_day_path(day: day)
|
|
||||||
expect(response).to have_http_status(:success)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "does not persist a day_log" do
|
|
||||||
expect {
|
|
||||||
get edit_day_log_for_day_path(day: day)
|
|
||||||
}.not_to change(DayLog, :count)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "PATCH /day_logs/:id" do
|
|
||||||
let!(:day_log) { create(:day_log, user: user, day: day) }
|
|
||||||
|
|
||||||
context "when not authenticated" do
|
|
||||||
it "redirects to the login page" do
|
|
||||||
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info" } }
|
|
||||||
expect(response).to redirect_to(new_session_path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when authenticated" do
|
|
||||||
before { login_as(user) }
|
|
||||||
|
|
||||||
context "with valid params" do
|
|
||||||
context "without mode" do
|
|
||||||
it "updates the day_log" do
|
|
||||||
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info" } }
|
|
||||||
expect(day_log.reload.info).to eq("Nouvelle info")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "does not create a mood" do
|
|
||||||
expect {
|
|
||||||
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info" } }
|
|
||||||
}.not_to change(Mood, :count)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "redirects to dashboard" do
|
|
||||||
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info" } }
|
|
||||||
expect(response).to redirect_to(dashboard_path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "with mode, no existing mood" do
|
|
||||||
it "creates a mood" do
|
|
||||||
expect {
|
|
||||||
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info", mode_id: mode.id } }
|
|
||||||
}.to change(Mood, :count).by(1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "with mode, existing mood" do
|
|
||||||
let!(:existing_mood) { create(:mood, user: user, mode: mode, recorded_at: DateTime.parse("2026-01-15 08:00:00")) }
|
|
||||||
let(:other_mode) { create(:mode, user: user) }
|
|
||||||
|
|
||||||
it "does not create a new mood" do
|
|
||||||
expect {
|
|
||||||
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info", mode_id: other_mode.id } }
|
|
||||||
}.not_to change(Mood, :count)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "updates the existing mood mode" do
|
|
||||||
patch day_log_path(day_log), params: { day_log: { info: "Nouvelle info", mode_id: other_mode.id } }
|
|
||||||
expect(existing_mood.reload.mode).to eq(other_mode)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "with invalid params" do
|
|
||||||
it "does not update the day_log" do
|
|
||||||
patch day_log_path(day_log), params: { day_log: { info: nil } }
|
|
||||||
expect(day_log.reload.info).not_to be_nil
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns unprocessable content" do
|
|
||||||
patch day_log_path(day_log), params: { day_log: { info: nil } }
|
|
||||||
expect(response).to have_http_status(:unprocessable_content)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,359 +0,0 @@
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe MoodCalendarService do
|
|
||||||
let(:user) { create(:user) }
|
|
||||||
let(:mode) { create(:mode, user: user) }
|
|
||||||
|
|
||||||
describe '.generate_calendar' do
|
|
||||||
context 'avec des jours manquants' do
|
|
||||||
it 'remplit les jours manquants avec mode: nil et guess égal au dernier mode' do
|
|
||||||
mode2 = create(:mode, user: user)
|
|
||||||
mode3 = create(:mode, user: user)
|
|
||||||
create(:mood, user: user, mode: mode2, recorded_at: Time.zone.parse("2025-02-12 14:20:00"))
|
|
||||||
create(:mood, user: user, mode: mode3, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-18 16:45:00"))
|
|
||||||
|
|
||||||
result = MoodCalendarService.generate_calendar(
|
|
||||||
user,
|
|
||||||
end_date: Date.parse("2025-02-28")
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(result.length).to be >= 1
|
|
||||||
expect(result.any? { |m| m[:month] == Date.parse("2025-02-01") }).to be true
|
|
||||||
|
|
||||||
all_days = result.find { |m| m[:month] == Date.parse("2025-02-01") }[:weeks].flatten
|
|
||||||
|
|
||||||
mood_12 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-12") }
|
|
||||||
expect(mood_12[:mode]).to eq(mode2)
|
|
||||||
expect(mood_12[:recorded_at].to_i).to eq(Time.zone.parse("2025-02-12 14:20:00").to_i)
|
|
||||||
|
|
||||||
mood_13 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-13") }
|
|
||||||
expect(mood_13[:mode]).to be_nil
|
|
||||||
expect(mood_13[:guess]).to eq(mode2)
|
|
||||||
|
|
||||||
mood_14 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-14") }
|
|
||||||
expect(mood_14[:mode]).to be_nil
|
|
||||||
expect(mood_14[:guess]).to eq(mode2)
|
|
||||||
|
|
||||||
mood_15 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-15") }
|
|
||||||
expect(mood_15[:mode]).to eq(mode3)
|
|
||||||
|
|
||||||
mood_16 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-16") }
|
|
||||||
expect(mood_16[:mode]).to be_nil
|
|
||||||
expect(mood_16[:guess]).to eq(mode3)
|
|
||||||
|
|
||||||
mood_3 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-03") }
|
|
||||||
expect(mood_3[:mode]).to be_nil
|
|
||||||
expect(mood_3[:guess]).to be_nil
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'gère correctement plusieurs mois avec des jours manquants' do
|
|
||||||
mode2 = create(:mode, user: user)
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
|
|
||||||
create(:mood, user: user, mode: mode2, recorded_at: Time.zone.parse("2025-04-20 11:30:00"))
|
|
||||||
|
|
||||||
result = MoodCalendarService.generate_calendar(
|
|
||||||
user,
|
|
||||||
end_date: Date.parse("2025-04-30")
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(result.length).to be >= 3
|
|
||||||
expect(result.any? { |m| m[:month] == Date.parse("2025-02-01") }).to be true
|
|
||||||
expect(result.any? { |m| m[:month] == Date.parse("2025-03-01") }).to be true
|
|
||||||
expect(result.any? { |m| m[:month] == Date.parse("2025-04-01") }).to be true
|
|
||||||
|
|
||||||
march = result.find { |m| m[:month] == Date.parse("2025-03-01") }
|
|
||||||
all_march_days = march[:weeks].flatten
|
|
||||||
expect(all_march_days.all? { |d| d[:mode].nil? }).to be true
|
|
||||||
all_march_days.each do |day|
|
|
||||||
expect(day[:guess]).to be_present,
|
|
||||||
"Le jour #{day[:recorded_at].to_date} devrait avoir un guess"
|
|
||||||
end
|
|
||||||
|
|
||||||
april = result.find { |m| m[:month] == Date.parse("2025-04-01") }
|
|
||||||
all_april_days = april[:weeks].flatten
|
|
||||||
mood_20_april = all_april_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-04-20") }
|
|
||||||
expect(mood_20_april[:mode]).to eq(mode2)
|
|
||||||
|
|
||||||
mood_10_april = all_april_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-04-10") }
|
|
||||||
expect(mood_10_april[:guess]).to eq(mode)
|
|
||||||
|
|
||||||
mood_21_april = all_april_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-04-21") }
|
|
||||||
expect(mood_21_april[:guess]).to eq(mode2)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'sans aucun recorded_at' do
|
|
||||||
it 'retourne un calendrier à partir de la date courante' do
|
|
||||||
result = MoodCalendarService.generate_calendar(user)
|
|
||||||
|
|
||||||
expect(result).not_to be_empty
|
|
||||||
expect(result.first[:month]).to eq(Date.current.beginning_of_month)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'structure du calendrier' do
|
|
||||||
it 'commence chaque mois par le premier lundi' do
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
|
|
||||||
|
|
||||||
result = MoodCalendarService.generate_calendar(
|
|
||||||
user,
|
|
||||||
end_date: Date.parse("2025-02-28")
|
|
||||||
)
|
|
||||||
|
|
||||||
february = result[0]
|
|
||||||
first_day = february[:weeks][0][0]
|
|
||||||
|
|
||||||
expect(first_day[:recorded_at].to_date).to eq(Date.parse("2025-02-03"))
|
|
||||||
expect(first_day[:recorded_at].to_date.monday?).to be true
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'va du premier lundi du mois au dimanche avant le premier lundi du mois suivant' do
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
|
|
||||||
|
|
||||||
result = MoodCalendarService.generate_calendar(
|
|
||||||
user,
|
|
||||||
end_date: Date.parse("2025-03-31")
|
|
||||||
)
|
|
||||||
|
|
||||||
february = result[0]
|
|
||||||
all_days = february[:weeks].flatten
|
|
||||||
|
|
||||||
first_day = all_days.first
|
|
||||||
expect(first_day[:recorded_at].to_date).to eq(Date.parse("2025-02-03"))
|
|
||||||
expect(first_day[:recorded_at].to_date.monday?).to be true
|
|
||||||
|
|
||||||
last_day = all_days.last
|
|
||||||
expect(last_day[:recorded_at].to_date).to eq(Date.parse("2025-03-02"))
|
|
||||||
expect(last_day[:recorded_at].to_date.sunday?).to be true
|
|
||||||
|
|
||||||
expect(all_days.any? { |d| d[:recorded_at].to_date == Date.parse("2025-03-03") }).to be false
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'toutes les semaines ont exactement 7 jours' do
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
|
|
||||||
|
|
||||||
result = MoodCalendarService.generate_calendar(
|
|
||||||
user,
|
|
||||||
end_date: Date.parse("2025-02-28")
|
|
||||||
)
|
|
||||||
|
|
||||||
february = result[0]
|
|
||||||
february[:weeks].each do |week|
|
|
||||||
expect(week.length).to eq(7)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'retourne month comme Date (le premier du mois)' do
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
|
|
||||||
|
|
||||||
result = MoodCalendarService.generate_calendar(
|
|
||||||
user,
|
|
||||||
end_date: Date.parse("2025-02-28")
|
|
||||||
)
|
|
||||||
|
|
||||||
february = result[0]
|
|
||||||
expect(february[:month]).to be_a(Date)
|
|
||||||
expect(february[:month]).to eq(Date.parse("2025-02-01"))
|
|
||||||
expect(february[:month].day).to eq(1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'avec paramètres par défaut' do
|
|
||||||
it "utilise le premier mood comme start_date et aujourd'hui comme end_date" do
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
|
|
||||||
|
|
||||||
result = MoodCalendarService.generate_calendar(user)
|
|
||||||
|
|
||||||
expect(result).not_to be_empty
|
|
||||||
expect(result.first[:month]).to eq(Date.parse("2025-02-01"))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'avec start_date spécifié' do
|
|
||||||
it 'commence à la date spécifiée' do
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
|
|
||||||
|
|
||||||
result = MoodCalendarService.generate_calendar(
|
|
||||||
user,
|
|
||||||
start_date: Date.parse("2025-03-01"),
|
|
||||||
end_date: Date.parse("2025-03-31")
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(result.first[:month]).to eq(Date.parse("2025-03-01"))
|
|
||||||
expect(result.any? { |m| m[:month] == Date.parse("2025-02-01") }).to be false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'avec end_date spécifié' do
|
|
||||||
it 'se termine à end_date' do
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
|
|
||||||
|
|
||||||
result = MoodCalendarService.generate_calendar(
|
|
||||||
user,
|
|
||||||
end_date: Date.parse("2025-09-30")
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(result.last[:month]).to eq(Date.parse("2025-09-01"))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'avec start_date et end_date spécifiés' do
|
|
||||||
it 'génère le calendrier pour la plage spécifiée' do
|
|
||||||
mode2 = create(:mode, user: user)
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
|
|
||||||
create(:mood, user: user, mode: mode2, recorded_at: Time.zone.parse("2025-04-20 10:00:00"))
|
|
||||||
|
|
||||||
result = MoodCalendarService.generate_calendar(
|
|
||||||
user,
|
|
||||||
start_date: Date.parse("2025-03-01"),
|
|
||||||
end_date: Date.parse("2025-03-31")
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(result.length).to eq(1)
|
|
||||||
expect(result.first[:month]).to eq(Date.parse("2025-03-01"))
|
|
||||||
|
|
||||||
all_days = result.first[:weeks].flatten
|
|
||||||
expect(all_days.all? { |d| d[:mode].nil? }).to be true
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'gère une plage de plusieurs mois' do
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
|
|
||||||
|
|
||||||
result = MoodCalendarService.generate_calendar(
|
|
||||||
user,
|
|
||||||
start_date: Date.parse("2025-02-01"),
|
|
||||||
end_date: Date.parse("2025-04-30")
|
|
||||||
)
|
|
||||||
|
|
||||||
months = result.map { |m| m[:month] }
|
|
||||||
expect(months).to include(
|
|
||||||
Date.parse("2025-02-01"),
|
|
||||||
Date.parse("2025-03-01"),
|
|
||||||
Date.parse("2025-04-01")
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'fonctionne avec DateTime en paramètres' do
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
|
|
||||||
|
|
||||||
result = MoodCalendarService.generate_calendar(
|
|
||||||
user,
|
|
||||||
start_date: Time.zone.parse("2025-02-01 00:00:00"),
|
|
||||||
end_date: Time.zone.parse("2025-02-28 23:59:59")
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(result).not_to be_empty
|
|
||||||
expect(result.first[:month]).to eq(Date.parse("2025-02-01"))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'avec une plage ne contenant aucun mood' do
|
|
||||||
it 'génère un calendrier avec tous les jours à nil et guess du dernier mood avant la plage' do
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
|
|
||||||
|
|
||||||
result = MoodCalendarService.generate_calendar(
|
|
||||||
user,
|
|
||||||
start_date: Date.parse("2025-04-01"),
|
|
||||||
end_date: Date.parse("2025-04-30")
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(result.first[:month]).to eq(Date.parse("2025-04-01"))
|
|
||||||
|
|
||||||
all_days = result.first[:weeks].flatten
|
|
||||||
expect(all_days.all? { |d| d[:mode].nil? }).to be true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'avec plusieurs mois' do
|
|
||||||
it 'chaque mois commence et finit correctement' do
|
|
||||||
mode2 = create(:mode, user: user)
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
|
|
||||||
create(:mood, user: user, mode: mode2, recorded_at: Time.zone.parse("2025-03-20 10:00:00"))
|
|
||||||
|
|
||||||
result = MoodCalendarService.generate_calendar(
|
|
||||||
user,
|
|
||||||
end_date: Date.parse("2025-03-31")
|
|
||||||
)
|
|
||||||
|
|
||||||
february = result.find { |m| m[:month] == Date.parse("2025-02-01") }
|
|
||||||
march = result.find { |m| m[:month] == Date.parse("2025-03-01") }
|
|
||||||
|
|
||||||
last_day_february = february[:weeks].flatten.last
|
|
||||||
expect(last_day_february[:recorded_at].to_date).to eq(Date.parse("2025-03-02"))
|
|
||||||
|
|
||||||
first_day_march = march[:weeks].flatten.first
|
|
||||||
expect(first_day_march[:recorded_at].to_date).to eq(Date.parse("2025-03-03"))
|
|
||||||
|
|
||||||
expect(last_day_february[:recorded_at].to_date + 1.day).to eq(first_day_march[:recorded_at].to_date)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'avec un scope spécifique' do
|
|
||||||
it 'fonctionne avec des moods filtrés par user' do
|
|
||||||
user2 = create(:user)
|
|
||||||
mode2 = create(:mode, user: user2)
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
|
|
||||||
create(:mood, user: user2, mode: mode2, recorded_at: Time.zone.parse("2025-02-16 10:00:00"))
|
|
||||||
|
|
||||||
result = MoodCalendarService.generate_calendar(
|
|
||||||
user,
|
|
||||||
end_date: Date.parse("2025-02-28")
|
|
||||||
)
|
|
||||||
|
|
||||||
all_days = result[0][:weeks].flatten
|
|
||||||
mood_15 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-15") }
|
|
||||||
expect(mood_15[:mode]).to eq(mode)
|
|
||||||
|
|
||||||
mood_16 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-16") }
|
|
||||||
expect(mood_16[:mode]).to be_nil
|
|
||||||
expect(mood_16[:guess]).to eq(mode)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
context 'avec day_log' do
|
|
||||||
it 'inclut le day_log dans les données du jour correspondant' do
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
|
|
||||||
day_log = create(:day_log, user: user, day: Date.parse("2025-02-15"), info: "Une belle journée")
|
|
||||||
|
|
||||||
result = MoodCalendarService.generate_calendar(user, end_date: Date.parse("2025-02-28"))
|
|
||||||
|
|
||||||
all_days = result.find { |m| m[:month] == Date.parse("2025-02-01") }[:weeks].flatten
|
|
||||||
day_15 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-15") }
|
|
||||||
expect(day_15[:day_log]).to eq(day_log)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'retourne nil pour day_log quand il n\'en existe pas pour ce jour' do
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
|
|
||||||
|
|
||||||
result = MoodCalendarService.generate_calendar(user, end_date: Date.parse("2025-02-28"))
|
|
||||||
|
|
||||||
all_days = result.find { |m| m[:month] == Date.parse("2025-02-01") }[:weeks].flatten
|
|
||||||
day_15 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-15") }
|
|
||||||
expect(day_15[:day_log]).to be_nil
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'inclut le day_log pour un jour sans mood' do
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
|
|
||||||
day_log = create(:day_log, user: user, day: Date.parse("2025-02-16"), info: "Jour sans mood")
|
|
||||||
|
|
||||||
result = MoodCalendarService.generate_calendar(user, end_date: Date.parse("2025-02-28"))
|
|
||||||
|
|
||||||
all_days = result.find { |m| m[:month] == Date.parse("2025-02-01") }[:weeks].flatten
|
|
||||||
day_16 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-16") }
|
|
||||||
expect(day_16[:mode]).to be_nil
|
|
||||||
expect(day_16[:day_log]).to eq(day_log)
|
|
||||||
end
|
|
||||||
it 'inclut le day_log pour un jour avec mood' do
|
|
||||||
create(:mood, user: user, mode: mode, recorded_at: Time.zone.parse("2025-02-15 10:00:00"))
|
|
||||||
day_log = create(:day_log, user: user, day: Date.parse("2025-02-15"), info: "Jour avec mood")
|
|
||||||
|
|
||||||
result = MoodCalendarService.generate_calendar(user, end_date: Date.parse("2025-02-28"))
|
|
||||||
|
|
||||||
all_days = result.find { |m| m[:month] == Date.parse("2025-02-01") }[:weeks].flatten
|
|
||||||
day_15 = all_days.find { |m| m[:recorded_at].to_date == Date.parse("2025-02-15") }
|
|
||||||
expect(day_15[:mode]).to eq(mode)
|
|
||||||
expect(day_15[:day_log]).to eq(day_log)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
module AuthenticationHelper
|
|
||||||
def login_as(user)
|
|
||||||
post session_path, params: {
|
|
||||||
email_address: user.email_address,
|
|
||||||
password: user.password
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
RSpec.configure do |config|
|
|
||||||
config.include AuthenticationHelper, type: :request
|
|
||||||
end
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
RSpec.configure do |config|
|
|
||||||
config.include FactoryBot::Syntax::Methods
|
|
||||||
end
|
|
||||||
|
|
@ -7,11 +7,6 @@
|
||||||
resolved "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz"
|
resolved "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz"
|
||||||
integrity sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==
|
integrity sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==
|
||||||
|
|
||||||
"@parcel/watcher-linux-x64-musl@2.5.1":
|
|
||||||
version "2.5.1"
|
|
||||||
resolved "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz"
|
|
||||||
integrity sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==
|
|
||||||
|
|
||||||
"@parcel/watcher@^2.4.1":
|
"@parcel/watcher@^2.4.1":
|
||||||
version "2.5.1"
|
version "2.5.1"
|
||||||
resolved "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz"
|
resolved "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue