Skip to content

Commit

Permalink
Neighborhood: Assign an Operator and create a Space on first run (
Browse files Browse the repository at this point in the history
#1141)

* `Neighborhood` Get rid of a "Default" Space

This gets rid of the idea of "Default" Space, in favor of a first-run
experience where an `Operator` creates a Space

* `Space`: A temporary attribute of Blueprint

OK, so maybe this should be persisted? But whatever, for now it makes sure
we can create a space via the API.

* `Neighborhood`: Allow `Authentication` at `Neibhorhood` level

Turns out, the `authenticated_session` path was implicitely a Space-only
action. With `Neighborhood` becoming the Root, we no longer can expect
sessions to only be at the Space level!

* `Neighborhood`: Provide step-by-step setup instructions

1. `Operator`s are told to configure email if they haven't already
2. `Operator`s are told how to make themselves an operator via the Rails
   console
3. Only `Operator`s may create a `Space`
4. All Links to Sign in and Out at the `Neighborhood` level work
  • Loading branch information
zspencer authored Mar 2, 2023
1 parent 7cf8d6e commit b65a233
Show file tree
Hide file tree
Showing 24 changed files with 163 additions and 71 deletions.
4 changes: 1 addition & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,9 @@ SMTP_ENABLE_TLS=false
PORT=3000
# Used to build URLs in mailers
APP_ROOT_URL=http://localhost:3000
EMAIL_DEFAULT_FROM='Convene Support <[email protected]>'
EMAIL_DEFAULT_FROM='Neighborhood Support <[email protected]>'

# Ensures the data in our database is fully encrypted
LOCKBOX_MASTER_KEY=0000000000000000000000000000000000000000000000000000000000000000


DEFAULT_SPACE=system-test
OPERATOR_API_KEY=a-secret-that-should-get-changed
14 changes: 4 additions & 10 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class ApplicationController < ActionController::Base

# @todo this should be tested 🙃 halp me
def ensure_on_byo_domain

return if current_space.blank?
if request.get? && current_space.branded_domain.present? && request.host != current_space.branded_domain
Rails.logger.debug("Request Host: #{request.host}")
redirect_url = URI(request.url)
Expand Down Expand Up @@ -114,16 +114,8 @@ def pundit_user
if params[:space_id]
space_repository.friendly.find(params[:space_id])
else
BrandedDomainConstraint.new(space_repository).space_for_request(request) ||
space_repository.friendly.find(params[:id])
BrandedDomainConstraint.new(space_repository).space_for_request(request)
end
rescue ActiveRecord::RecordNotFound
begin
@current_space ||= space_repository.default
rescue ActiveRecord::RecordNotFound
Rails.logger.error("No default space exists!")
@current_space = nil
end
end

helper_method def current_membership
Expand All @@ -139,6 +131,8 @@ def space_repository
# Retrieves the room based upon the current_space and params
# @return [nil, Room]
helper_method def current_room
return nil if current_space.blank?

@current_room ||=
policy_scope(current_space.rooms).friendly.find(
params[:room_id] || params[:id]
Expand Down
6 changes: 3 additions & 3 deletions app/controllers/authenticated_sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ class AuthenticatedSessionsController < ApplicationController
skip_after_action :verify_policy_scoped

def show
redirect_to current_space if authenticated_session.save
redirect_to(current_space.presence || :root) if authenticated_session.save
end

def new
end

def create
if authenticated_session.save
redirect_to current_space
redirect_to(current_space.presence || :root)
else
render :create, status: :unprocessable_entity
end
Expand All @@ -21,7 +21,7 @@ def create

def destroy
authenticated_session.destroy
redirect_to current_space
redirect_to(current_space.presence || :root)
end

helper_method def authenticated_session
Expand Down
8 changes: 8 additions & 0 deletions app/controllers/neighborhoods_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class NeighborhoodsController < ApplicationController
def show
end

helper_method def neighborhood
@neighborhood ||= authorize(Neighborhood.new)
end
end
38 changes: 33 additions & 5 deletions app/controllers/spaces_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ class SpacesController < ApplicationController
def show
end

def new
space
end

def edit
end

Expand All @@ -14,10 +18,24 @@ def create

space = Space::Factory.create(space_params)

if space.persisted?
render json: Space::Serializer.new(space).to_json, status: :created
else
render json: Space::Serializer.new(space).to_json, status: :unprocessable_entity
respond_to do |format|
if space.persisted?
format.json do
render json: Space::Serializer.new(space).to_json, status: :created
end

format.html do
redirect_to space.location
end
else
format.json do
render json: Space::Serializer.new(space).to_json, status: :unprocessable_entity
end

format.html do
render :new
end
end
end
end

Expand All @@ -38,8 +56,18 @@ def space_params
end

helper_method def space
@space ||= current_space.tap do |space|
@space ||= if params[:id]
policy_scope(Space).friendly.find(params[:id])
elsif params[:space]
policy_scope(Space).new(space_params)
else
policy_scope(Space).new
end.tap do |space|
authorize(space)
end
end

helper_method def current_space
space.persisted? ? space : nil
end
end
2 changes: 2 additions & 0 deletions app/models/authentication_method.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class AuthenticationMethod < ApplicationRecord
before_validation :set_one_time_password_secret, only: :create
before_validation :set_person, only: :create

scope :via_email, -> { where(contact_method: :email) }

def set_one_time_password_secret
self.one_time_password_secret ||= ROTP::Base32.random
end
Expand Down
33 changes: 27 additions & 6 deletions app/models/neighborhood.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
# frozen_string_literal: true

class Neighborhood
def self.config
@config ||= Config.new
def spaces
Space.all
end

class Config
def default_space_slug
ENV.fetch("DEFAULT_SPACE", "convene")
end
def email_configured?
if email_configuration[:address] == "localhost"
email_configuration.except(:user_name, :password)
else
email_configuration
end.values.none?(:nil?) &&
ENV["APP_ROOT_URL"].present? &&
ENV["EMAIL_DEFAULT_FROM"].present?
end

def operators
Person.where(operator: true)
end

def email_configuration
{
address: ENV["SMTP_ADDRESS"],
domain: ENV["SMTP_DOMAIN"],
port: ENV["SMTP_PORT"].to_i,
user_name: ENV["SMTP_USERNAME"],
password: ENV["SMTP_PASSWORD"],
authentication: ENV["SMTP_AUTHENTICATION"]&.to_sym,
tls: ENV.fetch("SMTP_ENABLE_TLS", true) != "false",
enable_starttls_auto: ENV.fetch("SMTP_ENABLE_TLS", true) != "false"
}
end
end
4 changes: 2 additions & 2 deletions app/models/space.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ class Space < ApplicationRecord

belongs_to :entrance, class_name: "Room", optional: true

scope :default, -> { friendly.find(Neighborhood.config.default_space_slug) }

# @see {Utilities}
# @see {UtilityHookup}
# @returns {ActiveRecord::Relation<UtilityHookups>}
Expand All @@ -53,4 +51,6 @@ class Space < ApplicationRecord
def parent_location
[]
end

attr_accessor :blueprint
end
5 changes: 5 additions & 0 deletions app/policies/neighborhood_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class NeighborhoodPolicy < ApplicationPolicy
def show?
true
end
end
14 changes: 8 additions & 6 deletions app/views/application/_breadcrumbs.html.erb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<div>
<%- if policy(current_space).edit? %>
<%= link_to "", [:edit, current_space], class: "icon --configure", aria: { label: "Configure Space" }, role: "img" %>
<% end %>
<%= breadcrumbs container_tag: 'nav', fragment_class: 'crumb', separator: " &rsaquo; ", semantic: true, display_single_fragment: true %>
</div>
<%- if current_space.present? %>
<div>
<%- if policy(current_space).edit? %>
<%= link_to "", [:edit, current_space], class: "icon --configure", aria: { label: "Configure Space" }, role: "img" %>
<% end %>
<%= breadcrumbs container_tag: 'nav', fragment_class: 'crumb', separator: " &rsaquo; ", semantic: true, display_single_fragment: true %>
</div>
<%- end %>
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Your one time password is <%= @authentication_method.one_time_password %>

Or, tap here: <%= space_authenticated_session_url(@space, authenticated_session: { one_time_password: @authentication_method.one_time_password, authentication_method_id: @authentication_method.id }) %>
Or, tap here: <%= polymorphic_url([@space, :authenticated_session].compact, authenticated_session: { one_time_password: @authentication_method.one_time_password, authentication_method_id: @authentication_method.id }) %>
4 changes: 2 additions & 2 deletions app/views/layouts/_header.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
<%- if current_membership.present? %>
<%= link_to "Account", [current_space, current_membership], title: current_membership.member.email %>
<%- end %>
<%= link_to "Sign out", [current_space, :authenticated_session], data: { turbo: true, turbo_method: :delete }, class: 'sign-out' %>
<%= link_to "Sign out", [current_space, :authenticated_session].compact, data: { turbo: true, turbo_method: :delete }, class: 'sign-out' %>
<%- else %>
<%= link_to "Sign in", [:new, current_space, :authenticated_session], class: "sign-in" %>
<%= link_to "Sign in", [:new, current_space, :authenticated_session].compact, class: "sign-in" %>
<%- end %>
</nav>
</header>
Expand Down
2 changes: 1 addition & 1 deletion app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<%- if current_room && policy(current_room).edit? %>
<%= link_to t('icons.edit'), [:edit, current_space, current_room], class: 'no-underline', aria: { label: "Configure Room"} %>
<%- end %>
<%= link_to "Convene", "https://convene.zinc.coop/" %>: Space to Work, Play, or Simply Be
<%= link_to "Convene", root_url %>: Space to Work, Play, or Simply Be
</div>
<div>
💾&nbsp;
Expand Down
35 changes: 35 additions & 0 deletions app/views/neighborhoods/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<%- @pagy, @records = pagy(policy_scope(neighborhood.spaces)) %>

<section class="max-w-3xl mx-auto">
<div class="mt-3 grid grid-cols-1 gap-5 sm:gap-6 sm:grid-cols-2 lg:grid-cols-4">
<%= render @records %>
</div>

<%== pagy_nav(@pagy, nav_extra: 'flex justify-between') %>
<%- if policy(Space).new? %>
<div class="text-center w-full">
<%= link_to "Add a Space",[:new, :space] %>
</div>
<%- end %>
</section>

<%- if !neighborhood.email_configured? %>
<p>It looks like you haven't configured Convene to send email. Check the <code>.env.example</code> file for variables named with <code>SMTP</code> and make sure each is set with your email providers configuration.</p>

<p>If you're hosting for you and your friends, you can use your current email service provider and send emails as yourself. If you want to go bigger, we recommend setting up a dedicated transactional email service, such as Mailgun.<p>

<%- elsif neighborhood.operators.blank? %>
<p>It looks like you don't have anyone set up as an Operator yet! To make yourself an Operator:

<ol>
<%- if current_person&.authenticated? %>
<li>Open a <code>rails console</code> on your server's command line.</li>
<li>Find yourself: <code>me = Person.joins(:authentication_methods).find_by(authentication_methods: { contact_method: :email, contact_location: "<%= current_person.authentication_methods.via_email.first.contact_location %>" })</code></li>
<li>Set your operator bit to true: <code>me.update(operator: true)</code></li>
<li>Reload this page!</li>
<%- else %>
<li><%= link_to "Sign in!", [:new, :authenticated_session]%></li>
<% end%>
</ol>
<%- end %>
3 changes: 1 addition & 2 deletions app/views/rooms/_room.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@

<div class="px-6 pt-3">
<ul class="mt-3 grid grid-cols-1 gap-5 sm:gap-6 sm:grid-cols-2 lg:grid-cols-4">
<% policy_scope(current_space.rooms.listed).each do |room| %>
<% policy_scope(room.space.rooms.listed).each do |room| %>
<%= render partial: 'spaces/room_card', locals: { room: room } %>
<% end %>
</ul>
</div>
</div>

5 changes: 5 additions & 0 deletions app/views/spaces/_space.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<%= render CardComponent.new do %>
<%= link_to space, class: "no-underline" do %>
<h2 class="text-center p-2"><%= space.name %></h2>
<%- end %>
<%- end %>
9 changes: 9 additions & 0 deletions app/views/spaces/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<%= form_with(model: space) do |form| %>
<%= render "text_field", attribute: :name, form: form %>
<%= form.fields_for(:client, space.client || space.build_client) do |client_form|%>
<%= render "text_field", attribute: :name, form: client_form %>
<%- end %>
<%= form.submit %>
<%- end %>
11 changes: 1 addition & 10 deletions config/initializers/action_mailer.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
Rails.application.reloader.to_prepare do
if ENV["SMTP_ADDRESS"]
ActionMailer::Base.smtp_settings = {
address: ENV["SMTP_ADDRESS"],
domain: ENV["SMTP_DOMAIN"],
port: ENV["SMTP_PORT"].to_i,
user_name: ENV["SMTP_USERNAME"],
password: ENV["SMTP_PASSWORD"],
authentication: ENV["SMTP_AUTHENTICATION"]&.to_sym,
tls: ENV.fetch("SMTP_ENABLE_TLS", true) != "false",
enable_starttls_auto: ENV.fetch("SMTP_ENABLE_TLS", true) != "false"
}.compact
ActionMailer::Base.smtp_settings = Neighborhood.new.email_configuration.compact

ActionMailer::Base.delivery_method = :smtp
end
Expand Down
5 changes: 5 additions & 0 deletions config/locales/client/en.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
en:
activerecord:
attributes:
client:
name: Client Name
13 changes: 6 additions & 7 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,31 +1,30 @@
# frozen_string_literal: true

Rails.application.routes.draw do
root "spaces#show"
mount Rswag::Ui::Engine => "/api-docs"
mount Rswag::Api::Engine => "/api-docs"

resources :authentication_methods, only: %i[create]
resource :authenticated_session, only: %i[new create update destroy show]

# get "/auth/:provider/callback", "sessions#create"
# post "/auth/:provider/callback", "sessions#create"
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
resources :spaces, only: %I[show edit update create destroy] do
resources :spaces, only: %I[new create show edit update create destroy] do
SpaceRoutes.append_routes(self)
end

resources :memberships, only: %I[create]

resource :me, only: %i[show], controller: "me"

constraints DefaultSpaceConstraint.new(Space) do
mount Rswag::Ui::Engine => "/api-docs"
mount Rswag::Api::Engine => "/api-docs"
end

constraints BrandedDomainConstraint.new(Space) do
get :edit, to: "spaces#edit"
get "/" => "spaces#show"
put "/" => "spaces#update"

SpaceRoutes.append_routes(self)
end

root "neighborhoods#show"
end
3 changes: 3 additions & 0 deletions features/rooms/entering-rooms.feature
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ Feature: Entering Rooms
However, we also want to support Reople entering a Room via
the Room's full URI or from a short URI when a Space has a Branded Domain.

Background:
Given a "System Test" Space

@built
Scenario: Entering Room via Room Picker from Space Dashboard
Given the Space Member is on the "System Test" Space Dashboard
Expand Down
Loading

0 comments on commit b65a233

Please sign in to comment.