"
4 | end
5 |
--------------------------------------------------------------------------------
/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | self.abstract_class = true
3 | end
4 |
--------------------------------------------------------------------------------
/app/models/concerns/archive_workflow.rb:
--------------------------------------------------------------------------------
1 | # issue with regular concerns https://github.com/geekq/workflow/issues/152
2 |
3 | module ArchiveWorkflow
4 |
5 | def self.included(base)
6 |
7 | base.scope :with_active_state, -> { where(workflow_state: :active) }
8 | base.scope :with_archived_state, -> { where(workflow_state: :archived) }
9 |
10 | base.workflow do
11 | state :active do
12 | event :archive, :transitions_to => :archived
13 | end
14 | state :archived do
15 | event :unarchive, :transitions_to => :active
16 | end
17 | after_transition do
18 |
19 | begin
20 | if archived?
21 | pg_search_document.destroy
22 | elsif active?
23 | update_pg_search_document
24 | end
25 | rescue
26 | end
27 |
28 | if respond_to?(:owner_id) and owner_id.present?
29 | User.unscoped.find(owner_id).update_all_device_ids!
30 | end
31 |
32 | end
33 | end
34 | end
35 |
36 | end
37 |
--------------------------------------------------------------------------------
/app/models/concerns/country_methods.rb:
--------------------------------------------------------------------------------
1 | module CountryMethods
2 | extend ActiveSupport::Concern
3 |
4 | included do
5 |
6 | def country_code
7 | super.try(:upcase)
8 | end
9 |
10 | end
11 |
12 | def country
13 | if country_code and country_code.match /\A\w{2}\z/
14 | ISO3166::Country[country_code]
15 | end
16 | end
17 |
18 | def country_name
19 | country ? country.to_s : nil
20 | end
21 |
22 | end
23 |
--------------------------------------------------------------------------------
/app/models/concerns/message_forwarding.rb:
--------------------------------------------------------------------------------
1 | module MessageForwarding
2 |
3 | extend ActiveSupport::Concern
4 |
5 | def forward_readings(device, readings)
6 | if device.forward_readings?
7 | MQTTForwardingJob.perform_later(device.id, readings: readings.map(&:stringify_keys))
8 | end
9 | end
10 |
11 | end
12 |
--------------------------------------------------------------------------------
/app/models/device_inventory.rb:
--------------------------------------------------------------------------------
1 | class DeviceInventory < ActiveRecord::Base
2 |
3 | self.table_name = 'devices_inventory'
4 |
5 | def report
6 | self[:report]
7 | end
8 |
9 | def report=(str)
10 | self[:report] = JSON.parse(str) rescue nil
11 | end
12 |
13 | end
14 |
--------------------------------------------------------------------------------
/app/models/devices_tag.rb:
--------------------------------------------------------------------------------
1 | # A Device can have many Tags, and a Tag can have many Devices.
2 |
3 | class DevicesTag < ActiveRecord::Base
4 | belongs_to :device
5 | belongs_to :tag
6 | end
7 |
--------------------------------------------------------------------------------
/app/models/ingest_error.rb:
--------------------------------------------------------------------------------
1 | class IngestError < ActiveRecord::Base
2 | belongs_to :device
3 | end
4 |
--------------------------------------------------------------------------------
/app/models/measurement.rb:
--------------------------------------------------------------------------------
1 | # Measurements are descriptions of what sensors do.
2 | class Measurement < ActiveRecord::Base
3 | has_many :sensors
4 | validates_presence_of :name, :description
5 | validates_uniqueness_of :name
6 |
7 | def for_sensor_json
8 | attributes.except(*%w{created_at updated_at})
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/app/models/orphan_device.rb:
--------------------------------------------------------------------------------
1 | class OrphanDevice < ActiveRecord::Base
2 | attr_readonly :onboarding_session, :device_token
3 |
4 | validates_uniqueness_of :device_token
5 | validates_uniqueness_of :onboarding_session
6 |
7 | validates_presence_of :device_token, allow_nil: false
8 | validates_presence_of :onboarding_session, allow_nil: false
9 |
10 | validates :exposure, inclusion: { in: %w(indoor outdoor) }, allow_nil: true
11 |
12 | after_initialize :generate_onbarding_session
13 |
14 | def device_attributes
15 | {
16 | name: name,
17 | description: description,
18 | user_tags: user_tags,
19 | exposure: exposure,
20 | latitude: latitude,
21 | longitude: longitude,
22 | device_token: device_token
23 | }
24 | end
25 |
26 | def generate_token!
27 | self.device_token = SecureRandom.alphanumeric(6).downcase
28 | end
29 |
30 | private
31 |
32 | def generate_onbarding_session
33 | self.onboarding_session = SecureRandom.uuid if new_record?
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/app/models/postprocessing.rb:
--------------------------------------------------------------------------------
1 | class Postprocessing < ApplicationRecord
2 | belongs_to :device
3 |
4 | def self.ransackable_attributes(auth_object = nil)
5 | ["blueprint_url", "created_at", "device_id", "forwarding_params", "hardware_url", "id", "latest_postprocessing", "meta", "updated_at"]
6 | end
7 |
8 | def self_ransackable_associations(auth_object = nil)
9 | []
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/app/models/sensor.rb:
--------------------------------------------------------------------------------
1 | # Every Device has one or more sensors.
2 |
3 | class Sensor < ActiveRecord::Base
4 |
5 | has_many :components
6 | has_many :devices, through: :components
7 |
8 | has_many :sensor_tags
9 | has_many :tag_sensors, through: :sensor_tags
10 |
11 | belongs_to :measurement, optional: :true
12 |
13 | attr_accessor :latest_reading
14 |
15 | has_ancestry
16 | validates_presence_of :name, :description#, :unit
17 |
18 | def self.ransackable_attributes(auth_object = nil)
19 | ["ancestry", "created_at", "description", "id", "measurement_id", "name", "unit", "updated_at", "uuid"]
20 | end
21 |
22 | def self.ransackable_associations(auth_object = nil)
23 | []
24 | end
25 |
26 | def tags
27 | tag_sensors.map(&:name)
28 | end
29 |
30 | def is_raw?
31 | tags&.include?("raw")
32 | end
33 | end
34 |
--------------------------------------------------------------------------------
/app/models/sensor_tag.rb:
--------------------------------------------------------------------------------
1 | class SensorTag < ActiveRecord::Base
2 | belongs_to :sensor
3 | belongs_to :tag_sensor
4 | end
5 |
--------------------------------------------------------------------------------
/app/models/tag.rb:
--------------------------------------------------------------------------------
1 | # Tags can be assigned to multiple Devices. They are currently managed solely by
2 | # admins, this is likely to change.
3 |
4 | class Tag < ActiveRecord::Base
5 | validates_uniqueness_of :name, case_sensitive: false
6 | validates_presence_of :name
7 | # validates_format_of :name, with: /\A[A-Za-z]+\z/
8 | has_many :devices_tags, dependent: :destroy
9 | has_many :devices, through: :devices_tags
10 |
11 | extend FriendlyId
12 | friendly_id :name
13 |
14 | def self.ransackable_attributes(auth_object = nil)
15 | ["created_at", "description", "id", "name", "updated_at", "uuid"]
16 | end
17 |
18 | def ransackable_associations(auth_object = nil)
19 | []
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/app/models/tag_sensor.rb:
--------------------------------------------------------------------------------
1 | class TagSensor < ActiveRecord::Base
2 | validates_presence_of :name
3 |
4 | has_many :sensor_tags
5 | has_many :sensors, through: :sensor_tags
6 | end
7 |
--------------------------------------------------------------------------------
/app/models/validators/datetime_validator.rb:
--------------------------------------------------------------------------------
1 | class DatetimeValidator < ActiveModel::EachValidator
2 | def validate_each(record, attribute, value)
3 | [:starts_at, :ends_at].each do |field|
4 | if value.is_a?(String)
5 | begin
6 | Date.iso8601(value)
7 | rescue Date::Error
8 | begin
9 | Time.iso8601(value)
10 | rescue ArgumentError
11 | record.errors.add(attribute, "is not a valid ISO8601 date or time")
12 | end
13 | end
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/app/policies/admin_policy.rb:
--------------------------------------------------------------------------------
1 | class AdminPolicy < ApplicationPolicy
2 |
3 | def show?
4 | true
5 | end
6 |
7 | def create?
8 | user.try(:is_admin?)
9 | end
10 |
11 | def destroy?
12 | create?
13 | end
14 |
15 | def update?
16 | create?
17 | end
18 |
19 | end
20 |
--------------------------------------------------------------------------------
/app/policies/application_policy.rb:
--------------------------------------------------------------------------------
1 | class ApplicationPolicy
2 | attr_reader :user, :record
3 |
4 | def initialize(user, record)
5 | @user = user
6 | @record = record
7 | end
8 |
9 | def index?
10 | false
11 | end
12 |
13 | def show?
14 | scope.where(:id => record.id).exists?
15 | end
16 |
17 | def create?
18 | false
19 | end
20 |
21 | def new?
22 | create?
23 | end
24 |
25 | def update?
26 | false
27 | end
28 |
29 | def edit?
30 | update?
31 | end
32 |
33 | def destroy?
34 | false
35 | end
36 |
37 | def scope
38 | Pundit.policy_scope!(user, record.class)
39 | end
40 |
41 | class Scope
42 | attr_reader :user, :scope
43 |
44 | def initialize(user, scope)
45 | @user = user
46 | @scope = scope
47 | end
48 |
49 | def resolve
50 | scope
51 | end
52 | end
53 | end
54 |
55 |
--------------------------------------------------------------------------------
/app/policies/component_policy.rb:
--------------------------------------------------------------------------------
1 | class ComponentPolicy < AdminPolicy
2 | end
3 |
--------------------------------------------------------------------------------
/app/policies/experiment_policy.rb:
--------------------------------------------------------------------------------
1 | class ExperimentPolicy < ApplicationPolicy
2 | def update?
3 | user.try(:is_admin?) || user == record.owner
4 | end
5 |
6 | def create?
7 | user
8 | end
9 |
10 | def destroy?
11 | update?
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/policies/measurement_policy.rb:
--------------------------------------------------------------------------------
1 | class MeasurementPolicy < AdminPolicy
2 | end
3 |
--------------------------------------------------------------------------------
/app/policies/oauth_application_policy.rb:
--------------------------------------------------------------------------------
1 | class OauthApplicationPolicy < ApplicationPolicy
2 |
3 | def show?
4 | true
5 | end
6 |
7 | def update?
8 | user.try(:is_admin?) || user == record.owner
9 | end
10 |
11 | def create?
12 | user
13 | end
14 |
15 | def destroy?
16 | update?
17 | end
18 |
19 | end
20 |
--------------------------------------------------------------------------------
/app/policies/password_reset_policy.rb:
--------------------------------------------------------------------------------
1 | class PasswordResetPolicy < ApplicationPolicy
2 |
3 | def show?
4 | true
5 | end
6 |
7 | def create?
8 | user
9 | end
10 |
11 | def update?
12 | user == record
13 | end
14 |
15 | end
16 |
--------------------------------------------------------------------------------
/app/policies/sensor_policy.rb:
--------------------------------------------------------------------------------
1 | class SensorPolicy < AdminPolicy
2 | end
3 |
--------------------------------------------------------------------------------
/app/policies/session_policy.rb:
--------------------------------------------------------------------------------
1 | class SessionPolicy < ApplicationPolicy
2 |
3 | def create?
4 | true
5 | end
6 |
7 | end
8 |
--------------------------------------------------------------------------------
/app/policies/tag_policy.rb:
--------------------------------------------------------------------------------
1 | class TagPolicy < AdminPolicy
2 | end
3 |
--------------------------------------------------------------------------------
/app/policies/tag_sensor_policy.rb:
--------------------------------------------------------------------------------
1 | class TagSensorPolicy < AdminPolicy
2 | end
3 |
--------------------------------------------------------------------------------
/app/policies/user_policy.rb:
--------------------------------------------------------------------------------
1 | class UserPolicy < ApplicationPolicy
2 |
3 | def show?
4 | true
5 | end
6 |
7 | def create?
8 | !user || user.is_admin?
9 | end
10 |
11 | def update?
12 | user.try(:is_admin?) || user == record
13 | end
14 |
15 | def destroy?
16 | user == record
17 | end
18 |
19 | def request_password_reset?
20 | create?
21 | end
22 |
23 | def update_password?
24 | create?
25 | end
26 |
27 | def show_private_info?
28 | update?
29 | end
30 |
31 | def show_secrets?
32 | user.try(:is_admin?) || user == record
33 | end
34 |
35 | def register_device?
36 | user == record
37 | end
38 |
39 | def create_experiment?
40 | user == record
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/app/views/layouts/_breadcrumbs.html.erb:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/app/views/layouts/_doorbell_embed.html.erb:
--------------------------------------------------------------------------------
1 |
14 |
15 |
--------------------------------------------------------------------------------
/app/views/layouts/_flashes.html.erb:
--------------------------------------------------------------------------------
1 | <% flash.each do |name, msg| %>
2 | ">
3 |
4 | <%= msg %>
5 |
6 |
7 |
8 | <% end %>
9 |
--------------------------------------------------------------------------------
/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= [t(:title), @title].compact.join(" – ") %>
5 |
6 |
7 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
8 | <%= javascript_pack_tag 'application', 'data-turbolinks-track' => true %>
9 | <%= csrf_meta_tags %>
10 |
11 |
12 |
13 | <%= render partial: "layouts/nav" %>
14 | <%= render partial: "layouts/flashes" %>
15 |
16 | <%= content_for?(:container) ? yield(:container) : yield %>
17 |
18 | <%= render partial: "layouts/footer" %>
19 | <%= render partial: "layouts/doorbell_embed" %>
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/views/ui/devices/_actions.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= sc_nav_button_to(t(:show_device_on_map_cta), "https://smartcitizen.me/kits/#{device.id}", local_assigns.merge(external: true)) %>
3 | <% if authorize? device, :update? %>
4 | <%= sc_nav_button_to(t(:show_device_edit_cta, name: device.name), edit_ui_device_path(device, goto: request.path), local_assigns) %>
5 | <% end %>
6 | <% if authorize? device, :upload? %>
7 | <%= sc_nav_button_to(t(:show_device_upload_cta), upload_ui_device_path(device, goto: request.path), local_assigns) %>
8 | <% end %>
9 | <% if authorize? device, :download? %>
10 | <%= sc_nav_button_to(t(:show_device_download_cta), download_ui_device_path(device, goto: request.path), local_assigns) %>
11 | <% end %>
12 |
13 |
--------------------------------------------------------------------------------
/app/views/ui/devices/_device.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= link_to(ui_device_path(device.id), class: "listing-link z-1") do %>
3 |
<%= device.name %>
4 | <% end %>
5 |
6 | <% if device.latitude && device.longitude %>
7 | <%= render partial: "ui/shared/map", locals: { points: [{lat: device.latitude, lng: device.longitude}] } %>
8 | <% end %>
9 |
10 |
11 |
<%= device.name %>
12 | <%= render partial: "ui/devices/meta", locals: { device: device, hide_owner: local_assigns[:hide_owner] } %>
13 | <% if local_assigns[:with_actions] %>
14 |
15 | <%= render partial: "ui/devices/actions", locals: { device: device } %>
16 |
17 | <% end %>
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/views/ui/devices/_profile_header.html.erb:
--------------------------------------------------------------------------------
1 | <% content_for :profile_header_image do %>
2 |
3 | <%= render partial: "ui/shared/map", locals: { points: [{lat: device.latitude, lng: device.longitude}] } %>
4 |
5 | <% end %>
6 | <% content_for :profile_header_meta do %>
7 | <%= render partial: "ui/devices/meta", locals: { device: device, hide_last_reading_at: true } %>
8 | <% end %>
9 | <% content_for :profile_header_actions do %>
10 | <%= render partial: "ui/devices/actions", locals: { device: device, dark_buttons: true } %>
11 | <% end %>
12 | <%= render partial: "ui/shared/profile_header", locals: { title: t(:show_device_headline, name: device.name), title_link: ui_device_path(device.id) } %>
13 |
--------------------------------------------------------------------------------
/app/views/ui/devices/delete.html.erb:
--------------------------------------------------------------------------------
1 | <%= render partial: "ui/devices/profile_header", locals: { device: @device } %>
2 | <%= render layout: "ui/shared/form_container", locals: { title: t(:delete_device_header) } do %>
3 | <%= bootstrap_form_tag url: ui_device_path(@device.id), method: :delete do |f| %>
4 | <%= t(:delete_device_warning_html, name: @device.name) %>
5 | <%= f.text_field :name, label: t(:delete_device_name_label) %>
6 |
7 | <%= f.primary t(:delete_device_submit), class: "btn btn-danger w-100 w-md-auto" %>
8 |
9 | <% end %>
10 | <% end %>
11 |
--------------------------------------------------------------------------------
/app/views/ui/devices/download.html.erb:
--------------------------------------------------------------------------------
1 | <%= render partial: "ui/devices/profile_header", locals: { device: @device } %>
2 | <%= render layout: "ui/shared/form_container", locals: { title: t(:download_device_header) } do %>
3 | <% if @device.csv_export_requested_recently? %>
4 | <%= t(:download_device_recently_requested_blurb) %>
5 |
6 | <%= link_to(t(:download_device_back), ui_device_path(@device.id), class: "btn btn-secondary w-100 w-md-auto") %>
7 |
8 | <% else %>
9 | <%= bootstrap_form_tag url: download_ui_device_path(@device.id), method: :post do |f| %>
10 | <%= hidden_field_tag :goto, params[:goto] %>
11 | <%= t(:download_device_confirmation_blurb) %>
12 |
13 | <%= f.primary t(:download_device_submit), class: "btn btn-primary w-100 w-md-auto" %>
14 |
15 | <% end %>
16 | <% end %>
17 | <% end %>
18 |
19 |
--------------------------------------------------------------------------------
/app/views/ui/devices/edit.html.erb:
--------------------------------------------------------------------------------
1 | <%= render partial: "ui/devices/profile_header", locals: { device: @device } %>
2 | <%= render layout: "ui/shared/form_container", locals: { title: t(:edit_device_header) } do %>
3 | <%= bootstrap_form_for @device, url: ui_device_path(@device) do |form| %>
4 | <%= hidden_field_tag :goto, params[:goto] %>
5 | <%= render partial: "fields", locals: { device: @device, form: form } %>
6 | <%= render partial: "ui/shared/form_buttons", locals: { form: form, back_href: ui_device_path(@device.id), submit_label: t(:edit_device_submit) } %>
7 | <% end %>
8 | <% if authorize? @device, :destroy? %>
9 | <%= render layout: "ui/shared/danger_zone" do %>
10 | <%= link_to t(:edit_device_delete_device_submit), delete_ui_device_path(@device.id), class: "btn btn-danger w-100 w-md-25 justify-content-center" %>
11 | <% end %>
12 | <% end %>
13 | <% end %>
14 |
--------------------------------------------------------------------------------
/app/views/ui/devices/new.html.erb:
--------------------------------------------------------------------------------
1 | <%= render partial: "ui/users/header", locals: { user: current_user } %>
2 | <%= render layout: "ui/shared/form_container" do %>
3 | <%= bootstrap_form_for @device, url: ui_devices_path do |form| %>
4 | <%= render partial: "fields", locals: { device: @device, form: form, include_hardware_info: true } %>
5 | <%= render partial: "ui/shared/form_buttons", locals: { form: form, back_href: register_ui_devices_path, submit_label: t(:new_device_submit) } %>
6 | <% end %>
7 | <% end %>
8 |
--------------------------------------------------------------------------------
/app/views/ui/devices/upload.html.erb:
--------------------------------------------------------------------------------
1 | <%= render partial: "ui/devices/profile_header", locals: { device: @device } %>
2 | <%= render layout: "ui/shared/form_container", locals: { title: t(:upload_device_header) } do %>
3 | <%= bootstrap_form_tag url: upload_ui_device_path(@device.id), method: :post, multipart: true do |f| %>
4 | <%= hidden_field_tag :goto, params[:goto] %>
5 | <%= t(:upload_device_blurb) %>
6 | <%= f.file_field :data_files, multiple: true, label: t(:upload_device_data_files_label), help: t(:upload_device_data_files_help), name: "data_files[]" %>
7 |
8 | <%= f.primary t(:upload_device_submit), class: "btn btn-primary w-100 w-md-auto" %>
9 |
10 | <% end %>
11 | <% end %>
12 |
13 |
--------------------------------------------------------------------------------
/app/views/ui/experiments/_actions.html.erb:
--------------------------------------------------------------------------------
1 | <% button_class = local_assigns[:dark_buttons] ? "btn-dark" : "btn-secondary" %>
2 |
3 | <% if authorize? experiment, :update? %>
4 | <%= sc_nav_button_to(t(:show_experiment_edit_cta, name: experiment.name), edit_ui_experiment_path(experiment, goto: request.path), local_assigns) %>
5 | <% end %>
6 |
7 |
--------------------------------------------------------------------------------
/app/views/ui/experiments/_devices_collection.html.erb:
--------------------------------------------------------------------------------
1 | <%= render layout: "ui/shared/box", locals: { no_padding: true } do %>
2 |
3 |
<%= t :experiment_devices_collection_heading %>
4 | <% if devices.length < 1 %>
5 |
<%= t :experiment_devices_collection_no_devices_message %>
6 | <% end %>
7 |
8 | ">
9 | <%= render partial: "ui/devices/device", collection: devices.by_last_reading, locals: { with_actions: true } %>
10 |
11 | <% if devices.total_pages > 1 %>
12 |
17 | <% end %>
18 | <% end %>
19 |
--------------------------------------------------------------------------------
/app/views/ui/experiments/_header.html.erb:
--------------------------------------------------------------------------------
1 | <% content_for :profile_header_image do %>
2 |
3 | <%= render partial: "ui/shared/map",
4 | locals: {
5 | points: experiment.devices.map { |d| { lat: d.latitude, lng: d.longitude } },
6 | class: "experiment",
7 | marker_url: asset_url("map-pin-experiment.svg")
8 | }
9 | %>
10 |
11 | <% end %>
12 | <% content_for :profile_header_meta do %>
13 | <%= render partial: "ui/experiments/meta", locals: { experiment: experiment } %>
14 | <% end %>
15 | <% content_for :profile_header_actions do %>
16 | <%= render partial: "ui/experiments/actions", locals: { experiment: experiment, dark_buttons: true} %>
17 | <% end %>
18 | <%= render partial: "ui/shared/profile_header", locals: { title: t(:show_experiment_title, name: experiment.name), title_link: ui_experiment_path(experiment.id) } %>
19 |
--------------------------------------------------------------------------------
/app/views/ui/experiments/_meta.html.erb:
--------------------------------------------------------------------------------
1 | ">
2 | <%= show_svg("status_icon.svg") %>
3 | <%= pluralize(experiment.devices.length, t(:experiment_meta_device_noun)) %> (<%= experiment.online_device_count %> <%= t(:experiment_meta_online_adjective) %>)
4 |
5 | <% unless local_assigns[:hide_owner] %>
6 |
7 | <%= show_svg("user_details_icon_light.svg") %>
8 | <%= link_to(experiment.owner.username, ui_user_path(experiment.owner.username), class: "d-inline-block position-relative subtle-link z-3") %>
9 |
10 | <% end %>
11 |
12 | <%= show_svg("clock-fill.svg") %>
13 | <% if experiment.last_reading_at %>
14 | <%= t :device_meta_last_reading_at, time: time_ago_in_words(experiment.last_reading_at) %>
15 | <% else %>
16 | <%= t :device_meta_no_readings_message %>
17 | <% end %>
18 |
19 |
--------------------------------------------------------------------------------
/app/views/ui/experiments/delete.html.erb:
--------------------------------------------------------------------------------
1 | <%= render partial: "ui/experiments/header", locals: { experiment: @experiment } %>
2 | <%= render layout: "ui/shared/form_container", locals: { title: t(:delete_experiment_header), box_inner_class: "drop-shadow-teal" } do %>
3 | <%= bootstrap_form_tag url: ui_experiment_path(@experiment.id), method: :delete do |f| %>
4 | <%= t(:delete_experiment_warning_html, name: @experiment.name) %>
5 | <%= f.text_field :name, label: t(:delete_experiment_name_label) %>
6 |
7 | <%= f.primary t(:delete_experiment_submit), class: "btn btn-danger w-100 w-md-auto" %>
8 |
9 | <% end %>
10 | <% end %>
11 |
--------------------------------------------------------------------------------
/app/views/ui/experiments/new.html.erb:
--------------------------------------------------------------------------------
1 | <%= render partial: "ui/users/header", locals: { user: current_user } %>
2 | <%= render layout: "ui/shared/form_container" do %>
3 | <%= t(:new_experiment_blurb) %>
4 | <%= bootstrap_form_for @experiment, url: ui_experiments_path do |form| %>
5 | <%= render partial: "fields", locals: { experiment: @experiment, form: form, include_hardware_info: true } %>
6 | <%= render partial: "ui/shared/form_buttons", locals: { form: form, back_href: ui_user_path(current_user.username), submit_label: t(:new_experiment_submit) } %>
7 | <% end %>
8 | <% end %>
9 |
--------------------------------------------------------------------------------
/app/views/ui/sessions/password_reset_landing.html.erb:
--------------------------------------------------------------------------------
1 | <%= render layout: "ui/shared/form_container" do %>
2 | <%= bootstrap_form_tag url: ui_change_password_path do |f| %>
3 | <%= f.hidden_field :token, value: @token %>
4 | <%= f.password_field :password %>
5 | <%= f.password_field :password_confirmation, label: t(:users_password_reset_landing_confirmation_label) %>
6 |
7 | <%= render partial: "ui/shared/form_buttons", locals: {form: f, submit_label: t(:users_password_reset_landing_submit), submit_name: "change_password" } %>
8 | <% end %>
9 | <% end %>
10 |
--------------------------------------------------------------------------------
/app/views/ui/shared/_box.html.erb:
--------------------------------------------------------------------------------
1 | <%= local_assigns[:class] || "" %>">
2 |
<%= "p-4 pb-md-5" unless local_assigns[:no_padding] %> <%= local_assigns[:inner_class] || "" %>">
3 | <%= yield %>
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/views/ui/shared/_container.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= yield %>
3 |
4 |
--------------------------------------------------------------------------------
/app/views/ui/shared/_copyable_input.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/app/views/ui/shared/_danger_zone.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
<%= t(:danger_zone_subhead) %>
3 | <%= yield %>
4 |
5 |
--------------------------------------------------------------------------------
/app/views/ui/shared/_extra_info.html.erb:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/app/views/ui/shared/_form_buttons.html.erb:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/app/views/ui/shared/_form_container.html.erb:
--------------------------------------------------------------------------------
1 | <%= render layout: "ui/shared/container" do %>
2 | <%= render layout: "ui/shared/box", locals: { inner_class: local_assigns[:box_inner_class] } do %>
3 | <%= render partial: "ui/shared/title", locals: { title: local_assigns[:title] } %>
4 | <%= yield %>
5 | <% end %>
6 | <% end %>
7 |
--------------------------------------------------------------------------------
/app/views/ui/shared/_map.html.erb:
--------------------------------------------------------------------------------
1 | " data-marker-shadow-url="<%= asset_url("map-pin-shadow.png") %>">
2 |
--------------------------------------------------------------------------------
/app/views/ui/shared/_map_location_picker.html.erb:
--------------------------------------------------------------------------------
1 | " data-marker-shadow-url="<%= asset_url("map-pin-shadow.png") %>">
2 |
--------------------------------------------------------------------------------
/app/views/ui/shared/_title.html.erb:
--------------------------------------------------------------------------------
1 | <% if local_assigns[:title] || @title %>
2 |
3 | <%= local_assigns[:title] || @title %>
4 |
5 | <% end %>
6 |
--------------------------------------------------------------------------------
/app/views/ui/shared/_two_column.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= yield :two_column_sidebar %>
5 |
6 |
7 | <%= yield :two_column_main %>
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/views/ui/users/_actions.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <% if authorize? user, :update? %>
3 | <%= sc_nav_button_to(t(:show_user_edit_cta, owner: possessive(user, current_user)), edit_ui_user_path(user), local_assigns) %>
4 | <% end %>
5 | <% if authorize? user, :show_secrets? %>
6 | <%= sc_nav_button_to(t(:show_user_secrets_cta, owner: possessive(user, current_user)), secrets_ui_user_path(user), local_assigns) %>
7 | <% end %>
8 | <% if authorize? user, :register_device? %>
9 | <%= sc_nav_button_to(t(:show_user_register_cta), register_ui_devices_path, local_assigns) %>
10 | <% end %>
11 | <% if authorize? user, :create_experiment? %>
12 | <%= sc_nav_button_to(t(:show_user_new_experiment_cta), new_ui_experiment_path, local_assigns) %>
13 | <% end %>
14 | <% if current_user == user %>
15 | <%= sc_nav_button_to(t(:show_user_log_out_cta), logout_path, local_assigns.merge(class: "hover-danger")) %>
16 | <% end %>
17 |
18 |
--------------------------------------------------------------------------------
/app/views/ui/users/_header.html.erb:
--------------------------------------------------------------------------------
1 | <% content_for :profile_header_image do %>
2 | <%= render partial: "ui/users/profile_image", locals: { user: user } %>
3 | <% end %>
4 | <% content_for :profile_header_meta do %>
5 | <%= render partial: "ui/users/meta", locals: { user: user } %>
6 | <% end %>
7 | <% content_for :profile_header_actions do %>
8 | <%= render partial: "ui/users/actions", locals: { user: user, dark_buttons: true } %>
9 | <% end %>
10 | <%= render partial: "ui/shared/profile_header", locals: { title: t(:show_user_headline, username: user.username), title_link: ui_user_path(user.username) } %>
11 |
12 |
--------------------------------------------------------------------------------
/app/views/ui/users/_meta.html.erb:
--------------------------------------------------------------------------------
1 | <% if user.city || user.country %>
2 |
3 | <%= show_svg("location_icon.svg") %>
4 | <%= [user.city, user.country].join(", ") %>
5 |
6 | <% end %>
7 | <% if user.url %>
8 |
9 | <%= image_tag("url_icon_light.svg", class: "pe-1") %>
10 | <%= link_to(user.url, user.url, class: "link-white") %>
11 |
12 | <% end %>
13 |
--------------------------------------------------------------------------------
/app/views/ui/users/_no_device_cta.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= t :no_device_cta_heading %>
3 | <%= t :no_device_cta_blurb %>
4 |
9 |
--------------------------------------------------------------------------------
/app/views/ui/users/_no_experiment_cta.html.erb:
--------------------------------------------------------------------------------
1 | <%= t :no_experiment_cta_heading %>
2 | <%= t :no_experiment_cta_blurb %>
3 |
6 |
--------------------------------------------------------------------------------
/app/views/ui/users/_page_nav.html.erb:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/app/views/ui/users/_profile_image.html.erb:
--------------------------------------------------------------------------------
1 | ">
2 | <%= image_tag(
3 | user.profile_picture.present? ? user.profile_picture : "default_avatar.svg",
4 | alt: local_assigns[:alt] || t(:profile_image_alt, username: user.username),
5 | class: "w-100"
6 | ) %>
7 |
8 |
--------------------------------------------------------------------------------
/app/views/ui/users/delete.html.erb:
--------------------------------------------------------------------------------
1 | <%= render partial: "ui/users/header", locals: { user: @user } %>
2 | <%= render layout: "ui/shared/form_container" do %>
3 | <%= bootstrap_form_tag url: ui_user_path(@user.id), method: :delete do |f| %>
4 | <%= t(:delete_user_warning_html, username: current_user.username) %>
5 | <%= f.text_field :username, label: t(:delete_user_username_label, owner: possessive(@user, current_user)) %>
6 |
7 | <%= f.primary t(:delete_user_submit, owner: possessive(@user, current_user, first_person: true)), class: "btn btn-danger w-100 w-md-auto" %>
8 |
9 | <% end %>
10 | <% end %>
11 |
--------------------------------------------------------------------------------
/app/views/ui/users/new.html.erb:
--------------------------------------------------------------------------------
1 | <%= render layout: "ui/shared/form_container" do %>
2 | <%= bootstrap_form_for @user, url: ui_users_path do |f| %>
3 | <%= hidden_field_tag :goto, params[:goto] %>
4 | <%= f.text_field :email %>
5 | <%= f.text_field :username %>
6 | <%= f.password_field :password %>
7 | <%= f.password_field :password_confirmation %>
8 |
9 | <%= f.check_box :ts_and_cs, label: t(:new_user_ts_and_cs_label_html) %>
10 |
11 | <%= render partial: "ui/shared/form_buttons", locals: {form: f, back_href: new_ui_session_path, back_label: t(:new_user_login_link), submit_label: t(:new_user_submit) } %>
12 | <% end %>
13 | <% end %>
14 |
--------------------------------------------------------------------------------
/app/views/ui/users/post_delete.html.erb:
--------------------------------------------------------------------------------
1 | <%= render layout: "ui/shared/form_container" do %>
2 | <%= t(:post_delete_user_blurb_html)%>
3 |
6 | <% end %>
7 |
--------------------------------------------------------------------------------
/app/views/ui/users/show.html.erb:
--------------------------------------------------------------------------------
1 | <%= render partial: "ui/users/header", locals: { user: @user } %>
2 | <% content_for :two_column_sidebar do %>
3 | <%= render partial: "ui/users/page_nav" %>
4 | <% end %>
5 | <% content_for :two_column_main do %>
6 |
7 | <%= render partial: "ui/users/devices_collection", locals: { user: @user, devices: @devices } %>
8 | <%= render partial: "ui/users/experiments_collection", locals: { user: @user, experiments: @experiments } %>
9 | <% end %>
10 | <%= render partial: "ui/shared/two_column" %>
11 |
--------------------------------------------------------------------------------
/app/views/v0/components/_component.jbuilder:
--------------------------------------------------------------------------------
1 | json.(component, :id, :uuid, :device_id, :sensor_id, :created_at, :updated_at)
2 |
--------------------------------------------------------------------------------
/app/views/v0/components/index.jbuilder:
--------------------------------------------------------------------------------
1 | json.array! @components, partial: 'component', as: :component
2 |
--------------------------------------------------------------------------------
/app/views/v0/components/show.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! "component", component: @component
2 |
--------------------------------------------------------------------------------
/app/views/v0/devices/_world_map_device.jbuilder:
--------------------------------------------------------------------------------
1 | json.(
2 | device,
3 | :id,
4 | :name,
5 | :description,
6 | :state,
7 | :system_tags,
8 | :user_tags,
9 | :last_reading_at,
10 | )
11 |
12 | json.merge!(location: device.formatted_location(true))
13 | json.merge!(hardware: device.hardware(false))
14 |
15 |
--------------------------------------------------------------------------------
/app/views/v0/devices/_world_map_list.jbuilder:
--------------------------------------------------------------------------------
1 | json.array! Device.for_world_map(never_authorized ? nil : current_user), partial: 'world_map_device', as: :device
2 |
--------------------------------------------------------------------------------
/app/views/v0/devices/fresh_world_map.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! 'devices/world_map_list', { never_authorized: false }
--------------------------------------------------------------------------------
/app/views/v0/devices/index.jbuilder:
--------------------------------------------------------------------------------
1 | json.array! @devices, partial: 'device', as: :device
--------------------------------------------------------------------------------
/app/views/v0/devices/show.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! "device", device: @device
2 |
--------------------------------------------------------------------------------
/app/views/v0/devices/world_map.jbuilder:
--------------------------------------------------------------------------------
1 | json.cache! ["world_map"], expires_in: 1.minute do
2 | json.partial! 'devices/world_map_list', { never_authorized: true}
3 | end
--------------------------------------------------------------------------------
/app/views/v0/experiments/_experiment.jbuilder:
--------------------------------------------------------------------------------
1 | json.(experiment,
2 | :id, :name, :description, :owner_id, :active, :is_test, :starts_at, :ends_at, :device_ids, :created_at, :updated_at
3 | )
4 |
--------------------------------------------------------------------------------
/app/views/v0/experiments/index.jbuilder:
--------------------------------------------------------------------------------
1 | json.array! @experiments, partial: 'experiment', as: :experiment
2 |
--------------------------------------------------------------------------------
/app/views/v0/experiments/show.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! "experiment", experiment: @experiment
2 |
--------------------------------------------------------------------------------
/app/views/v0/measurements/_measurement.jbuilder:
--------------------------------------------------------------------------------
1 | json.(measurement,
2 | :id, :uuid, :name, :description, :definition, :created_at, :updated_at
3 | # :is_childless?,
4 | )
5 |
6 | # json . sensor do
7 | # json.id sensor.id
8 | # json.parent_id sensor.parent_id
9 | # json.name sensor.name
10 | # json.description sensor.description
11 | # json.unit sensor.unit
12 | # json.created_at sensor.created_at.utc.iso8601
13 | # json.updated_at sensor.updated_at.utc.iso8601
14 | # end
15 |
--------------------------------------------------------------------------------
/app/views/v0/measurements/index.jbuilder:
--------------------------------------------------------------------------------
1 | json.array! @measurements, partial: 'measurement', as: :measurement
2 |
--------------------------------------------------------------------------------
/app/views/v0/measurements/show.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! "measurement", measurement: @measurement
2 |
--------------------------------------------------------------------------------
/app/views/v0/oauth_applications/_oauth_application.jbuilder:
--------------------------------------------------------------------------------
1 | json.(oauth_application, :id, :name, :uid, :secret, :redirect_uri, :scopes, :created_at, :updated_at)
2 |
--------------------------------------------------------------------------------
/app/views/v0/oauth_applications/index.jbuilder:
--------------------------------------------------------------------------------
1 | json.array! @oauth_applications, partial: 'oauth_application', as: :oauth_application
2 |
--------------------------------------------------------------------------------
/app/views/v0/oauth_applications/show.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! "oauth_application", oauth_application: @oauth_application
2 |
--------------------------------------------------------------------------------
/app/views/v0/sensors/_sensor.jbuilder:
--------------------------------------------------------------------------------
1 | json.(sensor,
2 | :id, :uuid, :parent_id, :name, :description, :unit, :tags, :datasheet, :unit_definition, :created_at, :updated_at
3 | # :is_childless?,
4 | )
5 |
6 | if sensor.measurement
7 | json.measurement(
8 | sensor.measurement, :id, :uuid, :name, :description, :definition
9 | )
10 | else
11 | json.merge! measurement: nil
12 | end
13 |
14 | # json . sensor do
15 | # json.id sensor.id
16 | # json.parent_id sensor.parent_id
17 | # json.name sensor.name
18 | # json.description sensor.description
19 | # json.unit sensor.unit
20 | # json.created_at sensor.created_at.utc.iso8601
21 | # json.updated_at sensor.updated_at.utc.iso8601
22 | # end
23 |
--------------------------------------------------------------------------------
/app/views/v0/sensors/index.jbuilder:
--------------------------------------------------------------------------------
1 | json.array! @sensors, partial: 'sensor', as: :sensor
2 |
--------------------------------------------------------------------------------
/app/views/v0/sensors/show.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! "sensor", sensor: @sensor
2 |
--------------------------------------------------------------------------------
/app/views/v0/tag_sensors/index.jbuilder:
--------------------------------------------------------------------------------
1 | json.array! @tags
2 |
--------------------------------------------------------------------------------
/app/views/v0/tag_sensors/show.jbuilder:
--------------------------------------------------------------------------------
1 | #json.(@tag, :id, :name, :description)
2 | json.merge! @tag.attributes
3 |
--------------------------------------------------------------------------------
/app/views/v0/tags/_tag.jbuilder:
--------------------------------------------------------------------------------
1 | json.(tag,
2 | :id, :uuid, :name, :description, :created_at, :updated_at
3 | )
4 |
--------------------------------------------------------------------------------
/app/views/v0/tags/index.jbuilder:
--------------------------------------------------------------------------------
1 | json.array! @tags, partial: 'tag', as: :tag
2 |
--------------------------------------------------------------------------------
/app/views/v0/tags/show.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! "tag", tag: @tag
2 |
--------------------------------------------------------------------------------
/app/views/v0/users/_user.jbuilder:
--------------------------------------------------------------------------------
1 | json.(user,
2 | :id,
3 | :uuid,
4 | :role,
5 | :username,
6 | :profile_picture,
7 | :url,
8 | :location,
9 | :updated_at
10 | )
11 |
12 | json.profile_picture profile_picture_url(user)
13 |
14 | authorized = current_user && current_user == user || current_user&.is_admin?
15 |
16 | if authorized
17 | json.merge! email: user.email
18 | json.merge! legacy_api_key: user.legacy_api_key
19 | json.merge! forwarding_token: user.forwarding_token
20 | json.merge! forwarding_username: user.forwarding_username
21 | else
22 | json.merge! email: '[FILTERED]'
23 | json.merge! legacy_api_key: '[FILTERED]'
24 | json.merge! forwarding_token: '[FILTERED]'
25 | json.merge! forwarding_username: '[FILTERED]'
26 | end
27 |
28 | json.devices user.devices.filter { |d|
29 | !d.is_private? || authorized
30 | }.map do |device|
31 | json.partial! "devices/device", device: device, with_data: false, with_owner: false, with_location: authorized
32 | end
33 |
--------------------------------------------------------------------------------
/app/views/v0/users/index.jbuilder:
--------------------------------------------------------------------------------
1 | json.array! @users, partial: 'users/user', as: :user
2 |
--------------------------------------------------------------------------------
/app/views/v0/users/show.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! "users/user", user: @user
2 |
--------------------------------------------------------------------------------
/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path('../config/application', __dir__)
3 | require_relative '../config/boot'
4 | require 'rails/commands'
5 |
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative '../config/boot'
3 | require 'rake'
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/bin/rspec:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | require 'bundler/setup'
8 | load Gem.bin_path('rspec-core', 'rspec')
9 |
--------------------------------------------------------------------------------
/bin/spring:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # This file loads spring without using Bundler, in order to be fast.
4 | # It gets overwritten when you run the `spring binstub` command.
5 |
6 | unless defined?(Spring)
7 | require 'rubygems'
8 | require 'bundler'
9 |
10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read)
11 | spring = lockfile.specs.detect { |spec| spec.name == "spring" }
12 | if spring
13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
14 | gem 'spring', spring.version
15 | require 'spring/binstub'
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/bin/update:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'fileutils'
3 | include FileUtils
4 |
5 | # path to your application root.
6 | APP_ROOT = File.expand_path('..', __dir__)
7 |
8 | def system!(*args)
9 | system(*args) || abort("\n== Command #{args} failed ==")
10 | end
11 |
12 | chdir APP_ROOT do
13 | # This script is a way to update your development environment automatically.
14 | # Add necessary update steps to this file.
15 |
16 | puts '== Installing dependencies =='
17 | system! 'gem install bundler --conservative'
18 | system('bundle check') || system!('bundle install')
19 |
20 | puts "\n== Updating database =="
21 | system! 'bin/rails db:migrate'
22 |
23 | puts "\n== Removing old logs and tempfiles =="
24 | system! 'bin/rails log:clear tmp:clear'
25 |
26 | puts "\n== Restarting application server =="
27 | system! 'bin/rails restart'
28 | end
29 |
--------------------------------------------------------------------------------
/bin/webpack:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
4 | ENV["NODE_ENV"] ||= "development"
5 |
6 | require "pathname"
7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
8 | Pathname.new(__FILE__).realpath)
9 |
10 | require "bundler/setup"
11 |
12 | require "webpacker"
13 | require "webpacker/webpack_runner"
14 |
15 | APP_ROOT = File.expand_path("..", __dir__)
16 | Dir.chdir(APP_ROOT) do
17 | Webpacker::WebpackRunner.run(ARGV)
18 | end
19 |
--------------------------------------------------------------------------------
/bin/webpack-dev-server:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
4 | ENV["NODE_ENV"] ||= "development"
5 |
6 | require "pathname"
7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
8 | Pathname.new(__FILE__).realpath)
9 |
10 | require "bundler/setup"
11 |
12 | require "webpacker"
13 | require "webpacker/dev_server_runner"
14 |
15 | APP_ROOT = File.expand_path("..", __dir__)
16 | Dir.chdir(APP_ROOT) do
17 | Webpacker::DevServerRunner.run(ARGV)
18 | end
19 |
--------------------------------------------------------------------------------
/ci.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -ae
3 | git config --global --add safe.directory /app
4 | source $NVM_DIR/nvm.sh
5 | nvm use default
6 | yarn install
7 | NODE_OPTIONS=--openssl-legacy-provider bundle exec bin/rake assets:precompile
8 | bundle exec bin/rake db:create
9 | bundle exec bin/rake db:schema:load
10 | unset DATABASE_URL
11 | RAILS_ENV=test bundle exec bin/rake db:create
12 | RAILS_ENV=test bundle exec bin/rake db:schema:load
13 | bundle exec bin/rake spec
14 |
--------------------------------------------------------------------------------
/compose.override.local.yml:
--------------------------------------------------------------------------------
1 | services:
2 | app:
3 | restart: "no"
4 | sidekiq:
5 | restart: "no"
6 | mqtt-task-main-1:
7 | restart: "no"
8 | mqtt-task-main-2:
9 | restart: "no"
10 | mqtt-task-secondary:
11 | restart: "no"
12 | telnet-task:
13 | restart: "no"
14 | grafana:
15 | entrypoint: ["echo", "Grafana service disabled in development"]
16 | restart: "no"
17 |
--------------------------------------------------------------------------------
/compose.override.production.yml:
--------------------------------------------------------------------------------
1 | version: '3.3'
2 | services:
3 | app:
4 | build:
5 | args:
6 | - BUNDLE_WITHOUT=test development
7 | mqtt:
8 | entrypoint: ["echo", "MQTT service disabled in production"]
9 | cassandra-1:
10 | entrypoint: ["echo", "Cassandra service disabled in production"]
11 | kairos:
12 | depends_on: !reset []
13 |
--------------------------------------------------------------------------------
/compose.override.staging.yml:
--------------------------------------------------------------------------------
1 | services:
2 | db:
3 | deploy:
4 | resources:
5 | limits:
6 | memory: 2gb
7 | app:
8 | build:
9 | args:
10 | - BUNDLE_WITHOUT=test development
11 | mqtt:
12 | entrypoint: ["echo", "MQTT service disabled on staging"]
13 | mqtt-task-main-1:
14 | environment:
15 | MQTT_CLIENT_ID: smartcitizen-staging-api-main-1
16 | mqtt-task-main-2:
17 | environment:
18 | MQTT_CLIENT_ID: smartcitizen-staging-api-main-2
19 | mqtt-task-secondary:
20 | environment:
21 | MQTT_CLIENT_ID: "smartcitizen-staging-api-secondary"
22 |
23 |
24 | # cassandra-1:
25 | # entrypoint: ["echo", "Cassandra service disabled on staging"]
26 | # kairos:
27 | # depends_on: !reset []
28 |
--------------------------------------------------------------------------------
/compose.yml:
--------------------------------------------------------------------------------
1 | include:
2 | - compose/db.yml
3 | - compose/redis.yml
4 | - compose/app.yml
5 | - compose/sidekiq.yml
6 | - compose/mqtt-task.yml
7 | - compose/telnet-task.yml
8 | - compose/mqtt.yml
9 | - compose/web.yml
10 | - compose/kairos.yml
11 | - compose/cassandra.yml
12 | - compose/grafana.yml
13 |
--------------------------------------------------------------------------------
/compose/db.yml:
--------------------------------------------------------------------------------
1 | services:
2 | db:
3 | image: postgres:10
4 | command: -c max_connections=200
5 | volumes:
6 | - sck-postgres:/var/lib/postgresql/data
7 | env_file: ../.env
8 | logging:
9 | driver: "json-file"
10 | options:
11 | max-size: "100m"
12 | #environment:
13 | # NOTE: Postgres 9.5 stopped allowing connections without passwords.
14 | # Enable this if needed.
15 | #- POSTGRES_HOST_AUTH_METHOD=trust
16 | volumes:
17 | sck-postgres:
18 |
--------------------------------------------------------------------------------
/compose/grafana.yml:
--------------------------------------------------------------------------------
1 | services:
2 | grafana:
3 | image: grafana/agent
4 | env_file: ../.env
5 | volumes:
6 | - ../scripts/grafana/agent.yaml:/etc/agent/agent.yaml
7 | entrypoint: ["/bin/grafana-agent", "-config.expand-env", "--config.file=/etc/agent/agent.yaml","--metrics.wal-directory=/etc/agent/data"]
--------------------------------------------------------------------------------
/compose/kairos.yml:
--------------------------------------------------------------------------------
1 | services:
2 | # There is no official docker image for Kairos 2017-12-18
3 | # kairos:
4 | # image: kairos
5 | kairos:
6 | env_file: ../.env
7 | build:
8 | context: ../scripts/
9 | dockerfile: Dockerfile-kairos
10 | depends_on:
11 | cassandra-1:
12 | condition: service_healthy
13 | deploy:
14 | restart_policy:
15 | condition: on-failure
16 | max_attempts: 3
17 | window: 120s
18 | ports:
19 | - 8080:8080
20 | - 4242:4242 #telnet
21 | # We better not start Cassandra container in production, it eats up memory
22 | #depends_on:
23 | #- cassandra-1
--------------------------------------------------------------------------------
/compose/mqtt-task-common.yml:
--------------------------------------------------------------------------------
1 | services:
2 | mqtt-task:
3 | build: ../
4 | env_file: ../.env
5 | command: ./mqtt_subscriber.sh
6 | restart: always
7 | volumes:
8 | - "../log:/app/log"
9 | logging:
10 | driver: "json-file"
11 | options:
12 | max-size: "100m"
13 | environment:
14 | db_pool_size: 5
15 |
--------------------------------------------------------------------------------
/compose/mqtt-task.yml:
--------------------------------------------------------------------------------
1 | services:
2 | mqtt-task-main-1:
3 | extends:
4 | file: mqtt-task-common.yml
5 | service: mqtt-task
6 | environment:
7 | MQTT_CLIENT_ID: smartcitizen-api-main-1
8 | MQTT_CLEAN_SESSION: false
9 | mqtt-task-main-2:
10 | extends:
11 | file: mqtt-task-common.yml
12 | service: mqtt-task
13 | environment:
14 | MQTT_CLIENT_ID: smartcitizen-api-main-2
15 | MQTT_CLEAN_SESSION: false
16 | mqtt-task-secondary:
17 | extends:
18 | file: mqtt-task-common.yml
19 | service: mqtt-task
20 | environment:
21 | MQTT_CLIENT_ID: "smartcitizen-api-secondary"
22 | MQTT_CLEAN_SESSION: true
23 | deploy:
24 | mode: replicated
25 | replicas: 2
26 |
27 |
28 |
--------------------------------------------------------------------------------
/compose/redis.yml:
--------------------------------------------------------------------------------
1 | services:
2 | redis:
3 | image: redis:5
--------------------------------------------------------------------------------
/compose/sidekiq.yml:
--------------------------------------------------------------------------------
1 | services:
2 | sidekiq:
3 | build: ../.
4 | env_file: ../.env
5 | command: bundle exec sidekiq -c 100
6 | restart: always
7 | volumes:
8 | - "../log:/app/log"
9 | environment:
10 | db_pool_size: 100
11 | logging:
12 | driver: "json-file"
13 | options:
14 | max-size: "100m"
15 | deploy:
16 | resources:
17 | limits:
18 | memory: 4G
19 |
--------------------------------------------------------------------------------
/compose/telnet-task.yml:
--------------------------------------------------------------------------------
1 | services:
2 | telnet-task:
3 | build: ../.
4 | env_file: ../.env
5 | command: bundle exec rake telnet:push
6 | restart: always
7 | environment:
8 | db_pool_size: 2
9 | logging:
10 | driver: "json-file"
11 | options:
12 | max-size: "100m"
13 |
--------------------------------------------------------------------------------
/compose/web.yml:
--------------------------------------------------------------------------------
1 | services:
2 | web:
3 | image: nginx
4 | depends_on:
5 | app:
6 | condition: service_healthy
7 | restart: true
8 | healthcheck:
9 | test: ["CMD-SHELL", "curl http://app:3000"]
10 | timeout: 10s
11 | restart: always
12 | ports:
13 | - 80:80
14 | - 80:80/udp
15 | - 443:443
16 | - 443:443/udp
17 | volumes:
18 | - ../public:/app/public
19 | - ../scripts/nginx-conf/api.smartcitizen.me.conf:/etc/nginx/conf.d/api.smartcitizen.me.conf
20 | - ../scripts/nginx.conf:/etc/nginx/nginx.conf
21 | - ../scripts/certs:/etc/ssl:ro
22 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 |
5 | use Rack::JSONP
6 | run Rails.application
7 |
--------------------------------------------------------------------------------
/config/banned_words.yml:
--------------------------------------------------------------------------------
1 | # replace with your own list
2 |
3 | - clashables:
4 | - map
5 | - about
6 |
7 | - profanities:
8 | - stupid
9 |
--------------------------------------------------------------------------------
/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
2 |
3 | require 'bundler/setup' # Set up gems listed in the Gemfile.
4 | require 'bootsnap/setup' # Speed up boot time by caching expensive operations.
5 |
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative 'application'
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/config/initializers/api_cache.rb:
--------------------------------------------------------------------------------
1 | APICache.store = Moneta.new(:Redis,{
2 | cache: 10.minutes, # After this time fetch new data
3 | valid: 1.day, # Maximum time to use old data :forever is a valid option
4 | period: 1.minute, # Maximum frequency to call API
5 | timeout: 5.seconds # API response timeout
6 | # :fail => # Value returned instead of exception on failure
7 | })
8 |
--------------------------------------------------------------------------------
/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # ActiveSupport::Reloader.to_prepare do
4 | # ApplicationController.renderer.defaults.merge!(
5 | # http_host: 'example.org',
6 | # https: false
7 | # )
8 | # end
9 |
--------------------------------------------------------------------------------
/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = '1.0'
5 |
6 | # Add additional assets to the asset load path
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 |
9 | # Precompile additional assets.
10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
11 | # Rails.application.config.assets.precompile += %w( search.js )
12 |
--------------------------------------------------------------------------------
/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Specify a serializer for the signed and encrypted cookie jars.
4 | # Valid options are :json, :marshal, and :hybrid.
5 | Rails.application.config.action_dispatch.cookies_serializer = :json
6 |
--------------------------------------------------------------------------------
/config/initializers/cors.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Avoid CORS issues when API is called from the frontend app.
4 | # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.
5 |
6 | # Read more: https://github.com/cyu/rack-cors
7 |
8 | # Rails.application.config.middleware.insert_before 0, Rack::Cors do
9 | # allow do
10 | # origins 'example.com'
11 | #
12 | # resource '*',
13 | # headers: :any,
14 | # methods: [:get, :post, :put, :patch, :delete, :options, :head]
15 | # end
16 | # end
17 |
--------------------------------------------------------------------------------
/config/initializers/countries.rb:
--------------------------------------------------------------------------------
1 | ISO3166::Data.register(
2 | alpha2: 'XK',
3 | alpha3: 'XKX',
4 | name: 'Kosovo',
5 | continent: "Europe",
6 | currency_code: 'EUR',
7 | geo: {
8 | latitude: 42.602636,
9 | longitude: 20.902977,
10 | },
11 | translations: {
12 | en: 'Kosovo'
13 | }
14 | )
--------------------------------------------------------------------------------
/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/config/initializers/force_ssl.rb:
--------------------------------------------------------------------------------
1 | if Rails.application.config.force_ssl
2 | Rails.application.routes.default_url_options[:protocol] = "https"
3 | end
--------------------------------------------------------------------------------
/config/initializers/git_info.rb:
--------------------------------------------------------------------------------
1 | GIT_REVISION = `git rev-parse --short HEAD`.chomp || 'revision not found'
2 | GIT_BRANCH = `git rev-parse --abbrev-ref HEAD`.chomp || 'branch not found'
3 |
4 | if File.exists?('VERSION')
5 | VERSION_FILE = `cat VERSION`
6 | else
7 | VERSION_FILE = 'VERSION file not found'
8 | end
9 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/config/initializers/json.rb:
--------------------------------------------------------------------------------
1 | MultiJson.use(:oj)
2 | ActiveSupport::JSON::Encoding.time_precision = 0
3 |
4 |
--------------------------------------------------------------------------------
/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/config/initializers/monkey_patches.rb:
--------------------------------------------------------------------------------
1 | class Float
2 | def round_to(x)
3 | (self * 10**x).round.to_f / 10**x
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/config/initializers/mysql.rb:
--------------------------------------------------------------------------------
1 | require 'active_record'
2 |
3 | class MySQL < ActiveRecord::Base
4 | end
5 |
6 | # TODO: remove this file. This breaks the test because the lines above are needed!
7 |
--------------------------------------------------------------------------------
/config/initializers/pg.rb:
--------------------------------------------------------------------------------
1 | PgSearch.multisearch_options = {
2 | :using => { :tsearch => {:prefix => true} },
3 | :ignoring => :accents
4 | }
5 |
--------------------------------------------------------------------------------
/config/initializers/premailer.rb:
--------------------------------------------------------------------------------
1 | class Premailer
2 | module Rails
3 | module CSSLoaders
4 | module AssetPipelineLoader
5 | extend self
6 | def asset_pipeline_present?
7 | false
8 | end
9 | end
10 | end
11 | end
12 | end
--------------------------------------------------------------------------------
/config/initializers/rack_attack.rb:
--------------------------------------------------------------------------------
1 | class Rack::Attack
2 | class Request < ::Rack::Request
3 | def remote_ip
4 | @remote_ip ||= ActionDispatch::Request.new(env).remote_ip
5 | end
6 | end
7 |
8 | if Rails.env.development?
9 | # In environments/development.rb, config.cache_store = :null_store
10 | # Without a 'normal' cache it cannot count how many times a request has been made.
11 | # Instead we manually configure this cache for development mode:
12 | Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
13 | end
14 |
15 | limit_proc = ->(req) {
16 | user_is_whitelisted = Rack::Attack.cache.read("throttle_whitelist_#{req.remote_ip}")
17 | user_is_whitelisted ? Float::INFINITY : ENV.fetch("THROTTLE_LIMIT", 150).to_i
18 | }
19 |
20 | throttle('Throttle by IP', limit: limit_proc, period: 1.minute) do |request|
21 | request.remote_ip
22 | end
23 |
24 | end
25 |
--------------------------------------------------------------------------------
/config/initializers/ransack.rb:
--------------------------------------------------------------------------------
1 | Ransack.configure do |config|
2 | # Raise errors if a query contains an unknown predicate or attribute.
3 | # Default is true (do not raise error on unknown conditions).
4 | config.ignore_unknown_conditions = false
5 | end
--------------------------------------------------------------------------------
/config/initializers/request_cloudflare_ip.rb:
--------------------------------------------------------------------------------
1 | class ActionDispatch::Request
2 | alias_method :original_remote_ip, :remote_ip
3 | def remote_ip
4 | headers["HTTP_CF_CONNECTING_IP"] || original_remote_ip
5 | end
6 | end
--------------------------------------------------------------------------------
/config/initializers/scheduler.rb:
--------------------------------------------------------------------------------
1 | # config/initializers/scheduler.rb
2 | require 'rufus-scheduler'
3 |
4 | # Let's use the rufus-scheduler singleton
5 | #
6 | s = Rufus::Scheduler.singleton
7 |
8 |
9 | # Only when NOT inside rake task or console
10 | return if defined?(Rails::Console) || Rails.env.development? || Rails.env.test? || File.split($0).last == 'rake'
11 |
12 |
13 | s.every '1m' do
14 | # debug
15 | #Rails.logger.info "hello, it's #{Time.now}"
16 | #Rails.logger.flush
17 | end
18 |
19 | s.every '1h' do
20 | CheckBatteryLevelBelowJob.perform_later
21 | CheckDeviceStoppedPublishingJob.perform_later
22 | end
23 |
24 | s.every '1d' do
25 | CheckupUserEmailBlankJob.perform_later
26 | DeleteArchivedDevicesJob.perform_later
27 | DeleteArchivedUsersJob.perform_later
28 | DeleteOrphanedDevicesJob.perform_later
29 | end
30 |
--------------------------------------------------------------------------------
/config/initializers/sentry.rb:
--------------------------------------------------------------------------------
1 | Sentry.init do |config|
2 | config.dsn = ENV['RAVEN_DSN_URL']
3 | config.breadcrumbs_logger = [:sentry_logger, :active_support_logger, :http_logger]
4 | config.excluded_exceptions = ["RetryMQTTMessageJob::RetryMessageHandlerError", 'ActionController::RoutingError', 'ActiveRecord::RecordNotFound']
5 | end
6 |
--------------------------------------------------------------------------------
/config/initializers/string.rb:
--------------------------------------------------------------------------------
1 | class String
2 | def numeric?
3 | Float(self) != nil rescue false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/config/initializers/timeout.rb:
--------------------------------------------------------------------------------
1 | #Rack::Timeout.timeout = 15 # seconds
2 | Rails.application.config.middleware.insert_before Rack::Runtime, Rack::Timeout, service_timeout: 20
3 |
--------------------------------------------------------------------------------
/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/config/locales/helpers/user/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | second_person_possessive: "your"
3 | third_person_possessive: "%{username}'s"
4 | first_person_possessive: "my"
5 |
--------------------------------------------------------------------------------
/config/locales/views/layout/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | title: "Smart Citizen platform"
3 |
--------------------------------------------------------------------------------
/config/locales/views/nav/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | logo_alt: "SmartCitizen"
3 | hamburger_label: "Toggle navigation"
4 | map_link_url: "https://smartcitizen.me/kits/"
5 | map_link_text: "Map"
6 | documentation_link_url: "https://docs.smartcitizen.me"
7 | documentation_link_text: "Documentation"
8 | forum_link_url: "https://forum.smartcitizen.me"
9 | forum_link_text: "Forum"
10 | api_link_url: "https://developer.smartcitizen.me"
11 | api_link_text: "API reference"
12 | about_link_url: "https://docs.smartcitizen.me/about/"
13 | about_link_text: "About"
14 | policy_link_url: "/ui/policy/"
15 | policy_link_text: "Policy"
16 | kit_link_url: "https://smartcitizen.me#get-your-kit"
17 | kit_link_text: "Get your kit"
18 | login_link_text: "Log in"
19 | profile_pic_alt_text: "Your profile"
20 | logout_link_text: "Log out"
21 |
--------------------------------------------------------------------------------
/config/locales/views/sessions/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | new_session_submit: "Log in"
3 | new_account_heading: "Don't have an account?"
4 | new_account_submit: "Sign up"
5 | forgot_password_heading: "Forgot your password?"
6 | forgot_password_submit: "Reset password"
7 |
--------------------------------------------------------------------------------
/config/locales/views/shared/copyable_input/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | copyable_input_button_title: Copy to clipboard
3 |
--------------------------------------------------------------------------------
/config/locales/views/shared/danger_zone/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | danger_zone_subhead: Danger zone!
3 |
--------------------------------------------------------------------------------
/config/secrets.yml:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key is used for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 |
6 | # Make sure the secret is at least 30 characters and all random,
7 | # no regular words or you'll be exposed to dictionary attacks.
8 | # You can use `rake secret` to generate a secure secret key.
9 |
10 | # Make sure the secrets in this file are kept private
11 | # if you're sharing your code publicly.
12 |
13 | development:
14 | secret_key_base: 424f949ea17a293558a0b72ec0f78b30b036495a24b0759b85dd017a5ac03d4dcb50e38032da14007ec248be8bd714d4f89a6754cc236bcd9a27c84109b19aac
15 |
16 | test:
17 | secret_key_base: 8d8bbd817204883412e5d9974101ccda616cd5a81589a1009ddf1734ab2fd7b32fd0b20bbf0bf9d74f5c022d8504a4a889e543e008155ad6b97c086bba18b1c0
18 |
19 | # Do not keep production secrets in the repository,
20 | # instead read values from the environment.
21 | production:
22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
23 |
--------------------------------------------------------------------------------
/config/sidekiq.yml:
--------------------------------------------------------------------------------
1 | ---
2 | :verbose: false
3 | :concurrency: 10
4 | :queues:
5 | - default
6 | - mailers
7 | - mqtt_retry
8 | - mqtt_forward
9 |
10 | production:
11 | :concurrency: 20
12 | staging:
13 | :concurrency: 15
14 |
--------------------------------------------------------------------------------
/config/webpack/development.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'
2 |
3 | const environment = require('./environment')
4 |
5 | module.exports = environment.toWebpackConfig()
6 |
--------------------------------------------------------------------------------
/config/webpack/environment.js:
--------------------------------------------------------------------------------
1 | const { environment } = require('@rails/webpacker')
2 |
3 | module.exports = environment
4 |
--------------------------------------------------------------------------------
/config/webpack/production.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = process.env.NODE_ENV || 'production'
2 |
3 | const environment = require('./environment')
4 |
5 | module.exports = environment.toWebpackConfig()
6 |
--------------------------------------------------------------------------------
/config/webpack/test.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development'
2 |
3 | const environment = require('./environment')
4 |
5 | module.exports = environment.toWebpackConfig()
6 |
--------------------------------------------------------------------------------
/db/migrate/20150126131930_create_users.rb:
--------------------------------------------------------------------------------
1 | class CreateUsers < ActiveRecord::Migration
2 | def change
3 | create_table :users do |t|
4 | t.string :first_name
5 | t.string :last_name
6 | t.string :username
7 | t.string :email
8 | t.string :password_digest
9 |
10 | t.timestamps null: false
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/db/migrate/20150126155743_create_devices.rb:
--------------------------------------------------------------------------------
1 | class CreateDevices < ActiveRecord::Migration
2 | def change
3 | create_table :devices do |t|
4 | t.belongs_to :owner, index: true
5 | t.string :name
6 | t.text :description
7 | t.macaddr :mac_address
8 | t.float :latitude
9 | t.float :longitude
10 | t.timestamps null: false
11 | end
12 | add_foreign_key :devices, :users, column: :owner_id
13 | end
14 | end
15 |
16 | # DEVICE
17 | # owner_id
18 | # name
19 | # description
20 | # mac_address # add_column :devices, :mac_address, :macaddr
21 | # kit_version
22 | # firmware_version
23 | # elevation
24 | # latitude
25 | # longitude
26 | # address
27 |
28 | # READING
29 | # device_id
30 |
--------------------------------------------------------------------------------
/db/migrate/20150128122757_create_api_tokens.rb:
--------------------------------------------------------------------------------
1 | class CreateApiTokens < ActiveRecord::Migration
2 | def change
3 | create_table :api_tokens do |t|
4 | t.belongs_to :owner, index: true, null: false
5 | t.string :token, null: false
6 | t.timestamps null: false
7 | end
8 | add_index :api_tokens, [:owner_id, :token], unique: true
9 | add_foreign_key :api_tokens, :users, column: :owner_id
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20150129141454_create_friendly_id_slugs.rb:
--------------------------------------------------------------------------------
1 | class CreateFriendlyIdSlugs < ActiveRecord::Migration
2 | def change
3 | create_table :friendly_id_slugs do |t|
4 | t.string :slug, :null => false
5 | t.integer :sluggable_id, :null => false
6 | t.string :sluggable_type, :limit => 50
7 | t.string :scope
8 | t.datetime :created_at
9 | end
10 | add_index :friendly_id_slugs, :sluggable_id
11 | add_index :friendly_id_slugs, [:slug, :sluggable_type]
12 | add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], :unique => true
13 | add_index :friendly_id_slugs, :sluggable_type
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/db/migrate/20150129163302_create_sensors.rb:
--------------------------------------------------------------------------------
1 | class CreateSensors < ActiveRecord::Migration
2 | def change
3 | create_table :sensors do |t|
4 | t.string :ancestry, index: true
5 | t.string :name
6 | t.text :description
7 | t.string :unit
8 |
9 | t.timestamps null: false
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/db/migrate/20150129170516_create_kits.rb:
--------------------------------------------------------------------------------
1 | class CreateKits < ActiveRecord::Migration
2 | def change
3 | create_table :kits do |t|
4 | t.string :name
5 | t.text :description
6 |
7 | t.timestamps null: false
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/db/migrate/20150129170545_add_kit_id_to_devices.rb:
--------------------------------------------------------------------------------
1 | class AddKitIdToDevices < ActiveRecord::Migration
2 | def change
3 | add_reference :devices, :kit, index: true
4 | add_foreign_key :devices, :kits
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20150129170854_create_components.rb:
--------------------------------------------------------------------------------
1 | class CreateComponents < ActiveRecord::Migration
2 | def change
3 | create_table :components do |t|
4 | t.belongs_to :board, polymorphic: true, index: true
5 | t.belongs_to :sensor, index: true
6 |
7 | t.timestamps null: false
8 | end
9 | add_foreign_key :components, :sensors
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20150130104734_add_hstore_to_devices.rb:
--------------------------------------------------------------------------------
1 | class AddHstoreToDevices < ActiveRecord::Migration
2 |
3 | def up
4 | enable_extension :hstore
5 | end
6 |
7 | def down
8 | disable_extension :hstore
9 | end
10 |
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20150130104735_add_latest_data_to_devices.rb:
--------------------------------------------------------------------------------
1 | class AddLatestDataToDevices < ActiveRecord::Migration
2 | def up
3 | add_column :devices, :latest_data, :hstore
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150201170035_add_password_reset_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddPasswordResetToUsers < ActiveRecord::Migration
2 | def change
3 | add_column :users, :password_reset_token, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150202140127_add_geohash_to_devices.rb:
--------------------------------------------------------------------------------
1 | class AddGeohashToDevices < ActiveRecord::Migration
2 | def change
3 | add_column :devices, :geohash, :string
4 | add_index :devices, :geohash
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20150202154555_add_old_password_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddOldPasswordToUsers < ActiveRecord::Migration
2 | def change
3 | add_column :users, :old_password, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150204174044_add_last_recorded_at_to_devices.rb:
--------------------------------------------------------------------------------
1 | class AddLastRecordedAtToDevices < ActiveRecord::Migration
2 | def change
3 | add_column :devices, :last_recorded_at, :timestamp
4 | add_index :devices, :last_recorded_at
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20150204202307_add_slug_to_kits.rb:
--------------------------------------------------------------------------------
1 | class AddSlugToKits < ActiveRecord::Migration
2 | def change
3 | add_column :kits, :slug, :string
4 | add_index :kits, :slug
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20150211081350_add_fields_to_devices.rb:
--------------------------------------------------------------------------------
1 | class AddFieldsToDevices < ActiveRecord::Migration
2 | def change
3 | add_column :devices, :meta, :hstore
4 | add_column :devices, :location, :hstore
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20150211081539_add_fields_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddFieldsToUsers < ActiveRecord::Migration
2 | def change
3 | add_column :users, :role, :string
4 | add_column :users, :meta, :hstore
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20150313203310_add_location_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddLocationToUsers < ActiveRecord::Migration
2 | def change
3 | add_column :users, :city, :string
4 | add_column :users, :country_code, :string
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20150331102507_add_data_to_devices.rb:
--------------------------------------------------------------------------------
1 | class AddDataToDevices < ActiveRecord::Migration
2 | def change
3 | add_column :devices, :data, :jsonb
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150424150102_add_url_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddUrlToUsers < ActiveRecord::Migration
2 | def change
3 | add_column :users, :url, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150424155114_add_avatar_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddAvatarToUsers < ActiveRecord::Migration
2 | def change
3 | add_column :users, :avatar, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150428221051_create_pg_search_documents.rb:
--------------------------------------------------------------------------------
1 | class CreatePgSearchDocuments < ActiveRecord::Migration
2 | def self.up
3 | say_with_time("Creating table for pg_search multisearch") do
4 | create_table :pg_search_documents do |t|
5 | t.text :content
6 | t.belongs_to :searchable, :polymorphic => true, :index => true
7 | t.timestamps null: false
8 | end
9 | end
10 | end
11 |
12 | def self.down
13 | say_with_time("Dropping table for pg_search multisearch") do
14 | drop_table :pg_search_documents
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/db/migrate/20150514150525_add_old_data_to_devices.rb:
--------------------------------------------------------------------------------
1 | class AddOldDataToDevices < ActiveRecord::Migration
2 | def change
3 | add_column :devices, :old_data, :jsonb
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150514150744_add_trigger_to_devices.rb:
--------------------------------------------------------------------------------
1 | class AddTriggerToDevices < ActiveRecord::Migration
2 | def up
3 | execute <<-SQL
4 | CREATE FUNCTION replace_old_data() RETURNS trigger AS $$
5 | BEGIN
6 | NEW.old_data := OLD.data;
7 | RETURN NEW;
8 | END;
9 | $$ language plpgsql;
10 |
11 | CREATE TRIGGER old_data_trigger
12 | BEFORE UPDATE
13 | ON devices
14 | FOR EACH ROW
15 | EXECUTE PROCEDURE replace_old_data();
16 | SQL
17 | end
18 |
19 | def down
20 | execute <<-SQL
21 | DROP TRIGGER old_data_trigger ON devices;
22 | DROP FUNCTION replace_old_data();
23 | SQL
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/db/migrate/20150520114511_add_owner_username_to_devices.rb:
--------------------------------------------------------------------------------
1 | class AddOwnerUsernameToDevices < ActiveRecord::Migration
2 | def change
3 | add_column :devices, :owner_username, :string
4 | Device.where.not(owner_id: nil).includes(:owner).each do |device|
5 | device.update_attribute(:owner_username, device.owner.username)
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/db/migrate/20150602182642_create_pg_readings.rb:
--------------------------------------------------------------------------------
1 | class CreatePgReadings < ActiveRecord::Migration
2 | def change
3 | create_table :pg_readings do |t|
4 | t.belongs_to :device, index: true, foreign_key: true
5 | t.jsonb :data
6 | t.jsonb :raw_data
7 | t.datetime :recorded_at, index: true
8 |
9 | t.datetime :created_at
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/db/migrate/20150701142525_create_measurements.rb:
--------------------------------------------------------------------------------
1 | class CreateMeasurements < ActiveRecord::Migration
2 | def change
3 | create_table :measurements do |t|
4 | t.string :name
5 | t.text :description
6 | t.string :unit
7 |
8 | t.timestamps null: false
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20150701142607_add_measurement_id_to_sensors.rb:
--------------------------------------------------------------------------------
1 | class AddMeasurementIdToSensors < ActiveRecord::Migration
2 | def change
3 | add_reference :sensors, :measurement, index: true, foreign_key: true
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150701190639_add_role_mask_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddRoleMaskToUsers < ActiveRecord::Migration
2 | def change
3 | add_column :users, :role_mask, :integer, default: 0, null: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150702152151_enable_uuid_extension.rb:
--------------------------------------------------------------------------------
1 | class EnableUuidExtension < ActiveRecord::Migration
2 | def change
3 | enable_extension 'uuid-ossp'
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150702152315_add_uuids_to_models.rb:
--------------------------------------------------------------------------------
1 | class AddUuidsToModels < ActiveRecord::Migration
2 | def change
3 | [:components, :devices, :kits, :measurements, :sensors, :users].each do |model|
4 | add_column model, :uuid, :uuid, default: 'uuid_generate_v4()', null: false
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/db/migrate/20150716090911_create_uploads.rb:
--------------------------------------------------------------------------------
1 | class CreateUploads < ActiveRecord::Migration
2 | def change
3 | create_table :uploads do |t|
4 | t.string :type
5 | t.string :original_filename
6 | t.jsonb :metadata
7 |
8 | t.timestamps null: false
9 | end
10 | add_index :uploads, :type
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/db/migrate/20150721114116_add_legacy_api_key_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddLegacyApiKeyToUsers < ActiveRecord::Migration
2 | def change
3 | add_column :users, :legacy_api_key, :string
4 | User.reset_column_information
5 | User.all.each do |u|
6 | u.update_attribute(:legacy_api_key, Digest::SHA1.hexdigest("#{SecureRandom.uuid}#{rand(1000)}".split("").shuffle.join) )
7 | end
8 | change_column :users, :legacy_api_key, :string, null: false
9 | add_index :users, :legacy_api_key, unique: true
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20150722141027_add_old_data_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddOldDataToUsers < ActiveRecord::Migration
2 | def change
3 | add_column :users, :old_data, :jsonb
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150723151339_drop_unneeded_user_fields.rb:
--------------------------------------------------------------------------------
1 | class DropUnneededUserFields < ActiveRecord::Migration
2 | def change
3 | remove_column :users, :first_name, :string
4 | remove_column :users, :last_name, :string
5 | remove_column :users, :old_password, :string
6 | remove_column :users, :role, :string
7 | remove_column :users, :meta, :hstore
8 | rename_column :users, :avatar, :avatar_url
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/db/migrate/20150727075855_add_uuids_to_uploads.rb:
--------------------------------------------------------------------------------
1 | class AddUuidsToUploads < ActiveRecord::Migration
2 | def change
3 | add_column :uploads, :uuid, :uuid, default: 'uuid_generate_v4()', null: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150727121643_add_user_id_to_uploads.rb:
--------------------------------------------------------------------------------
1 | class AddUserIdToUploads < ActiveRecord::Migration
2 | def change
3 | add_reference :uploads, :user, index: true, foreign_key: true
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150727150738_add_key_to_uploads.rb:
--------------------------------------------------------------------------------
1 | class AddKeyToUploads < ActiveRecord::Migration
2 | def change
3 | add_column :uploads, :key, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150728112029_drop_fk_restraint_on_devices.rb:
--------------------------------------------------------------------------------
1 | class DropFkRestraintOnDevices < ActiveRecord::Migration
2 | def change
3 | remove_foreign_key :devices, column: :owner_id
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150731101949_fix_pks.rb:
--------------------------------------------------------------------------------
1 | class FixPks < ActiveRecord::Migration
2 | def change
3 | ActiveRecord::Base.connection.tables.each do |t|
4 | ActiveRecord::Base.connection.reset_pk_sequence!(t)
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/db/migrate/20150806215427_add_trigrams.rb:
--------------------------------------------------------------------------------
1 | class AddTrigrams < ActiveRecord::Migration
2 | def change
3 | enable_extension "pg_trgm"
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150806215704_add_unaccent.rb:
--------------------------------------------------------------------------------
1 | class AddUnaccent < ActiveRecord::Migration
2 | def change
3 | enable_extension "unaccent"
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150813072846_add_migration_data_to_devices.rb:
--------------------------------------------------------------------------------
1 | class AddMigrationDataToDevices < ActiveRecord::Migration
2 | def change
3 | add_column :devices, :migration_data, :jsonb
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150825103243_create_places.rb:
--------------------------------------------------------------------------------
1 | class CreatePlaces < ActiveRecord::Migration
2 | def change
3 | create_table :places do |t|
4 | t.string :name
5 | t.string :country_code
6 | t.string :country_name
7 | t.float :lat
8 | t.float :lng
9 |
10 | t.timestamps null: false
11 | end
12 | add_index :places, [:name, :country_code], unique: true
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/db/migrate/20150827133315_add_workflow_state_to_devices.rb:
--------------------------------------------------------------------------------
1 | class AddWorkflowStateToDevices < ActiveRecord::Migration
2 | def change
3 | add_column :devices, :workflow_state, :string
4 | add_index :devices, :workflow_state
5 | Device.reset_column_information
6 | Device.unscoped.update_all(workflow_state: 'active')
7 | # Device.all.each do |device|
8 | # device.update_attributes(:workflow_state => 'normal')
9 | # end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20150907223941_create_tags.rb:
--------------------------------------------------------------------------------
1 | class CreateTags < ActiveRecord::Migration
2 | def change
3 | create_table :tags do |t|
4 | t.string :name
5 | t.text :description
6 | t.timestamps null: false
7 | end
8 | add_index :tags, :name, unique: true
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/db/migrate/20150907232654_create_devices_tags.rb:
--------------------------------------------------------------------------------
1 | class CreateDevicesTags < ActiveRecord::Migration
2 | def change
3 | create_table :devices_tags do |t|
4 | t.belongs_to :device, foreign_key: true
5 | t.belongs_to :tag, foreign_key: true
6 | t.timestamps null: false
7 | end
8 |
9 | add_index :devices_tags, [:device_id, :tag_id], unique: true
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20150912192902_add_uuid_to_tags.rb:
--------------------------------------------------------------------------------
1 | class AddUuidToTags < ActiveRecord::Migration
2 | def change
3 | add_column :tags, :uuid, :uuid, default: 'uuid_generate_v4()', null: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150916160713_add_equation_to_sensors.rb:
--------------------------------------------------------------------------------
1 | class AddEquationToSensors < ActiveRecord::Migration
2 | def change
3 | add_column :sensors, :equation, :text
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150916163343_add_sensor_map_to_kits.rb:
--------------------------------------------------------------------------------
1 | class AddSensorMapToKits < ActiveRecord::Migration
2 | def change
3 | add_column :kits, :sensor_map, :jsonb
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150917235839_add_equation_to_components.rb:
--------------------------------------------------------------------------------
1 | class AddEquationToComponents < ActiveRecord::Migration
2 | def change
3 | add_column :components, :equation, :text
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150918012211_drop_equation_from_sensors.rb:
--------------------------------------------------------------------------------
1 | class DropEquationFromSensors < ActiveRecord::Migration
2 | def change
3 | remove_column :sensors, :equation, :text
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20150920212633_add_cached_device_ids_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddCachedDeviceIdsToUsers < ActiveRecord::Migration
2 | def change
3 | add_column :users, :cached_device_ids, :integer, array: true
4 | User.reset_column_information
5 |
6 | User.unscoped.all.map(&:update_all_device_ids!)
7 |
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/db/migrate/20151007152201_create_bad_readings.rb:
--------------------------------------------------------------------------------
1 | class CreateBadReadings < ActiveRecord::Migration
2 | def change
3 | create_table :bad_readings do |t|
4 | t.integer :tags
5 | t.string :remote_ip
6 | t.jsonb :data, null: false
7 | t.datetime :created_at, null: false
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/db/migrate/20151007191504_drop_pg_readings.rb:
--------------------------------------------------------------------------------
1 | class DropPgReadings < ActiveRecord::Migration
2 | def change
3 | drop_table :pg_readings
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20151029153355_add_csv_export_requested_at_to_devices.rb:
--------------------------------------------------------------------------------
1 | class AddCsvExportRequestedAtToDevices < ActiveRecord::Migration
2 | def change
3 | add_column :devices, :csv_export_requested_at, :datetime
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20151113111350_change_hstore_fields_to_jsonb_on_devices.rb:
--------------------------------------------------------------------------------
1 | class ChangeHstoreFieldsToJsonbOnDevices < ActiveRecord::Migration
2 | def up
3 | change_column :devices, :meta, 'jsonb USING CAST(meta AS jsonb)'
4 | change_column :devices, :location, 'jsonb USING CAST(location AS jsonb)'
5 | end
6 |
7 | def down
8 | puts("****************** Data Migration Warning ******************".red)
9 | puts("This will WIPE meta and location data".yellow)
10 | puts("press 'y' if you wish to continue".yellow)
11 |
12 | if STDIN.gets.chomp == "y"
13 | puts("Ok then!".green)
14 | else
15 | fail
16 | end
17 |
18 | remove_column :devices, :meta
19 | remove_column :devices, :location
20 | add_column :devices, :meta, :hstore
21 | add_column :devices, :location, :hstore
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/db/migrate/20151117190908_add_message_to_bad_readings.rb:
--------------------------------------------------------------------------------
1 | class AddMessageToBadReadings < ActiveRecord::Migration
2 | def change
3 | add_column :bad_readings, :message, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20151117194000_add_device_id_and_mac_address_to_bad_readings.rb:
--------------------------------------------------------------------------------
1 | class AddDeviceIdAndMacAddressToBadReadings < ActiveRecord::Migration
2 | def change
3 | add_column :bad_readings, :device_id, :integer
4 | add_column :bad_readings, :mac_address, :string
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20151117194820_add_version_to_bad_readings.rb:
--------------------------------------------------------------------------------
1 | class AddVersionToBadReadings < ActiveRecord::Migration
2 | def change
3 | add_column :bad_readings, :version, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20151117200126_add_timestamp_to_bad_readings.rb:
--------------------------------------------------------------------------------
1 | class AddTimestampToBadReadings < ActiveRecord::Migration
2 | def change
3 | add_column :bad_readings, :timestamp, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20151118083900_add_backtrace_to_bad_readings.rb:
--------------------------------------------------------------------------------
1 | class AddBacktraceToBadReadings < ActiveRecord::Migration
2 | def change
3 | add_column :bad_readings, :backtrace, :text
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20151118143226_create_backup_readings.rb:
--------------------------------------------------------------------------------
1 | class CreateBackupReadings < ActiveRecord::Migration
2 | def change
3 | create_table :backup_readings do |t|
4 | t.jsonb :data
5 | t.string :mac
6 | t.string :version
7 | t.string :ip
8 | t.boolean :stored
9 | t.datetime :created_at
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/db/migrate/20151209135345_add_workflow_state_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddWorkflowStateToUsers < ActiveRecord::Migration
2 | def change
3 | add_column :users, :workflow_state, :string
4 | add_index :users, :workflow_state
5 | User.update_all(workflow_state: 'active')
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/db/migrate/20160313185543_add_old_mac_address_to_devices.rb:
--------------------------------------------------------------------------------
1 | class AddOldMacAddressToDevices < ActiveRecord::Migration
2 | def change
3 | add_column :devices, :old_mac_address, :macaddr
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20160404101737_drop_places.rb:
--------------------------------------------------------------------------------
1 | class DropPlaces < ActiveRecord::Migration
2 |
3 | def up
4 | drop_table :places
5 | end
6 |
7 | def down
8 | create_table "places", force: :cascade do |t|
9 | t.string "name"
10 | t.string "country_code"
11 | t.string "country_name"
12 | t.float "lat"
13 | t.float "lng"
14 | t.datetime "created_at", null: false
15 | t.datetime "updated_at", null: false
16 | end
17 |
18 | add_index "places", ["name", "country_code"], name: "index_places_on_name_and_country_code", unique: true, using: :btree
19 | end
20 |
21 | end
22 |
--------------------------------------------------------------------------------
/db/migrate/20160411194100_add_owner_to_application.rb:
--------------------------------------------------------------------------------
1 | class AddOwnerToApplication < ActiveRecord::Migration
2 | def change
3 | add_column :oauth_applications, :owner_id, :integer, null: true
4 | add_column :oauth_applications, :owner_type, :string, null: true
5 | add_index :oauth_applications, [:owner_id, :owner_type]
6 | end
7 | end
--------------------------------------------------------------------------------
/db/migrate/20160601120221_add_reverse_equation_to_components.rb:
--------------------------------------------------------------------------------
1 | class AddReverseEquationToComponents < ActiveRecord::Migration
2 | def change
3 | add_column :components, :reverse_equation, :text
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20160607101112_add_state_to_devices.rb:
--------------------------------------------------------------------------------
1 | class AddStateToDevices < ActiveRecord::Migration
2 | def change
3 | add_column :devices, :state, :string
4 | add_index :devices, :state
5 |
6 | Device.all.each do |d|
7 | d.update_column(:state, d.soft_state)
8 | end
9 |
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20161026171920_create_orphan_devices.rb:
--------------------------------------------------------------------------------
1 | class CreateOrphanDevices < ActiveRecord::Migration
2 | def change
3 | create_table :orphan_devices do |t|
4 | t.string :name
5 | t.text :description
6 | t.integer :kit_id
7 | t.string :exposure
8 | t.float :latitude
9 | t.float :longitude
10 | t.text :user_tags
11 | t.string :device_token, null: false
12 | t.string :onboarding_session
13 |
14 | t.timestamps null: false
15 | end
16 |
17 | add_index :orphan_devices, [:device_token], unique: true
18 |
19 | add_column :devices, :device_token, :string
20 | add_index :devices, [:device_token], unique: true
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/db/migrate/20180719113808_create_devices_inventory.rb:
--------------------------------------------------------------------------------
1 | class CreateDevicesInventory < ActiveRecord::Migration
2 | def change
3 | drop_table :devices_inventory
4 | create_table :devices_inventory do |t|
5 | t.jsonb :report, default: '{}'
6 | t.datetime :created_at
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/db/migrate/20180903161334_add_confidential_to_doorkeeper_application.rb:
--------------------------------------------------------------------------------
1 | class AddConfidentialToDoorkeeperApplication < ActiveRecord::Migration
2 | def change
3 | add_column(
4 | :oauth_applications,
5 | :confidential,
6 | :boolean,
7 | null: false,
8 | default: true # maintaining backwards compatibility: require secrets
9 | )
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20181011105328_drop_bad_readings.rb:
--------------------------------------------------------------------------------
1 | class DropBadReadings < ActiveRecord::Migration
2 | def change
3 | drop_table :bad_readings
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20181011105345_drop_backup_readings.rb:
--------------------------------------------------------------------------------
1 | class DropBackupReadings < ActiveRecord::Migration
2 | def change
3 | drop_table :backup_readings
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20181105160320_create_sensor_tags.rb:
--------------------------------------------------------------------------------
1 | class CreateSensorTags < ActiveRecord::Migration
2 | def change
3 | create_table :tag_sensors do |t|
4 | t.string :name
5 | t.string :description
6 |
7 | t.timestamps null: false
8 | end
9 |
10 | create_table :sensor_tags do |t|
11 | t.timestamps null: false
12 | t.belongs_to :sensor, index: true
13 | t.belongs_to :tag_sensor, index: true
14 | end
15 |
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/db/migrate/20181212142627_add_hardware_info_to_devices.rb:
--------------------------------------------------------------------------------
1 | class AddHardwareInfoToDevices < ActiveRecord::Migration[5.2]
2 | def change
3 | add_column :devices, :hardware_info, :jsonb
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20190116161536_add_notifications_to_devices.rb:
--------------------------------------------------------------------------------
1 | class AddNotificationsToDevices < ActiveRecord::Migration[5.2]
2 | def change
3 | add_column :devices, :notify_stopped_publishing_timestamp, :timestamp, :default => Time.now
4 | add_column :devices, :notify_low_battery_timestamp, :timestamp, :default => Time.now
5 | add_column :devices, :notify_low_battery, :boolean, default: false
6 | add_column :devices, :notify_stopped_publishing, :boolean,default: false
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/db/migrate/20190222130041_add_device_handshake_to_orphan_devices.rb:
--------------------------------------------------------------------------------
1 | class AddDeviceHandshakeToOrphanDevices < ActiveRecord::Migration[5.2]
2 | def change
3 | add_column :orphan_devices, :device_handshake, :boolean, default: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20190819084816_add_is_private_to_devices.rb:
--------------------------------------------------------------------------------
1 | class AddIsPrivateToDevices < ActiveRecord::Migration[5.2]
2 | def change
3 | add_column :devices, :is_private, :boolean, default: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20191115103215_add_foreign_key_constraint_to_active_storage_attachments_for_blob_id.active_storage.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from active_storage (originally 20180723000244)
2 | class AddForeignKeyConstraintToActiveStorageAttachmentsForBlobId < ActiveRecord::Migration[6.0]
3 | def up
4 | return if foreign_key_exists?(:active_storage_attachments, column: :blob_id)
5 |
6 | if table_exists?(:active_storage_blobs)
7 | add_foreign_key :active_storage_attachments, :active_storage_blobs, column: :blob_id
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/db/migrate/20200703144927_add_post_processing_info_to_device.rb:
--------------------------------------------------------------------------------
1 | class AddPostProcessingInfoToDevice < ActiveRecord::Migration[6.0]
2 | def change
3 | add_column :devices, :postprocessing_info, :jsonb
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20210105123052_add_is_test_to_devices.rb:
--------------------------------------------------------------------------------
1 | class AddIsTestToDevices < ActiveRecord::Migration[6.0]
2 | def change
3 | add_column :devices, :is_test, :boolean, null: false, default: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20210204124227_create_postprocessings.rb:
--------------------------------------------------------------------------------
1 | class CreatePostprocessings < ActiveRecord::Migration[6.0]
2 | def change
3 | remove_column :devices, :postprocessing_info, :jsonb
4 |
5 | create_table :postprocessings do |t|
6 | t.string :blueprint_url
7 | t.string :hardware_url
8 | t.belongs_to :device, null: false, foreign_key: true
9 | t.jsonb :forwarding_params
10 | t.jsonb :meta
11 | t.datetime :latest_postprocessing
12 |
13 | t.timestamps
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/db/migrate/20230512075843_add_archived_at_to_devices.rb:
--------------------------------------------------------------------------------
1 | class AddArchivedAtToDevices < ActiveRecord::Migration[6.0]
2 | def up
3 | add_column :devices, :archived_at, :datetime, null: true
4 | execute %{
5 | UPDATE devices
6 | SET archived_at = NOW()
7 | WHERE state = 'archived'
8 | }
9 | end
10 |
11 | def down
12 | remove_column :devices, :archived_at
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/db/migrate/20230616151554_add_service_name_to_active_storage_blobs.active_storage.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from active_storage (originally 20190112182829)
2 | class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0]
3 | def up
4 | return unless table_exists?(:active_storage_blobs)
5 |
6 | unless column_exists?(:active_storage_blobs, :service_name)
7 | add_column :active_storage_blobs, :service_name, :string
8 |
9 | if configured_service = ActiveStorage::Blob.service.name
10 | ActiveStorage::Blob.unscoped.update_all(service_name: configured_service)
11 | end
12 |
13 | change_column :active_storage_blobs, :service_name, :string, null: false
14 | end
15 | end
16 |
17 | def down
18 | return unless table_exists?(:active_storage_blobs)
19 |
20 | remove_column :active_storage_blobs, :service_name
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/db/migrate/20230705095430_populate_device_archived_at_column.rb:
--------------------------------------------------------------------------------
1 | class PopulateDeviceArchivedAtColumn < ActiveRecord::Migration[6.0]
2 | def change
3 | execute %{
4 | UPDATE devices
5 | SET archived_at = NOW()
6 | WHERE workflow_state = 'archived'
7 | }
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/db/migrate/20230929114837_further_kits_refactor_changes.rb:
--------------------------------------------------------------------------------
1 | class FurtherKitsRefactorChanges < ActiveRecord::Migration[6.1]
2 | def change
3 | rename_column :devices, :last_recorded_at, :last_reading_at
4 | add_column :components, :location, :integer, default: 1
5 | connection.execute(%{
6 | UPDATE components
7 | SET location=1
8 | WHERE location IS NULL
9 | })
10 | change_column_null :components, :location, false
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/db/migrate/20231005153412_rename_component_location_to_bus.rb:
--------------------------------------------------------------------------------
1 | class RenameComponentLocationToBus < ActiveRecord::Migration[6.1]
2 | def change
3 | rename_column :components, :location, :bus
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20231006064514_add_last_reading_at_to_components.rb:
--------------------------------------------------------------------------------
1 | class AddLastReadingAtToComponents < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :components, :last_reading_at, :datetime
4 | execute %{
5 | UPDATE components
6 | SET last_reading_at = devices.last_reading_at
7 | FROM devices
8 | WHERE components.device_id = devices.id
9 | }
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20240228125910_remove_hardware_description_override_from_devices.rb:
--------------------------------------------------------------------------------
1 | class RemoveHardwareDescriptionOverrideFromDevices < ActiveRecord::Migration[6.1]
2 | def change
3 | remove_column :devices, :hardware_description_override, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20240318110256_add_world_map_indexes.rb:
--------------------------------------------------------------------------------
1 | class AddWorldMapIndexes < ActiveRecord::Migration[6.1]
2 | def change
3 | add_index :devices, [:workflow_state, :is_test, :last_reading_at, :latitude], name: "world_map_request"
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20240318171656_add_component_device_sensor_index.rb:
--------------------------------------------------------------------------------
1 | class AddComponentDeviceSensorIndex < ActiveRecord::Migration[6.1]
2 | def change
3 | remove_index :components, [:sensor_id]
4 | add_index :components, [:device_id, :sensor_id]
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20240423162838_add_forwarding_token_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddForwardingTokenToUsers < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :users, :forwarding_token, :string
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20240512132257_add_forwarding_username_to_users.rb:
--------------------------------------------------------------------------------
1 | class AddForwardingUsernameToUsers < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :users, :forwarding_username, :string
4 | add_index :users, :forwarding_token
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20240624161155_add_data_policy_fields_to_device.rb:
--------------------------------------------------------------------------------
1 | class AddDataPolicyFieldsToDevice < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :devices, :precise_location, :boolean, null: false, default: false
4 | add_column :devices, :enable_forwarding, :boolean, null: false, default: false
5 | # Existing devices have precise locations, despite the default for all new ones.
6 | execute "UPDATE devices SET precise_location = true"
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/db/migrate/20240624175242_add_extra_measurement_and_sensor_fields.rb:
--------------------------------------------------------------------------------
1 | class AddExtraMeasurementAndSensorFields < ActiveRecord::Migration[6.1]
2 | def change
3 | add_column :measurements, :definition, :string
4 | add_column :sensors, :datasheet, :string
5 | add_column :sensors, :unit_definition, :string
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/db/migrate/20240704062854_remove_avatars_and_uploads.rb:
--------------------------------------------------------------------------------
1 | class RemoveAvatarsAndUploads < ActiveRecord::Migration[6.1]
2 | def change
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/db/migrate/20240718054447_create_experiments.rb:
--------------------------------------------------------------------------------
1 | class CreateExperiments < ActiveRecord::Migration[6.1]
2 | def change
3 | create_table :experiments do |t|
4 | t.string :name, null: false
5 | t.string :description
6 | t.belongs_to :owner, index: true
7 | t.boolean :active, null: false, default: true
8 | t.boolean :is_test, null: false, default: false
9 | t.datetime :starts_at
10 | t.datetime :ends_at
11 | t.timestamps
12 | end
13 | add_foreign_key :experiments, :users, column: :owner_id
14 |
15 | create_table :devices_experiments, id: false do |t|
16 | t.belongs_to :device, index: true
17 | t.belongs_to :experiment, index: true
18 | end
19 | add_foreign_key :devices_experiments, :devices, column: :device_id
20 | add_foreign_key :devices_experiments, :experiments, column: :experiment_id
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/db/migrate/20240812081108_remove_active_flag_from_experiments.rb:
--------------------------------------------------------------------------------
1 | class RemoveActiveFlagFromExperiments < ActiveRecord::Migration[6.1]
2 | def change
3 | remove_column :experiments, :active, :boolean
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20241001080033_change_device_precise_location_default.rb:
--------------------------------------------------------------------------------
1 | class ChangeDevicePreciseLocationDefault < ActiveRecord::Migration[6.1]
2 | def change
3 | change_column_default :devices, :precise_location, from: false, to: true
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20241009174732_unique_index_on_components.rb:
--------------------------------------------------------------------------------
1 | class UniqueIndexOnComponents < ActiveRecord::Migration[6.1]
2 | def up
3 | remove_index :components, [:device_id, :sensor_id]
4 | add_index :components, [:device_id, :sensor_id], unique: true
5 | execute %{
6 | ALTER TABLE components ADD CONSTRAINT unique_sensor_for_device UNIQUE (device_id, sensor_id)
7 | }
8 | execute %{
9 | ALTER TABLE components ADD CONSTRAINT unique_key_for_device UNIQUE (device_id, key)
10 | }
11 | end
12 |
13 | def down
14 | execute %{
15 | ALTER TABLE components DROP CONSTRAINT IF EXISTS unique_key_for_device
16 | }
17 | execute %{
18 | ALTER TABLE components DROP CONSTRAINT IF EXISTS unique_sensor_for_device
19 | }
20 | remove_index :components, [:device_id, :sensor_id], unique: true
21 | add_index :components, [:device_id, :sensor_id]
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/db/migrate/20241014052837_create_device_ingest_errors.rb:
--------------------------------------------------------------------------------
1 | class CreateDeviceIngestErrors < ActiveRecord::Migration[6.1]
2 | def change
3 | create_table :ingest_errors do |t|
4 | t.references :device, null: false, foreign_key: true
5 | t.text :topic
6 | t.text :message
7 | t.text :error_class
8 | t.text :error_message
9 | t.text :error_trace
10 | t.timestamps
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/db/migrate/20241113155952_remove_null_strings_from_measurement_units.rb:
--------------------------------------------------------------------------------
1 | class RemoveNullStringsFromMeasurementUnits < ActiveRecord::Migration[6.1]
2 | def up
3 | execute "UPDATE measurements SET unit = NULL WHERE unit = 'NULL'"
4 | execute "UPDATE sensors SET unit = NULL WHERE unit = 'NULL'"
5 | end
6 |
7 | def down; end
8 | end
9 |
--------------------------------------------------------------------------------
/db/migrate/20250505081245_make_users_with_postprocessings_into_researchers.rb:
--------------------------------------------------------------------------------
1 | class MakeUsersWithPostprocessingsIntoResearchers < ActiveRecord::Migration[6.1]
2 | def change
3 | execute %{
4 | UPDATE users
5 | SET role_mask = 2
6 | FROM devices
7 | INNER JOIN postprocessings on devices.id = postprocessings.device_id
8 | WHERE devices.owner_id = users.id AND postprocessings.id IS NOT NULL and users.role_mask = 0;
9 | }
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #git stash
3 | #git pull --rebase
4 | #git stash pop
5 | docker compose build
6 | docker compose exec app bundle exec bin/rake db:migrate
7 | docker compose exec app bash -l -c "bundle exec yarn install"
8 | docker compose exec app bash -l -c "bundle exec bin/rake assets:clobber"
9 | docker compose exec app bash -l -c "NODE_OPTIONS=--openssl-legacy-provider bundle exec bin/rake assets:precompile"
10 | docker compose up -d
11 |
--------------------------------------------------------------------------------
/docs/adr/0000-use-markdown-architectural-decision-records.md:
--------------------------------------------------------------------------------
1 | # Use Markdown Architectural Decision Records
2 |
3 | - Status: trial
4 | - Deciders: viktorsmari
5 | - Date: 2020-12-03
6 |
7 | ## Why?
8 |
9 | Sometimes we forget why we made architectural changes, and finding the correct git commit which explains the 'why' can be difficult.
10 |
--------------------------------------------------------------------------------
/docs/adr/0001-minimize-kit-payload.md:
--------------------------------------------------------------------------------
1 | - Deciders: vicobarberan
2 | - Date: 2020-12-03
3 |
4 | ## Why?
5 |
6 | - Try do reduce the payload that gets sent from a device to the API by ~35%.
7 | - Instead, the API will convert the new data type into JSON.
8 |
9 |
10 | New payload example:
11 | - https://github.com/fablabbcn/smartcitizen-kit-21/commit/f195dbc010c8cddc419cb4357875c9de942aab48#diff-f978f2d74f7bc8854e6bb019c93369fa30b05808be6de85f5571b5cc804db18fR414
12 |
13 |
14 | ```
15 | {
16 | t:2017-03-24T13:35:14Z,
17 | 29:48.45,
18 | 13:66,
19 | 12:28,
20 | 10:4.45
21 | }
22 | ```
23 |
24 | Old payload:
25 | - https://github.com/fablabbcn/smartcitizen-kit-21/blob/master/esp/src/SckESP.cpp#L361-L373
26 |
27 | ```json
28 | { "data":[
29 | {"recorded_at":"2017-03-24T13:35:14Z",
30 | "sensors":[
31 | {"id":29,"value":48.45},
32 | {"id":13,"value":66},
33 | {"id":12,"value":28},
34 | {"id":10,"value":4.45}
35 | ]
36 | }
37 | ]
38 | }
39 | ```
40 |
--------------------------------------------------------------------------------
/docs/adr/0002-private-devices.md:
--------------------------------------------------------------------------------
1 | - Deciders: pral2a
2 | - Date: 2019-09-01
3 |
4 | ## Problem
5 |
6 | - Some devices might contain data you don't want to be public.
7 | - Example: How often / when you turn on the lights in your bathroom?
8 |
9 | ## Solution
10 |
11 | - Add a boolean field `is_private` to devices, that can be used to hide their data.
12 |
13 | Who can see a private device data?
14 | - The data is only visible to the owner + admins
15 |
16 | What about the World Map?
17 | - The device name + location will be visible to EVERYONE on the World Map, but not it's data.
18 |
--------------------------------------------------------------------------------
/docs/adr/0003-hide-test-devices.md:
--------------------------------------------------------------------------------
1 | - Deciders: pral2a oscgonfer
2 | - Date: 2021-01-04
3 |
4 | ## Problem
5 |
6 | - We have multiple 'test' devices that we create internally while developing features / testing sensors etc.
7 | - We don't want these devices to show up on the World Map, because they are clutter / noise.
8 | - The `is_test` field can also be used to quickly delete all test devices.
9 |
10 | ## Solution
11 |
12 | - Add a boolean field `is_test` (or similar) to devices, that we can activate in order to hide devices.
13 |
14 |
15 | ## Thoughts
16 |
17 | - Should our users also be able to do this themselves on their own devices?
18 | - Yes, but only users with ADMIN or RESEARCHER rights.
19 | - We have the `is_private` but that shows devices on the World Map. Can we change is_private to also hide devices on the world_map?
20 |
--------------------------------------------------------------------------------
/docs/adr/README.md:
--------------------------------------------------------------------------------
1 | ## We are trying to use ADR - in order to document WHY we make decisions.
2 |
3 | Inspired by:
4 |
5 | - https://adr.github.io/
6 | - https://github.com/joelparkerhenderson/architecture_decision_record
7 | - https://github.com/island-is/handbook/blob/master/docs/adr/
8 |
--------------------------------------------------------------------------------
/docs/adr/current-architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabbcn/smartcitizen-api/cfd1394f9efd0bf49cba3b30b40b4e326d32bc7b/docs/adr/current-architecture.png
--------------------------------------------------------------------------------
/docs/erd.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabbcn/smartcitizen-api/cfd1394f9efd0bf49cba3b30b40b4e326d32bc7b/docs/erd.pdf
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | You are here: http://fablabbcn.github.io/smartcitizen-api/
2 |
3 | This is the index.md file from /docs folder, in the repository.
4 |
5 | [Architectural Decision Records](adr)
6 |
7 | [CSV_upload](CSV_upload.md)
8 |
9 | [mqtt_device_readings](mqtt_device_readings.md)
10 |
11 | [mqtt_handler](mqtt_handler.md)
12 |
13 | [onboarding](onboarding)
14 |
--------------------------------------------------------------------------------
/docs/throttling.md:
--------------------------------------------------------------------------------
1 | Throttling is done as follows:
2 | - If the request is unauthenticated or the authorisation level doesn't allow it, `role_mask == 1`, the request is throttled at a rate of 150 requests/ minute
3 | - If the request is authorised with a `role_mask >= 2` (`researcher` or `admin`), the request is not throttled and it will do so based on the request remote ip for 5'
4 |
5 | This implies that if `role_mask>=2` requests are followed by unauthenticated or unauthorised requests from the same IP, those requests will not be throttled - we consider this a borderline case.
6 | Also, if an IP is throttled - returning 429s, a request `role_mask>=2` coming from that IP will get a 429 until 1' passes.
7 |
--------------------------------------------------------------------------------
/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 | source $NVM_DIR/nvm.sh
4 |
5 | # Remove a potentially pre-existing server.pid for Rails.
6 | rm -f /app/tmp/pids/server.pid
7 |
8 |
9 | # Then exec the container's main process (what's set as CMD in the Dockerfile).
10 | exec "$@"
11 |
--------------------------------------------------------------------------------
/lib/tasks/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabbcn/smartcitizen-api/cfd1394f9efd0bf49cba3b30b40b4e326d32bc7b/lib/tasks/.keep
--------------------------------------------------------------------------------
/lib/tasks/devices.rake:
--------------------------------------------------------------------------------
1 | namespace :devices do
2 | task :truncate_and_fuzz_locations => :environment do
3 | Device.all.each do |device|
4 | device.truncate_and_fuzz_location!
5 | device.save!(validate: false)
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/tasks/postgres.rake:
--------------------------------------------------------------------------------
1 | namespace :postgres do
2 | desc 'Resets Postgres auto-increment ID column sequences to fix duplicate ID errors'
3 | task :reset_sequences => :environment do
4 | Rails.application.eager_load!
5 |
6 | ActiveRecord::Base.connection.tables.each do |model|
7 | begin
8 | ActiveRecord::Base.connection.reset_pk_sequence!(model)
9 | puts "reset #{model} sequence"
10 | rescue => e
11 | Rails.logger.error "Error resetting #{model} sequence: #{e.class.name}/#{e.message}"
12 | end
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabbcn/smartcitizen-api/cfd1394f9efd0bf49cba3b30b40b4e326d32bc7b/log/.keep
--------------------------------------------------------------------------------
/mqtt_subscriber.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | bundle exec rake mqtt:sub
3 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "browserslist": [
3 | "defaults",
4 | "IE 11"
5 | ],
6 | "dependencies": {
7 | "@popperjs/core": "^2.11.8",
8 | "@rails/activestorage": "^8.0.100",
9 | "@rails/ujs": "^7.1.3-4",
10 | "@rails/webpacker": "5.4.4",
11 | "autocompleter": "^9.3.2",
12 | "bootstrap": "^5.3.3",
13 | "bootstrap5-tags": "^1.7.6",
14 | "d3": "^7.9.0",
15 | "flatpickr": "^4.6.13",
16 | "jquery": "^3.7.1",
17 | "leaflet": "^1.9.4",
18 | "leaflet-defaulticon-compatibility": "^0.1.2",
19 | "sorted-btree": "^1.8.1",
20 | "strftime": "^0.10.3",
21 | "webpack": "^4.46.0",
22 | "webpack-cli": "^3.3.12"
23 | },
24 | "devDependencies": {
25 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
26 | "webpack-dev-server": "^3"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('postcss-import'),
4 | require('postcss-flexbugs-fixes'),
5 | require('postcss-preset-env')({
6 | autoprefixer: {
7 | flexbox: 'no-2009'
8 | },
9 | stage: 3
10 | })
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/public/examples/single_device.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": [
3 | { "t": "2015-02-02 15:59:52 +0000",
4 | "v": {
5 | "13": {
6 | "5": 2.0,
7 | "5_raw": 1450,
8 | "6": 13.319,
9 | "6_raw": 91390
10 | }
11 | }
12 | },
13 | { "t": "2015-02-01 15:59:52 +0000",
14 | "v": {
15 | "13": {
16 | "5": 1.0,
17 | "5_raw": 550,
18 | "6": 14.19,
19 | "6_raw": 34390
20 | }
21 | }
22 | }
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/public/examples/sockets.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Smart Citizen Websockets
6 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabbcn/smartcitizen-api/cfd1394f9efd0bf49cba3b30b40b4e326d32bc7b/public/favicon.ico
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow: /
3 |
--------------------------------------------------------------------------------
/release.sh:
--------------------------------------------------------------------------------
1 | # Build and publish image to DOCKER HUB
2 |
3 | set -ex
4 | USERNAME=smartcitizen
5 | IMAGE=api
6 | version=`cat VERSION`
7 |
8 | echo "VERSION: $version"
9 |
10 | docker build -t $USERNAME/$IMAGE:latest .
11 | docker tag $USERNAME/$IMAGE:latest $USERNAME/$IMAGE:$version
12 | docker push $USERNAME/$IMAGE:latest
13 | docker push $USERNAME/$IMAGE:$version
14 |
--------------------------------------------------------------------------------
/scripts/Dockerfile-kairos:
--------------------------------------------------------------------------------
1 | FROM openjdk:8-jdk-alpine
2 |
3 | RUN apk upgrade libssl1.0 --update-cache && \
4 | apk add wget \
5 | ca-certificates \
6 | gettext \
7 | bash
8 | RUN wget -O /tmp/kairosdb_dl.tar.gz \
9 | https://github.com/kairosdb/kairosdb/releases/download/v1.2.2/kairosdb-1.2.2-1.tar.gz
10 |
11 | RUN mkdir -p /opt/ && \
12 | cd /opt/ && \
13 | tar -xvf /tmp/kairosdb_dl.tar.gz
14 |
15 | COPY conf/kairosdb.properties /tmp/kairosdb.properties
16 | COPY runkairos.sh /usr/bin/runkairos.sh
17 | RUN chmod +x /usr/bin/runkairos.sh
18 |
19 | EXPOSE 4242 8080 2003 2004
20 | ENTRYPOINT [ "/usr/bin/runkairos.sh"]
21 |
22 | CMD [ "run" ]
23 |
--------------------------------------------------------------------------------
/scripts/cassandra/cassandra-settings.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Cassandra Recommended Settings
3 | DefaultDependencies=no
4 | After=sysinit.target local-fs.target
5 | Before=cassandra.service
6 |
7 | [Service]
8 | Type=oneshot
9 | ExecStart=/bin/sh -c 'echo never > /sys/kernel/mm/transparent_hugepage/defrag && \
10 | echo mq-deadline > /sys/block/sda/queue/scheduler && \
11 | echo 0 > /sys/class/block/sda/queue/rotational && \
12 | echo 8 > /sys/class/block/sda/queue/read_ahead_kb && \
13 | echo 0 > /proc/sys/vm/zone_reclaim_mode && \
14 | echo 32 > /sys/block/sda/queue/nr_requests'
15 |
16 | [Install]
17 | WantedBy=basic.target
18 |
--------------------------------------------------------------------------------
/scripts/cassandra/cassandra-sysctl.conf:
--------------------------------------------------------------------------------
1 | # Settings from https://docs.datastax.com/en/cassandra-oss/3.0/cassandra/install/installRecommendSettings.html
2 | net.core.rmem_max = 16777216
3 | net.core.wmem_max = 16777216
4 | net.core.rmem_default = 16777216
5 | net.core.wmem_default = 16777216
6 | net.core.optmem_max = 40960
7 | net.ipv4.tcp_rmem = 4096 87380 16777216
8 | net.ipv4.tcp_wmem = 4096 65536 16777216i
9 | vm.max_map_count = 1048575
10 |
--------------------------------------------------------------------------------
/scripts/cassandra/cassandra.service:
--------------------------------------------------------------------------------
1 | # /usr/lib/systemd/system/cassandra.service
2 |
3 | [Unit]
4 | Description=Cassandra
5 | After=network.target
6 | StartLimitInterval=200
7 | StartLimitBurst=5
8 |
9 | [Service]
10 | Type=forking
11 | PIDFile=/var/lib/cassandra/cassandra.pid
12 | User=cassandra
13 | Group=cassandra
14 | Environment="CASSANDRA_INCLUDE=/opt/cassandra/cassandra.in.sh"
15 | PassEnvironment="CASSANDRA_INCLUDE"
16 | ExecStart=/opt/cassandra/bin/cassandra -p /var/lib/cassandra/cassandra.pid
17 | Restart=always
18 | RestartSec=10
19 | SuccessExitStatus=143
20 | LimitMEMLOCK=infinity
21 | LimitNOFILE=10000
22 | LimitNPROC=32768
23 | LimitAS=infinity
24 |
25 | [Install]
26 | WantedBy=multi-user.target
27 |
--------------------------------------------------------------------------------
/scripts/cassandra/env.example:
--------------------------------------------------------------------------------
1 | STORAGE_DIR=/var/lib/cassandra
2 | LOG_DIR=/var/log/cassandra
3 | CLUSTER_NAME=clustername # Cluster name
4 | SEEDS=127.0.0.1 # CSV list of nodes on current cluster
5 | LISTEN_ADDRESS=127.0.0.1 # Private IP of this node
6 | RPC_ADDRESS=127.0.0.1 # Private IP of this node
7 | AUTHENTICATOR=PasswordAuthenticator
8 | ENDPOINT_SNITCH=GossipingPropertyFileSnitch
9 | MAX_HEAP_SIZE=2048M
10 | HEAP_NEWSIZE=512M
11 |
--------------------------------------------------------------------------------
/scripts/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Always pull from master? What if staging should deploy 'dev' branch?
3 | git pull origin master;
4 | docker compose pull auth;
5 | # Accept containers as params. Supports starting only 'app db' f.x.
6 | docker compose build && docker compose up -d $@
7 |
8 | # Do we want to auto migrate?
9 | # For now, we only check if migration is needed
10 | docker compose exec app bin/rails db:migrate:status
11 | #docker compose exec app bin/rails db:migrate
12 |
13 | echo $(date) $(git rev-parse HEAD) >> deploy_history.txt
14 |
--------------------------------------------------------------------------------
/scripts/dev-tools/get-token.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | if [ $# -eq 0 ]; then
4 | echo "Username or password missing"
5 | echo "Usage: get_token.sh USER PASSWORD localhost:3000"
6 | exit
7 | fi
8 |
9 | curl -XPOST 'http://'$3'/v0/sessions?username='$1'&password='$2 -d ''
10 |
--------------------------------------------------------------------------------
/scripts/dev-tools/query-readings.sh:
--------------------------------------------------------------------------------
1 | #/bin/bash
2 |
3 | # You need to install curl and jq
4 |
5 | # Gets reading for device 4
6 |
7 | if [ "$1" = "localhost" ]; then
8 | echo "Querying localhost"
9 | curl -s http://localhost:3000/v0/devices/4 | jq '.data.sensors[2].value'
10 | else
11 | echo "Querying staging"
12 | curl -s http://staging-api.smartcitizen.me/v0/devices/4 | jq '.data.sensors[2].value'
13 | fi
14 |
--------------------------------------------------------------------------------
/scripts/docker_backup_db.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | if ! [[ $1 ]]; then
3 | echo "Database name missing for BACKUP."
4 | echo "Usage: 'docker_backup_db.sh my_db_name'"
5 | exit
6 | fi
7 |
8 | #docker exec -i $(docker compose ps -q db) pg_dump -Upostgres $1 > dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql
9 | docker exec -i smartcitizen-api-db-1 pg_dump -Upostgres $1 > backup/dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql
10 |
--------------------------------------------------------------------------------
/scripts/docker_restore_db.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | if ! [[ $1 ]]; then
3 | echo "Database name missing for RESTORE."
4 | echo "Usage: 'docker_restore_db.sh my_db_name'"
5 | exit
6 | fi
7 |
8 | docker exec -i $(docker compose ps -q db) psql -Upostgres $1 < dump_latest.sql
9 |
--------------------------------------------------------------------------------
/scripts/grafana/agent.yaml:
--------------------------------------------------------------------------------
1 | server:
2 | log_level: info
3 | metrics:
4 | wal_directory: /tmp/grafana-agent-wal
5 | global:
6 | scrape_interval: 15s
7 | integrations:
8 | node_exporter:
9 | enabled: true
10 | instance: ${PROMETHEUS_INSTANCE_LABEL}
11 | prometheus_remote_write:
12 | - url: ${PROMETHEUS_URL}
13 | basic_auth:
14 | username: ${PROMETHEUS_USERNAME}
15 | password: ${PROMETHEUS_PASSWORD}
16 |
--------------------------------------------------------------------------------
/spec/controllers/v0/devices_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe V0::DevicesController do
4 | skip { is_expected.to permit(:name,:description,:mac_address,:latitude,:longitude,:elevation,:exposure,:meta,:user_tags).for(:create) }
5 | end
6 |
--------------------------------------------------------------------------------
/spec/controllers/v0/discourse_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe V0::DiscourseController, type: :controller do
4 |
5 | end
6 |
--------------------------------------------------------------------------------
/spec/controllers/v0/me_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe V0::MeController do
4 | skip { is_expected.to permit(:email,:username,:password,:city,:country_code,:url).for(:update) }
5 | end
6 |
--------------------------------------------------------------------------------
/spec/controllers/v0/measurements_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe V0::MeasurementsController do
4 | it { is_expected.to permit(:name,:description,:unit).for(:create) }
5 | end
6 |
--------------------------------------------------------------------------------
/spec/controllers/v0/oauth_applications_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe V0::OauthApplicationsController do
4 | skip { is_expected.to permit(:name,:description,:unit).for(:create) }
5 | end
6 |
--------------------------------------------------------------------------------
/spec/controllers/v0/onboarding/device_registrations_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe V0::Onboarding::DeviceRegistrationsController, type: :controller do
4 | it { is_expected.to permit(:email).for(:find_user, verb: :post) }
5 | end
6 |
--------------------------------------------------------------------------------
/spec/controllers/v0/onboarding/orphan_devices_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe V0::Onboarding::OrphanDevicesController, type: :controller do
4 | it { is_expected.to permit(:name, :description, :exposure, :latitude, :longitude,
5 | :user_tags).for(:create) }
6 |
7 | describe "save_orphan_device" do
8 | before do
9 | @controller = V0::Onboarding::OrphanDevicesController.new
10 | @controller.params = ActionController::Parameters.new
11 |
12 | allow_any_instance_of(OrphanDevice).to receive(:generate_token!).and_raise(ActiveRecord::RecordInvalid.new(OrphanDevice.new))
13 | end
14 |
15 | it 'tries 10 times generating_token & saving it' do
16 | expect(@controller).to receive(:raise).with(Smartcitizen::UnprocessableEntity.new)
17 | expect_any_instance_of(OrphanDevice).to receive(:generate_token!).exactly(10).times
18 | @controller.send(:create)
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/spec/controllers/v0/readings_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe V0::ReadingsController do
4 | it "should use strong parameters"
5 | end
6 |
--------------------------------------------------------------------------------
/spec/controllers/v0/sensors_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe V0::SensorsController do
4 | it { is_expected.to permit(:name,:description,:unit,:measurement_id).for(:create) }
5 | end
6 |
--------------------------------------------------------------------------------
/spec/controllers/v0/tags_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe V0::TagsController do
4 | it { is_expected.to permit(:name,:description).for(:create) }
5 | end
6 |
--------------------------------------------------------------------------------
/spec/controllers/v0/users_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe V0::UsersController do
4 | it { is_expected.to permit(:email,:username,:password,:city,:country_code,:url).for(:create) }
5 | end
6 |
--------------------------------------------------------------------------------
/spec/controllers/v1/devices_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe V1::DevicesController do
4 | skip { is_expected.to permit(:name,:description,:mac_address,:latitude,:longitude,:elevation,:exposure,:meta,:user_tags).for(:create) }
5 | end
6 |
--------------------------------------------------------------------------------
/spec/factories/backup_readings.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :backup_reading do
3 | data { "MyText" }
4 | mac { "MyString" }
5 | version { "MyString" }
6 | ip { "MyString" }
7 | created_at { "2015-11-18 14:32:27" }
8 | end
9 |
10 | end
11 |
--------------------------------------------------------------------------------
/spec/factories/bad_readings.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :bad_reading do
3 | tags { 1 }
4 | source_ip { "MyString" }
5 | data { "" }
6 | created_at { "2015-10-07 17:22:01" }
7 | end
8 |
9 | end
10 |
--------------------------------------------------------------------------------
/spec/factories/components.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :component do
3 | uuid { SecureRandom.uuid }
4 | association :device
5 | association :sensor
6 | end
7 |
8 | end
9 |
--------------------------------------------------------------------------------
/spec/factories/devices.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :device do
3 | uuid { SecureRandom.uuid }
4 | association :owner, factory: :user
5 | sequence("name") { |n| "device#{n}"}
6 | description { "my device" }
7 | mac_address { Faker::Internet.mac_address }
8 | latitude { 41.3966908 }
9 | longitude { 2.1921909 }
10 | elevation { 100 }
11 | hardware_info { { "id":47,"uuid":"7d45fead-defd-4482-bc6a-a1b711879e2d" } }
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/spec/factories/devices_inventory.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :device_inventory do
3 | report { "{'random_property':'random_result'}" }
4 | created_at { "2015-10-07 17:22:01" }
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/spec/factories/devices_tags.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :devices_tag do
3 | association :device
4 | association :tag
5 | end
6 |
7 | end
8 |
--------------------------------------------------------------------------------
/spec/factories/doorkeeper.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :access_grant, class: Doorkeeper::AccessGrant do
3 | sequence(:resource_owner_id) { |n| n }
4 | application
5 | redirect_uri { 'https://app.com/callback' }
6 | expires_in { 100 }
7 | scopes { 'public write' }
8 | end
9 |
10 | factory :access_token, class: Doorkeeper::AccessToken do
11 | sequence(:resource_owner_id) { |n| n }
12 | application
13 | expires_in { 2.hours }
14 |
15 | factory :clientless_access_token do
16 | application { nil }
17 | end
18 | end
19 |
20 | factory :application, class: Doorkeeper::Application do
21 | sequence(:name) { |n| "Application #{n}" }
22 | redirect_uri { 'https://app.com/callback' }
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/spec/factories/experiment.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :experiment do
3 | sequence("name") { |n| "experiment#{n}"}
4 | description { "my experiment" }
5 | association :owner, factory: :user
6 | is_test { false }
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/spec/factories/measurements.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :measurement do
3 | sequence(:name) { |i| "Temperature #{i}" }
4 | description { "How hot something is" }
5 | unit { "C" }
6 | end
7 | end
--------------------------------------------------------------------------------
/spec/factories/orphan_devices.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :orphan_device do
3 | name { "OrphanDeviceName" }
4 | description { "OrphanDeviceDescription" }
5 | exposure { "indoor" }
6 | # same coordinates used for testing Device
7 | latitude { 41.3966908 }
8 | longitude { 2.1921909 }
9 | user_tags { "tag1,tag2" }
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/spec/factories/sensors.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :sensor do
3 | name { "MiCS-2710" }
4 | description { "Metaloxide gas sensor" }
5 | unit { "KΩ" }
6 | default_key { "key_#{SecureRandom.alphanumeric(4)}"}
7 | end
8 | end
--------------------------------------------------------------------------------
/spec/factories/tag_sensors.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :tag_sensor do
3 | name { "TagSensor1" }
4 | description { "TagSensorDescription1" }
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/spec/factories/tags.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :tag do
3 | sequence(:name) { |n| "tag#{n}"}
4 | description { "tag description" }
5 | end
6 |
7 | end
8 |
--------------------------------------------------------------------------------
/spec/factories/users.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :user do
3 | uuid { SecureRandom.uuid }
4 | sequence(:username) { |n| "user#{n}" }
5 | sequence(:email) { |n| "user#{n}@bitsushi.com" }
6 | password { "password1" }
7 | password_confirmation { "password1"}
8 | ts_and_cs { true }
9 | url { "http://www.yahoo.com" }
10 | role_mask { 0 }
11 |
12 | factory :admin do
13 | role_mask { 5 }
14 | end
15 |
16 | factory :researcher do
17 | role_mask { 2 }
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/spec/fixtures/fake_device_data.csv:
--------------------------------------------------------------------------------
1 | TIME,TEMP,HUM
2 | ISO 8601,C,%
3 | Time,Temperature,Humidity
4 | ,55,56
5 | 2025-01-01T00:00:00Z,1.1,10.01
6 | 2025-01-01T00:01:00Z,2.2,20.02
7 | 2025-01-01T00:02:00Z,3.3,30.03
8 | 2025-01-01T00:03:00Z,4.4,40.04
9 | 2025-01-01T00:04:00Z,5.5,50.05
10 | 2025-01-01T00:05:00Z,null,50.05
11 | 2025-01-01T00:06:00Z,null,null
12 |
--------------------------------------------------------------------------------
/spec/jobs/check_battery_level_below_job_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe CheckBatteryLevelBelowJob, type: :job do
4 |
5 | it 'should update notify_low_battery_timestamp and send email' do
6 | device = create(:device, notify_low_battery: true, updated_at: "2023-01-01 00:00:00")
7 | updated_at_before = device.updated_at
8 | time_before = device.notify_low_battery_timestamp
9 | device.update_columns(data: { "10": '11'})
10 |
11 | expect(device.data["10"].to_i).to eq(11)
12 |
13 | CheckBatteryLevelBelowJob.perform_now
14 |
15 | device.reload
16 | expect(time_before).not_to eq(device.notify_low_battery_timestamp)
17 | expect(device.updated_at).to eq(updated_at_before)
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/spec/jobs/check_device_stopped_publishing_job_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe CheckDeviceStoppedPublishingJob, type: :job do
4 | pending "add some examples to (or delete) #{__FILE__}"
5 | end
6 |
--------------------------------------------------------------------------------
/spec/jobs/checkup_notify_job_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe CheckupNotifyJob, type: :job do
4 | pending "add some examples to (or delete) #{__FILE__}"
5 | end
6 |
--------------------------------------------------------------------------------
/spec/jobs/checkup_user_email_blank_job_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe CheckupUserEmailBlankJob, type: :job do
4 | pending "add some examples to (or delete) #{__FILE__}"
5 | end
6 |
--------------------------------------------------------------------------------
/spec/jobs/delete_archived_users_job_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe DeleteArchivedUsersJob, type: :job do
4 | describe "#perform_later" do
5 | ActiveJob::Base.queue_adapter = :test
6 |
7 | it "should have an enqueued job" do
8 | expect {
9 | DeleteArchivedUsersJob.perform_later
10 | }.to have_enqueued_job
11 | end
12 |
13 | it "should delete all archived users, created_at at least 72 hours ago" do
14 | userNormal = create(:user, username: "normalUser")
15 | userArchived = create(:user, username: "dontDeleteMe", workflow_state: "archived", created_at: 71.hours.ago)
16 | userArchived = create(:user, username: "deleteMe1", workflow_state: "archived", created_at: 73.hours.ago)
17 | userArchived = create(:user, username: "deleteMe2", workflow_state: "archived", created_at: 74.hours.ago)
18 | expect {
19 | DeleteArchivedUsersJob.perform_now
20 | }.to change(User.unscoped, :count).by(-2)
21 | end
22 | end
23 |
24 | end
25 |
--------------------------------------------------------------------------------
/spec/jobs/delete_orphaned_devices_job_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe DeleteOrphanedDevicesJob, type: :job do
4 |
5 | describe "#perform_later" do
6 | ActiveJob::Base.queue_adapter = :test
7 |
8 | it "should have an enqueued job" do
9 | expect {
10 | DeleteArchivedUsersJob.perform_later
11 | }.to have_enqueued_job
12 | end
13 |
14 | it "should delete all orphaned devices, older than 24 hours" do
15 | orp = create(:orphan_device, name: "dontDeleteMe", device_token: '123460', updated_at: 1.days.ago)
16 | orp = create(:orphan_device, name: "dontDeleteMe", device_token: '123457', updated_at: 8.days.ago)
17 | orp = create(:orphan_device, name: "dontDeleteMe", device_token: '123458', updated_at: 9.days.ago)
18 |
19 | expect(OrphanDevice.count).to eq 3
20 |
21 | expect {
22 | DeleteOrphanedDevicesJob.perform_now
23 | }.to change(OrphanDevice, :count).by(-2)
24 |
25 | expect(OrphanDevice.count).to eq 1
26 | end
27 | end
28 |
29 | end
30 |
--------------------------------------------------------------------------------
/spec/jobs/send_to_datastore_job_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe SendToDatastoreJob, type: :job do
4 | pending "add some examples to (or delete) #{__FILE__}"
5 | end
6 |
--------------------------------------------------------------------------------
/spec/lib/mqtt_forwarder_spec.rb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabbcn/smartcitizen-api/cfd1394f9efd0bf49cba3b30b40b4e326d32bc7b/spec/lib/mqtt_forwarder_spec.rb
--------------------------------------------------------------------------------
/spec/mailers/previews/user_mailer_preview.rb:
--------------------------------------------------------------------------------
1 | class UserMailerPreview < ActionMailer::Preview
2 |
3 | def welcome_email
4 | UserMailer.with(user: User.first).welcome(User.first.id)
5 | end
6 |
7 | def password_reset
8 | UserMailer.password_reset(User.first.id)
9 | end
10 |
11 | def device_archive
12 | UserMailer.device_archive(User.first.devices.first.id, User.first.id)
13 | end
14 |
15 | def device_battery_low
16 | UserMailer.device_battery_low(User.last.devices.first.id)
17 | end
18 |
19 | def device_stopped_publishing
20 | UserMailer.device_stopped_publishing(User.last.devices.first.id)
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/spec/models/devices_tag_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe DevicesTag, type: :model do
4 | pending "add some examples to (or delete) #{__FILE__}"
5 | end
6 |
--------------------------------------------------------------------------------
/spec/models/measurement_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Measurement, type: :model do
4 | it { is_expected.to have_many(:sensors) }
5 |
6 | it { is_expected.to validate_uniqueness_of(:name) }
7 | it { is_expected.to validate_presence_of(:name) }
8 | it { is_expected.to validate_presence_of(:description) }
9 | end
10 |
--------------------------------------------------------------------------------
/spec/models/sensor_tag_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe TagSensor, type: :model do
4 |
5 | let(:the_sensor) { build(:tag_sensor) }
6 |
7 | context 'SensorTag' do
8 | it "has a name and description from the factory" do
9 | expect( the_sensor.name ).to eq('TagSensor1')
10 | expect( the_sensor.description ).to eq('TagSensorDescription1')
11 | end
12 |
13 | it { is_expected.to validate_presence_of(:name) }
14 | it { should have_many(:sensors).through(:sensor_tags) }
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/spec/models/tag_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | RSpec.describe Tag, type: :model do
4 | pending "add some examples to (or delete) #{__FILE__}"
5 | end
6 |
--------------------------------------------------------------------------------
/spec/policies/application_policy_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe ApplicationPolicy do
4 | subject { ApplicationPolicy.new(user, Device.new) }
5 |
6 | context "for a visitor" do
7 | let(:user) { nil }
8 | it { is_expected.to_not permitz(:index) }
9 | it { is_expected.to_not permitz(:new) }
10 | it { is_expected.to_not permitz(:show) }
11 | it { is_expected.to_not permitz(:edit) }
12 | it { is_expected.to_not permitz(:create) }
13 | it { is_expected.to_not permitz(:update) }
14 | it { is_expected.to_not permitz(:destroy) }
15 | end
16 |
17 | end
--------------------------------------------------------------------------------
/spec/policies/component_policy_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe ComponentPolicy do
4 | subject { ComponentPolicy.new(user, component) }
5 | let(:component) { FactoryBot.build(:component) }
6 |
7 | context "for a visitor" do
8 | let(:user) { nil }
9 | it { is_expected.to permitz(:show) }
10 | end
11 |
12 | context "for a user" do
13 | let(:user) { FactoryBot.build(:user) }
14 | it { is_expected.to permitz(:show) }
15 | end
16 |
17 | context "for an admin" do
18 | let(:user) { FactoryBot.build(:admin) }
19 | it { is_expected.to permitz(:show) }
20 | end
21 |
22 | end
23 |
--------------------------------------------------------------------------------
/spec/policies/measurement_policy_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe MeasurementPolicy do
4 | subject { MeasurementPolicy.new(user, measurement) }
5 |
6 | let(:measurement) { FactoryBot.build(:measurement) }
7 |
8 | context "for a visitor" do
9 | let(:user) { nil }
10 | it { is_expected.to permitz(:show) }
11 | it { is_expected.to_not permitz(:update) }
12 | it { is_expected.to_not permitz(:create) }
13 | end
14 |
15 | context "for a user" do
16 | let(:user) { FactoryBot.create(:user) }
17 | it { is_expected.to permitz(:show) }
18 | it { is_expected.to_not permitz(:update) }
19 | it { is_expected.to_not permitz(:create) }
20 | end
21 |
22 | context "for an admin" do
23 | let(:user) { FactoryBot.create(:admin) }
24 | it { is_expected.to permitz(:show) }
25 | it { is_expected.to permitz(:update) }
26 | it { is_expected.to permitz(:create) }
27 | end
28 |
29 | end
30 |
--------------------------------------------------------------------------------
/spec/policies/oauth_application_policy_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe OauthApplicationPolicy do
4 |
5 | it "needs specs"
6 |
7 | end
--------------------------------------------------------------------------------
/spec/policies/password_reset_policy_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe PasswordResetPolicy do
4 | subject { PasswordResetPolicy.new(user, password_reset) }
5 |
6 | let(:password_reset) { FactoryBot.create(:user, password_reset_token: '12345') }
7 |
8 | context "for a visitor" do
9 | let(:user) { nil }
10 | it { is_expected.to permitz(:show) }
11 | it { is_expected.to_not permitz(:create) }
12 | it { is_expected.to_not permitz(:update) }
13 | end
14 |
15 | context "for a general user" do
16 | let(:user) { FactoryBot.create(:user) }
17 | it { is_expected.to permitz(:show) }
18 | it { is_expected.to permitz(:create) }
19 | it { is_expected.to_not permitz(:update) }
20 | end
21 |
22 | context "for the requesting user" do
23 | let(:user) { password_reset }
24 | it { is_expected.to permitz(:show) }
25 | it { is_expected.to permitz(:create) }
26 | it { is_expected.to permitz(:update) }
27 | end
28 |
29 | end
30 |
--------------------------------------------------------------------------------
/spec/policies/reading_policy_spec.rb:
--------------------------------------------------------------------------------
1 | # require 'spec_helper'
2 |
3 | # describe ReadingPolicy do
4 | # subject { ReadingPolicy.new(user, reading) }
5 |
6 | # let(:reading) { FactoryBot.create(:reading) }
7 |
8 | # skip "for a visitor" do
9 | # let(:user) { nil }
10 | # it { is_expected.to permitz(:show) }
11 | # it { is_expected.to permitz(:create) }
12 | # end
13 |
14 | # end
--------------------------------------------------------------------------------
/spec/policies/sensor_policy_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe SensorPolicy do
4 | subject { SensorPolicy.new(user, sensor) }
5 |
6 | let(:sensor) { FactoryBot.build(:sensor) }
7 |
8 | context "for a visitor" do
9 | let(:user) { nil }
10 | it { is_expected.to permitz(:show) }
11 | it { is_expected.to_not permitz(:update) }
12 | it { is_expected.to_not permitz(:create) }
13 | it { is_expected.to_not permitz(:destroy) }
14 | end
15 |
16 | context "for a user" do
17 | let(:user) { FactoryBot.create(:user) }
18 | it { is_expected.to permitz(:show) }
19 | it { is_expected.to_not permitz(:update) }
20 | it { is_expected.to_not permitz(:create) }
21 | it { is_expected.to_not permitz(:destroy) }
22 | end
23 |
24 | context "for an admin" do
25 | let(:user) { FactoryBot.create(:admin) }
26 | it { is_expected.to permitz(:show) }
27 | it { is_expected.to permitz(:update) }
28 | it { is_expected.to permitz(:create) }
29 | it { is_expected.to permitz(:destroy) }
30 | end
31 |
32 | end
33 |
--------------------------------------------------------------------------------
/spec/policies/tag_policy_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe TagPolicy do
4 | subject { TagPolicy.new(user, tag) }
5 |
6 | let(:tag) { FactoryBot.create(:tag) }
7 |
8 | context "for a visitor" do
9 | let(:user) { nil }
10 | it { is_expected.to permitz(:show) }
11 | it { is_expected.to_not permitz(:update) }
12 | it { is_expected.to_not permitz(:create) }
13 | it { is_expected.to_not permitz(:destroy) }
14 | end
15 |
16 | context "for a user" do
17 | let(:user) { FactoryBot.create(:user) }
18 | it { is_expected.to permitz(:show) }
19 | it { is_expected.to_not permitz(:update) }
20 | it { is_expected.to_not permitz(:create) }
21 | it { is_expected.to_not permitz(:destroy) }
22 | end
23 |
24 | context "for an admin" do
25 | let(:user) { FactoryBot.create(:admin) }
26 | it { is_expected.to permitz(:show) }
27 | it { is_expected.to permitz(:update) }
28 | it { is_expected.to permitz(:create) }
29 | it { is_expected.to permitz(:destroy) }
30 | end
31 |
32 | end
33 |
--------------------------------------------------------------------------------
/spec/policies/user_policy_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe UserPolicy do
4 | subject { UserPolicy.new(user, usermodel) }
5 |
6 | let(:usermodel) { FactoryBot.create(:user) }
7 |
8 | context "for a visitor" do
9 | let(:user) { nil }
10 | it { is_expected.to permitz(:show) }
11 | it { is_expected.to permitz(:create) }
12 | it { is_expected.to_not permitz(:update) }
13 | it { is_expected.to_not permitz(:destroy) }
14 | it { is_expected.to permitz(:request_password_reset) }
15 | it { is_expected.to permitz(:update_password) }
16 | end
17 |
18 | context "for a user" do
19 | let(:user) { usermodel }
20 | it { is_expected.to permitz(:show) }
21 | it { is_expected.to_not permitz(:create) }
22 | it { is_expected.to permitz(:update) }
23 | it { is_expected.to permitz(:destroy) }
24 | it { is_expected.to_not permitz(:request_password_reset) }
25 | it { is_expected.to_not permitz(:update_password) }
26 | end
27 |
28 | end
29 |
--------------------------------------------------------------------------------
/spec/requests/v0/application_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe V0::ApplicationController do
4 |
5 | describe "format" do
6 | it "(JSON) returns ugly JSON, with JSON Mimetype" do
7 | json = api_get '/devices'
8 | #expect( response.body.to_s ).to_not eq( JSON.pretty_generate(json) )
9 | expect(response.header['Content-Type']).to include('application/json')
10 | end
11 |
12 | skip "(JSON) returns pretty JSON, with JSON Mimetype if ?pretty=true" do
13 | json = api_get '/v0/devices?pretty=true'
14 | expect( response.body.to_s ).to eq( JSON.pretty_generate(json) )
15 | expect(response.header['Content-Type']).to include('application/json')
16 | end
17 |
18 | skip "(JSON-P) returns JS Mimetype if callback param present" do
19 | # rails now handles this
20 | api_get '/v0/devices?callback=something'
21 | expect(response.header['Content-Type']).to include('text/javascript')
22 | end
23 | end
24 |
25 | end
26 |
--------------------------------------------------------------------------------
/spec/requests/v0/errors_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe V0::ErrorsController do
4 |
5 | describe "GET 404" do
6 | it "returns 404 error" do
7 | j = api_get '/404'
8 | expect(j['id']).to eq('not_found')
9 | expect(response.status).to eq(404)
10 | expect(response.body).to match("Endpoint not found")
11 | end
12 | end
13 |
14 | skip "raises 500 error" do
15 | j = api_get '/test_error'
16 | expect(j['id']).to eq('internal_server_error')
17 | expect(response.status).to eq(500)
18 | end
19 |
20 | end
21 |
--------------------------------------------------------------------------------
/spec/requests/v0/oauth_applications_spec.rb:
--------------------------------------------------------------------------------
1 | require 'rails_helper'
2 |
3 | describe V0::OauthApplicationsController do
4 |
5 | let(:application) { create :application }
6 | let(:user) { create :user }
7 | let(:token) { create :access_token, application: application, resource_owner_id: user.id }
8 | let(:admin) { create :admin }
9 | let(:admin_token) { create :access_token, application: application, resource_owner_id: admin.id }
10 |
11 | it "needs specs"
12 |
13 | end
14 |
--------------------------------------------------------------------------------
/spec/support/api_macros.rb:
--------------------------------------------------------------------------------
1 | module ApiMacros
2 |
3 | def api_get action, p={}, version="0", h={}
4 | get "/v#{version}/#{action}", params:p, headers:h
5 | JSON.parse(response.body) rescue {}
6 | end
7 |
8 | def api_post action, p={}, version="0", h={}
9 | post "/v#{version}/#{action}", params:p, headers:h
10 | JSON.parse(response.body) rescue {}
11 | end
12 |
13 | def api_delete action, p={}, version="0", h={}
14 | delete "/v#{version}/#{action}", params:p, headers:h
15 | JSON.parse(response.body) rescue {}
16 | end
17 |
18 | def api_put action, p={}, version="0", h={}
19 | patch "/v#{version}/#{action}", params:p, headers:h
20 | JSON.parse(response.body) rescue {}
21 | end
22 |
23 | end
24 |
--------------------------------------------------------------------------------
/spec/support/env_vars.rb:
--------------------------------------------------------------------------------
1 | module EnvVars
2 |
3 | def set_env_var(name, value)
4 | allow(ENV).to receive(:[]).with(name).and_return(value)
5 | end
6 |
7 | end
8 |
--------------------------------------------------------------------------------
/spec/support/mailer_macros.rb:
--------------------------------------------------------------------------------
1 | module MailerMacros
2 | def last_email
3 | ActionMailer::Base.deliveries.last
4 | end
5 |
6 | def reset_email
7 | ActionMailer::Base.deliveries = []
8 | end
9 | end
10 |
11 | RSpec.configure do |config|
12 |
13 | config.before(:each) do
14 | reset_email
15 | end
16 |
17 | end
18 |
--------------------------------------------------------------------------------
/spec/support/pundit_macros.rb:
--------------------------------------------------------------------------------
1 | # Why .permitz? The global namespace is a bit overcrowded.
2 | # If you can think of a better term then please change it.
3 |
4 | RSpec::Matchers.define :permitz do |action|
5 | match do |policy|
6 | policy.public_send("#{action}?")
7 | end
8 |
9 | failure_message do |policy|
10 | "#{policy.class} does not permit #{action} on #{policy.record} for #{policy.user.inspect}."
11 | end
12 |
13 | failure_message_when_negated do |policy|
14 | "#{policy.class} does not forbid #{action} on #{policy.record} for #{policy.user.inspect}."
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/tmp/grafana_wal_data/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fablabbcn/smartcitizen-api/cfd1394f9efd0bf49cba3b30b40b4e326d32bc7b/tmp/grafana_wal_data/.keep
--------------------------------------------------------------------------------