Skip to content

Commit

Permalink
Add Community.
Browse files Browse the repository at this point in the history
While a community will eventually have multiple organizers, we need at least one person
to be responsible if an issue is raised.

The community has one owner.  In later PRs, there will be multiple members and multiple organizers.
  • Loading branch information
openbrian committed May 30, 2024
1 parent aca5b59 commit 74dc025
Show file tree
Hide file tree
Showing 36 changed files with 1,184 additions and 4 deletions.
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ gem "unicode-display_width"
# Keep ruby 3.0 compatibility
gem "multi_xml", "~> 0.6.0"

# Used to provide clean urls like /community/mappingdc
gem "friendly_id"

# Gems useful for development
group :development do
gem "better_errors"
Expand Down
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ GEM
ffi-libarchive (1.1.14)
ffi (~> 1.0)
file_exists (0.2.0)
friendly_id (5.4.2)
activerecord (>= 4.0.0)
frozen_record (0.27.1)
activemodel
fspath (3.1.2)
Expand Down Expand Up @@ -635,6 +637,7 @@ DEPENDENCIES
faraday
ffi-libarchive
file_exists
friendly_id
frozen_record
gd2-ffij (>= 0.4.0)
htmlentities
Expand Down
3 changes: 3 additions & 0 deletions app/abilities/ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def initialize(user)
can :index, ChangesetComment
can [:confirm, :confirm_resend, :confirm_email], :confirmation
can [:index, :rss, :show, :comments], DiaryEntry
can [:index, :show], Community
can [:index], Note
can [:new, :create, :edit, :update], :password
can [:index, :show], Redaction
Expand All @@ -44,6 +45,8 @@ def initialize(user)
can [:new, :create, :edit, :update, :comment, :subscribe, :unsubscribe], DiaryEntry
can [:make_friend, :remove_friend], Friendship
can [:new, :create, :reply, :show, :inbox, :outbox, :muted, :mark, :unmute, :destroy], Message
can [:create, :new], Community
can [:edit, :update], Community, { :organizer_id => user.id }
can [:close, :reopen], Note
can [:show, :edit, :update], :preference
can [:edit, :update], :profile
Expand Down
68 changes: 68 additions & 0 deletions app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,71 @@ $(document).ready(function () {
$("#edit_tab")
.attr("title", I18n.t("javascripts.site.edit_disabled_tooltip"));
});

window.showMap = function (id) {
var params = $("#" + id).data();
var map = L.map(id, {
attributionControl: false,
zoomControl: false
});
map.addLayer(new L.OSM.Mapnik());
var show_marker = true;
if (!params.lon || !params.lat) {
params.lon = 0;
params.lat = 0;
params.zoom = 1;
show_marker = false;
}
map.setView([params.lat, params.lon], params.zoom);
if (show_marker) {
L.marker([params.lat, params.lon], { icon: OSM.getUserIcon() }).addTo(map);
}
};

window.formMapInput = function (id, type) {
var map = L.map(id, {
attributionControl: false
});
map.addLayer(new L.OSM.Mapnik());

var lat_field = document.getElementById(type + "_latitude");
var lon_field = document.getElementById(type + "_longitude");

if (lat_field.value) {
map.setView([lat_field.value, lon_field.value], 12);
} else {
map.setView([0, 0], 0);
}

L.Control.Watermark = L.Control.extend({
onAdd: function () {
var container = map.getContainer();
var img = L.DomUtil.create("img");
img.src = "/assets/marker-blue.png"; // 25x41 px
// img.style.width = '200px';
img.style["margin-left"] = ((container.offsetWidth / 2) - 12) + "px";
img.style["margin-bottom"] = ((container.offsetHeight / 2) - 20) + "px";
return img;
},
onRemove: function () {
// Nothing to do here
}
});
L.control.watermark = function (opts) {
return new L.Control.Watermark(opts);
};
L.control.watermark({ position: "bottomleft" }).addTo(map);

map.on("move", function () {
var center = map.getCenter();
$("#" + type + "_latitude").val(center.lat);
$("#" + type + "_longitude").val(center.lng);
if ($("#" + type + "_min_lat")) {
var bounds = map.getBounds();
$("#" + type + "_min_lat").val(bounds._southWest.lat);
$("#" + type + "_max_lat").val(bounds._northEast.lat);
$("#" + type + "_min_lon").val(bounds._southWest.lng);
$("#" + type + "_max_lon").val(bounds._northEast.lng);
}
});
};
10 changes: 10 additions & 0 deletions app/assets/javascripts/communities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*global showMap,formMapInput*/


