Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a4e076416 | |||
| 02df03282e | |||
| 4ad701c1a5 | |||
| bdd1d248db | |||
| 7c69f0a0bc | |||
| 11ba8e7434 | |||
| 383872b800 | |||
| ee8861355b | |||
| df04e99447 | |||
| 4bee13aae7 | |||
| 12836dd14b | |||
| 5dbb6dbeae | |||
| 3e4bcc80c8 | |||
| a755d8a205 | |||
| 1f52448253 | |||
| efccbc2064 | |||
| 7364de2a45 | |||
| 814ac1b808 | |||
| 0559ede912 | |||
| 4e1d3bd052 | |||
| 2711edb167 | |||
| e624ec2d0b | |||
| 63b19cb78a | |||
| be3ee9096b | |||
| a1c1a0c34e | |||
| f0c561311f |
@@ -7,6 +7,7 @@ source 'https://rubygems.org'
|
||||
gem 'rails', '~> 8.1.1'
|
||||
#gem 'rails', '7.1.1'
|
||||
gem 'rack-cors', require: 'rack/cors'
|
||||
gem 'redis', '~> 5.0'
|
||||
|
||||
# Bundle edge Rails instead:
|
||||
# gem 'rails', git: 'git://github.com/rails/rails.git'
|
||||
@@ -73,8 +74,8 @@ gem 'simply_stored', github: 'bterkuile/simply_stored', branch: :master
|
||||
gem 'devise' #, github: 'plataformatec/devise', branch: 'lm-rails-4-2' #, '3.1.0' #, '2.0.4'
|
||||
gem 'devise_simply_stored', github: 'bterkuile/devise_simply_stored', branch: :master
|
||||
gem 'devise-i18n'
|
||||
gem 'omniauth-facebook'
|
||||
gem 'omniauth-instagram'
|
||||
#gem 'omniauth-facebook'
|
||||
#gem 'omniauth-instagram'
|
||||
#gem 'simple_form'
|
||||
gem 'active_decorator' #, path: '/Users/bterkuile/companytools/development/rails/components/active_decorator'
|
||||
#gem 'cmtool', github: 'bterkuile/cmtool'
|
||||
|
||||
+37
-69
@@ -10,7 +10,7 @@ GIT
|
||||
|
||||
GIT
|
||||
remote: https://github.com/bterkuile/cmtool.git
|
||||
revision: 647cc38bad68c3bebc304f52fd54540f4559cae3
|
||||
revision: 1a1edf675bf25303184977ad809a1d7a57419660
|
||||
branch: master
|
||||
specs:
|
||||
cmtool (3.0.0)
|
||||
@@ -151,9 +151,9 @@ GEM
|
||||
airbrussh (1.5.3)
|
||||
sshkit (>= 1.6.1, != 1.7.0)
|
||||
base64 (0.3.0)
|
||||
bcrypt (3.1.20)
|
||||
bcrypt (3.1.21)
|
||||
bcrypt_pbkdf (1.1.1)
|
||||
bigdecimal (3.3.1)
|
||||
bigdecimal (4.0.1)
|
||||
builder (3.3.0)
|
||||
cancancan (3.6.1)
|
||||
capistrano (3.19.2)
|
||||
@@ -197,8 +197,8 @@ GEM
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.12.2)
|
||||
concurrent-ruby (1.3.5)
|
||||
connection_pool (2.5.4)
|
||||
concurrent-ruby (1.3.6)
|
||||
connection_pool (3.0.2)
|
||||
cookiejar (0.3.4)
|
||||
couchrest (2.0.1)
|
||||
httpclient (~> 2.8)
|
||||
@@ -213,11 +213,11 @@ GEM
|
||||
cucumber-messages (29.0.1)
|
||||
cuke_modeler (3.26.0)
|
||||
cucumber-gherkin (< 37.0)
|
||||
date (3.5.0)
|
||||
devise (4.9.4)
|
||||
date (3.5.1)
|
||||
devise (5.0.0)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0)
|
||||
railties (>= 7.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
devise-i18n (1.15.0)
|
||||
@@ -238,7 +238,7 @@ GEM
|
||||
eventmachine (>= 1.0.0.beta.4)
|
||||
email_validator (2.2.4)
|
||||
activemodel
|
||||
erb (6.0.0)
|
||||
erb (6.0.1)
|
||||
erubi (1.13.1)
|
||||
eventmachine (1.2.7)
|
||||
exception_notification (5.0.1)
|
||||
@@ -250,12 +250,6 @@ GEM
|
||||
factory_bot_rails (6.5.1)
|
||||
factory_bot (~> 6.5)
|
||||
railties (>= 6.1.0)
|
||||
faraday (2.14.0)
|
||||
faraday-net_http (>= 2.0, < 3.5)
|
||||
json
|
||||
logger
|
||||
faraday-net_http (3.4.2)
|
||||
net-http (~> 0.5)
|
||||
faye (1.4.1)
|
||||
cookiejar (>= 0.3.0)
|
||||
em-http-request (>= 1.1.6)
|
||||
@@ -303,15 +297,15 @@ GEM
|
||||
google-protobuf (>= 3.25, < 5.0)
|
||||
googleapis-common-protos-types (~> 1.0)
|
||||
hashdiff (1.2.1)
|
||||
hashie (5.0.0)
|
||||
http_parser.rb (0.8.0)
|
||||
httpclient (2.9.0)
|
||||
mutex_m
|
||||
i18n (1.14.7)
|
||||
i18n (1.14.8)
|
||||
concurrent-ruby (~> 1.0)
|
||||
io-console (0.8.1)
|
||||
irb (1.15.3)
|
||||
io-console (0.8.2)
|
||||
irb (1.17.0)
|
||||
pp (>= 0.6.0)
|
||||
prism (>= 1.3.0)
|
||||
rdoc (>= 4.0.0)
|
||||
reline (>= 0.4.2)
|
||||
iso_country_codes (0.7.8)
|
||||
@@ -324,9 +318,7 @@ GEM
|
||||
js-routes (2.3.5)
|
||||
railties (>= 5)
|
||||
sorbet-runtime
|
||||
json (2.16.0)
|
||||
jwt (3.1.2)
|
||||
base64
|
||||
json (2.18.1)
|
||||
kaminari (1.2.2)
|
||||
activesupport (>= 4.1.0)
|
||||
kaminari-actionview (= 1.2.2)
|
||||
@@ -352,7 +344,7 @@ GEM
|
||||
letter_opener (1.10.0)
|
||||
launchy (>= 2.2, < 4)
|
||||
logger (1.7.0)
|
||||
loofah (2.24.1)
|
||||
loofah (2.25.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
mail (2.9.0)
|
||||
@@ -374,16 +366,13 @@ GEM
|
||||
mini_magick (5.3.1)
|
||||
logger
|
||||
mini_mime (1.1.5)
|
||||
minitest (5.26.2)
|
||||
minitest (6.0.1)
|
||||
prism (~> 1.5)
|
||||
momentjs-rails (2.29.4.1)
|
||||
railties (>= 3.1)
|
||||
multi_json (1.17.0)
|
||||
multi_xml (0.7.2)
|
||||
bigdecimal (~> 3.1)
|
||||
mutex_m (0.3.0)
|
||||
naught (1.1.0)
|
||||
net-http (0.8.0)
|
||||
uri (>= 0.11.1)
|
||||
net-imap (0.5.12)
|
||||
date
|
||||
net-protocol
|
||||
@@ -399,32 +388,12 @@ GEM
|
||||
net-protocol
|
||||
net-ssh (7.3.0)
|
||||
nio4r (2.7.5)
|
||||
nokogiri (1.18.10-aarch64-linux-gnu)
|
||||
nokogiri (1.19.0-aarch64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.10-x86_64-darwin)
|
||||
nokogiri (1.19.0-x86_64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.18.10-x86_64-linux-gnu)
|
||||
nokogiri (1.19.0-x86_64-linux-gnu)
|
||||
racc (~> 1.4)
|
||||
oauth2 (2.0.18)
|
||||
faraday (>= 0.17.3, < 4.0)
|
||||
jwt (>= 1.0, < 4.0)
|
||||
logger (~> 1.2)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 4)
|
||||
snaky_hash (~> 2.0, >= 2.0.3)
|
||||
version_gem (~> 1.1, >= 1.1.9)
|
||||
omniauth (1.9.2)
|
||||
hashie (>= 3.4.6)
|
||||
rack (>= 1.6.2, < 3)
|
||||
omniauth-facebook (10.0.0)
|
||||
bigdecimal
|
||||
omniauth-oauth2 (>= 1.2, < 3)
|
||||
omniauth-instagram (1.3.0)
|
||||
omniauth (~> 1)
|
||||
omniauth-oauth2 (~> 1)
|
||||
omniauth-oauth2 (1.7.3)
|
||||
oauth2 (>= 1.4, < 3)
|
||||
omniauth (>= 1.9, < 3)
|
||||
orm_adapter (0.5.0)
|
||||
ostruct (0.6.3)
|
||||
paperclip (6.1.0)
|
||||
@@ -443,6 +412,7 @@ GEM
|
||||
pp (0.6.3)
|
||||
prettyprint
|
||||
prettyprint (0.2.0)
|
||||
prism (1.9.0)
|
||||
pry (0.15.2)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
@@ -451,23 +421,23 @@ GEM
|
||||
yard (~> 0.9.11)
|
||||
pry-rails (0.3.11)
|
||||
pry (>= 0.13.0)
|
||||
psych (5.2.6)
|
||||
psych (5.3.1)
|
||||
date
|
||||
stringio
|
||||
public_suffix (6.0.2)
|
||||
puma (7.1.0)
|
||||
nio4r (~> 2.0)
|
||||
racc (1.8.1)
|
||||
rack (2.2.21)
|
||||
rack (3.2.4)
|
||||
rack-cors (2.0.2)
|
||||
rack (>= 2.0.0)
|
||||
rack-session (1.0.2)
|
||||
rack (< 3)
|
||||
rack-session (2.1.1)
|
||||
base64 (>= 0.1.0)
|
||||
rack (>= 3.0.0)
|
||||
rack-test (2.2.0)
|
||||
rack (>= 1.3)
|
||||
rackup (1.0.1)
|
||||
rack (< 3)
|
||||
webrick
|
||||
rackup (2.3.1)
|
||||
rack (>= 3)
|
||||
rails (8.1.1)
|
||||
actioncable (= 8.1.1)
|
||||
actionmailbox (= 8.1.1)
|
||||
@@ -506,10 +476,14 @@ GEM
|
||||
tsort (>= 0.2)
|
||||
zeitwerk (~> 2.6)
|
||||
rake (13.3.1)
|
||||
rdoc (6.15.1)
|
||||
rdoc (7.2.0)
|
||||
erb
|
||||
psych (>= 4.0.0)
|
||||
tsort
|
||||
redis (5.4.1)
|
||||
redis-client (>= 0.22.0)
|
||||
redis-client (0.29.0)
|
||||
connection_pool
|
||||
regexp_parser (2.11.3)
|
||||
reline (0.6.3)
|
||||
io-console (~> 0.5)
|
||||
@@ -573,9 +547,6 @@ GEM
|
||||
actionpack (>= 3.1)
|
||||
railties (>= 3.1)
|
||||
slim (>= 3.0, < 6.0, != 5.0.0)
|
||||
snaky_hash (2.0.3)
|
||||
hashie (>= 0.1.0, < 6)
|
||||
version_gem (>= 1.1.8, < 3)
|
||||
sorbet-runtime (0.6.12780)
|
||||
sprockets (4.2.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
@@ -592,13 +563,13 @@ GEM
|
||||
net-sftp (>= 2.1.2)
|
||||
net-ssh (>= 2.8.0)
|
||||
ostruct
|
||||
stringio (3.1.8)
|
||||
stringio (3.2.0)
|
||||
temple (0.10.4)
|
||||
terrapin (0.6.0)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
test_squad (0.1.3)
|
||||
rails
|
||||
thor (1.4.0)
|
||||
thor (1.5.0)
|
||||
tilt (2.6.1)
|
||||
timecop (0.9.10)
|
||||
timeout (0.4.4)
|
||||
@@ -612,14 +583,12 @@ GEM
|
||||
execjs (>= 0.3.0, < 3)
|
||||
uri (1.1.1)
|
||||
useragent (0.16.11)
|
||||
version_gem (1.1.9)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
webmock (3.26.1)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webrick (1.9.1)
|
||||
websocket-driver (0.8.0)
|
||||
base64
|
||||
websocket-extensions (>= 0.1.0)
|
||||
@@ -627,7 +596,7 @@ GEM
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
yard (0.9.37)
|
||||
zeitwerk (2.7.3)
|
||||
zeitwerk (2.7.4)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux
|
||||
@@ -674,8 +643,6 @@ DEPENDENCIES
|
||||
mini_magick
|
||||
mutex_m
|
||||
naught
|
||||
omniauth-facebook
|
||||
omniauth-instagram
|
||||
pickadate-rails
|
||||
poltergeist
|
||||
pry-doc
|
||||
@@ -684,6 +651,7 @@ DEPENDENCIES
|
||||
rack-cors
|
||||
rails (~> 8.1.1)
|
||||
rails-controller-testing
|
||||
redis (~> 5.0)
|
||||
rqrcode
|
||||
rspec-its
|
||||
rspec-rails
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# Mozo Project Index
|
||||
|
||||
This file is the **canonical source of truth** for how the Mozo repositories relate to each other.
|
||||
When in doubt about cross-repo conventions, contracts, or terminology, the notes here win.
|
||||
|
||||
## Repositories
|
||||
|
||||
| Repo | Role | Stack | Notes |
|
||||
|-----------------------|----------------------------|----------------------|----------------------------------------------------|
|
||||
| **mozo-backend** | Server / API (this repo) | Ruby on Rails + CouchDB | Main repo. Authoritative data model + API. Owns this INDEX. |
|
||||
| **mozo-user** | End-user client app | Ember.js (client-side) | Customer-facing app, consumes the backend API. |
|
||||
| **mozo-supplier** | Supplier client app | Ember.js (client-side) | Supplier-facing app, consumes the backend API. |
|
||||
| **mozo-employee** *(planned/related)* | Employee client app | Ember.js (client-side) | Internal-staff app, consumes the backend API. |
|
||||
|
||||
All client apps speak HTTP(S) to **mozo-backend**. The backend is the single
|
||||
producer of canonical data; clients are presentation/UX layers around the
|
||||
same API surface, scoped to their respective user roles.
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────┐
|
||||
│ mozo-backend │
|
||||
│ Rails API · CouchDB · auth · domain │
|
||||
│ (single source of truth) │
|
||||
└───────────────▲────────────▲───────────────┘
|
||||
│ │
|
||||
┌────────────────────────┘ └────────────────────────┐
|
||||
│ HTTPS / JSON HTTPS / JSON │
|
||||
│ │
|
||||
┌──────────┴──────────┐ ┌──────────────────────┐ ┌────────────────────┴┐
|
||||
│ mozo-user │ │ mozo-employee │ │ mozo-supplier │
|
||||
│ Ember client app │ │ Ember client app │ │ Ember client app │
|
||||
│ (end customers) │ │ (internal staff) │ │ (suppliers) │
|
||||
└─────────────────────┘ └──────────────────────┘ └─────────────────────┘
|
||||
```
|
||||
|
||||
## Source of truth rules
|
||||
|
||||
- **Domain model / data model:** defined in `mozo-backend` (`app/models/`, `db/`, `doc/`).
|
||||
Client apps must follow what the backend exposes; if a client needs new
|
||||
data, it lands in the backend first.
|
||||
- **API contracts:** the backend's controllers + serializers are authoritative.
|
||||
Client repos should not invent endpoints or payload shapes that don't exist
|
||||
in the backend.
|
||||
- **Terminology / business rules:** documented in this repo (`doc/`, `wip.md`,
|
||||
ADRs, this INDEX.md). If a client repo has a conflicting note, the
|
||||
backend's version wins until it's reconciled here.
|
||||
- **Auth / sessions:** issued and validated by the backend; clients only
|
||||
consume tokens/cookies, they don't define the rules.
|
||||
- **Cross-cutting decisions** (naming, statuses, lifecycle of entities,
|
||||
deployment flow) belong in this INDEX or in `doc/decisions/` in this repo.
|
||||
|
||||
## Client repos (Ember apps) — shared characteristics
|
||||
|
||||
- Bootstrapped with Ember CLI (`.ember-cli`, `ember-cli-build.js`,
|
||||
`testem.js`, `template-lintrc`, `eslint`).
|
||||
- Each one is a **separate Ember app** targeting a specific audience
|
||||
(user / supplier / employee), not a monorepo of routes.
|
||||
- Each app talks to the backend over HTTPS (see backend `config/`,
|
||||
`expose.sh`, SSL notes in `README.md`).
|
||||
- Build/deploy is per-client via `deploy.sh` in each client repo; the
|
||||
backend handles its own deploy via Capistrano (`Capfile`,
|
||||
`config/deploy*.rb`).
|
||||
|
||||
## When you don't know which repo a question belongs in
|
||||
|
||||
Ask in this order:
|
||||
|
||||
1. Is it about **data, validation, business rules, or the API**? → `mozo-backend`.
|
||||
2. Is it about **what an end customer sees / does**? → `mozo-user`.
|
||||
3. Is it about **what a supplier sees / does**? → `mozo-supplier`.
|
||||
4. Is it about **internal staff tooling / dashboards**? → `mozo-employee`.
|
||||
5. Still unsure? → default to `mozo-backend` and document the answer here.
|
||||
|
||||
## Maintenance
|
||||
|
||||
Update this file whenever:
|
||||
|
||||
- A new client app or service repo joins the Mozo project.
|
||||
- A cross-repo convention changes (auth flow, deploy flow, shared naming).
|
||||
- A repo's role or scope materially shifts.
|
||||
|
||||
Keep it short. Detailed design lives in `doc/` and `doc/decisions/`; this file
|
||||
is a map, not a manual.
|
||||
@@ -10,6 +10,8 @@ cd ~/projects/couchdb/couchdb
|
||||
./dev/run --admin=admin:admin
|
||||
```
|
||||
|
||||
Did not work the last time. The docker version did.
|
||||
|
||||
### Start rails
|
||||
Note that the couchdb admin password can be changed for better security:
|
||||
```
|
||||
|
||||
@@ -9,3 +9,4 @@
|
||||
//= link qr_sheet/application.css
|
||||
//
|
||||
// link user/foundation/application.css
|
||||
//= link admin/application.js
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
h3
|
||||
color: $form-error-color
|
||||
font-size: 1.2em
|
||||
// Temporary hack for some forms
|
||||
.control-group
|
||||
.controls
|
||||
.error
|
||||
// compensate the margin-bottom of the input above
|
||||
margin-top: -16px
|
||||
body
|
||||
label
|
||||
&.number
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ApplicationCable
|
||||
class Channel < ActionCable::Channel::Base
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ApplicationCable
|
||||
class Connection < ActionCable::Connection::Base
|
||||
# Authenticate via auth_token (same mechanism used in ApplicationController#authenticate_employee!)
|
||||
# Clients should pass ?auth_token=TOKEN when connecting to the WebSocket.
|
||||
identified_by :current_user, :current_entity_type
|
||||
|
||||
def connect
|
||||
token = request.params[:auth_token].presence
|
||||
reject_unauthorized_connection unless token
|
||||
|
||||
if (employee = Employee.find_by_authentication_token(token))
|
||||
self.current_user = employee
|
||||
self.current_entity_type = :employee
|
||||
elsif (user = User.find_by_authentication_token(token))
|
||||
self.current_user = user
|
||||
self.current_entity_type = :user
|
||||
elsif (supplier = Supplier.find_by_authentication_token(token))
|
||||
self.current_user = supplier
|
||||
self.current_entity_type = :supplier
|
||||
else
|
||||
reject_unauthorized_connection
|
||||
end
|
||||
end
|
||||
|
||||
# Allow subscribing to the entity's own channel
|
||||
def subscribe_to_self
|
||||
case current_entity_type
|
||||
when :user then "user_#{current_user.id}"
|
||||
when :supplier then "supplier_#{current_user.id}"
|
||||
when :employee then "employee_#{current_user.id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Base channel. Streams are set up dynamically by clients subscribing
|
||||
# to their entity channel (user_123, supplier_456, etc.).
|
||||
#
|
||||
# The server broadcasts TO these channels via:
|
||||
# ActionCable.server.broadcast("user_123", { event: "...", data: {...} })
|
||||
#
|
||||
# Clients connect and subscribe via:
|
||||
# consumer.subscriptions.create({ channel: "MozoChannel", id: "user_123" })
|
||||
#
|
||||
class MozoChannel < ApplicationCable::Channel
|
||||
def subscribed
|
||||
stream_name = params[:id]
|
||||
if authorized?(stream_name)
|
||||
stream_from stream_name
|
||||
else
|
||||
reject
|
||||
end
|
||||
end
|
||||
|
||||
def unsubscribed
|
||||
# cleanup
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authorized?(stream_name)
|
||||
prefix, id = stream_name.to_s.split('_', 2)
|
||||
case prefix
|
||||
when 'user'
|
||||
connection.current_entity_type == :user && connection.current_user.id.to_s == id
|
||||
when 'supplier'
|
||||
connection.current_entity_type == :supplier && connection.current_user.id.to_s == id
|
||||
when 'employee'
|
||||
connection.current_entity_type == :employee && connection.current_user.id.to_s == id
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -6,10 +6,11 @@ module Admin
|
||||
end
|
||||
|
||||
def new
|
||||
|
||||
@svg_element = SvgElement.new
|
||||
end
|
||||
|
||||
def create
|
||||
@svg_element = SvgElement.new(svg_element_params)
|
||||
if @svg_element.save
|
||||
redirect_to [:edit, :admin, @svg_element]
|
||||
else
|
||||
@@ -18,6 +19,7 @@ module Admin
|
||||
end
|
||||
|
||||
def update
|
||||
@svg_element = SvgElement.find(params[:id])
|
||||
if @svg_element.update_attributes svg_element_params
|
||||
redirect_to [:edit, :admin, @svg_element]
|
||||
else
|
||||
@@ -26,11 +28,11 @@ module Admin
|
||||
end
|
||||
|
||||
def show
|
||||
|
||||
@svg_element = SvgElement.find(params[:id])
|
||||
end
|
||||
|
||||
def edit
|
||||
|
||||
@svg_element = SvgElement.find(params[:id])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -13,7 +13,20 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
rescue_from SimplyStored::RecordNotFound, with: :show_404
|
||||
|
||||
private
|
||||
# protected
|
||||
#
|
||||
# def after_sign_in_path_for(resource)
|
||||
# case resource
|
||||
# when 'user' then Mozo.user_url
|
||||
# else
|
||||
# main_app.root_path
|
||||
# end
|
||||
# # Customize the redirect path here
|
||||
# # For example, redirect to a dashboard page
|
||||
# dashboard_path || root_path
|
||||
# end
|
||||
#
|
||||
private
|
||||
|
||||
def authenticate_employee!
|
||||
if auth_token = params[:auth_token].presence || request.headers['HTTP_AUTH_TOKEN'].presence
|
||||
@@ -67,8 +80,9 @@ private
|
||||
end
|
||||
|
||||
def set_locale
|
||||
#session[:locale] = (params[:locale].presence || session[:locale] || Rails.configuration.i18n.default_locale).to_sym
|
||||
I18n.locale = params[:locale].presence.try(:to_sym) || Rails.configuration.i18n.default_locale
|
||||
session[:locale] = (params[:locale].presence || session[:locale] || Rails.configuration.i18n.default_locale).to_sym
|
||||
I18n.locale = session[:locale]
|
||||
# I18n.locale = params[:locale].presence.try(:to_sym) || Rails.configuration.i18n.default_locale
|
||||
end
|
||||
|
||||
def _render_with_renderer_json(resource, options)
|
||||
@@ -98,9 +112,10 @@ private
|
||||
|
||||
def after_sign_in_path_for(resource)
|
||||
case resource
|
||||
when Employee then supplier_root_path
|
||||
when Administrator then cmtool.root_path
|
||||
else root_path
|
||||
when User then Mozo.user_url
|
||||
when Employee then Mozo.supplier_url
|
||||
when Administrator then cmtool.root_path
|
||||
else root_path
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# Contact page form
|
||||
class ContactFormsController < ApplicationController
|
||||
def create
|
||||
@contact_form = Cmtool::ContactForm.new(contact_form_params)
|
||||
if @contact_form.save
|
||||
Notifier.contact_form(@contact_form.id).deliver_later
|
||||
redirect_to root_path, notice: t('contact_form.submitted')
|
||||
redirect_to root_path, notice: t('website.contact_form.submitted')
|
||||
else
|
||||
redirect_to page_path('contact', locale: I18n.locale), alert: @contact_form.errors.full_messages.join(', ')
|
||||
end
|
||||
|
||||
+2
-1
@@ -1,4 +1,4 @@
|
||||
class Suppliers::SessionsController < Devise::SessionsController
|
||||
class Employees::SessionsController < Devise::SessionsController
|
||||
respond_to :json
|
||||
|
||||
def create
|
||||
@@ -10,6 +10,7 @@ class Suppliers::SessionsController < Devise::SessionsController
|
||||
render json: {employee_id: current_employee.id, auth_token: current_employee.authentication_token}
|
||||
end
|
||||
|
||||
# deprecated?
|
||||
def destroy
|
||||
session[:supplier_id] = nil
|
||||
super
|
||||
@@ -0,0 +1,5 @@
|
||||
class ErrorsController < ApplicationController
|
||||
def not_found
|
||||
head :not_found # Renders an empty body with 404 status [5]
|
||||
end
|
||||
end
|
||||
@@ -1,13 +0,0 @@
|
||||
class RegistrationsController < Devise::RegistrationsController
|
||||
protected
|
||||
|
||||
#def after_sign_up_path(resource)
|
||||
#end
|
||||
|
||||
private
|
||||
|
||||
# override devise internal to allow name as sign_up param
|
||||
def sign_up_params
|
||||
params.require(resource_name).permit resource_class.authentication_keys + [:name, :password, :password_confirmation]
|
||||
end
|
||||
end
|
||||
@@ -114,7 +114,7 @@ module Suppliers
|
||||
image_type = match[1]
|
||||
decoded_attribute = Base64.decode64 value.sub BASE64_IMAGE_MATCHER, ''
|
||||
file = Tempfile.new(['image', ".#{image_type}"])
|
||||
tempfiles << file
|
||||
@tempfiles << file
|
||||
file.binmode
|
||||
file.write decoded_attribute
|
||||
authorized_params[attribute] = file
|
||||
|
||||
@@ -41,4 +41,7 @@ class UserController < Users::ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def login
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -28,7 +28,6 @@ module Users
|
||||
orders
|
||||
orders.product_orders
|
||||
]
|
||||
#include_config << 'users' if @list.user_ids.size > 1
|
||||
render json: @list, include: include_config, serializer: Users::ListSerializer, is_collection: false
|
||||
end
|
||||
|
||||
@@ -60,6 +59,14 @@ module Users
|
||||
render json: @list
|
||||
end
|
||||
|
||||
# POST /user/remove_list_needs_payment.json
|
||||
def remove_needs_payment
|
||||
@list = active_list
|
||||
render json: json_alert('messages.no_active_list', list_active: false) and return unless @list.try(:id).to_s == params[:id]
|
||||
@list.remove_needs_payment!
|
||||
render json: @list
|
||||
end
|
||||
|
||||
# POST /user/lists/:id/move_table.json?table_id=....
|
||||
# used to move the table
|
||||
# TODO wrap logic of actions
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
class Users::RegistrationsController < Devise::RegistrationsController
|
||||
def create
|
||||
devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
|
||||
super
|
||||
# if resource.persisted?
|
||||
# self.response_body = nil
|
||||
# end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,6 @@
|
||||
class UsersController < ApplicationController
|
||||
def show
|
||||
authenticate_user!
|
||||
@user = current_user
|
||||
end
|
||||
end
|
||||
@@ -1,8 +1,8 @@
|
||||
module ApplicationHelper
|
||||
# overwrite i18n l, to handle nil values
|
||||
def l(*args)
|
||||
def l(*args, **options)
|
||||
return '' unless args.first
|
||||
super(*args)
|
||||
super(*args, **options)
|
||||
end
|
||||
|
||||
def user_root_path
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Include this in any model that needs to broadcast events to users/suppliers.
|
||||
#
|
||||
# Replaces the old model_broadcast.rb initializer which monkey-patched
|
||||
# SimplyStored::Couch and created ApplicationController.new per broadcast
|
||||
# (memory-unsafe, no request context, to be removed once all callers migrate).
|
||||
#
|
||||
# Usage:
|
||||
# class List < ApplicationRecord
|
||||
# include Broadcastable
|
||||
#
|
||||
# def close!
|
||||
# broadcast_user(user.id, 'list_closed', { id: id })
|
||||
# broadcast_supplier(supplier_id, 'list_closed', { id: id })
|
||||
# end
|
||||
# end
|
||||
#
|
||||
module Broadcastable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def broadcast_supplier(sid, event, data = {})
|
||||
Mozo.broadcast_supplier(sid, event, data)
|
||||
end
|
||||
|
||||
def broadcast_user(uid, event, data = {})
|
||||
Mozo.broadcast_user(uid, event, data)
|
||||
end
|
||||
end
|
||||
@@ -19,7 +19,10 @@ class Employee
|
||||
end
|
||||
|
||||
#view :by_confirmation_token, key: :confirmation_token # devise confirmable
|
||||
devise :database_authenticatable, :recoverable, :rememberable, :trackable, :validatable #, :registerable #, :confirmable
|
||||
devise_plugins = [:database_authenticatable, :registerable, :confirmable, :recoverable, :rememberable, :trackable] #, :omniauthable, {omniauth_providers: [:facebook, :instagram]}] #, :token_authenticatable , :registerable
|
||||
devise_plugins -= [:trackable] if Rails.env.test? # creates conflicts
|
||||
devise *devise_plugins
|
||||
# devise :database_authenticatable, :recoverable, :rememberable, :trackable, :validatable #, :registerable #, :confirmable
|
||||
property :unconfirmed_email
|
||||
|
||||
property :name
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
class List
|
||||
include SimplyStored::Couch
|
||||
include ActiveModel::SerializerSupport
|
||||
include Broadcastable
|
||||
include List::JoinRequests
|
||||
per_page_method :limit_value #kaminari
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
class Order
|
||||
include SimplyStored::Couch
|
||||
include ActiveModel::SerializerSupport
|
||||
include Broadcastable
|
||||
|
||||
property :state, default: 'placed' # placed, active, delivered, cancelled, closed
|
||||
|
||||
|
||||
@@ -38,6 +38,10 @@ class Supplier
|
||||
property :lat, type: Float #, default: 52.08062426379751
|
||||
property :lng, type: Float #, default: 4.312562942504883
|
||||
|
||||
def suggested_tips
|
||||
[0, 5, 10, 15, 20]
|
||||
end
|
||||
|
||||
#WIFI
|
||||
property :offer_wifi
|
||||
property :wifi_ssid
|
||||
@@ -215,7 +219,7 @@ class Supplier
|
||||
private
|
||||
|
||||
def add_section_on_create
|
||||
@section = Section.create supplier: self, title: I18n.t('supplier.section.first_section_title')
|
||||
@section = Section.create supplier: self, title: I18n.t('supplier.section.first_section_title', default: nil) || 'Room'
|
||||
end
|
||||
|
||||
|
||||
|
||||
+7
-1
@@ -6,6 +6,7 @@ class User
|
||||
property :name
|
||||
property :active_list_id
|
||||
property :admin, type: :boolean, default: false
|
||||
property :email_sha256
|
||||
|
||||
#FACEBOOK
|
||||
property :provider
|
||||
@@ -14,7 +15,7 @@ class User
|
||||
property :oauth_expires_at
|
||||
property :auth_data
|
||||
|
||||
devise_plugins = [:database_authenticatable, :recoverable, :rememberable, :trackable, :omniauthable, {omniauth_providers: [:facebook, :instagram]}] #, :token_authenticatable , :registerable
|
||||
devise_plugins = [:database_authenticatable, :registerable, :confirmable, :recoverable, :rememberable, :trackable] #, :omniauthable, {omniauth_providers: [:facebook, :instagram]}] #, :token_authenticatable , :registerable
|
||||
devise_plugins -= [:trackable] if Rails.env.test? # creates conflicts
|
||||
devise *devise_plugins
|
||||
|
||||
@@ -26,6 +27,7 @@ class User
|
||||
|
||||
validates_uniqueness_of :email
|
||||
before_save :ensure_authentication_token
|
||||
before_create :set_email_sha256
|
||||
|
||||
#has_many :error_logs
|
||||
has_many :user_feedbacks
|
||||
@@ -150,6 +152,10 @@ class User
|
||||
reset_authentication_token! if authentication_token.blank?
|
||||
end
|
||||
|
||||
def set_email_sha256
|
||||
self.email_sha256 = Digest::SHA256.hexdigest email.to_s.strip.downcase
|
||||
end
|
||||
|
||||
def self.authentication_token
|
||||
SecureRandom.hex(24)
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Suppliers::UserSerializer
|
||||
include Mozo::SupplierBaseSerializer
|
||||
attributes :email, :provider, :uid, :avatar, :number_of_lists_at_supplier
|
||||
attributes :email, :email_sha256, :provider, :uid, :avatar, :number_of_lists_at_supplier
|
||||
attribute(:name) { object.supplier_name }
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class Users::UserSerializer
|
||||
include Mozo::UserBaseSerializer
|
||||
|
||||
attributes :email, :provider, :uid, :avatar
|
||||
attributes :email, :email_sha256, :provider, :uid, :avatar
|
||||
attribute(:name){ object.friends_name }
|
||||
end
|
||||
|
||||
@@ -7,5 +7,19 @@ module Mozo
|
||||
lnd = Lnrpc::Client.new(credentials_path: Rails.application.config.lnd_credentials_path, macaroon_path: Rails.application.config.lnd_macaroon_path)
|
||||
end
|
||||
|
||||
def self.get_info
|
||||
begin
|
||||
client.lightning.get_info
|
||||
|
||||
|
||||
rescue StandardError => exception
|
||||
if exception&.message =~ /wallet locked/
|
||||
Rails.logger.fatal 'FATAL: LND wallet locked'
|
||||
#TODO handle wallet locket
|
||||
end
|
||||
raise exception
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
- model_class = Order
|
||||
div.page-header= title :index, model_class
|
||||
- title :index, model_class
|
||||
- if @orders.any?
|
||||
table.table.table-striped
|
||||
thead
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
- model_class = ProductCategory
|
||||
.page-header= title :index, model_class
|
||||
- title :index, model_class
|
||||
- if @product_categories.any?
|
||||
table.table.table-striped
|
||||
thead
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
- model_class = Product
|
||||
.page-header= title :index, model_class
|
||||
- title :index, model_class
|
||||
- if @products.any?
|
||||
table.table.table-striped
|
||||
thead
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
- model_class = Section
|
||||
.page-header= title :index, model_class
|
||||
- title :index, model_class
|
||||
- if @sections.any?
|
||||
table.table.table-striped
|
||||
thead
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
= simple_form_for [:admin, @table], html: {class: 'form-horizontal' } do |f|
|
||||
= form_for [:admin, @table], html: {class: 'form-horizontal' } do |f|
|
||||
= render 'error_messages', target: @table
|
||||
= f.input :number
|
||||
.control-group class=(@table.errors[:supplier_id].any? ? 'error' : nil)
|
||||
= f.label :supplier_id, Supplier.model_name.human, class: 'control-label'
|
||||
.controls
|
||||
= f.select :supplier_id, options_for_select(@suppliers.map{|a| [a.name, a.id]}), include_blank: nil
|
||||
.form-actions
|
||||
.form-row
|
||||
.form-label= f.label :number
|
||||
.form-field= f.number_field :number
|
||||
.form-row class=(@table.errors[:supplier_id].any? ? 'error' : nil)
|
||||
.form-label= f.label :supplier_id, Supplier.model_name.human, class: 'control-label'
|
||||
.form-field= f.select :supplier_id, options_for_select(@suppliers.map{|a| [a.name, a.id]}), include_blank: nil
|
||||
/.form-actions
|
||||
= f.submit nil, class: 'btn btn-primary'
|
||||
'
|
||||
= link_to t("helpers.links.cancel"), admin_tables_path, class: 'btn'
|
||||
/.form-row: .form-actions
|
||||
= f.submit @submit || update_button_text(f.object), class: 'button'
|
||||
= link_to t("helpers.links.cancel"), admin_tables_path, class: 'btn'
|
||||
- content_for :page_links do
|
||||
ul
|
||||
li= f.submit @submit || update_button_text(f.object), class: 'button'
|
||||
li= link_to link_to_index_content(Table), [:admin, Table], class: 'to-index-button'
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
- model_class = Table
|
||||
.page-header= title :edit, model_class
|
||||
= render 'form'
|
||||
- title :edit, model_class
|
||||
= render 'form'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
- model_class = Table
|
||||
div.page-header= title :index, model_class
|
||||
- title :index, model_class
|
||||
- if @tables.any?
|
||||
table.table.table-striped
|
||||
thead
|
||||
@@ -20,5 +20,8 @@ div.page-header= title :index, model_class
|
||||
= link_to t("helpers.links.destroy"), [:admin, table], method: :delete, data: {confirm: are_you_sure? }, class: 'btn btn-mini btn-danger'
|
||||
- else
|
||||
= no_content_given model_class
|
||||
= link_to t("helpers.links.new"), new_admin_table_path, class: 'btn btn-primary'
|
||||
/= link_to t("helpers.links.new"), new_admin_table_path, class: 'btn btn-primary'
|
||||
|
||||
- content_for :page_links do
|
||||
ul
|
||||
li= link_to link_to_new_content(Table), new_admin_table_path, class: 'record-new-button'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
- model_class = Table
|
||||
.page-header
|
||||
= title :new, model_class
|
||||
- title :edit, model_class
|
||||
= render 'form'
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
h1 QR Scan landing page
|
||||
p |
|
||||
You are
|
||||
p
|
||||
span You scanned a QR code of a
|
||||
a<> href="https://mozo.bar" mozo.bar
|
||||
span table.
|
||||
br
|
||||
span If you see this page, that means that you did not scan it using the Mozo Web App.
|
||||
span. If you already have an ccount, please go to the:
|
||||
a<> href="https://user.mozo.bar/" Mozo User App.
|
||||
br
|
||||
span If not, please go to the:
|
||||
a< href="https://mozo.bar" Mozo User Account Page
|
||||
|
||||
+3
-2
@@ -2,6 +2,7 @@ h2= t('devise.registrations.title')
|
||||
= form_for(resource, :as => resource_name, :url => registration_path(resource_name), html: {class: 'form-horizontal'}) do |f|
|
||||
= devise_error_messages!
|
||||
.control-group
|
||||
- binding.pry
|
||||
= f.label :name, class: 'control-label'
|
||||
.controls= f.text_field :name
|
||||
.control-group
|
||||
@@ -15,5 +16,5 @@ h2= t('devise.registrations.title')
|
||||
.controls= f.password_field :password_confirmation
|
||||
.control-group
|
||||
.controls
|
||||
= f.submit t('devise.registrations.button'), class: 'btn btn-primary'
|
||||
= render "devise_links"
|
||||
= f.submit t('devise.registrations.button'), class: 'button2'
|
||||
= render "devise/links"
|
||||
@@ -1,29 +0,0 @@
|
||||
<h2>Sign up</h2>
|
||||
|
||||
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
|
||||
<%= devise_error_messages! %>
|
||||
|
||||
<div class="field">
|
||||
<%= f.label :email %><br />
|
||||
<%= f.email_field :email, autofocus: true %>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<%= f.label :password %>
|
||||
<% if @validatable %>
|
||||
<em>(<%= @minimum_password_length %> characters minimum)</em>
|
||||
<% end %><br />
|
||||
<%= f.password_field :password, autocomplete: "off" %>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<%= f.label :password_confirmation %><br />
|
||||
<%= f.password_field :password_confirmation, autocomplete: "off" %>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit "Sign up" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= render "devise/shared/links" %>
|
||||
@@ -0,0 +1,11 @@
|
||||
h2= t('devise.employee.confirmations.title')
|
||||
= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: {class: 'form-horizontal'}) do |f|
|
||||
= devise_error_messages!
|
||||
.control-group
|
||||
= f.label :email, class: 'control-label'
|
||||
.controls
|
||||
= f.email_field :email
|
||||
.control-group
|
||||
.controls
|
||||
= f.submit t('devise.employee.confirmations.button'), class: 'button'
|
||||
= render 'employees/devise/links'
|
||||
@@ -0,0 +1,23 @@
|
||||
dl.devise-links
|
||||
- devise_mapping = Devise.mappings[resource_name]
|
||||
dt= t 'devise.links.prefix'
|
||||
- if controller_name != 'sessions'
|
||||
dd= link_to t('devise.employee.sign_in.link'), new_session_path(resource_name), class: ['devise-link', 'new-session']
|
||||
|
||||
- if devise_mapping.registerable? && controller_name != 'registrations'
|
||||
dd= link_to t('devise.employee.registrations.link'), new_registration_path(resource_name), class: ['devise-link', 'new-registration']
|
||||
- if resource_name == :employee and controller_name != 'new_suppliers'
|
||||
dd= link_to t('devise.employee.registrations.link'), new_suppliers_path, class: ['devise-link', 'new-registration']
|
||||
|
||||
- if devise_mapping.recoverable? && controller_name != 'passwords'
|
||||
dd= link_to t('devise.employee.passwords.link'), new_password_path(resource_name), class: ['devise-link', 'forgot-password']
|
||||
|
||||
- if devise_mapping.confirmable? && controller_name != 'confirmations'
|
||||
dd= link_to t('devise.employee.confirmations.did_not_receive_instructions_link'), new_confirmation_path(resource_name), class: ['devise-link', 'did-not-receive-instructions']
|
||||
|
||||
- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks'
|
||||
dd= link_to t('devise.employee.unlocks.did_not_receive_instructions_link'), new_unlock_path(resource_name), class: ['devise-link', 'did-not-receive-instructions']
|
||||
|
||||
- if devise_mapping.omniauthable?
|
||||
- resource_class.omniauth_providers.each do |provider|
|
||||
dd= link_to t('devise.employee.omniauth_callbacks.sign_in_with', provider: provider.to_s.titleize), omniauth_authorize_path(resource_name, provider), class: ['devise-link', 'omniauth', provider]
|
||||
@@ -1,12 +1,14 @@
|
||||
h2= t('devise.passwords.edit.title')
|
||||
= form_for(resource, :as => resource_name, :url => password_path(resource_name), html: {method: :put}) do |f|
|
||||
h2= t('devise.employee.passwords.edit.title')
|
||||
= form_for(resource, :as => resource_name, :url => password_path(resource_name), html: {class: 'form-horizontal', method: :put}) do |f|
|
||||
= devise_error_messages!
|
||||
= f.hidden_field :reset_password_token
|
||||
= f.row :password
|
||||
.form-label= f.label :password
|
||||
.form-field= f.password_field :password
|
||||
= f.row :password_confirmation
|
||||
.form-label= f.label :password_confirmation
|
||||
.form-field= f.password_field :password_confirmation
|
||||
.form-row= f.submit t('devise.passwords.edit.button'), class: 'button'
|
||||
= render "devise/links"
|
||||
.control-group
|
||||
= f.label :password, class: 'control-label'
|
||||
.controls= f.password_field :password
|
||||
.control-group
|
||||
= f.label :password_confirmation, class: 'control-label'
|
||||
.controls= f.password_field :password_confirmation
|
||||
.control-group
|
||||
.controls
|
||||
= f.submit t('devise.employee.passwords.edit.button'), class: 'button'
|
||||
= render 'employees/devise/links'
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
h2= t('devise.passwords.title')
|
||||
= form_for(resource, :as => resource_name, :url => password_path(resource_name)) do |f|
|
||||
h2= t('devise.employee.passwords.title')
|
||||
= form_for(resource, :as => resource_name, :url => password_path(resource_name), html: {class: 'form-horizontal'}) do |f|
|
||||
= devise_error_messages!
|
||||
= f.row :email do
|
||||
.form-label= f.label :email, class: 'control-label'
|
||||
.form-field= f.email_field :email, autofocus: true
|
||||
.form-row.form-actions= f.submit t('devise.passwords.button'), class: 'button'
|
||||
= render "devise/links"
|
||||
.control-group
|
||||
= f.label :email, class: 'control-label'
|
||||
.controls= f.email_field :email
|
||||
.control-group
|
||||
.controls
|
||||
= f.submit t('devise.employee.passwords.button'), class: 'button'
|
||||
= render 'employees/devise/links'
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<h2>Edit <%= resource_name.to_s.humanize %></h2>
|
||||
|
||||
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
|
||||
<%= devise_error_messages! %>
|
||||
|
||||
<div><%= f.label :email %><br />
|
||||
<%= f.email_field :email %></div>
|
||||
|
||||
<div><%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
|
||||
<%= f.password_field :password, :autocomplete => "off" %></div>
|
||||
|
||||
<div><%= f.label :password_confirmation %><br />
|
||||
<%= f.password_field :password_confirmation %></div>
|
||||
|
||||
<div><%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
|
||||
<%= f.password_field :current_password %></div>
|
||||
|
||||
<div><%= f.submit "Update" %></div>
|
||||
<% end %>
|
||||
|
||||
<h3>Cancel my account</h3>
|
||||
|
||||
<p>Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :confirm => "Are you sure?", :method => :delete %>.</p>
|
||||
|
||||
<%= link_to "Back", :back %>
|
||||
@@ -0,0 +1,22 @@
|
||||
h2= t('devise.employee.registrations.title')
|
||||
= form_for(resource, as: resource_name, url: registration_path(resource_name), html: {class: 'form-horizontal'}) do |f|
|
||||
= devise_error_messages!
|
||||
.control-group
|
||||
= f.label :name, class: 'control-label'
|
||||
.controls= f.text_field :name
|
||||
.control-group
|
||||
= f.label :email, class: 'control-label'
|
||||
.controls
|
||||
= f.email_field :email
|
||||
- if f.object.errors[:email].present?
|
||||
small.error= f.object.errors[:email].to_sentence
|
||||
.control-group
|
||||
= f.label :password, class: 'control-label'
|
||||
.controls= f.password_field :password
|
||||
.control-group
|
||||
= f.label :password_confirmation, class: 'control-label'
|
||||
.controls= f.password_field :password_confirmation
|
||||
.control-group
|
||||
.controls
|
||||
= f.submit t('devise.employee.registrations.button'), class: 'button'
|
||||
= render 'employees/devise/links'
|
||||
@@ -0,0 +1,25 @@
|
||||
h2= t('devise.employee.sign_in.title')
|
||||
= form_for(resource, :as => resource_name, :url => session_path(resource_name), html: {class: 'form-horizontal'}) do |f|
|
||||
= devise_error_messages!
|
||||
.control-group
|
||||
= f.label :email, class: 'control-label'
|
||||
.controls
|
||||
= f.email_field :email
|
||||
- if f.object.errors[:email].present?
|
||||
small.error= f.object.errors[:email].to_sentence
|
||||
.control-group
|
||||
= f.label :password, class: 'control-label'
|
||||
.controls
|
||||
= f.password_field :password
|
||||
- if f.object.errors[:password].present?
|
||||
small.error= f.object.errors[:password].to_sentence
|
||||
.control-group
|
||||
.controls
|
||||
= f.label :remember_me do
|
||||
= f.check_box :remember_me
|
||||
|
|
||||
= t('devise.employee.sign_in.remember_me')
|
||||
.control-group
|
||||
.controls
|
||||
= f.submit t('devise.employee.sign_in.button'), class: 'button'
|
||||
= render 'employees/devise/links'
|
||||
@@ -38,4 +38,4 @@
|
||||
|
||||
.row
|
||||
.small-12.columns== @page.footer
|
||||
= render "devise/links", resource_name: :employee
|
||||
/= render "devise/links", resource_name: :employee
|
||||
|
||||
@@ -16,5 +16,5 @@
|
||||
.large-8.columns.end= f.email_field :email
|
||||
.row
|
||||
.large-12.columns
|
||||
= f.submit t('contact_form.send_button'), class: 'button'
|
||||
= f.submit t('website.contact_form.send_button'), class: 'button'
|
||||
.large-3.columns== @page.sidebar
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<h2>Resend confirmation instructions</h2>
|
||||
|
||||
<%= form_for(resource, :as => resource_name, :url => confirmation_path(resource_name), :html => { :method => :post }) do |f| %>
|
||||
<%= devise_error_messages! %>
|
||||
|
||||
<div><%= f.label :email %><br />
|
||||
<%= f.email_field :email %></div>
|
||||
|
||||
<div><%= f.submit "Resend confirmation instructions" %></div>
|
||||
<% end %>
|
||||
|
||||
<%= render "links" %>
|
||||
@@ -0,0 +1,11 @@
|
||||
h2= t('devise.user.confirmations.title')
|
||||
= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: {class: 'form-horizontal'}) do |f|
|
||||
= devise_error_messages!
|
||||
.control-group
|
||||
= f.label :email, class: 'control-label'
|
||||
.controls
|
||||
= f.email_field :email
|
||||
.control-group
|
||||
.controls
|
||||
= f.submit t('devise.user.confirmations.button'), class: 'button'
|
||||
= render 'users/devise/links'
|
||||
@@ -0,0 +1,23 @@
|
||||
dl.devise-links
|
||||
- devise_mapping = Devise.mappings[resource_name]
|
||||
dt= t 'devise.links.prefix'
|
||||
- if controller_name != 'sessions'
|
||||
dd= link_to t('devise.user.sign_in.link'), new_session_path(resource_name), class: ['devise-link', 'new-session']
|
||||
|
||||
- if devise_mapping.registerable? && controller_name != 'registrations'
|
||||
dd= link_to t('devise.user.registrations.link'), new_registration_path(resource_name), class: ['devise-link', 'new-registration']
|
||||
- if resource_name == :employee and controller_name != 'new_suppliers'
|
||||
dd= link_to t('devise.user.registrations.link'), new_suppliers_path, class: ['devise-link', 'new-registration']
|
||||
|
||||
- if devise_mapping.recoverable? && controller_name != 'passwords'
|
||||
dd= link_to t('devise.user.passwords.link'), new_password_path(resource_name), class: ['devise-link', 'forgot-password']
|
||||
|
||||
- if devise_mapping.confirmable? && controller_name != 'confirmations'
|
||||
dd= link_to t('devise.user.confirmations.did_not_receive_instructions_link'), new_confirmation_path(resource_name), class: ['devise-link', 'did-not-receive-instructions']
|
||||
|
||||
- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks'
|
||||
dd= link_to t('devise.user.unlocks.did_not_receive_instructions_link'), new_unlock_path(resource_name), class: ['devise-link', 'did-not-receive-instructions']
|
||||
|
||||
- if devise_mapping.omniauthable?
|
||||
- resource_class.omniauth_providers.each do |provider|
|
||||
dd= link_to t('devise.user.omniauth_callbacks.sign_in_with', provider: provider.to_s.titleize), omniauth_authorize_path(resource_name, provider), class: ['devise-link', 'omniauth', provider]
|
||||
@@ -1,6 +1,6 @@
|
||||
p= t('devise.mailer.confirmation_instructions.salutation', email: @resource.email)
|
||||
p== t('mailer.user.confirmation_instructions.salutation', email: @resource.email)
|
||||
|
||||
p=raw t \
|
||||
'devise.mailer.confirmation_instructions.body',
|
||||
p== t \
|
||||
'mailer.user.confirmation_instructions.body',
|
||||
unconfirmed_email: @resource.unconfirmed_email,
|
||||
confirm_url: confirmation_url(@resource, :confirmation_token => @token)
|
||||
confirm_url: confirmation_url(@resource, confirmation_token: @token)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
h2= t('devise.passwords.edit.title')
|
||||
h2= t('devise.user.passwords.edit.title')
|
||||
= form_for(resource, :as => resource_name, :url => password_path(resource_name), html: {class: 'form-horizontal', method: :put}) do |f|
|
||||
= devise_error_messages!
|
||||
= f.hidden_field :reset_password_token
|
||||
@@ -10,5 +10,5 @@ h2= t('devise.passwords.edit.title')
|
||||
.controls= f.password_field :password_confirmation
|
||||
.control-group
|
||||
.controls
|
||||
= f.submit t('devise.passwords.edit.button'), class: 'btn btn-primary'
|
||||
= render "links"
|
||||
= f.submit t('devise.user.passwords.edit.button'), class: 'button'
|
||||
= render 'users/devise/links'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
h2= t('devise.passwords.title')
|
||||
h2= t('devise.user.passwords.title')
|
||||
= form_for(resource, :as => resource_name, :url => password_path(resource_name), html: {class: 'form-horizontal'}) do |f|
|
||||
= devise_error_messages!
|
||||
.control-group
|
||||
@@ -6,5 +6,5 @@ h2= t('devise.passwords.title')
|
||||
.controls= f.email_field :email
|
||||
.control-group
|
||||
.controls
|
||||
= f.submit t('devise.passwords.button'), class: 'btn btn-primary'
|
||||
= render "links"
|
||||
= f.submit t('devise.user.passwords.button'), class: 'button'
|
||||
= render 'users/devise/links'
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
h2= t('devise.registrations.title')
|
||||
= form_for(resource, :as => resource_name, :url => registration_path(resource_name), html: {class: 'form-horizontal'}) do |f|
|
||||
h2= t('devise.user.registrations.title')
|
||||
= form_for(resource, as: resource_name, url: registration_path(resource_name), html: {class: 'form-horizontal'}) do |f|
|
||||
= devise_error_messages!
|
||||
.control-group
|
||||
= f.label :name, class: 'control-label'
|
||||
.controls= f.text_field :name
|
||||
.control-group
|
||||
= f.label :email, class: 'control-label'
|
||||
.controls= f.email_field :email
|
||||
= f.label :email, class: 'control-label'
|
||||
.controls
|
||||
= f.email_field :email
|
||||
- if f.object.errors[:email].present?
|
||||
small.error= f.object.errors[:email].to_sentence
|
||||
.control-group
|
||||
= f.label :password, class: 'control-label'
|
||||
.controls= f.password_field :password
|
||||
= f.label :password, class: 'control-label'
|
||||
.controls= f.password_field :password
|
||||
.control-group
|
||||
= f.label :password_confirmation, class: 'control-label'
|
||||
.controls= f.password_field :password_confirmation
|
||||
= f.label :password_confirmation, class: 'control-label'
|
||||
.controls= f.password_field :password_confirmation
|
||||
.control-group
|
||||
.controls
|
||||
= f.submit t('devise.registrations.button'), class: 'btn btn-primary'
|
||||
= render "links"
|
||||
= f.submit t('devise.user.registrations.button'), class: 'button'
|
||||
= render 'users/devise/links'
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
h2= t('devise.sessions.title')
|
||||
h2= t('devise.user.sign_in.title')
|
||||
= form_for(resource, :as => resource_name, :url => session_path(resource_name), html: {class: 'form-horizontal'}) do |f|
|
||||
= devise_error_messages!
|
||||
.control-group
|
||||
= f.label :email, class: 'control-label'
|
||||
.controls= f.email_field :email
|
||||
= f.label :email, class: 'control-label'
|
||||
.controls
|
||||
= f.email_field :email
|
||||
- if f.object.errors[:email].present?
|
||||
small.error= f.object.errors[:email].to_sentence
|
||||
.control-group
|
||||
= f.label :password, class: 'control-label'
|
||||
.controls= f.password_field :password
|
||||
= f.label :password, class: 'control-label'
|
||||
.controls
|
||||
= f.password_field :password
|
||||
- if f.object.errors[:password].present?
|
||||
small.error= f.object.errors[:password].to_sentence
|
||||
.control-group
|
||||
.controls
|
||||
= f.label :remember_me do
|
||||
= f.check_box :remember_me
|
||||
|
|
||||
= t('devise.sign_in.remember_me')
|
||||
= t('devise.user.sign_in.remember_me')
|
||||
.control-group
|
||||
.controls
|
||||
= f.submit t('devise.sign_in.button'), class: 'button'
|
||||
= render "links"
|
||||
= f.submit t('devise.user.sign_in.button'), class: 'button'
|
||||
= render 'users/devise/links'
|
||||
|
||||
@@ -5,6 +5,7 @@ require 'rails'
|
||||
#require 'active_record/railtie'
|
||||
require 'action_controller/railtie'
|
||||
require 'action_mailer/railtie'
|
||||
require 'action_cable/engine'
|
||||
#require 'active_resource/railtie'
|
||||
require 'rails/test_unit/railtie'
|
||||
require 'sprockets/railtie'
|
||||
@@ -15,6 +16,7 @@ require 'net/http' # lib/mozo/broadcaster/faye.rb
|
||||
|
||||
Bundler.require(*Rails.groups(assets: %w[development test user_app]))
|
||||
Bundler.require(:assets) if ENV['DEPLOY'] == 'yes'
|
||||
Bundler.require(:test) if ENV['RAILS_TEST'] == 'yes'
|
||||
|
||||
#NOTE: the JSON.create_id getter/setter has been moved to Thread.current implementation which
|
||||
# leads to "json_class" fallbacks for created threads. Maybe this will be fixed for future
|
||||
@@ -40,7 +42,7 @@ if Rails.env.development?
|
||||
alias_method :old_execute, :execute
|
||||
def execute(method, path, options, payload = nil, &block)
|
||||
Rails.logger.debug "Couch: #{method} #{Rack::Utils.unescape path} #{options}"
|
||||
puts "Couch: #{method} #{Rack::Utils.unescape path} #{options}"
|
||||
#puts "Couch: #{method} #{Rack::Utils.unescape path} #{options}"
|
||||
old_execute(method, path, options, payload, &block)
|
||||
end
|
||||
end
|
||||
@@ -254,7 +256,8 @@ module Mozo
|
||||
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
|
||||
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
|
||||
config.i18n.default_locale = :en
|
||||
config.i18n.available_locales = [:en, :nl]
|
||||
# config.i18n.available_locales = [:en, :es, :nl]
|
||||
config.i18n.available_locales = [:en]
|
||||
|
||||
# Configure the default encoding used in templates for Ruby 1.9.
|
||||
config.encoding = "utf-8"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# ActionCable configuration for real-time broadcasting.
|
||||
#
|
||||
# Development: async adapter (in-process, no external dependency).
|
||||
# Test: test adapter.
|
||||
# Production: Redis adapter — required for multi-worker deployments.
|
||||
# Redis is also used for Mozo::Counter (replacing DrbCounter).
|
||||
#
|
||||
development:
|
||||
adapter: redis
|
||||
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/2" } %>
|
||||
channel_prefix: mozo_backend_dev
|
||||
|
||||
test:
|
||||
adapter: test
|
||||
|
||||
production:
|
||||
adapter: redis
|
||||
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
|
||||
channel_prefix: mozo_backend
|
||||
@@ -14,6 +14,8 @@ Mozo::Application.configure do
|
||||
resource '*', headers: :any, methods: %i[get post put patch delete options]
|
||||
end
|
||||
end
|
||||
#config.action_cable.allowed_request_origins = ['https://localhost:4201', 'https://localhost:4202']
|
||||
|
||||
config.lnd_credentials_path = '/mnt/ext1/.lnd/tls.cert'
|
||||
config.lnd_macaroon_path = '/mnt/ext1/.lnd/data/chain/bitcoin/mainnet/admin.macaroon'
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Ensure ActionCable has a logger in all environments.
|
||||
# In apps upgraded from older Rails versions, the logger chain
|
||||
# may not propagate to ActionCable out of the box, causing:
|
||||
# NoMethodError (undefined method 'info' for nil)
|
||||
# in ActionCable::Connection::TaggedLoggerProxy#log
|
||||
#
|
||||
Rails.application.config.after_initialize do
|
||||
ActionCable.server.config.logger ||= Rails.logger || ActiveSupport::Logger.new($stdout)
|
||||
ActionCable.server.config.logger.level = Rails.logger&.level || Logger::INFO
|
||||
end
|
||||
@@ -103,6 +103,7 @@ Devise.setup do |config|
|
||||
# requests for sign in and sign up, you need to get a new CSRF token
|
||||
# from the server. You can disable this option at your own risk.
|
||||
# config.clean_up_csrf_token_on_authentication = true
|
||||
config.clean_up_csrf_token_on_authentication = false
|
||||
|
||||
# ==> Configuration for :database_authenticatable
|
||||
# For bcrypt, this is the cost for hashing the password and defaults to 10. If
|
||||
@@ -240,6 +241,7 @@ Devise.setup do |config|
|
||||
#
|
||||
# The "*/*" below is required to match Internet Explorer requests.
|
||||
# config.navigational_formats = ['*/*', :html]
|
||||
config.navigational_formats = ["*/*", :html, :turbo_stream, :json]
|
||||
|
||||
# The default HTTP method used to sign out a resource. Default is :delete.
|
||||
config.sign_out_via = [:delete, :get]
|
||||
@@ -248,19 +250,19 @@ Devise.setup do |config|
|
||||
# Add a new OmniAuth provider. Check the wiki for more information on setting
|
||||
# up on your models and hooks.
|
||||
# config.omniauth :github, 'APP_ID', 'APP_SECRET', :scope => 'user,public_repo'
|
||||
if Rails.env.production?
|
||||
# config.omniauth :facebook, "505160086210072", "fcc474a3fb13c6bcc0f7c83a92ad1b17",
|
||||
# scope: 'email,user_birthday,publish_actions'
|
||||
config.omniauth :facebook, "653729178057509", "d4cea86f70803f1b75ed03c506a4d78e",
|
||||
scope: 'email,user_birthday,user_gender,user_hometown,user_link,user_location',
|
||||
provider_ignores_state: true
|
||||
config.omniauth :instagram, "cd7bdfbee825499b94fb3783d1bc143b", "6b4f9ecf251c462993a696eebc0189be"
|
||||
else
|
||||
config.omniauth :facebook, "168928633304849", "22bc53e1a390c1e62d004195c55fe336",
|
||||
scope: 'email,user_birthday,user_gender,user_hometown,user_link,user_location',
|
||||
provider_ignores_state: true
|
||||
config.omniauth :instagram, "81c78b969a7046d6b6b5b5fe3f30929c", "3697c16762ad4f1ca088e829efbaddde"
|
||||
end
|
||||
# if Rails.env.production?
|
||||
# # config.omniauth :facebook, "505160086210072", "fcc474a3fb13c6bcc0f7c83a92ad1b17",
|
||||
# # scope: 'email,user_birthday,publish_actions'
|
||||
# config.omniauth :facebook, "653729178057509", "d4cea86f70803f1b75ed03c506a4d78e",
|
||||
# scope: 'email,user_birthday,user_gender,user_hometown,user_link,user_location',
|
||||
# provider_ignores_state: true
|
||||
# config.omniauth :instagram, "cd7bdfbee825499b94fb3783d1bc143b", "6b4f9ecf251c462993a696eebc0189be"
|
||||
# else
|
||||
# config.omniauth :facebook, "168928633304849", "22bc53e1a390c1e62d004195c55fe336",
|
||||
# scope: 'email,user_birthday,user_gender,user_hometown,user_link,user_location',
|
||||
# provider_ignores_state: true
|
||||
# config.omniauth :instagram, "81c78b969a7046d6b6b5b5fe3f30929c", "3697c16762ad4f1ca088e829efbaddde"
|
||||
# end
|
||||
|
||||
# ==> Warden configuration
|
||||
# If you want to use other strategies, that are not supported by Devise, or
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
#TODO: this is really ugly, can cause memory leaks and much more bad stuff. We need a new broadcaster....
|
||||
require 'simply_stored/couch'
|
||||
module ModelBroadcast
|
||||
def broadcast_supplier(*args)
|
||||
ApplicationController.new.send(:broadcast_supplier, *args)
|
||||
end
|
||||
def broadcast_user(*args)
|
||||
ApplicationController.new.send(:broadcast_user, *args)
|
||||
end
|
||||
end
|
||||
SimplyStored::Couch.send(:include, ModelBroadcast)
|
||||
#SimplyStored::Couch.send(:extend, ModelBroadcast) # this should never happen!!!
|
||||
@@ -8,12 +8,14 @@ else
|
||||
Mozo.user_url = 'https://user.mozo.bar'
|
||||
end
|
||||
|
||||
Mozo.broadcaster = Mozo::Broadcaster::Faye.new
|
||||
# Broadcaster: swap Faye ↔ ActionCable
|
||||
# Mozo.broadcaster = Mozo::Broadcaster::Faye.new # current (HTTP POST to Faye)
|
||||
# Mozo.broadcaster = Mozo::Broadcaster::ActionCable.new # new (in-process async)
|
||||
#Mozo.broadcaster = Mozo::Broadcaster::Faye.new
|
||||
Mozo.broadcaster = Mozo::Broadcaster::ActionCable.new # new (in-process async)
|
||||
|
||||
# use the connection from couchbase-structures/documents
|
||||
# will be overwritten in the specs since flushing the real
|
||||
# thing is difficult
|
||||
# Mozo::Counter.connection = $cb unless Rails.env.test?
|
||||
|
||||
# Use the Drb counter
|
||||
Mozo::Counter.connection = Mozo::DrbCounter.object unless Rails.env.test?
|
||||
# Counter: swap DrbCounter ↔ Redis
|
||||
# Mozo::Counter.connection = Mozo::DrbCounter.object # current (DRb in-memory)
|
||||
# Mozo::Counter.connection = Mozo::Counter::Redis.new # new (persistent, multi-process)
|
||||
#Mozo::Counter.connection = Mozo::DrbCounter.object unless Rails.env.test?
|
||||
Mozo::Counter.connection = Mozo::Counter::Redis.new unless Rails.env.test? # new (persistent, multi-process)
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
OmniAuth.config.full_host = Rails.env.production? ? 'https://www.mozo.bar' : 'https://localhost:3002'
|
||||
#OmniAuth.config.full_host = Rails.env.production? ? 'https://www.mozo.bar' : 'https://localhost:3002'
|
||||
|
||||
|
||||
@@ -1,19 +1,45 @@
|
||||
en:
|
||||
devise:
|
||||
sign_in:
|
||||
title: Sign in
|
||||
remember_me: Remember me
|
||||
link: Sign in
|
||||
button: Sign in
|
||||
passwords:
|
||||
title: Forgot password
|
||||
link: Forgot password
|
||||
button: Send reset
|
||||
edit:
|
||||
title: Change your password
|
||||
button: Change password
|
||||
registrations:
|
||||
title: Sign up for mozo as a restaurant
|
||||
link: Sign up
|
||||
user:
|
||||
sign_in:
|
||||
title: Sign in as user
|
||||
remember_me: Remember me
|
||||
link: Sign in
|
||||
button: Sign in
|
||||
passwords:
|
||||
title: Recover user password
|
||||
link: Recover password
|
||||
button: Send reset
|
||||
edit:
|
||||
title: Change your password
|
||||
button: Change password
|
||||
registrations:
|
||||
title: Sign up for mozo as a user
|
||||
link: Sign up
|
||||
button: Sign up
|
||||
confirmations:
|
||||
title: Resend confirmation instructions for user
|
||||
button: Resend confirmation instructions
|
||||
did_not_receive_instructions_link: I did not receive the confirmation e-mail.
|
||||
employee:
|
||||
sign_in:
|
||||
title: Sign in as employee
|
||||
remember_me: Remember me
|
||||
link: Sign in
|
||||
button: Sign in
|
||||
passwords:
|
||||
title: Recover password for employee
|
||||
link: Recover password
|
||||
button: Send reset
|
||||
edit:
|
||||
title: Change employee password
|
||||
button: Change password
|
||||
registrations:
|
||||
title: Sign up for mozo as a employee
|
||||
link: Sign up
|
||||
button: Sign up
|
||||
confirmations:
|
||||
title: Resend confirmation instructions for employee
|
||||
button: Resend confirmation instructions
|
||||
links:
|
||||
prefix: 'OR:'
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
es:
|
||||
devise:
|
||||
user:
|
||||
sign_in:
|
||||
title: Iniciar sesión como usuario
|
||||
remember_me: Recordarme
|
||||
link: Iniciar sesión
|
||||
button: Iniciar sesión
|
||||
passwords:
|
||||
title: Recuperar contraseña de usuario
|
||||
link: Recuperar contraseña
|
||||
button: Enviar reinicio
|
||||
edit:
|
||||
title: Cambiar tu contraseña
|
||||
button: Cambiar contraseña
|
||||
registrations:
|
||||
title: Regístrate en mozo como usuario
|
||||
link: Registrarse
|
||||
button: Registrarse
|
||||
confirmations:
|
||||
title: Reenviar instrucciones de confirmación para usuario
|
||||
button: Reenviar instrucciones de confirmación
|
||||
did_not_receive_instructions_link: No recibí el correo con instrucciones de confirmar
|
||||
employee:
|
||||
sign_in:
|
||||
title: Iniciar sesión como empleado
|
||||
remember_me: Recordarme
|
||||
link: Iniciar sesión
|
||||
button: Iniciar sesión
|
||||
passwords:
|
||||
title: Recuperar contraseña de empleado
|
||||
link: Recuperar contraseña
|
||||
button: Enviar reinicio
|
||||
edit:
|
||||
title: Cambiar contraseña de empleado
|
||||
button: Cambiar contraseña
|
||||
registrations:
|
||||
title: Regístrate en mozo como empleado
|
||||
link: Registrarse
|
||||
button: Registrarse
|
||||
confirmations:
|
||||
title: Reenviar instrucciones de confirmación para empleado
|
||||
button: Reenviar instrucciones de confirmación
|
||||
links:
|
||||
prefix: 'O:'
|
||||
@@ -1,19 +1,45 @@
|
||||
nl:
|
||||
devise:
|
||||
sign_in:
|
||||
title: Inloggen
|
||||
remember_me: Mij onthouden
|
||||
link: Inloggen
|
||||
button: Inloggen
|
||||
passwords:
|
||||
title: Wachtwoord vergeten
|
||||
link: Wachtwoord vergeten
|
||||
button: Stuur reset
|
||||
edit:
|
||||
title: Change your password
|
||||
button: Change password
|
||||
registrations:
|
||||
title: Aanmelden als restaurant
|
||||
link: Aanmelden
|
||||
user:
|
||||
sign_in:
|
||||
title: Sign in as user
|
||||
remember_me: Remember me
|
||||
link: Sign in
|
||||
button: Sign in
|
||||
passwords:
|
||||
title: Recover user password
|
||||
link: Recover password
|
||||
button: Send reset
|
||||
edit:
|
||||
title: Change your password
|
||||
button: Change password
|
||||
registrations:
|
||||
title: Sign up for mozo as a user
|
||||
link: Sign up
|
||||
button: Sign up
|
||||
confirmations:
|
||||
title: Resend confirmation instructions for user
|
||||
button: Resend confirmation instructions
|
||||
did_not_receive_instructions_link: I did not receive the confirmation e-mail.
|
||||
employee:
|
||||
sign_in:
|
||||
title: Sign in as employee
|
||||
remember_me: Remember me
|
||||
link: Sign in
|
||||
button: Sign in
|
||||
passwords:
|
||||
title: Recover password for employee
|
||||
link: Recover password
|
||||
button: Send reset
|
||||
edit:
|
||||
title: Change employee password
|
||||
button: Change password
|
||||
registrations:
|
||||
title: Sign up for mozo as a employee
|
||||
link: Sign up
|
||||
button: Sign up
|
||||
confirmations:
|
||||
title: Resend confirmation instructions for employee
|
||||
button: Resend confirmation instructions
|
||||
links:
|
||||
prefix: 'OF:'
|
||||
prefix: 'OR:'
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
es:
|
||||
hello: "Hola mundo"
|
||||
page:
|
||||
not_found: "404 La página que buscaste no se encontró"
|
||||
helpers:
|
||||
links:
|
||||
are_you_sure: '¿Estás seguro?'
|
||||
place_order: Realizar pedido de %{models.order}
|
||||
show_active_list: Mostrar %{list}
|
||||
edit: Editar
|
||||
show: Mostrar
|
||||
new: Nuevo
|
||||
destroy: Eliminar
|
||||
back: Volver
|
||||
cancel: Cancelar
|
||||
index: Resumen
|
||||
forms:
|
||||
errors:
|
||||
title: Se encontraron problemas al guardar (%{count})
|
||||
submit:
|
||||
create: 'Agregar %{model}'
|
||||
update: 'Actualizar %{model}'
|
||||
submit: 'Guardar %{model}'
|
||||
list:
|
||||
no_records: No hay elementos presentes
|
||||
actions:
|
||||
title: Acciones
|
||||
messages:
|
||||
cannot_order_on_non_active_list: No puedes realizar un %{models.order} en una %{models.list} cerrada
|
||||
no_active_list: No hay una %{models.list} activa
|
||||
order_is_placed: Tu pedido ha sido recibido correctamente
|
||||
new_list_created: Se ha creado una nueva %{models.list}
|
||||
the_list_has_been_closed: La %{models.list} ha sido cerrada
|
||||
illegal_history_list_attempt: La %{models.list} que intentas acceder no es tuya
|
||||
table_not_found: La %{models.table} solicitada no se encontró o no fue proporcionada
|
||||
table_is_occupied: La %{models.table} en la que quieres sentarte ya está ocupada
|
||||
table_is_reserved: La %{models.table} que quieres ocupar está reservada por otra persona
|
||||
table_is_closed: La %{models.table} en la que quieres sentarte no está disponible para atender
|
||||
supplier_is_closed: El propietario de esta %{models.table} actualmente no está atendiendo pedidos
|
||||
join_request_rejected: Tu solicitud para unirte a la %{models.table} ha sido rechazada
|
||||
join_request_approved: Tu solicitud para unirte a la %{models.table} ha sido aprobada
|
||||
table_is_from_other_supplier: No puedes moverte a otra %{models.list} cuando tienes una %{models.list} abierta
|
||||
moved_to_another_table: Te has movido exitosamente a otra %{models.table}
|
||||
cannot_identify_table: La aplicación no puede determinar el %{models.table} %{attributes.table.number}
|
||||
action:
|
||||
index:
|
||||
label: Listado de %{models}
|
||||
new:
|
||||
label: Nuevo %{model}
|
||||
show:
|
||||
label: Mostrando %{model}
|
||||
edit:
|
||||
label: Editar %{model}
|
||||
create:
|
||||
successfull: '%{model} se ha creado exitosamente'
|
||||
update:
|
||||
successfull: '%{model} se ha actualizado exitosamente'
|
||||
destroy:
|
||||
successfull: '%{model} se ha eliminado exitosamente'
|
||||
table:
|
||||
is_occupied: Esta %{models.table} está ocupada
|
||||
general:
|
||||
boolean:
|
||||
boolean_yes: "Sí"
|
||||
boolean_no: "No"
|
||||
selected_products:
|
||||
clear: Limpiar
|
||||
order: Pedir
|
||||
product_variant:
|
||||
add_product_variant: Agregar %{models.product_variant}
|
||||
@@ -0,0 +1,12 @@
|
||||
en:
|
||||
mailer:
|
||||
supplier:
|
||||
creation:
|
||||
subject: 'Weklom bij mozo.bar'
|
||||
title: 'De aanmelding van %{name} bij mozo.bar is gelukt!'
|
||||
user:
|
||||
confirmation_instructions:
|
||||
salutation: Welcome to mozo.bar. You registered a new account as %{email}
|
||||
body: |
|
||||
Your registration at the moment is unconfirmed. To confirm your email, click on the following link:
|
||||
<a href="%{confirm_url}">Confirm %{unconfirmed_email}</a>
|
||||
@@ -0,0 +1,12 @@
|
||||
es:
|
||||
mailer:
|
||||
supplier:
|
||||
creation:
|
||||
subject: 'Bienvenido a mozo.bar'
|
||||
title: '¡El registro de %{name} en mozo.bar ha sido exitoso!'
|
||||
user:
|
||||
confirmation_instructions:
|
||||
salutation: Bienvenido a mozo.bar. Te registraste con el correo %{email}
|
||||
body: |
|
||||
Tu registro actualmente no está confirmado. Para confirmar tu correo, haz clic en el siguiente enlace:
|
||||
<a href="%{confirm_url}">Confirmar %{unconfirmed_email}</a>
|
||||
@@ -4,3 +4,9 @@ nl:
|
||||
creation:
|
||||
subject: 'Weklom bij mozo.bar'
|
||||
title: 'De aanmelding van %{name} bij mozo.bar is gelukt!'
|
||||
user:
|
||||
confirmation_instructions:
|
||||
salutation: Welcome to mozo.bar. You registered a new account as %{email}
|
||||
body: |
|
||||
Your registration at the moment is unconfirmed. To confirm your email, click on the following link:
|
||||
<a href="%{confirm_url}">Confirm %{unconfirmed_email}>
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
es:
|
||||
activemodel:
|
||||
models:
|
||||
user: Usuario
|
||||
supplier: Restaurante
|
||||
new_supplier: Nuevo restaurante
|
||||
table: Mesa
|
||||
list: Cuenta
|
||||
product: Producto
|
||||
order: Pedido
|
||||
product_category: Categoría de producto
|
||||
product_variant: Variante
|
||||
section: Sección
|
||||
join_request: Solicitud de unión
|
||||
user_feedback: Comentario de usuario
|
||||
employee: Empleado
|
||||
employee_shift: Turno
|
||||
svg_element: Elemento SVG
|
||||
section_element: Elemento de sección
|
||||
section_area: Área de sección
|
||||
plural:
|
||||
user: Usuarios
|
||||
supplier: Restaurantes
|
||||
new_supplier: Nuevos restaurantes
|
||||
table: Mesas
|
||||
list: Cuentas
|
||||
product: Productos
|
||||
order: Pedidos
|
||||
product_category: Categorías de producto
|
||||
product_variant: Variantes
|
||||
section: Secciones
|
||||
join_request: Solicitudes de unión
|
||||
user_feedback: Comentarios de usuario
|
||||
employee: Empleados
|
||||
employee_shift: Turnos
|
||||
svg_element: Elementos SVG
|
||||
section_element: Elementos de sección
|
||||
section_area: Áreas de sección
|
||||
attributes:
|
||||
product_category:
|
||||
name: Nombre
|
||||
position: Posición
|
||||
week_days: Disponibilidad
|
||||
full_day: Todo el día
|
||||
start_from: Desde
|
||||
end_on: Hasta
|
||||
visible_on: Activo el
|
||||
product:
|
||||
name: Nombre
|
||||
code: Código
|
||||
price: Precio
|
||||
description: Descripción
|
||||
active: "¿Activo?"
|
||||
visible: "¿Visible?"
|
||||
created_at: Creado
|
||||
image: Imagen
|
||||
product_variant:
|
||||
name: Nombre
|
||||
list:
|
||||
created_at: Creado
|
||||
state: Estado
|
||||
needs_help: Necesita atención
|
||||
needs_payment: Quiere pagar
|
||||
closed_at: Cerrado a las
|
||||
price: Total
|
||||
section:
|
||||
title: Título
|
||||
width: Ancho
|
||||
height: Largo
|
||||
created_at: Creado
|
||||
supplier:
|
||||
name: Nombre del %{models.supplier}
|
||||
user_message: Mensaje para %{models.plural.user}
|
||||
email: 'Correo electrónico'
|
||||
password: 'Contraseña'
|
||||
password_confirmation: 'Confirmación'
|
||||
location: Ubicación
|
||||
time_zone: Zona horaria
|
||||
iens_profile: ID de perfil Iens
|
||||
address: Dirección
|
||||
postal_code: Código postal
|
||||
city: Ciudad
|
||||
country: País
|
||||
new_supplier:
|
||||
supplier_name: Nombre del restaurante
|
||||
email: Correo electrónico
|
||||
password: Contraseña
|
||||
password_confirmation: Confirmación de contraseña
|
||||
table:
|
||||
table_number: Número
|
||||
from_number: Desde número
|
||||
to_number: Hasta número
|
||||
created_at: Creado
|
||||
width: Ancho
|
||||
height: Alto
|
||||
user:
|
||||
name: Nombre
|
||||
email: Correo electrónico
|
||||
password: 'Contraseña'
|
||||
password_confirmation: 'Confirmación'
|
||||
employee:
|
||||
name: Nombre
|
||||
email: Correo electrónico
|
||||
manager: '¿Gerente?'
|
||||
active: '¿Activo?'
|
||||
color: Color
|
||||
employee_shift:
|
||||
description: Descripción
|
||||
cmtool/contact_form:
|
||||
name: "Tu nombre:"
|
||||
body: 'Tu pregunta o comentarios:'
|
||||
email: "Tu correo electrónico:*"
|
||||
male: "Sr."
|
||||
female: "Sra."
|
||||
svg_element:
|
||||
name: Nombre
|
||||
svg: SVG
|
||||
dpm: Puntos por metro
|
||||
box_width: Ancho de caja
|
||||
box_height: Alto de caja
|
||||
snap_code: Código QR
|
||||
section_element:
|
||||
name: Nombre
|
||||
svg: SVG
|
||||
dpm: Puntos por metro
|
||||
box_width: Ancho de caja
|
||||
box_height: Alto de caja
|
||||
snap_code: Código QR
|
||||
position_x: X
|
||||
position_y: Y
|
||||
rotation: Ángulo
|
||||
section_area:
|
||||
title: Título
|
||||
width: Ancho
|
||||
height: Alto
|
||||
rounded: "¿Redondeado?"
|
||||
@@ -0,0 +1,9 @@
|
||||
es:
|
||||
simple_form:
|
||||
"yes": 'Sí'
|
||||
"no": 'No'
|
||||
required:
|
||||
text: 'requerido'
|
||||
mark: '*'
|
||||
error_notification:
|
||||
default_message: "Por favor revisa los problemas a continuación:"
|
||||
@@ -0,0 +1,13 @@
|
||||
es:
|
||||
site:
|
||||
home:
|
||||
introduction: >
|
||||
Bienvenido a la página de mozo.bar. Mozo.bar es una aplicación que te permite hacer pedidos
|
||||
simplemente escaneando un código de una mesa en una terraza o restaurante. Entonces,
|
||||
directamente aparece el menú y puedes hacer tu pedido. ¡Mira el progreso mientras esperas tus bebidas!
|
||||
development: >
|
||||
Actualmente mozo.bar está en fase de desarrollo. Esto significa que todavía hay mucho por hacer y que
|
||||
estamos totalmente abiertos a toda la información que podamos recibir. Nuestra misión es complacer tanto al cliente como
|
||||
al propietario del bar con las posibilidades que nos ofrece esta época.
|
||||
enroll:
|
||||
Inscríbete en %{facebook} o %{twitter} para mantenerte informado.
|
||||
@@ -0,0 +1,9 @@
|
||||
es:
|
||||
new_supplier:
|
||||
already_signed_in_new_restaurant_button_text: "Agregar un nuevo %{supplier} contigo como primer gerente"
|
||||
employee_already_signed_in: |
|
||||
Ya has iniciado sesión como %{employee}, por lo que hay algunas acciones posibles:
|
||||
<ol>
|
||||
<li><a href="/supplier" class="button">Ir a la aplicación para gestionar los pedidos</a></li>
|
||||
<li>%{new_supplier_button}</li>
|
||||
</ol>
|
||||
@@ -0,0 +1,6 @@
|
||||
es:
|
||||
waiter:
|
||||
product_orders:
|
||||
order_button: Pedir
|
||||
total: Total
|
||||
no_orders: Sin productos
|
||||
@@ -0,0 +1,5 @@
|
||||
es:
|
||||
website:
|
||||
contact_form:
|
||||
submitted: Has enviado el formulario de contacto exitosamente
|
||||
send_button: "Enviar formulario"
|
||||
+17
-5
@@ -1,13 +1,23 @@
|
||||
ALLOWED_LOCALES = /nl|de|fr|en|es/
|
||||
Mozo::Application.routes.draw do
|
||||
devise_for :users, controllers: { omniauth_callbacks: "users/omniauth_callbacks" }
|
||||
# ActionCable WebSocket endpoint (replaces Faye at events.mozo.bar/faye)
|
||||
# Clients connect via: wss://mozo.bar/cable?auth_token=TOKEN
|
||||
mount ActionCable.server => '/cable'
|
||||
|
||||
match '/.well-known/*rest', to: 'errors#not_found', via: :all
|
||||
match '/system/*rest', to: 'errors#not_found', via: :all
|
||||
devise_for :users, controllers: {
|
||||
registrations: 'users/registrations',
|
||||
} #, controllers: { omniauth_callbacks: "users/omniauth_callbacks" }
|
||||
resources :users, only: [:show]
|
||||
#devise_for :suppliers, controllers: { confirmations: 'confirmations', registrations: 'registrations' }
|
||||
devise_for :employees, controllers: {
|
||||
#confirmations: 'confirmations',
|
||||
#registrations: 'registrations',
|
||||
sessions: 'suppliers/sessions'
|
||||
sessions: 'employees/sessions'
|
||||
}
|
||||
devise_for :administrators
|
||||
|
||||
namespace :admin do
|
||||
resources :users do
|
||||
collection do
|
||||
@@ -35,9 +45,6 @@ Mozo::Application.routes.draw do
|
||||
get 'empty-page' => 'dashboard#empty_page', as: :empty_page
|
||||
|
||||
|
||||
post '/user_app' => 'dashboard#user_app_log' #TODO: separate high speed app at log.mozo.bar
|
||||
post '/user_feedback' => 'user#feedback'
|
||||
|
||||
#WAITER
|
||||
#get '/waiter' => 'waiter#index' #, controller: 'waiter', action: 'index'
|
||||
#get '/waiter/sections' => 'waiter#sections'
|
||||
@@ -73,7 +80,11 @@ Mozo::Application.routes.draw do
|
||||
#post '/user/reject_join_request' => 'user#reject_join_request'
|
||||
#post '/user/approve_join_request' => 'user#approve_join_request'
|
||||
|
||||
post '/user_app' => 'dashboard#user_app_log' #TODO: separate high speed app at log.mozo.bar
|
||||
post '/user_feedback' => 'user#feedback'
|
||||
|
||||
get '/user/obtain_token' => 'user#obtain_token', as: :user_obtain_token
|
||||
post '/user/login' => 'user#login', as: :user_login
|
||||
#post '/user/obtain_token' => 'user#obtain_token', constraints: {format: :json}
|
||||
get '/close_window' => 'dashboard#close_window'
|
||||
namespace :users, path: '/user/api/v1' do
|
||||
@@ -92,6 +103,7 @@ Mozo::Application.routes.draw do
|
||||
get :orders
|
||||
get :users
|
||||
post :needs_payment
|
||||
post :remove_needs_payment
|
||||
post :move_to_table
|
||||
post :order_products
|
||||
post :reject_join_request
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
# Broadcasting: Faye → ActionCable Migration Guide
|
||||
|
||||
## Current state
|
||||
|
||||
```
|
||||
Model (SimplyStored::Couch)
|
||||
→ ApplicationController.new.broadcast_user # ⚠️ anti-pattern
|
||||
→ Mozo.broadcast_user
|
||||
→ Mozo::Broadcaster::Faye.new.broadcast # HTTP POST to Faye
|
||||
→ Faye server (Thin, port 9296)
|
||||
→ WebSocket → browser clients
|
||||
```
|
||||
|
||||
## Target state
|
||||
|
||||
```
|
||||
Model (Broadcastable concern)
|
||||
→ Mozo.broadcast_user
|
||||
→ Mozo::Broadcaster::ActionCable.new.broadcast # in-process async
|
||||
→ ActionCable (Rails built-in)
|
||||
→ WebSocket → browser clients
|
||||
```
|
||||
|
||||
## What this branch adds
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `lib/mozo/broadcaster/action_cable.rb` | Drop-in ActionCable broadcaster adapter |
|
||||
| `config/cable.yml` | ActionCable configuration (async for single-server) |
|
||||
| `app/channels/application_cable/connection.rb` | WebSocket auth via auth_token |
|
||||
| `app/channels/mozo_channel.rb` | Channel authorization for user/supplier/employee |
|
||||
| `app/models/concerns/broadcastable.rb` | Clean module for models (replaces old monkey-patch) |
|
||||
| `config/routes.rb` | Mounts `/cable` WebSocket endpoint |
|
||||
| `config/initializers/model_broadcast.rb` | Fixed: delegates to Mozo directly (no more `ApplicationController.new`) |
|
||||
|
||||
## How to switch
|
||||
|
||||
### 1. Server (one-line change)
|
||||
|
||||
In `config/initializers/mozo_settings.rb`, change:
|
||||
|
||||
```ruby
|
||||
# Mozo.broadcaster = Mozo::Broadcaster::Faye.new # old
|
||||
Mozo.broadcaster = Mozo::Broadcaster::ActionCable.new # new
|
||||
```
|
||||
|
||||
### 2. Client (mozo-user / mozo-supplier)
|
||||
|
||||
**Old Faye client (conceptual):**
|
||||
```js
|
||||
var client = new Faye.Client('https://events.mozo.bar/faye');
|
||||
client.subscribe('/user/123', function(msg) { ... });
|
||||
```
|
||||
|
||||
**New ActionCable client:**
|
||||
```js
|
||||
// Using @rails/actioncable npm package
|
||||
import { createConsumer } from "@rails/actioncable";
|
||||
|
||||
const consumer = createConsumer(
|
||||
`wss://mozo.bar/cable?auth_token=${authToken}`
|
||||
);
|
||||
|
||||
consumer.subscriptions.create(
|
||||
{ channel: "MozoChannel", id: "user_123" },
|
||||
{
|
||||
received(data) {
|
||||
// data = { event: "list_closed", data: { id: 42 } }
|
||||
handleEvent(data.event, data.data);
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### 3. Remove Faye
|
||||
|
||||
Once stable:
|
||||
- Remove `gem 'faye'` from Gemfile
|
||||
- Remove `faye/` directory
|
||||
- Remove nginx `events.mozo.bar` vhost
|
||||
- Stop the Faye Thin process
|
||||
|
||||
## Benefits
|
||||
|
||||
- **No extra process** — ActionCable runs inside Puma
|
||||
- **Async** — `broadcast` is non-blocking
|
||||
- **Simpler deploys** — one less service to manage
|
||||
- **WebSocket native** — no long-polling fallback complexity
|
||||
- **Rails auth** — cookies/sessions work automatically
|
||||
|
||||
---
|
||||
|
||||
# Counter: DrbCounter → Redis Migration
|
||||
|
||||
## Current state
|
||||
|
||||
```
|
||||
Supplier::Counters (app/models/supplier/counters.rb)
|
||||
→ Mozo::Counter.incr/decr/get/set
|
||||
→ Mozo::Counter.connection (Mozo::DrbCounter.object)
|
||||
→ DRb → druby://localhost:9022
|
||||
→ InMemoryQCounter (separate Ruby process)
|
||||
→ on startup: reloads counts from CouchDB
|
||||
→ in-memory only (lost on restart)
|
||||
```
|
||||
|
||||
## Problems
|
||||
|
||||
1. **In-memory only** — restart the DRb process = lose all counts until CouchDB reload
|
||||
2. **Single-process** — DRb runs one Ruby process, single point of failure
|
||||
3. **Separate process** — another thing to monitor, deploy, and restart
|
||||
4. **Race conditions** — between Puma workers, increment/decrement is not atomic across the DRb boundary
|
||||
5. **Custom code** — `InMemoryQCounter` is 100 lines of hand-rolled counter logic
|
||||
|
||||
## Target state
|
||||
|
||||
```
|
||||
Supplier::Counters
|
||||
→ Mozo::Counter.incr/decr/get/set
|
||||
→ Mozo::Counter.connection (Mozo::Counter::Redis.new)
|
||||
→ Redis (localhost:6379)
|
||||
→ persistent, atomic, multi-process safe
|
||||
```
|
||||
|
||||
## How to switch
|
||||
|
||||
In `config/initializers/mozo_settings.rb`, change:
|
||||
|
||||
```ruby
|
||||
# Mozo::Counter.connection = Mozo::DrbCounter.object # old
|
||||
Mozo::Counter.connection = Mozo::Counter::Redis.new # new
|
||||
```
|
||||
|
||||
That's it. All existing `Mozo::Counter.get/set/incr/decr` calls work unchanged.
|
||||
|
||||
## What Redis provides
|
||||
|
||||
- **Atomic INCR/DECR** — no race conditions
|
||||
- **Persistence** — RDB snapshots + AOF, survives restarts
|
||||
- **Multi-process** — all Puma workers share the same Redis
|
||||
- **Already needed** — ActionCable uses Redis for pub/sub in production
|
||||
- **Battle-tested** — millions of deployments
|
||||
|
||||
## Migration steps
|
||||
|
||||
1. `apt-get install redis-server` — already done on vmi3300327
|
||||
2. `gem 'redis', '~> 5.0'` — added to Gemfile
|
||||
3. Switch `Mozo::Counter.connection` — one-line change in mozo_settings.rb
|
||||
4. Stop the DRb counter process (`drb_counter/drb_counter.rb`)
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem "couchrest"
|
||||
gem "pry"
|
||||
#gem "pry"
|
||||
|
||||
+10
-12
@@ -1,27 +1,25 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
coderay (1.1.2)
|
||||
couchrest (2.0.1)
|
||||
httpclient (~> 2.8)
|
||||
mime-types (>= 1.15)
|
||||
multi_json (~> 1.7)
|
||||
httpclient (2.8.3)
|
||||
method_source (0.9.2)
|
||||
mime-types (3.3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2019.1009)
|
||||
multi_json (1.14.1)
|
||||
pry (0.12.2)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.9.0)
|
||||
httpclient (2.9.0)
|
||||
mutex_m
|
||||
logger (1.7.0)
|
||||
mime-types (3.7.0)
|
||||
logger
|
||||
mime-types-data (~> 3.2025, >= 3.2025.0507)
|
||||
mime-types-data (3.2026.0203)
|
||||
multi_json (1.19.1)
|
||||
mutex_m (0.3.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
couchrest
|
||||
pry
|
||||
|
||||
BUNDLED WITH
|
||||
1.17.3
|
||||
2.7.2
|
||||
|
||||
@@ -65,12 +65,13 @@ class InMemoryQCounter
|
||||
def reload_stats!
|
||||
require 'yaml'
|
||||
require 'couchrest'
|
||||
require 'pry'
|
||||
#require 'pry'
|
||||
couch_settings_path = 'config/couchdb.yml'
|
||||
puts "Couch settings path: #{couch_settings_path}"
|
||||
puts "Environment: #{environment.inspect}"
|
||||
#couch_settings = YAML.load_file(couch_settings_path)[environment]
|
||||
couch_settings = YAML.safe_load(ERB.new(File.read(couch_settings_path)).result, permitted_classes: [Symbol])[environment]
|
||||
puts "Couch-Settings: \n#{couch_settings.to_yaml}"
|
||||
database = couch_settings['database']
|
||||
#database = database.sub 'localhost', 'host.docker.internal' unless environment == 'development'
|
||||
#database = couch_settings['database']
|
||||
|
||||
@@ -1,15 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
# This script rebuilds the whole environment and starts a new container
|
||||
#
|
||||
# Set the prompt message
|
||||
environments=("production" "development" "test" "Quit")
|
||||
echo "Choose the environment (1-3): "
|
||||
for i in "${!environments[@]}"; do
|
||||
echo "$((i+1))) ${environments[$i]}"
|
||||
done
|
||||
|
||||
read -p "Enter number: " choice
|
||||
|
||||
if [[ $choice -eq ${#environments[@]} ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Validate and use the choice (subtract 1 for 0-based array index)
|
||||
if [[ $choice -gt 0 && $choice -lt ${#environments[@]} ]]; then
|
||||
environment=${environments[$((choice-1))]}
|
||||
echo "You selected: $environment"
|
||||
else
|
||||
echo "Invalid selection."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 1. ensure this script is run from the project's root, not the drb_counter directory
|
||||
pwd_dirname=$(basename $(pwd));
|
||||
script_dirname="drb_counter";
|
||||
arch=$(uname)
|
||||
environment="${1:-production}"
|
||||
if [ "$pwd_dirname" == "$script_dirname" ]; then
|
||||
echo "PWD DIRNAME: "$pwd_dirname;
|
||||
echo "You must run this script from the project's root dir (../) for the Dockerfile to have access to the configs to COPY";
|
||||
exit 1;
|
||||
|
||||
# Check for sanity, was debugging database mismatch
|
||||
if [ "$#" -lt 1 ]; then
|
||||
nodename=$(uname -n)
|
||||
known_development_machines=("fedorasahi" "blackview")
|
||||
for item in "${known_development_machines[@]}"; do
|
||||
if [[ "$item" == "$nodename" ]]; then
|
||||
echo "Stupid Error: You are on a known development device: $nodename. As a developer, always explicitly supply the environment as the first argument" >&2
|
||||
echo "Usage: ./drb_counter/rebuild-docker.sh development"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# 2. stop and remove all running/existing containers
|
||||
@@ -31,6 +60,7 @@ if [ $arch == "Darwin" ]; then
|
||||
else
|
||||
# docker run --network=host --env DRB_ENV=production --env COUCHDB_ADMIN_PASSWORD=$COUCHDB_ADMIN_PASSWORD --add-host=host.docker.internal:host-gateway --restart unless-stopped --detach --name=mozo_drb_counter mozo_drb_counter
|
||||
docker run --network=host --env DRB_ENV=$environment --env COUCHDB_ADMIN_PASSWORD=$COUCHDB_ADMIN_PASSWORD --restart unless-stopped --detach --name=mozo_drb_counter mozo_drb_counter
|
||||
#docker run -p 9022:9022 --env DRB_ENV=$environment --env COUCHDB_ADMIN_PASSWORD=$COUCHDB_ADMIN_PASSWORD --restart unless-stopped --detach --name=mozo_drb_counter mozo_drb_counter
|
||||
fi
|
||||
|
||||
# To just start the container created through al these steps without rebuilding them:
|
||||
|
||||
+2
-2
@@ -19,12 +19,12 @@ module Mozo
|
||||
autoload :DrbCounter
|
||||
|
||||
def self.broadcast_user(uid, event, data)
|
||||
message = {channel: "/user/#{uid}", data: {event: event, data: data}}
|
||||
message = {channel: "/user_#{uid}", data: {event: event, data: data}}
|
||||
broadcaster.broadcast message
|
||||
end
|
||||
|
||||
def self.broadcast_supplier(sid, event, data)
|
||||
message = {channel: "/supplier/#{sid}", data: {event: event, data: data}}
|
||||
message = {channel: "/supplier_#{sid}", data: {event: event, data: data}}
|
||||
broadcaster.broadcast message
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,5 +2,6 @@ module Mozo
|
||||
module Broadcaster
|
||||
extend ActiveSupport::Autoload
|
||||
autoload :Faye
|
||||
autoload :ActionCable
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Mozo
|
||||
module Broadcaster
|
||||
# Drop-in replacement for Mozo::Broadcaster::Faye that uses
|
||||
# Rails' built-in ActionCable instead of an external Faye process.
|
||||
#
|
||||
# Benefits over Faye:
|
||||
# - Async by default (ActionCable.server.broadcast is non-blocking)
|
||||
# - No extra gem / process / port to manage
|
||||
# - Integrated with Rails authentication (cookies, sessions)
|
||||
# - WebSocket native (no long-polling fallback needed with modern browsers)
|
||||
#
|
||||
# Channel naming: accepts both Faye format and underscore format:
|
||||
# /user/123 or /user_123 → user_123
|
||||
# /supplier/456 or /supplier_456 → supplier_456
|
||||
#
|
||||
# To use:
|
||||
# Set Mozo.broadcaster = Mozo::Broadcaster::ActionCable.new
|
||||
# in config/initializers/mozo_settings.rb
|
||||
#
|
||||
class ActionCable
|
||||
CHANNEL_PREFIX_REMAP = {
|
||||
%r{^/user[/_](.+)$} => 'user_\1',
|
||||
%r{^/supplier[/_](.+)$} => 'supplier_\1'
|
||||
}.freeze
|
||||
|
||||
def broadcast(message)
|
||||
channel = message[:channel] || message['channel']
|
||||
data = message[:data] || message['data']
|
||||
|
||||
remapped = remap_channel(channel)
|
||||
unless remapped
|
||||
Rails.logger.warn("[ACTION_CABLE] broadcast skipped: unknown channel #{channel}")
|
||||
return
|
||||
end
|
||||
|
||||
Rails.logger.debug("[ACTION_CABLE] broadcasting to #{remapped}: #{data.inspect}")
|
||||
::ActionCable.server.broadcast(remapped, data)
|
||||
rescue => e
|
||||
Rails.logger.error("[ACTION_CABLE][ERROR] #{e.message}")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def remap_channel(channel)
|
||||
CHANNEL_PREFIX_REMAP.each do |pattern, replacement|
|
||||
return channel.sub(pattern, replacement) if channel.match?(pattern)
|
||||
end
|
||||
Rails.logger.warn("[ACTION_CABLE] Unknown channel format: #{channel}")
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,8 @@
|
||||
module Mozo
|
||||
module Counter
|
||||
extend ActiveSupport::Autoload
|
||||
autoload :Redis
|
||||
|
||||
mattr_accessor :connection
|
||||
|
||||
# mainly for testing purposes
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Mozo
|
||||
module Counter
|
||||
# Redis-backed counter adapter. Replaces the DrbCounter (in-memory,
|
||||
# single-process, DRb-based) with a persistent, multi-process-safe,
|
||||
# battle-tested key-value store.
|
||||
#
|
||||
# Benefits over DrbCounter:
|
||||
# - Persistent (survives restarts, no CouchDB reload dance)
|
||||
# - Atomic INCR/DECR (no race conditions between Puma workers)
|
||||
# - No separate process to manage (Redis is already needed for ActionCable)
|
||||
# - Production-ready, widely deployed
|
||||
#
|
||||
# Usage:
|
||||
# Mozo::Counter.connection = Mozo::Counter::Redis.new
|
||||
# # or with custom config:
|
||||
# Mozo::Counter.connection = Mozo::Counter::Redis.new(url: "redis://localhost:6379/2")
|
||||
#
|
||||
class Redis
|
||||
def initialize(url: nil)
|
||||
require 'redis'
|
||||
@redis = ::Redis.new(url: url || ENV.fetch('REDIS_URL', 'redis://localhost:6379/7'))
|
||||
end
|
||||
|
||||
def get(key, options = {})
|
||||
value = @redis.get(key)
|
||||
quiet = options[:quiet]
|
||||
unless quiet
|
||||
Rails.logger.debug("[REDIS_COUNTER] GET #{key} = #{value.inspect}")
|
||||
end
|
||||
value&.to_i || 0
|
||||
rescue => e
|
||||
Rails.logger.error("[REDIS_COUNTER] GET #{key} failed: #{e.message}")
|
||||
0
|
||||
end
|
||||
|
||||
def set(key, value)
|
||||
Rails.logger.debug("[REDIS_COUNTER] SET #{key} = #{value}")
|
||||
@redis.set(key, value)
|
||||
rescue => e
|
||||
Rails.logger.error("[REDIS_COUNTER] SET #{key} failed: #{e.message}")
|
||||
value
|
||||
end
|
||||
|
||||
def incr(key, options = {})
|
||||
initial = options[:initial] || 1
|
||||
Rails.logger.debug("[REDIS_COUNTER] INCR #{key}")
|
||||
if @redis.exists?(key)
|
||||
@redis.incr(key)
|
||||
else
|
||||
@redis.set(key, initial)
|
||||
initial
|
||||
end
|
||||
rescue => e
|
||||
Rails.logger.error("[REDIS_COUNTER] INCR #{key} failed: #{e.message}")
|
||||
initial
|
||||
end
|
||||
|
||||
def decr(key, options = {})
|
||||
initial = options[:initial] || 0
|
||||
Rails.logger.debug("[REDIS_COUNTER] DECR #{key}")
|
||||
if @redis.exists?(key)
|
||||
@redis.decr(key)
|
||||
else
|
||||
@redis.set(key, initial)
|
||||
initial
|
||||
end
|
||||
rescue => e
|
||||
Rails.logger.error("[REDIS_COUNTER] DECR #{key} failed: #{e.message}")
|
||||
initial
|
||||
end
|
||||
|
||||
def flush
|
||||
Rails.logger.debug("[REDIS_COUNTER] FLUSHDB")
|
||||
@redis.flushdb
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,5 +4,10 @@ module Mozo
|
||||
require 'drb'
|
||||
DRbObject.new_with_uri('druby://localhost:9022')
|
||||
end
|
||||
|
||||
# propagation method. Might save some searching. Hint: drb_counter/drb_counter.rb
|
||||
def self.reload_stats!
|
||||
object.reload_stats!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user