Compare commits
29 Commits
Author | SHA1 | Date |
---|---|---|
Arthur POULET | 35b69ef33a | |
Arthur POULET | 46d14c2917 | |
Arthur POULET | 3d607ade56 | |
Arthur POULET | 08a2729ce6 | |
Arthur POULET | d4de167df2 | |
Arthur POULET | 29e78b7f52 | |
Arthur POULET | 5f8afb008e | |
Arthur POULET | b9bc73b3b8 | |
Arthur POULET | 8efe5d3ab3 | |
Arthur POULET | db9c5cd7a4 | |
Arthur POULET | 76da1fd1a8 | |
Arthur POULET | 1f44afb893 | |
Arthur POULET | 0485f8131d | |
Arthur POULET | ac29e5bb41 | |
Arthur POULET | 690d41d33f | |
Arthur POULET | fef976ed5c | |
Arthur POULET | c2b5dd96b2 | |
Arthur POULET | 5a24cdb6b7 | |
Arthur POULET | ab708456ed | |
Arthur POULET | f2ad52586f | |
Arthur POULET | d1fae9bf63 | |
Arthur POULET | fb02993fa3 | |
Arthur POULET | b36249ae76 | |
Arthur POULET | 2559c8c7a7 | |
Arthur POULET | fd23535924 | |
Arthur POULET | 0e26243315 | |
Arthur POULET | 9315c422a0 | |
Arthur POULET | 08c895f3a5 | |
Arthur POULET | 52a8d4b5fe |
|
@ -3,11 +3,11 @@ name: default
|
|||
|
||||
steps:
|
||||
- name: test
|
||||
image: ruby:3.0
|
||||
image: ruby:3.1
|
||||
environment:
|
||||
LIFEPEX_DB: "sqlite://test.db"
|
||||
LIFEPEX_ENV: "test"
|
||||
commands:
|
||||
- bundle install --jobs=1 --retry=1
|
||||
- ./init/database.rb
|
||||
- rake db:migrate
|
||||
- rake test
|
||||
|
|
32
Gemfile
32
Gemfile
|
@ -3,34 +3,34 @@
|
|||
source "https://rubygems.org"
|
||||
|
||||
# web
|
||||
gem "puma", "~> 5.3"
|
||||
gem "sinatra", "~> 2.1"
|
||||
gem "sinatra-contrib", "~> 2.1"
|
||||
gem "slim", "~> 4.1"
|
||||
gem "puma", "~> 5"
|
||||
gem "sinatra", "~> 2"
|
||||
gem "sinatra-contrib", "~> 2"
|
||||
gem "slim", "~> 4"
|
||||
|
||||
# database
|
||||
# you # comment what you don't want
|
||||
gem "sequel", "~> 5.43"
|
||||
gem "sqlite3", "~> 1.4"
|
||||
gem "pg", "~> 1.2"
|
||||
gem "sequel", "~> 5"
|
||||
# you # comment the drivers you don't want
|
||||
gem "sqlite3", "~> 1"
|
||||
# gem "pg", "~> 1.2"
|
||||
|
||||
# security
|
||||
gem "jwt", "~> 2.2"
|
||||
gem "bcrypt", "~> 3.1"
|
||||
gem "rack_csrf", "~> 2.6"
|
||||
gem "jwt", "~> 2"
|
||||
gem "bcrypt", "~> 3"
|
||||
gem "rack_csrf", "~> 2"
|
||||
|
||||
# api tools
|
||||
gem "doc_my_routes"
|
||||
|
||||
# debug and helpers
|
||||
gem "colorize", "~> 0.8.1"
|
||||
gem "activesupport", "= 6.1.3.1"
|
||||
gem "colorize", "~> 0.8"
|
||||
gem "activesupport", "~> 6"
|
||||
|
||||
# tests
|
||||
group :test do
|
||||
gem "pry", "~> 0.14.1"
|
||||
gem "rack-test", "~> 1.1", require: false
|
||||
gem "pry"
|
||||
gem "rack-test", "~> 1", require: false
|
||||
# gem "simplecov", "~> 0.21.2", require: false
|
||||
end
|
||||
|
||||
gem "dotenv", "~> 2.7"
|
||||
gem "dotenv", "~> 2"
|
||||
|
|
80
Gemfile.lock
80
Gemfile.lock
|
@ -1,83 +1,81 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (6.1.3.1)
|
||||
activesupport (6.1.6.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
zeitwerk (~> 2.3)
|
||||
bcrypt (3.1.16)
|
||||
bcrypt (3.1.18)
|
||||
coderay (1.1.3)
|
||||
colorize (0.8.1)
|
||||
concurrent-ruby (1.1.9)
|
||||
concurrent-ruby (1.1.10)
|
||||
doc_my_routes (0.13.0)
|
||||
dotenv (2.7.6)
|
||||
i18n (1.8.10)
|
||||
dotenv (2.8.1)
|
||||
i18n (1.12.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jwt (2.2.3)
|
||||
jwt (2.4.1)
|
||||
method_source (1.0.0)
|
||||
minitest (5.14.4)
|
||||
minitest (5.16.2)
|
||||
multi_json (1.15.0)
|
||||
mustermann (1.1.1)
|
||||
mustermann (2.0.2)
|
||||
ruby2_keywords (~> 0.0.1)
|
||||
nio4r (2.5.7)
|
||||
pg (1.2.3)
|
||||
nio4r (2.5.8)
|
||||
pry (0.14.1)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
puma (5.3.2)
|
||||
puma (5.6.4)
|
||||
nio4r (~> 2.0)
|
||||
rack (2.2.3)
|
||||
rack-protection (2.1.0)
|
||||
rack (2.2.4)
|
||||
rack-protection (2.2.2)
|
||||
rack
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rack_csrf (2.6.0)
|
||||
rack (>= 1.1.0)
|
||||
ruby2_keywords (0.0.4)
|
||||
sequel (5.45.0)
|
||||
sinatra (2.1.0)
|
||||
mustermann (~> 1.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
sequel (5.58.0)
|
||||
sinatra (2.2.2)
|
||||
mustermann (~> 2.0)
|
||||
rack (~> 2.2)
|
||||
rack-protection (= 2.1.0)
|
||||
rack-protection (= 2.2.2)
|
||||
tilt (~> 2.0)
|
||||
sinatra-contrib (2.1.0)
|
||||
sinatra-contrib (2.2.2)
|
||||
multi_json
|
||||
mustermann (~> 1.0)
|
||||
rack-protection (= 2.1.0)
|
||||
sinatra (= 2.1.0)
|
||||
mustermann (~> 2.0)
|
||||
rack-protection (= 2.2.2)
|
||||
sinatra (= 2.2.2)
|
||||
tilt (~> 2.0)
|
||||
slim (4.1.0)
|
||||
temple (>= 0.7.6, < 0.9)
|
||||
tilt (>= 2.0.6, < 2.1)
|
||||
sqlite3 (1.4.2)
|
||||
sqlite3 (1.4.4)
|
||||
temple (0.8.2)
|
||||
tilt (2.0.10)
|
||||
tzinfo (2.0.4)
|
||||
tilt (2.0.11)
|
||||
tzinfo (2.0.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
zeitwerk (2.4.2)
|
||||
zeitwerk (2.6.0)
|
||||
|
||||
PLATFORMS
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
activesupport (= 6.1.3.1)
|
||||
bcrypt (~> 3.1)
|
||||
colorize (~> 0.8.1)
|
||||
activesupport (~> 6)
|
||||
bcrypt (~> 3)
|
||||
colorize (~> 0.8)
|
||||
doc_my_routes
|
||||
dotenv (~> 2.7)
|
||||
jwt (~> 2.2)
|
||||
pg (~> 1.2)
|
||||
pry (~> 0.14.1)
|
||||
puma (~> 5.3)
|
||||
rack-test (~> 1.1)
|
||||
rack_csrf (~> 2.6)
|
||||
sequel (~> 5.43)
|
||||
sinatra (~> 2.1)
|
||||
sinatra-contrib (~> 2.1)
|
||||
slim (~> 4.1)
|
||||
sqlite3 (~> 1.4)
|
||||
dotenv (~> 2)
|
||||
jwt (~> 2)
|
||||
pry
|
||||
puma (~> 5)
|
||||
rack-test (~> 1)
|
||||
rack_csrf (~> 2)
|
||||
sequel (~> 5)
|
||||
sinatra (~> 2)
|
||||
sinatra-contrib (~> 2)
|
||||
slim (~> 4)
|
||||
sqlite3 (~> 1)
|
||||
|
||||
BUNDLED WITH
|
||||
2.2.16
|
||||
|
|
10
README.md
10
README.md
|
@ -46,7 +46,7 @@ Since I suck with SGDB administration, this is a sample of what you may do.
|
|||
psql -U postgres postgres -c "CREATE USER root WITH PASSWORD 'toor' SUPERUSER;"
|
||||
psql -U postgres postgres -c "CREATE DATABASE life_pex"
|
||||
echo LIFEPEX_DB=postgres://root:toor@localhost/life_pex >> .env.local
|
||||
./init/database.rb
|
||||
rake db:migrate
|
||||
```
|
||||
|
||||
### Start
|
||||
|
@ -91,7 +91,13 @@ editor .env.test.local # don't forget to set a new database !!!
|
|||
Then init the database
|
||||
|
||||
```
|
||||
LIFEPEX_ENV=test ./init/database.rb
|
||||
LIFEPEX_ENV=test rake db:migrate
|
||||
```
|
||||
|
||||
Then if you want to run the test, simply type `rake test` (you will need the startup env variable to be set first).
|
||||
|
||||
### Debug
|
||||
|
||||
Some debug options can be enabled with the env variable
|
||||
|
||||
`LIFEPEX_ENV=debug`
|
||||
|
|
13
Rakefile
13
Rakefile
|
@ -5,4 +5,17 @@ Rake::TestTask.new do |t|
|
|||
t.pattern = "test/*_test.rb"
|
||||
end
|
||||
|
||||
namespace "db" do
|
||||
desc "Migrate the database to the lasted schema"
|
||||
task "migrate" do
|
||||
require_relative "./init/migrate_db"
|
||||
end
|
||||
|
||||
desc "Reset all tables, schema, data"
|
||||
task "reset" do
|
||||
require_relative "./init/load_env"
|
||||
DB.tables.each {|t| DB.drop_table t }
|
||||
end
|
||||
end
|
||||
|
||||
task default: :test
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
require "sequel"
|
||||
require "colorize"
|
||||
|
||||
require_relative "../src/utils/env"
|
||||
require_relative "../src/utils/semver"
|
||||
|
||||
load_dotenv
|
||||
DB = Sequel.connect ENV["LIFEPEX_DB"]
|
||||
|
||||
if ENV["environment"] == "debug"
|
||||
require "pry"
|
||||
binding.pry
|
||||
end
|
|
@ -1,41 +1,30 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require "sequel"
|
||||
require "colorize"
|
||||
require "dotenv"
|
||||
Dotenv.load(".env.local", ".env")
|
||||
|
||||
require_relative "../src/utils/semver.rb"
|
||||
|
||||
DB = Sequel.connect ENV["LIFEPEX_DB"]
|
||||
|
||||
if ENV["environment"] == "debug"
|
||||
require "pry"
|
||||
binding.pry
|
||||
end
|
||||
require_relative "./load_env"
|
||||
|
||||
def current_version
|
||||
DB[:meta].first[:version] rescue 0
|
||||
end
|
||||
|
||||
def migrate(version, &block)
|
||||
puts "Check migration #{version}"
|
||||
def migrate(version, message = nil, &block)
|
||||
puts "Check migration #{version}".on_blue
|
||||
if current_version < version
|
||||
puts "Migrate #{version}".blue
|
||||
begin
|
||||
yield
|
||||
DB[:meta].update(version: version)
|
||||
puts "Successfuly set version #{version}"
|
||||
puts "Successfuly set version #{version}".green
|
||||
puts message.green if message
|
||||
rescue => err
|
||||
puts err.message.red
|
||||
puts err.message.on_red
|
||||
exit 1
|
||||
end
|
||||
else
|
||||
puts "Already migrated #{version}".blue
|
||||
puts "Already migrated #{version}".yellow
|
||||
end
|
||||
end
|
||||
|
||||
migrate 1 do
|
||||
migrate 1, "Initialized database" do
|
||||
DB.create_table :meta do
|
||||
primary_key :id
|
||||
Int :version
|
||||
|
@ -60,33 +49,29 @@ migrate 1 do
|
|||
String :username
|
||||
String :hashed_password
|
||||
end rescue puts "users already exists".yellow
|
||||
puts "Initialized database".green
|
||||
end
|
||||
|
||||
migrate 2 do
|
||||
migrate 2, "Add pex categories" do
|
||||
DB.alter_table :pexs do
|
||||
add_column :category, String
|
||||
end rescue puts "pexs.category already exists".yellow
|
||||
puts "Migrated categories".green
|
||||
end
|
||||
|
||||
migrate 3 do
|
||||
migrate 3, "Add pex user belonging" do
|
||||
DB.alter_table :pexs do
|
||||
add_column :user_id, :Int
|
||||
end rescue puts "pexs.user_id already exists".yellow
|
||||
DB[:pexs].update(user_id: 1)
|
||||
puts "Migrated pex belonging to user".green
|
||||
end
|
||||
|
||||
migrate 4 do
|
||||
migrate 4, "Fix pex category default behavior" do
|
||||
DB.alter_table :pexs do
|
||||
set_column_default(:category, '')
|
||||
end rescue puts "pexs.category default already exists".yellow
|
||||
DB[:pexs].where(category: nil).update(category: '')
|
||||
puts "Migrated pex default category".green
|
||||
end
|
||||
|
||||
migrate 5 do
|
||||
migrate 5, "Add meta schema" do
|
||||
DB.alter_table :meta do
|
||||
add_column :code_version, String
|
||||
end rescue puts "meta.code_version already exists".yellow
|
||||
|
@ -99,18 +84,20 @@ migrate 6 do
|
|||
end rescue puts "meta.code_date already exists".yellow
|
||||
end
|
||||
|
||||
migrate 7 do
|
||||
DB[:pexs].each { |pex| DB[:pexs].where(id: pex[:id]).update(category: pex[:category].to_s.downcase) }
|
||||
migrate 7, "Fix pex initial category" do
|
||||
DB[:pexs].each do |pex|
|
||||
DB[:pexs].where(id: pex[:id]).update(category: pex[:category].to_s.downcase)
|
||||
end
|
||||
end
|
||||
|
||||
migrate 8 do
|
||||
migrate 8, "Add default hidden for pexs" do
|
||||
DB.alter_table :pexs do
|
||||
add_column :hidden, TrueClass
|
||||
end rescue puts "pex.hidden already exists".yellow
|
||||
DB[:pexs].each { |pex| DB[:pexs].where(id: pex[:id]).update(hidden: false) }
|
||||
end
|
||||
|
||||
migrate 9 do
|
||||
migrate 9, "Add achievements" do
|
||||
DB.create_table :achievements do
|
||||
primary_key :id
|
||||
Int :user_id
|
||||
|
@ -121,14 +108,25 @@ migrate 9 do
|
|||
end
|
||||
end
|
||||
|
||||
migrate 10 do
|
||||
migrate 10, "Add generic flag to pexs, for bookmarking" do
|
||||
DB.alter_table :pexs do
|
||||
add_column :flag, :String
|
||||
end rescue puts "pexs.flag already exists".yellow
|
||||
puts "Migrated pex flag for bookmarks".green
|
||||
end
|
||||
|
||||
puts "End migration".green
|
||||
migrate 11, "Add recalls" do
|
||||
DB.create_table :recalls do
|
||||
primary_key :id
|
||||
Int :user_id
|
||||
Int :pex_id
|
||||
String :name
|
||||
Int :span_duration
|
||||
Int :repeated
|
||||
end
|
||||
puts "Migrated recalls".green
|
||||
end
|
||||
|
||||
puts "End migration".on_blue
|
||||
CODE_VERSION = `git tag`.split("\n").map{ |str| Utils::Semver.new(str) }.sort.last.to_s
|
||||
CODE_DATE=`git show #{CODE_VERSION} --pretty="format:%as"`.split("\n").first
|
||||
DB[:meta].update(code_version: CODE_VERSION)
|
|
@ -70,6 +70,10 @@ h2 {
|
|||
height: 45px;
|
||||
}
|
||||
|
||||
.smaller {
|
||||
font-size: x-small !important;
|
||||
}
|
||||
|
||||
.highcharts-figure, .highcharts-data-table table {
|
||||
min-width: 360px;
|
||||
max-width: 800px;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
function setupChart(...cumuls) {
|
||||
function setupChart({ all_dates }, ...cumuls) {
|
||||
const chart = Highcharts.chart('recap-xp-container', {
|
||||
chart: {
|
||||
backgroundColor: '#000',
|
||||
|
@ -7,6 +7,9 @@ function setupChart(...cumuls) {
|
|||
text: '',
|
||||
color: '#55f5f5',
|
||||
},
|
||||
xAxis: {
|
||||
categories: all_dates,
|
||||
},
|
||||
series: cumuls.map((cumul, idx) => ({
|
||||
// color: '#55f5f5',
|
||||
visible: idx != 0,
|
||||
|
@ -16,15 +19,41 @@ function setupChart(...cumuls) {
|
|||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async function () {
|
||||
function objectToQueryParam(obj) {
|
||||
return Object.entries(obj).map(tuple => tuple.join("=")).join("&");
|
||||
}
|
||||
|
||||
function urlWithQueryParams(base, object_with_params) {
|
||||
if (Object.keys(object_with_params).length > 0) {
|
||||
return `${base}?${objectToQueryParam(object_with_params)}`;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
async function requestChart() {
|
||||
let url = "/api/pex/v1/recap";
|
||||
const days_ago = document.querySelector("#inputDaysAgo").value;
|
||||
if (days_ago.length > 0) {
|
||||
url = urlWithQueryParams(url, { days_ago });
|
||||
}
|
||||
ajax({
|
||||
method: "GET",
|
||||
url: "/api/pex/v1/recap",
|
||||
url: url,
|
||||
body: null,
|
||||
headers: { Accept: "application/json" },
|
||||
on_success: (body) => {
|
||||
const json_output = JSON.parse(body);
|
||||
setupChart(...json_output.pex_tables);
|
||||
setupChart({ all_dates: json_output.all_dates }, ...json_output.pex_tables);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async function () {
|
||||
requestChart();
|
||||
|
||||
const form = document.querySelector(".recap-xp form#reloader");
|
||||
form.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
requestChart();
|
||||
});
|
||||
});
|
||||
|
|
14
src/app.rb
14
src/app.rb
|
@ -18,16 +18,15 @@ require "active_support"
|
|||
require "active_support/core_ext"
|
||||
require "pry" # debug
|
||||
|
||||
# Load environment variables
|
||||
require_relative "./utils/env"
|
||||
module LifePex
|
||||
APP_ENV = ENV.fetch("LIFEPEX_ENV") { "development" }
|
||||
APP_ENV = load_dotenv
|
||||
end
|
||||
|
||||
require "dotenv"
|
||||
Dotenv.load(".env.#{LifePex::APP_ENV}.local", ".env.local", ".env")
|
||||
|
||||
# Initialize framework
|
||||
require_relative "./utils/url.rb"
|
||||
require_relative "./utils/boot_framework"
|
||||
|
||||
LifePex::BootFramework.boot_application do
|
||||
harshly_need_env "LIFEPEX_DB"
|
||||
kindly_ask_env "LIFEPEX_BASE_URL"
|
||||
|
@ -40,6 +39,7 @@ LifePex::BootFramework.boot_application do
|
|||
end
|
||||
end.finish!
|
||||
|
||||
# Setup module variables
|
||||
module LifePex
|
||||
DB = Sequel.connect ENV["LIFEPEX_DB"]
|
||||
BASE_URL = ENV["LIFEPEX_BASE_URL"] || "http://localhost:4567"
|
||||
|
@ -53,6 +53,8 @@ module LifePex
|
|||
end
|
||||
end
|
||||
|
||||
# Then we load all the systems
|
||||
|
||||
Dir[File.join(__dir__, 'utils', '*.rb')].each { |file| require file }
|
||||
|
||||
require_relative "./achievements/dsl.rb"
|
||||
|
@ -61,10 +63,12 @@ require_relative "./achievements/achievement.rb"
|
|||
Dir[File.join(__dir__, 'models', '*.rb')].each { |file| require file }
|
||||
Dir[File.join(__dir__, 'systems', '*.rb')].each { |file| require file }
|
||||
|
||||
# Static file serving in this file because it is overkill to create a file for this
|
||||
class LifePex::Systems::PublicSystem < Sinatra::Base
|
||||
set :public_folder, "public"
|
||||
end
|
||||
|
||||
# Main app
|
||||
class LifePex::App < Sinatra::Base
|
||||
DocMyRoutes.configure do |config|
|
||||
config.title = "LifePex"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
class LifePex::Pex < Sequel::Model(LifePex::DB[:pexs].order(:category, :name))
|
||||
one_to_many :user_pexs
|
||||
many_to_one :user
|
||||
one_to_many :recalls
|
||||
|
||||
# note: wont work on #update
|
||||
def before_validation
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
class LifePex::Recall < Sequel::Model(LifePex::DB[:recalls].order(:name))
|
||||
many_to_one :user
|
||||
many_to_one :pex
|
||||
end
|
|
@ -1,8 +1,23 @@
|
|||
class LifePex::User < Sequel::Model(:users)
|
||||
one_to_many :user_pexs
|
||||
one_to_many :pexs
|
||||
one_to_many :recalls
|
||||
|
||||
def password=(clear_password)
|
||||
self.hashed_password = BCrypt::Password.create(clear_password)
|
||||
end
|
||||
|
||||
def recalls_not_validated(cached: true)
|
||||
@recalls_not_validated = nil if !cached
|
||||
|
||||
pexs = LifePex::Pex.setup_user_pexs(user_id: id, user_pexs: user_pexs)
|
||||
|
||||
@recalls_not_validated ||= recalls.filter do |recall|
|
||||
pex = pexs[recall[:pex_id]]
|
||||
validated = pex[:count_by_date].filter do |date, _|
|
||||
date >= Date.today - recall[:span_duration]
|
||||
end.values.sum
|
||||
validated < recall[:repeated]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
class LifePex::UserPex < Sequel::Model(:user_pexs)
|
||||
many_to_one :user
|
||||
many_to_one :pex
|
||||
|
||||
def self.last_inserted_at(user_id)
|
||||
LifePex::UserPex
|
||||
# .join(:pexs, :id => :pex_id)
|
||||
# .where(Sequel[:user_pexs][:user_id] => user_id)
|
||||
# .group(:pex_id)
|
||||
# .select_append{max(created_at).as(:last_inserted_at)}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,11 +1,33 @@
|
|||
class LifePex::Systems::AuthSystem < Sinatra::Base
|
||||
helpers Sinatra::Cookies
|
||||
include JSON::API
|
||||
include LifePex::UsersHelper
|
||||
|
||||
def setup_user_cookie!(user_id)
|
||||
response.set_cookie(
|
||||
"auth",
|
||||
{
|
||||
value: JWT.encode({ "user_id" => user_id }, LifePex::SECRET),
|
||||
expires: Time.now + 2.days,
|
||||
path: "/",
|
||||
})
|
||||
end
|
||||
|
||||
def renew_user_cookie!
|
||||
response.set_cookie(
|
||||
"auth",
|
||||
{
|
||||
value: cookies["auth"],
|
||||
expires: Time.now + 2.days,
|
||||
path: "/",
|
||||
})
|
||||
end
|
||||
|
||||
def user_id_decoded(cookies = nil)
|
||||
cookies = cookies() if cookies.nil?
|
||||
begin
|
||||
decoded = JWT.decode(cookies["auth"], LifePex::SECRET)
|
||||
renew_user_cookie!
|
||||
decoded[0]["user_id"]
|
||||
rescue => err
|
||||
STDERR.puts "user_id_decoded: #{err}"
|
||||
|
@ -37,7 +59,7 @@ class LifePex::Systems::AuthSystem < Sinatra::Base
|
|||
condition do
|
||||
unless logged_in?
|
||||
if accept_json?
|
||||
halt 401, { message: 'You need to /api/v1/register an account and /api/v1/login to get a cookie first' }.to_json
|
||||
halt 401, { message: 'You need to POST /api/user/v1/register to register an account and POST /api/user/v1/login to get a cookie first' }.to_json
|
||||
else
|
||||
redirect "/login", 303
|
||||
end
|
||||
|
|
|
@ -1,32 +1,14 @@
|
|||
require "date"
|
||||
require "active_support/all"
|
||||
require_relative "./auth.rb"
|
||||
require_relative "./csrf.rb"
|
||||
require_relative "../utils/users.rb"
|
||||
|
||||
class LifePex::Systems::PexSystem < LifePex::Systems::AuthSystem
|
||||
include JSON::API
|
||||
include LifePex::Systems::CrlfHelper
|
||||
|
||||
def my_user_pexs(cookies, date = nil)
|
||||
params = {
|
||||
user_id: user_id_decoded(cookies),
|
||||
}
|
||||
params[:created_at] = date unless date.nil?
|
||||
LifePex::UserPex.where(params).all()
|
||||
end
|
||||
|
||||
def get_user_date
|
||||
date = cookies["date"]
|
||||
if date && Date.respond_to?(date)
|
||||
Date.send date
|
||||
elsif date == "yesterday"
|
||||
Date.today - 1
|
||||
else
|
||||
Date.today
|
||||
end
|
||||
end
|
||||
|
||||
get "/today", auth: [] do
|
||||
cookies.set "date", { value: "today", httponly: false }
|
||||
cookies.set "date", { value: "now", httponly: false }
|
||||
redirect "/"
|
||||
end
|
||||
|
||||
|
@ -39,13 +21,18 @@ class LifePex::Systems::PexSystem < LifePex::Systems::AuthSystem
|
|||
user_pexs = my_user_pexs(cookies, get_user_date)
|
||||
user_pexs_amounts = user_pexs.group_by { |user_pex| user_pex[:pex_id] }
|
||||
pexs = LifePex::Pex.where(user_id: current_user_id)
|
||||
|
||||
pexs = pexs.where(hidden: false) if filter_hidden
|
||||
pexs = pexs.all
|
||||
|
||||
user_pexs_last_inserted_at = LifePex::UserPex.last_inserted_at(current_user_id).all
|
||||
|
||||
pex_by_models = pexs.map do |pex|
|
||||
{
|
||||
pex: pex,
|
||||
user_pexs: {
|
||||
at_date: (user_pexs_amounts[pex[:id]] || []).length
|
||||
at_date: (user_pexs_amounts[pex[:id]] || []).length,
|
||||
last_inserted_at: (user_pexs_last_inserted_at.find{ |up| up[:pex_id] == pex[:id] } || {})[:last_inserted_at],
|
||||
},
|
||||
}
|
||||
end
|
||||
|
@ -71,12 +58,22 @@ class LifePex::Systems::PexSystem < LifePex::Systems::AuthSystem
|
|||
end
|
||||
|
||||
get "/pexs", auth: [] do
|
||||
slim :pex_form
|
||||
categories = LifePex::Pex
|
||||
.select(:category)
|
||||
.where(user_id: current_user_id)
|
||||
.distinct
|
||||
.pluck(:category)
|
||||
slim :pex_form, locals: { categories: categories }
|
||||
end
|
||||
|
||||
get "/pexs/update", auth: [] do
|
||||
pex = LifePex::Pex.where(id: params["id"]).first
|
||||
slim :pex_form, locals: { pex: pex, submit_name: "update" }
|
||||
categories = LifePex::Pex
|
||||
.select(:category)
|
||||
.where(user_id: current_user_id)
|
||||
.distinct
|
||||
.pluck(:category)
|
||||
slim :pex_form, locals: { pex: pex, categories: categories, submit_name: "update" }
|
||||
end
|
||||
|
||||
post "/pexs", auth: [] do
|
||||
|
@ -118,19 +115,28 @@ class LifePex::Systems::PexSystem < LifePex::Systems::AuthSystem
|
|||
end
|
||||
end
|
||||
|
||||
def get_recap_infos(cookies)
|
||||
def get_recap_infos(params, cookies)
|
||||
user_pexs = my_user_pexs(cookies)
|
||||
pexs = LifePex::Pex.setup_user_pexs(user_id: current_user_id, user_pexs: user_pexs)
|
||||
total_xp = pexs.values.sum { |up| up[:total_by_date].values.sum }
|
||||
level = LifePex::Level.new total_xp
|
||||
all_dates = user_pexs.map(&:created_at).uniq
|
||||
all_category = pexs.values.map(&:category).uniq
|
||||
|
||||
start_date = 60.days.ago.to_date
|
||||
if params["days_ago"]
|
||||
start_date = params["days_ago"].to_i.days.ago.to_date
|
||||
elsif params["since_first_jan"]
|
||||
year = Date.today.year
|
||||
first_jan = Date.new(year, 1, 1)
|
||||
start_date = first_jan
|
||||
end
|
||||
all_dates = (start_date..(Date.today)).to_a
|
||||
|
||||
pex_table_by_date = all_dates.map { |date| [date, 0] }.to_h
|
||||
pexs.values.each do |pex|
|
||||
pex[:total_by_date].each do |date, total|
|
||||
# pex_table_by_date[date] ||= 0
|
||||
pex_table_by_date[date] += total
|
||||
pex_table_by_date[date] += total if pex_table_by_date[date]
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -146,7 +152,7 @@ class LifePex::Systems::PexSystem < LifePex::Systems::AuthSystem
|
|||
category = pex[:category]
|
||||
# pex_table_by_category_and_date[category] ||= {}
|
||||
# pex_table_by_category_and_date[category][date] ||= 0
|
||||
pex_table_by_category_and_date[category][date] += total
|
||||
pex_table_by_category_and_date[category][date] += total if pex_table_by_category_and_date[category][date]
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -161,6 +167,15 @@ class LifePex::Systems::PexSystem < LifePex::Systems::AuthSystem
|
|||
}
|
||||
end.unshift(highchart_total)
|
||||
|
||||
recalls = LifePex::Recall.where(user_id: current_user_id).all
|
||||
recalls_not_validated = recalls.filter do |recall|
|
||||
pex = pexs[recall[:pex_id]]
|
||||
validated = pex[:count_by_date].filter do |date, _|
|
||||
date >= Date.today - recall[:span_duration]
|
||||
end.values.sum
|
||||
validated < recall[:repeated]
|
||||
end
|
||||
|
||||
all_setup = LifePex::Achievement.where(user_id: current_user_id).map(&:to_dsl_setup)
|
||||
medals = all_setup.filter do |success|
|
||||
success.call(user_pexs, pexs.values)
|
||||
|
@ -168,13 +183,16 @@ class LifePex::Systems::PexSystem < LifePex::Systems::AuthSystem
|
|||
|
||||
{
|
||||
pex_tables: highchart_tables,
|
||||
start_date: start_date,
|
||||
all_dates: all_dates,
|
||||
level: level,
|
||||
medals: medals,
|
||||
recalls_not_validated: recalls_not_validated,
|
||||
}
|
||||
end
|
||||
|
||||
get "/recap", auth: [], provides: 'html' do
|
||||
recap_infos = get_recap_infos(cookies)
|
||||
recap_infos = get_recap_infos(params, cookies)
|
||||
slim :recap, locals: recap_infos
|
||||
end
|
||||
|
||||
|
@ -185,9 +203,10 @@ class LifePex::Systems::PexSystem < LifePex::Systems::AuthSystem
|
|||
produces 'application/json'
|
||||
status_codes [200]
|
||||
get "/recap", auth: [], provides: 'json' do
|
||||
recap_infos = get_recap_infos(cookies)
|
||||
recap_infos = get_recap_infos(params, cookies)
|
||||
{
|
||||
pex_tables: recap_infos[:pex_tables],
|
||||
all_dates: recap_infos[:all_dates],
|
||||
level: recap_infos[:level].to_h,
|
||||
}.to_json
|
||||
end
|
||||
|
|
|
@ -25,7 +25,7 @@ class LifePex::Systems::Pex2System < LifePex::Systems::PexSystem
|
|||
produces 'application/json'
|
||||
status_codes [200]
|
||||
parameter :pluck, required: false, type: 'string', in: 'query', description: 'improve performance by only fetching one field'
|
||||
get "", auth: [], provides: 'json' do
|
||||
get '', auth: [], provides: "json" do
|
||||
pexs = current_user.pexs
|
||||
pexs = pexs.select(json_params["pluck"]) if json_params["pluck"]
|
||||
pexs.to_json
|
||||
|
@ -38,7 +38,7 @@ class LifePex::Systems::Pex2System < LifePex::Systems::PexSystem
|
|||
parameter :category, required: true, type: 'string', in: 'body'
|
||||
parameter :amount, required: true, type: 'number', in: 'body'
|
||||
parameter :auth, required: true, type: 'string', in: 'cookies'
|
||||
post "", auth: [], provides: 'json' do
|
||||
post '', auth: [], provides: 'json' do
|
||||
halt 400, "\"name\" is required" unless json_params["name"]
|
||||
halt 400, "\"category\" is required" unless json_params["category"]
|
||||
halt 400, "\"amount\" is required" unless json_params["amount"]
|
||||
|
@ -67,7 +67,7 @@ class LifePex::Systems::Pex2System < LifePex::Systems::PexSystem
|
|||
parameter :category, required: false, type: 'string', in: 'body'
|
||||
parameter :amount, required: false, type: 'number', in: 'body'
|
||||
parameter :auth, required: true, ype: 'string', in: 'cookies'
|
||||
put "/:id", auth: [], provides: 'json' do
|
||||
put '/:id', auth: [], provides: 'json' do
|
||||
pex = LifePex::Pex.find(
|
||||
id: params["id"],
|
||||
user_id: current_user.id,
|
||||
|
@ -88,7 +88,7 @@ class LifePex::Systems::Pex2System < LifePex::Systems::PexSystem
|
|||
status_codes [200]
|
||||
parameter :id, require: true, type: 'integer', in: 'path'
|
||||
parameter :auth, required: true, type: 'string', in: 'cookies'
|
||||
delete "/:id", auth: [], provides: 'json' do
|
||||
delete '/:id', auth: [], provides: 'json' do
|
||||
pex = LifePex::Pex.find(
|
||||
id: params["id"],
|
||||
user_id: current_user.id,
|
||||
|
@ -105,7 +105,7 @@ class LifePex::Systems::Pex2System < LifePex::Systems::PexSystem
|
|||
produces 'application/json'
|
||||
status_codes [200]
|
||||
parameter :id, required: true, type: 'string', in: 'path'
|
||||
get "/:id", auth: [], provides: 'json' do
|
||||
get '/:id', auth: [], provides: 'json' do
|
||||
pex = LifePex::Pex.find(
|
||||
id: params["id"],
|
||||
user_id: current_user.id,
|
||||
|
@ -120,7 +120,7 @@ class LifePex::Systems::Pex2System < LifePex::Systems::PexSystem
|
|||
produces 'application/json,text/html'
|
||||
status_codes [200]
|
||||
parameter :id, required: true, type: 'string', in: 'path'
|
||||
get "/:id/more", auth: [], provides: 'json' do
|
||||
get '/:id/more', auth: [], provides: 'json' do
|
||||
pex = LifePex::Pex.find(
|
||||
id: params["id"],
|
||||
user_id: current_user_id,
|
||||
|
@ -152,27 +152,55 @@ class LifePex::Systems::Pex2System < LifePex::Systems::PexSystem
|
|||
end
|
||||
end
|
||||
|
||||
summary 'Get the amount of a pex each day since first occurence since 1 january'
|
||||
notes ""
|
||||
summary 'Get the amount of a pex each day since first occurence since a given date'
|
||||
notes ''
|
||||
produces 'application/json'
|
||||
status_codes [200]
|
||||
parameter :id, required: true, type: 'integer', in: 'path'
|
||||
parameter :auth, required: true, type: 'string', in: 'cookies'
|
||||
post "/:id/recap", auth: [], provides: 'json' do
|
||||
parameter :days_ago, required: false, type: 'integer', in: 'body', description: "default: 30, limit the amount of results to the n last days included"
|
||||
parameter :since_first_jan, required: false, type: 'boolean', in: 'body', description: "default: false, if no days_ago defined, limit to all dates after (included) 1st jan of the current year"
|
||||
post '/:id/recap', auth: [], provides: 'json' do
|
||||
pex_id = params["id"]
|
||||
pex = LifePex::Pex.find(id: pex_id, user_id: current_user_id)
|
||||
halt 404, { message: "No pex #{params["id"]}" }.to_json if pex.nil?
|
||||
year = Date.today.year
|
||||
first_jan = Date.new(year, 1, 1)
|
||||
|
||||
user_pexs = LifePex::UserPex.where {
|
||||
self.user_id == current_user_id && self.pex_id == pex_id && created_at > first_jan
|
||||
start_date = 30.days.ago
|
||||
if params["days_ago"]
|
||||
start_date = params["days_ago"].to_i.days.ago
|
||||
elsif params["since_first_jan"]
|
||||
year = Date.today.year
|
||||
first_jan = Date.new(year, 1, 1)
|
||||
start_date = first_jan
|
||||
end
|
||||
|
||||
user_pexs = LifePex::UserPex.where { # TODO query that
|
||||
self.user_id == current_user_id && self.pex_id == pex_id && created_at >= start_date
|
||||
}
|
||||
{ pex: pex, user_pexs: user_pexs }.to_json
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
namespace '/infos' do
|
||||
|
||||
summary 'Get for all existing pex the last date a user_pex has been added'
|
||||
produces 'application/json'
|
||||
status_codes [200]
|
||||
parameter :id, required: true, type: 'string', in: 'path'
|
||||
get '/last-inserts', auth: [], provides: 'json' do
|
||||
pexs = LifePex::UserPex.last_inserted_at(current_user.id)
|
||||
pexs.map { |pex|
|
||||
{
|
||||
pex_id: pex[:pex_id],
|
||||
name: pex[:name],
|
||||
last_inserted_at: pex[:last_inserted_at],
|
||||
}
|
||||
}.to_json
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
include LifePex::Systems::ApiList
|
||||
end
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
require_relative "./csrf.rb"
|
||||
require_relative "./auth.rb"
|
||||
|
||||
class LifePex::Systems::RecallSystem < LifePex::Systems::AuthSystem
|
||||
include LifePex::Systems::CrlfHelper
|
||||
|
||||
get "/recalls", auth: [] do
|
||||
recalls = current_user.recalls
|
||||
slim :recalls, locals: { recalls: recalls }
|
||||
end
|
||||
|
||||
get "/recalls/new", auth: [] do
|
||||
pexs = current_user.pexs
|
||||
slim :recall_form, locals: { pexs: pexs }
|
||||
end
|
||||
|
||||
post "/recalls", auth: [] do
|
||||
flash = {}
|
||||
if !LifePex::Pex.where(user_id: current_user_id, id: params["pex_id"]).first
|
||||
flash[:danger] = "Invalid pex"
|
||||
else
|
||||
recall = LifePex::Recall.new(
|
||||
user_id: current_user_id,
|
||||
pex_id: params["pex_id"],
|
||||
name: params["name"],
|
||||
span_duration: params["span_duration"],
|
||||
repeated: params["repeated"],
|
||||
).save
|
||||
flash[:success] = "Recall #{recall.name} created"
|
||||
end
|
||||
recalls = current_user.recalls
|
||||
slim :recalls, locals: { recalls: recalls, flash: flash }
|
||||
end
|
||||
|
||||
post "/recalls/delete", auth: [] do
|
||||
recall = LifePex::Recall.where(user_id: current_user_id, id: params["id"]).first
|
||||
flash = {}
|
||||
if recall
|
||||
recall.destroy
|
||||
flash[:success] = "Recall #{recall.name} removed"
|
||||
else
|
||||
flash[:danger] = "Recall do not exists"
|
||||
end
|
||||
recalls = current_user.recalls
|
||||
slim :recalls, locals: { recalls: recalls, flash: flash }
|
||||
end
|
||||
end
|
|
@ -3,7 +3,6 @@ require_relative "./auth.rb"
|
|||
require_relative "./csrf.rb"
|
||||
|
||||
class LifePex::Systems::UserSystem < LifePex::Systems::AuthSystem
|
||||
include JSON::API
|
||||
include LifePex::Systems::CrlfHelper
|
||||
|
||||
DEFAULT_PEXS_FOR_NEW_USERS = YAML.load_file "config/default_pexs_for_new_users.yaml"
|
||||
|
@ -12,14 +11,10 @@ class LifePex::Systems::UserSystem < LifePex::Systems::AuthSystem
|
|||
slim :login
|
||||
end
|
||||
|
||||
def setup_user_cookie!(user_id)
|
||||
cookies["auth"] = JWT.encode({ "user_id" => user_id }, LifePex::SECRET)
|
||||
end
|
||||
|
||||
def login(params)
|
||||
user = LifePex::User.where(username: params["username"]).first
|
||||
if user && BCrypt::Password.new(user[:hashed_password]) == params["password"]
|
||||
cookies["auth"] = JWT.encode({ "user_id" => user[:id] }, LifePex::SECRET)
|
||||
setup_user_cookie!(user[:id])
|
||||
user
|
||||
else
|
||||
nil
|
||||
|
@ -28,7 +23,6 @@ class LifePex::Systems::UserSystem < LifePex::Systems::AuthSystem
|
|||
|
||||
post "/login", provides: 'html' do
|
||||
if user = login(params)
|
||||
cookies["auth"] = JWT.encode({ "user_id" => user[:id] }, LifePex::SECRET)
|
||||
redirect "/"
|
||||
else
|
||||
slim :login, locals: { flash: { danger: "Failed to login" } }
|
||||
|
@ -89,6 +83,40 @@ class LifePex::Systems::UserSystem < LifePex::Systems::AuthSystem
|
|||
slim :about
|
||||
end
|
||||
|
||||
get "/preferences" do
|
||||
show_preferences
|
||||
end
|
||||
|
||||
UserPreferenceCookie = Struct.new(:cookie, :allow_blank, :convert, :html, :description)
|
||||
USER_PREFERENCES = {
|
||||
"inputRecapDays" => UserPreferenceCookie.new("recap_days", false, :to_i, { type: "text" }, "Amount of days to show in the recap"),
|
||||
"inputLateDayOffset" => UserPreferenceCookie.new("late_day_offset", false, :to_f, { type: "number", min: "0", max: "24", step: "0.1" }, "Offset for today (so a few hours after midnight is still today)"),
|
||||
"inputShowFullDate" => UserPreferenceCookie.new("show_full_date", true, :to_s, { type: "checkbox" }, "Show or hide the date of the current tab"),
|
||||
}
|
||||
|
||||
post "/preferences" do
|
||||
USER_PREFERENCES.each do |param_name, upc|
|
||||
current_param = params[param_name]
|
||||
next if !upc.allow_blank && current_param.blank?
|
||||
|
||||
cookies[upc.cookie] =
|
||||
case upc.convert
|
||||
when Symbol
|
||||
current_param.send(upc.convert)
|
||||
when Proc
|
||||
upc.convert.call(current_param)
|
||||
else
|
||||
current_param
|
||||
end
|
||||
end
|
||||
|
||||
show_preferences
|
||||
end
|
||||
|
||||
def show_preferences
|
||||
slim :preferences
|
||||
end
|
||||
|
||||
extend DocMyRoutes::Annotatable
|
||||
register Sinatra::Namespace
|
||||
namespace '/api/user/v1' do
|
||||
|
|
|
@ -1,14 +1,57 @@
|
|||
require_relative "./auth.rb"
|
||||
require_relative "./api_response"
|
||||
require "csv"
|
||||
|
||||
class LifePex::Systems::UserPexSystem < LifePex::Systems::AuthSystem
|
||||
include JSON::API
|
||||
include LifePex::Systems::ApiResponse
|
||||
set :protection, :except => [:frame_options, :json_csrf]
|
||||
|
||||
extend DocMyRoutes::Annotatable # included by PexSystem
|
||||
register Sinatra::Namespace # included by PexSystem
|
||||
namespace "/api/user-pex/v1" do
|
||||
|
||||
summary "Export every single user pex and pex"
|
||||
produces "application/json,application/csv"
|
||||
status_codes [200]
|
||||
# parameter :id, required: true, type: "integer", in: "path"
|
||||
# parameter :date, required: true, type: "string", in: "path"
|
||||
get "/export", auth: [], provides: %w(json application/csv) do
|
||||
if accept? "application/csv"
|
||||
export_csv
|
||||
else
|
||||
pexs = LifePex::Pex.where(user_id: current_user_id).map{ |pex| [pex, pex.user_pexs] }.to_h
|
||||
api_response({ pexs: pexs, entity_type: "pexs" })
|
||||
end
|
||||
end
|
||||
|
||||
summary "Export every single user pex and pex as CSV"
|
||||
produces "application/csv"
|
||||
status_codes [200]
|
||||
# parameter :id, required: true, type: "integer", in: "path"
|
||||
# parameter :date, required: true, type: "string", in: "path"
|
||||
get "/export.csv", auth: [], provides: %w(application/csv) do
|
||||
export_csv
|
||||
end
|
||||
|
||||
private def export_csv
|
||||
pexs = LifePex::Pex.where(user_id: current_user_id).order(:id).all
|
||||
pexs_names = pexs.map { |pex| "[#{pex.category}] #{pex.name}" }
|
||||
pexs_ids = pexs.map(&:id)
|
||||
user_pexs_by_date = LifePex::UserPex
|
||||
.where(Sequel.qualify(:user_pexs, :user_id) => current_user_id)
|
||||
.order_by(:created_at).all.group_by(&:created_at)
|
||||
csv_output = CSV.generate do |csv|
|
||||
csv << ["date", *pexs_names]
|
||||
user_pexs_by_date.each do |date, user_pexs|
|
||||
amount_by_column = user_pexs.group_by(&:pex_id).transform_values { |v| v.size }
|
||||
user_pex_amount_by_date_all_column = pexs_ids.map { |id| amount_by_column[id] || 0 }
|
||||
csv << [date, *user_pex_amount_by_date_all_column]
|
||||
end
|
||||
end
|
||||
content_type "application/csv"
|
||||
csv_output
|
||||
end
|
||||
|
||||
namespace "/pexs" do
|
||||
|
||||
summary "Get the amount of user_pex for a given day"
|
||||
|
@ -90,5 +133,6 @@ class LifePex::Systems::UserPexSystem < LifePex::Systems::AuthSystem
|
|||
end
|
||||
|
||||
end
|
||||
|
||||
include LifePex::Systems::ApiList
|
||||
end
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
require "dotenv"
|
||||
|
||||
def get_env
|
||||
ENV.fetch("LIFEPEX_ENV") { "development" }
|
||||
end
|
||||
|
||||
def load_dotenv(app_env = nil)
|
||||
app_env ||= get_env
|
||||
Dotenv.load(".env.#{app_env}.local", ".env.local", ".env")
|
||||
app_env
|
||||
end
|
|
@ -1,3 +1,5 @@
|
|||
require "active_support/all"
|
||||
|
||||
module JSON::API
|
||||
def json_params
|
||||
begin
|
||||
|
@ -10,18 +12,26 @@ module JSON::API
|
|||
end
|
||||
end
|
||||
|
||||
def date_input_convertor(date)
|
||||
if date && Date.respond_to?(date)
|
||||
Date.send date
|
||||
elsif date == "yesterday"
|
||||
Date.today - 1
|
||||
else
|
||||
begin
|
||||
Date.parse date
|
||||
rescue => _
|
||||
Date.today
|
||||
DATE_GENERATOR = {
|
||||
"yesterday" => ->() { DateTime.now - 24.hours },
|
||||
"now" => ->() { DateTime.now },
|
||||
"today" => ->() { DateTime.now },
|
||||
nil => ->() { DateTime.now },
|
||||
}
|
||||
# @param [String] date: either a date iso formatted or a word to be sent to DateTime
|
||||
# @param [Float] offset: an amount of hours to remove from the date, useful for setting the start of the day hours after midnight
|
||||
def date_input_convertor(date = "now", offset = 0.0)
|
||||
base_time =
|
||||
if DATE_GENERATOR.key?(date)
|
||||
DATE_GENERATOR[date].call()
|
||||
else
|
||||
begin
|
||||
DateTime.parse date
|
||||
rescue => _
|
||||
DateTime.now
|
||||
end
|
||||
end
|
||||
end
|
||||
(base_time - offset.hours).to_date
|
||||
end
|
||||
|
||||
# params:
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
module LifePex::UsersHelper
|
||||
def my_user_pexs(cookies, date = nil)
|
||||
params = {
|
||||
user_id: user_id_decoded(cookies),
|
||||
}
|
||||
params[:created_at] = date unless date.nil?
|
||||
LifePex::UserPex.where(params).all()
|
||||
end
|
||||
|
||||
def get_user_date
|
||||
date = cookies["date"]
|
||||
offset = cookies["late_day_offset"].to_f
|
||||
date_input_convertor(date, offset)
|
||||
end
|
||||
end
|
|
@ -17,7 +17,10 @@
|
|||
I engage my honor to do never read or modify personnal data you may have put on
|
||||
the server, and do my best to ensure its security.
|
||||
You should look at the code source if you want to audit it.
|
||||
br
|
||||
|
||||
h2
|
||||
| GDPR
|
||||
p
|
||||
| In case you want to take a look at your data or want to delete it, I may in the futur
|
||||
provide a feature for do it yourself.
|
||||
Meanwhile you can still drop me an issue or a message on
|
||||
|
@ -26,6 +29,8 @@
|
|||
|
|
||||
i
|
||||
| #lifepex.
|
||||
a.btn.btn-success(href="/api/user-pex/v1/export.csv")
|
||||
| Export as CSV
|
||||
|
||||
h2
|
||||
| Service version
|
||||
|
|
|
@ -20,9 +20,13 @@ html lang="en"
|
|||
- if cookies["date"] == "yesterday"
|
||||
a.navbar-brand href="/"
|
||||
| Yesterday
|
||||
- if cookies["show_full_date"] == "on"
|
||||
.smaller= get_user_date
|
||||
- else
|
||||
a.navbar-brand href="/"
|
||||
| Today
|
||||
- if cookies["show_full_date"] == "on"
|
||||
.smaller= get_user_date
|
||||
button.navbar-toggler type="button" data-bs-toggle="collapse" data-bs-target="#navbar-collapser" aria-controls="navbar-collapser" aria-expanded="false" aria-label="Toggle navigation"
|
||||
span.navbar-toggler-icon/
|
||||
.collapse.navbar-collapse#navbar-collapser
|
||||
|
@ -35,13 +39,21 @@ html lang="en"
|
|||
li.nav-item
|
||||
a.btn.btn-lg.btn-dark href="/yesterday" Yesterday
|
||||
li.nav-item
|
||||
a.btn.btn-lg.btn-dark href="/recap" Recap
|
||||
a.btn.btn-lg.btn-dark.position-relative href="/recap"
|
||||
| Recap
|
||||
- if (recalls_count = current_user.recalls_not_validated.count) > 0
|
||||
span.position-absolute.top-0.start-100.translate-middle.badge.rounded-pill.bg-danger
|
||||
= recalls_count
|
||||
li.nav-item
|
||||
a.btn.btn-lg.btn-dark href="/achievements" Achievements
|
||||
li.nav-item
|
||||
a.btn.btn-lg.btn-dark href="/recalls" Recalls
|
||||
li.nav-item
|
||||
a.btn.btn-lg.btn-dark href="/password" Change password
|
||||
li.nav-item
|
||||
a.btn.btn-lg.btn-dark href="/about" About lifepex
|
||||
li.nav-item
|
||||
a.btn.btn-lg.btn-dark href="/preferences" Profil preference
|
||||
li.nav-item
|
||||
a.btn.btn-lg.btn-dark href="/?filter_hidden=false" Show hidden
|
||||
li.nav-item
|
||||
|
|
|
@ -28,7 +28,12 @@ tr.full-row(class=@row_class)
|
|||
| <<
|
||||
a.btn.btn-warning href="/pexs/update?id=#{pex_with_amount[:pex][:id]}"
|
||||
| u
|
||||
td.col-8=pex_with_amount[:pex][:name]
|
||||
td.col-8
|
||||
.row
|
||||
.col-8
|
||||
| #{pex_with_amount[:pex][:name]}
|
||||
.col-4
|
||||
.small=pex_with_amount[:user_pexs][:last_inserted_at]
|
||||
td.col-1.center
|
||||
form.userpexvalidation.userpexvalidationdecrease method="POST" action="/" onsubmit="return userpexValidation(event)"
|
||||
== csrf_tag
|
||||
|
|
|
@ -15,7 +15,10 @@ main.col-md-9.ms-sm-auto.col-lg-10.px-md-4
|
|||
input#inputUsername.form-control.form-control-lg name="name" type="text" value=pex&.name /
|
||||
.form-group.row
|
||||
label.col-sm-12.col-form-label for="inputCategory" Category
|
||||
input#inputCategory.form-control.form-control-lg name="category" type="text" placeholder="sport" value=pex&.category /
|
||||
input#inputCategory.form-control.form-control-lg name="category" type="text" placeholder="sport" list="categoryList" value=pex&.category /
|
||||
datalist#categoryList
|
||||
- categories.each do |category|
|
||||
option value=category
|
||||
.form-group.row
|
||||
label.col-sm-12.col-form-label for="inputAmount" Xp Amount by check
|
||||
input#inputAmount.form-control.form-control-lg name="amount" type="integer" min="-50" max="50" value=(pex&.amount||"1") /
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
.container
|
||||
h1
|
||||
| Profil preferences
|
||||
|
||||
h2
|
||||
| Recap
|
||||
form(method="POST")
|
||||
== csrf_tag
|
||||
/-
|
||||
- USER_PREFERENCES.each do |param_name, upc|
|
||||
- if upc.html[:type] == "checkbox"
|
||||
.form-group.form-check.form-switch
|
||||
label.col-sm-12.form-check-label for=param_name
|
||||
strong=upc.description
|
||||
- if cookies[upc.cookie] == "on"
|
||||
input.form-check-input *upc.html name=param_name checked="on" /
|
||||
- else
|
||||
input.form-check-input *upc.html name=param_name /
|
||||
- else
|
||||
.form-group.row
|
||||
label.col-sm-12.col-form-label for=param_name
|
||||
strong=upc.description
|
||||
input.form-control.form-control-lg *upc.html name=param_name value=cookies[upc.cookie] /
|
||||
.form-group.row
|
||||
input.btn.btn-lg.btn-block type="submit" value="Update"
|
||||
|
||||
- if LifePex::APP_ENV == "debug"
|
||||
h2
|
||||
| Debug
|
||||
p
|
||||
table
|
||||
- cookies.each do |k, v|
|
||||
tr
|
||||
td= "cookies.#{k}"
|
||||
td= v
|
||||
tr
|
||||
td
|
||||
| get_user_date
|
||||
td= get_user_date
|
||||
|
||||
h2
|
||||
| Private data export
|
||||
a.btn.btn-success(href="/api/user-pex/v1/export.csv")
|
||||
| Export as CSV
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
main.col-md-9.ms-sm-auto.col-lg-10.px-md-4
|
||||
h1
|
||||
| Add a new Recall
|
||||
form.col-md-6 method="POST" action="/recalls"
|
||||
== csrf_tag
|
||||
.form-group.row
|
||||
label.col-sm-12.col-form-label for="name"
|
||||
strong
|
||||
| Name
|
||||
input#name.form-control.form-control-lg name="name" type="text" /
|
||||
.form-group.row
|
||||
label.col-sm-12.col-form-label for="span_duration"
|
||||
strong
|
||||
| Span duration
|
||||
input#span_duration.form-control.form-control-lg name="span_duration" type="number" value="7" /
|
||||
.form-group.row
|
||||
label.col-sm-12.col-form-label for="repeated"
|
||||
strong
|
||||
| Repeated
|
||||
input#inputRepeated.form-control.form-control-lg name="repeated" type="number" value="1" /
|
||||
.form-group.row
|
||||
label.col-sm-12.col-form-label for="pex_id"
|
||||
strong
|
||||
| PexId
|
||||
select.form-select.form-control-lg arial-label="Pex Id" name="pex_id"
|
||||
- pexs.each.with_index do |pex, pex_idx|
|
||||
- if pex_idx == 0
|
||||
option value=pex[:id] selected="true" =pex[:name]
|
||||
- else
|
||||
option value=pex[:id] =pex[:name]
|
||||
|
||||
.form-group.row
|
||||
input.btn.btn-lg.btn-block type="submit" value="create"
|
|
@ -0,0 +1,25 @@
|
|||
h1
|
||||
| Current recalls
|
||||
|
||||
a.btn.btn-dark href="/recalls/new"
|
||||
| Add a new recall
|
||||
|
||||
table.table
|
||||
thead
|
||||
tr
|
||||
th Name
|
||||
th Span duration
|
||||
th Repeated
|
||||
th
|
||||
tbody
|
||||
- recalls.each do |recall|
|
||||
tr
|
||||
td=recall.name
|
||||
td=recall.span_duration
|
||||
td=recall.repeated
|
||||
td
|
||||
form method="POST" action="/recalls/delete"
|
||||
== csrf_tag
|
||||
input type="hidden" name="id" value=recall[:id]
|
||||
button.btn.btn-danger type="submit" onclick="return confirm('Confirm to permanently REMOVE \'#{recall[:name]}\' ?')"
|
||||
| x
|
|
@ -2,6 +2,17 @@ script src="https://code.highcharts.com/highcharts.js"
|
|||
|
||||
h1 Recap
|
||||
|
||||
.recap-recalls
|
||||
- if !recalls_not_validated.empty?
|
||||
p
|
||||
| You have a warning to consider!
|
||||
|
||||
h2
|
||||
- recalls_not_validated.each do |recall|
|
||||
span.badge.bg-danger
|
||||
| #{recall[:name]}
|
||||
br/
|
||||
|
||||
.recap-level
|
||||
span.badge.bg-primary
|
||||
| Level #{level.current_level.round}
|
||||
|
@ -10,6 +21,15 @@ h1 Recap
|
|||
| #{level.xp_from_current_level.round} / #{level.xp_for_complete_level.round}
|
||||
|
||||
.recap-xp
|
||||
.float-end
|
||||
form#reloader.col-md-6 method="GET"
|
||||
== csrf_tag
|
||||
.form-group.row
|
||||
label.col-form-label for="inputDaysAgo" Load how many days since today ?
|
||||
input#inputDaysAgo.form-control.form-control-lg name="days_ago" type="number" min="3" max="365" step="1" value=(params["days_ago"] || cookies["recap_days"] || "60") /
|
||||
.form-group.row
|
||||
p
|
||||
input.btn.btn-lg.btn-block type="submit" value="Reload"
|
||||
#recap-xp-container style="width:100%; height:400px;"
|
||||
|
||||
.recap-success
|
||||
|
|
|
@ -22,7 +22,7 @@ class UserSystemTest < LifePexTest
|
|||
api_post '/api/user/v1/register', data
|
||||
assert last_response.ok?
|
||||
assert_not_empty last_response.headers["Set-Cookie"]
|
||||
assert_match /auth=[\-\.\w]+; domain=example.org; path=\//, last_response.headers["Set-Cookie"]
|
||||
assert_match (/auth=[\-\.\w]+; domain=example.org; path=\//), last_response.headers["Set-Cookie"]
|
||||
end
|
||||
|
||||
def test_register_refuse_duplicate
|
||||
|
|
Loading…
Reference in New Issue