$(document).ready(function () {
if ($("#community_map_form").length) {
formMapInput("community_map_form", "community");
} else if ($("#community_map").length) {
showMap("community_map");
}
});
18 changes: 18 additions & 0 deletions app/assets/stylesheets/communities.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Place all the styles related to the Communities controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: https://sass-lang.com/

.community_details {
h1 {
margin-top: 0;
}
label {
font-weight: bold;
}
ul {
display: inline-block;
}
ul > li {
display: inline-block;
}
}
84 changes: 84 additions & 0 deletions app/controllers/communities_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
class CommunitiesController < ApplicationController
include UserMethods

layout "site"
before_action :authorize_web

before_action :set_community, :only => [:edit, :show, :update]

helper_method :recent_changesets

load_and_authorize_resource :except => [:create, :new]
authorize_resource

def index
display_name = params[:user_display_name]
if display_name
@user = User.active.find_by(:display_name => display_name)
if @user
@title = t ".title", :display_name => @user.display_name
@communities_organized = @user.communities_organized
else
render_unknown_user display_name
return
end
elsif current_user
@title = t ".title", :display_name => current_user.display_name
@communities_organized = current_user.communities_organized
end

@all_communities = Community.order(:name)
end

# GET /communities/mycity
# GET /communities/mycity.json
def show; end

def new
@title = t ".title"
@community = Community.new
end

def edit; end

def create
@community = Community.new(community_params)
@community.organizer = current_user
if @community.save
redirect_to @community, :notice => t(".success")
else
render "new"
end
end

def update
if @community.update(community_params)
redirect_to @community, :notice => t(".success")
else
flash.now[:alert] = t(".failure")
render :edit
end
end

private

def recent_changesets
bbox = @community.bbox.to_scaled
Changeset
.where("min_lon < ? and max_lon > ? and min_lat < ? and max_lat > ?",
bbox.max_lon.to_i, bbox.min_lon.to_i, bbox.max_lat.to_i, bbox.min_lat.to_i)
.order("changesets.id DESC").limit(20).preload(:user, :changeset_tags, :comments)
end

def set_community
@community = Community.friendly.find(params[:id])
end

def community_params
params.require(:community).permit(
:name, :location, :latitude, :longitude,
:min_lat, :max_lat, :min_lon, :max_lon,
:description
)
end
end
1 change: 1 addition & 0 deletions app/controllers/issues_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def index
@title = t ".title"

@issue_types = []
@issue_types.push("Community", "Note") if current_user.moderator?
@issue_types.push("Note", "User") if current_user.moderator?
@issue_types.push("DiaryEntry", "DiaryComment", "User") if current_user.administrator?

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/reports_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def issue_params

def default_assigned_role
case issue_params[:reportable_type]
when "Note"
when "Community", "Note"
"moderator"
when "User"
case report_params[:category]
Expand Down
4 changes: 4 additions & 0 deletions app/helpers/issues_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ def reportable_url(reportable)
user_url(reportable)
when DiaryComment
diary_entry_url(reportable.diary_entry.user, reportable.diary_entry, :anchor => "comment#{reportable.id}")
when Community
community_url(reportable)
when Note
note_url(reportable)
end
Expand All @@ -20,6 +22,8 @@ def reportable_title(reportable)
reportable.display_name
when DiaryComment
I18n.t("issues.helper.reportable_title.diary_comment", :entry_title => reportable.diary_entry.title, :comment_id => reportable.id)
when Community
reportable.name
when Note
I18n.t("issues.helper.reportable_title.note", :note_id => reportable.id)
end
Expand Down
64 changes: 64 additions & 0 deletions app/models/community.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# == Schema Information
#
# Table name: communities
#
# id :bigint(8) not null, primary key
# name :string not null
# description :text not null
# organizer_id :bigint(8) not null
# slug :string not null
# location :string not null
# latitude :float not null
# longitude :float not null
# min_lat :float not null
# max_lat :float not null
# min_lon :float not null
# max_lon :float not null
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_communities_on_organizer_id (organizer_id)
# index_communities_on_slug (slug) UNIQUE
#
# Foreign Keys
#
# fk_rails_... (organizer_id => users.id)
#

# At this time a community has one organizer. The first organizer is
# the user that created the community.

class Community < ApplicationRecord
extend FriendlyId
friendly_id :name, :use => :slugged

belongs_to :organizer, :class_name => "User"

validates :name, :presence => true, :length => 1..255, :characters => true
validates :description, :presence => true, :length => 1..1023, :characters => true
validates :location, :presence => true, :length => 1..255, :characters => true
validates :latitude, :numericality => true, :inclusion => { :in => -90..90 }
validates :longitude, :numericality => true, :inclusion => { :in => -180..180 }
validates :min_lat, :numericality => true, :inclusion => { :in => -90.0..90.0 }
validates :max_lat, :numericality => true, :inclusion => { :in => -90.0..90.0 }
validates :min_lon, :numericality => true, :inclusion => { :in => -180.0..180.0 }
validates :max_lon, :numericality => true, :inclusion => { :in => -180.0..180.0 }

def longitude=(longitude)
super(OSM.normalize_longitude(longitude))
end

def min_lon=(longitude)
super(OSM.normalize_longitude(longitude))
end

def max_lon=(longitude)
super(OSM.normalize_longitude(longitude))
end

def bbox
BoundingBox.new(min_lon, min_lat, max_lon, max_lat)
end
end
2 changes: 2 additions & 0 deletions app/models/issue.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ def set_reported_user
self.reported_user = case reportable.class.name
when "User"
reportable
when "Community"
reportable.organizer
when "Note"
reportable.author
else
Expand Down
1 change: 1 addition & 0 deletions app/models/report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Report < ApplicationRecord
def self.categories_for(reportable)
case reportable.class.name
when "DiaryEntry", "DiaryComment" then %w[spam offensive threat other]
when "Community" then %w[spam offensive other]
when "User" then %w[spam offensive threat vandal other]
when "Note" then %w[spam personal abusive other]
else %w[other]
Expand Down
2 changes: 2 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ class User < ApplicationRecord

has_many :reports

has_many :communities_organized, :class_name => "Community", :foreign_key => :organizer_id, :inverse_of => :organizer

scope :visible, -> { where(:status => %w[pending active confirmed]) }
scope :active, -> { where(:status => %w[active confirmed]) }
scope :identifiable, -> { where(:data_public => true) }
Expand Down
33 changes: 33 additions & 0 deletions app/views/communities/_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<%= stylesheet_link_tag "communities" %>
<%= javascript_include_tag "communities" %>

<p>
All fields are required.
</p>
<%= bootstrap_form_for @community do |form| %>
<%= form.text_field :name, :id => :community_name %>
<%= form.text_field :location, :id => :community_location %>
<div class="row">
<%= form.text_field :latitude, :wrapper_class => "col-sm-6", :id => :community_latitude %>
<%= form.text_field :longitude, :wrapper_class => "col-sm-6", :id => :community_longitude %>
</div>
<div class="community_set_location">
<div id="community_map_form" class="content_map">
</div>
</div>
<div class="row">
<div class="col-sm-4"></div>
<%= form.text_field :max_lat, :wrapper_class => "col-sm-4", :id => :community_max_lat %>
</div>
<div class="row">
<%= form.text_field :min_lon, :wrapper_class => "col-sm-4", :id => :community_min_lon %>
<div class="col-sm-4"></div>
<%= form.text_field :max_lon, :wrapper_class => "col-sm-4", :id => :community_max_lon %>
</div>
<div class="row">
<div class="col-sm-4"></div>
<%= form.text_field :min_lat, :wrapper_class => "col-sm-4", :id => :community_min_lat %>
</div>
<%= form.text_area :description, :id => :community_description %>
<%= form.primary %>
<% end %>
Loading

0 comments on commit 74dc025

Please sign in to comment.