40 Commits

Author SHA1 Message Date
eac8fa6edb 0.9.0
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-07 14:48:27 +01:00
43f918a074 Update liquor-cabinet image, fix LC/redis networking issue on Linux
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-06 22:07:35 +01:00
e322867d79 Merge pull request 'Fix login redirect for existing RS auth' (#180) from bugfix/178-rs_login_redirect into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #180
2024-03-06 21:06:27 +00:00
4d6fa318b7 Fix login redirect for existing RS auth
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
fixes #178
2024-03-06 22:00:15 +01:00
4e8878a4b5 Merge pull request 'Allow running specs in Docker container, update README' (#177) from dev/docker_rspec into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #177
Reviewed-by: galfert <garret.alfert@gmail.com>
2024-03-03 11:47:53 +00:00
e65b890880 Update db schema
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2024-03-02 17:31:44 +01:00
f57edd4d3b Update README to account for Docker Compose everywhere
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-03-02 16:57:07 +01:00
1afd56fb80 Allow running specs in Docker (Web) container 2024-03-02 16:56:07 +01:00
71669a4b96 Merge pull request 'Refactor admin settings routes' (#156) from feature/content_settings into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #156
2024-03-02 14:30:21 +00:00
c312e30c17 Fix link in admin settings/services sidenav
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 2s
2024-03-02 15:26:12 +01:00
51f4556ede Refactor admin settings routes
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
This is much cleaner, and semantically more correct.
2024-03-02 14:22:08 +00:00
4fa4ae6b54 Merge pull request 'Comment out settings in .env.example' (#175) from task/env-example into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #175
Reviewed-by: Râu Cao <raucao@kosmos.org>
2024-03-02 13:30:18 +00:00
869ff4691b Comment out settings in .env.example
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 2s
2024-03-02 12:43:59 +01:00
822a2dc018 Fix specs
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-01 17:15:02 +01:00
5b7fc3707b Hide avatar settings behind feature flag
Some checks failed
continuous-integration/drone/push Build is failing
In favor of #157
2024-03-01 11:13:49 +01:00
0e2dc54dc6 Merge pull request 'Upgrade Rails to 7.1, update dependencies, require Ruby 3.x' (#160) from chore/update_dependencies into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #160
Reviewed-by: slvrbckt <slvrbckt@noreply.kosmos.org>
2024-02-27 18:56:59 +00:00
87f09c94d0 Merge pull request 'Fix/improve local ActiveStorage backend usage and handling of WebApp icons' (#162) from bugfix/local_web_app_icons into chore/update_dependencies
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 2s
Reviewed-on: #162
Reviewed-by: greg <greg@noreply.kosmos.org>
2024-02-27 16:07:55 +00:00
b33b8104a8 Fix typo
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2024-02-27 14:33:37 +01:00
4a4a222973 Merge branch 'chore/update_dependencies' into bugfix/local_web_app_icons
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-23 18:25:23 +00:00
8c524abcf5 Merge pull request 'Fix Docker volume permissions on some host platforms' (#171) from bugfix/macos_docker_volumes into chore/update_dependencies
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Reviewed-on: #171
Reviewed-by: galfert <garret.alfert@gmail.com>
2024-02-23 18:24:10 +00:00
a852ab75ae Fix Docker volume permissions on some host platforms
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 2s
Use named volumes instead of bind mounts.
2024-02-23 16:43:56 +01:00
de1f234c15 Merge branch 'chore/update_dependencies' into bugfix/local_web_app_icons
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-22 15:13:18 +01:00
4581900427 Merge pull request 'Fix Ruby in Docker container on Apple silicon' (#168) from chore/fix_docker_ruby_on_apple_silicon into chore/update_dependencies
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Reviewed-on: #168
Reviewed-by: slvrbckt <slvrbckt@noreply.kosmos.org>
2024-02-22 14:12:05 +00:00
56d91083e5 Fix seeds for new keyword argument
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2024-02-22 13:24:41 +01:00
ba7c3795f8 Add pkg-config
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-22 11:29:56 +01:00
bbf3fb91a0 Fix Ruby in Docker container on Apple silicon
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-22 10:47:21 +01:00
1754df73cb Merge pull request 'Allow admins to add and remove invitations per account' (#167) from feature/164-invites into chore/update_dependencies
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Reviewed-on: #167
Reviewed-by: galfert <garret.alfert@gmail.com>
2024-02-17 10:17:47 +00:00
9a1f9abf84 Formatting
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2024-02-10 12:53:26 +01:00
2753388e1e Add specs for admin user management 2024-02-10 12:53:11 +01:00
f3159d30f1 Allow admins to add and remove invitations per account
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-10 11:21:45 +01:00
ca238be6f4 Add option for hiding close button in modal windows 2024-02-10 10:24:09 +01:00
8747ce4eb0 Remove multi-domain support on admin user pages
All checks were successful
continuous-integration/drone/push Build is passing
refs #166
2024-02-10 08:55:15 +01:00
fcda3b9c8c WIP Make dropdowns more configurable, add invitations menu to admin page 2024-02-09 18:57:07 +01:00
bd1b177993 Rescue all icon download/upload errors, send to Sentry
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-02-08 13:36:17 +01:00
3f110995a4 Add timestamp to icon filenames
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
There can be race condition when a background job is supposed to delete
an icon while there is a new one being attached. Also, this encodes the
date/time when the icon has been added, for inspection and convenience.
2024-02-08 13:03:32 +01:00
a7410058fa Save WebApp before fetching icons 2024-02-08 13:02:08 +01:00
411587456b Destroy dependent RS auths when destroying a WebApp 2024-02-08 13:01:19 +01:00
84e915ece9 Allow custom path for ActiveStorage local/disk backend 2024-02-08 13:01:07 +01:00
70ac3b0a70 Fix RS dashboard for auths without Web App
RS auths without a valid domain name will not fetch any metadata and
therefore not create a WebApp record. This fixes icons being looked up
anyway, resulting in exceptions
2024-02-08 12:51:53 +01:00
a7cbd8ce36 Allow disabling S3 explicitly, disable in Docker Compose
For example when there is a .env.development for running the app on a
host machine directly, but as a developer you also want to run it with
Docker Compose from time to time.
2024-02-08 12:50:34 +01:00
46 changed files with 406 additions and 174 deletions

View File

@@ -1,14 +1,14 @@
PRIMARY_DOMAIN=kosmos.org
AKKOUNTS_DOMAIN=accounts.example.com
# PRIMARY_DOMAIN=kosmos.org
# AKKOUNTS_DOMAIN=accounts.example.com
SMTP_SERVER=smtp.example.com
SMTP_PORT=587
SMTP_LOGIN=accounts
SMTP_PASSWORD=123abc
SMTP_FROM_ADDRESS=accounts@example.com
SMTP_DOMAIN=example.com
SMTP_AUTH_METHOD=plain
SMTP_ENABLE_STARTTLS=auto
# SMTP_SERVER=smtp.example.com
# SMTP_PORT=587
# SMTP_LOGIN=accounts
# SMTP_PASSWORD=123abc
# SMTP_FROM_ADDRESS=accounts@example.com
# SMTP_DOMAIN=example.com
# SMTP_AUTH_METHOD=plain
# SMTP_ENABLE_STARTTLS=auto
# S3_ENABLED=true
# S3_ENDPOINT=https://s3.kosmos.org
@@ -18,47 +18,47 @@ SMTP_ENABLE_STARTTLS=auto
# S3_ACCESS_KEY=123456abcdefg
# S3_SECRET_KEY=123456789123456789123456789
LDAP_HOST=localhost
LDAP_PORT=389
LDAP_ADMIN_PASSWORD=passthebutter
LDAP_SUFFIX='dc=kosmos,dc=org'
# LDAP_HOST=localhost
# LDAP_PORT=389
# LDAP_ADMIN_PASSWORD=passthebutter
# LDAP_SUFFIX='dc=kosmos,dc=org'
REDIS_URL='redis://localhost:6379/1'
# REDIS_URL='redis://localhost:6379/1'
WEBHOOKS_ALLOWED_IPS='10.1.1.163'
# WEBHOOKS_ALLOWED_IPS='10.1.1.163'
#
# Service Integrations
#
BTCPAY_API_URL='http://localhost:23001/api/v1'
BTCPAY_STORE_ID=''
BTCPAY_AUTH_TOKEN=''
# BTCPAY_API_URL='http://localhost:23001/api/v1'
# BTCPAY_STORE_ID=''
# BTCPAY_AUTH_TOKEN=''
DISCOURSE_PUBLIC_URL='https://community.kosmos.org'
DISCOURSE_CONNECT_SECRET='discourse_connect_ftw'
# DISCOURSE_PUBLIC_URL='https://community.kosmos.org'
# DISCOURSE_CONNECT_SECRET='discourse_connect_ftw'
DRONECI_PUBLIC_URL='https://drone.kosmos.org'
# DRONECI_PUBLIC_URL='https://drone.kosmos.org'
EJABBERD_ADMIN_URL='https://xmpp.kosmos.org/admin'
EJABBERD_API_URL='https://xmpp.kosmos.org/api'
# EJABBERD_ADMIN_URL='https://xmpp.kosmos.org/admin'
# EJABBERD_API_URL='https://xmpp.kosmos.org/api'
GITEA_PUBLIC_URL='https://gitea.kosmos.org'
# GITEA_PUBLIC_URL='https://gitea.kosmos.org'
LNDHUB_API_URL='http://localhost:3023'
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
LNDHUB_PUBLIC_KEY='0123d3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946'
LNDHUB_ADMIN_UI=true
LNDHUB_ADMIN_TOKEN=123456789
LNDHUB_PG_HOST=localhost
LNDHUB_PG_PORT=5432
LNDHUB_PG_DATABASE=lndhub
LNDHUB_PG_USERNAME=lndhub
LNDHUB_PG_PASSWORD=''
# LNDHUB_API_URL='http://localhost:3023'
# LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
# LNDHUB_PUBLIC_KEY='0123d3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946'
# LNDHUB_ADMIN_UI=true
# LNDHUB_ADMIN_TOKEN=123456789
# LNDHUB_PG_HOST=localhost
# LNDHUB_PG_PORT=5432
# LNDHUB_PG_DATABASE=lndhub
# LNDHUB_PG_USERNAME=lndhub
# LNDHUB_PG_PASSWORD=''
MASTODON_PUBLIC_URL='https://kosmos.social'
# MASTODON_PUBLIC_URL='https://kosmos.social'
MEDIAWIKI_PUBLIC_URL='https://wiki.kosmos.org'
# MEDIAWIKI_PUBLIC_URL='https://wiki.kosmos.org'
RS_STORAGE_URL='https://storage.kosmos.org'
RS_REDIS_URL='redis://localhost:6379/2'
# RS_STORAGE_URL='https://storage.kosmos.org'
# RS_REDIS_URL='redis://localhost:6379/2'

View File

@@ -1,10 +1,18 @@
# syntax=docker/dockerfile:1
FROM ruby:3.3.0
FROM debian:bullseye-slim as base
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN apt-get update -qq && apt-get install -y --no-install-recommends curl \
ldap-utils tini libvips
# TODO Remove when upstream Ruby works properly on Apple silicon
RUN apt update && apt install -y build-essential wget autoconf libpq-dev pkg-config
RUN wget https://github.com/postmodern/ruby-install/releases/download/v0.9.3/ruby-install-0.9.3.tar.gz \
&& tar -xzvf ruby-install-0.9.3.tar.gz \
&& cd ruby-install-0.9.3/ \
&& make install
RUN ruby-install -p https://github.com/ruby/ruby/pull/9371.diff ruby 3.3.0
ENV PATH="/opt/rubies/ruby-3.3.0/bin:${PATH}"
RUN apt-get install -y --no-install-recommends curl ldap-utils tini libvips
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt-get update && apt-get install -y nodejs

View File

@@ -14,8 +14,10 @@ so:
1. Make sure [Docker Compose is installed][1] and Docker is running (included in
Docker Desktop)
3. Run `docker compose up` and wait until 389ds announces its successful start
in the log output
3. Run `docker compose up --build` and wait until all services have started
(389ds might take an extra minute to be ready). This will take a while when
running for the first time, so you might want to do something else in the
meantime.
4. `docker-compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be-name="dev"`
5. `docker compose run web rails ldap:setup`
6. `docker compose run web rails db:setup`
@@ -28,38 +30,44 @@ have the password "user is user".
### Rails app
_Note: when using Docker Compose, prefix the following commands with `docker-compose
run web`._
Installing dependencies:
bundle install
yarn install
Setting up local database (SQLite):
Migrating the local database (after schema changes):
bundle exec rails db:create
bundle exec rails db:migrate
Running the dev server and auto-building CSS files on change:
Running the dev server, and auto-building CSS files on change _(automatic with Docker Compose)_:
bin/dev
Running the background workers (requires Redis):
Running the background workers (requires Redis) _(automatic with Docker Compose)_:
bundle exec sidekiq -C config/sidekiq.yml
Running all specs:
Running the test suite:
bundle exec rspec
### Docker (Compose)
Running the test suite with Docker Compose requires overriding the Rails
environment:
There is a working Docker Compose config file, which define a number of services including
an app server for Rails as well as a local 389ds (LDAP) server.
docker-compose run -e "RAILS_ENV=test" web rspec
For Rails developers, you probably just want to start the LDAP server: `docker-compose up ldap`,
listening on port 389 on your machine.
### Docker Compose
You can pick and choose your services adding them by name (listed in `docker-compose.yml`) at
the end of the docker compose command. eg. `docker compose up ldap redis`
Services/containers are configured in `docker-compose.yml`.
You can run services selectively, for example if you want to run the Rails app
and test suite on the host machine. Just add the service names of the
containers you want to run to the `up` command, like so:
docker-compose up ldap redis
#### LDAP server
@@ -76,13 +84,15 @@ Now you can seed the back-end with data using this Rails task:
The setup task will first delete any existing entries in the directory tree
("dc=kosmos,dc=org"), and then create our development entries.
Note that all 389ds data is stored in `tmp/389ds`. So if you want to start over
with a fresh installation, delete both that directory as well as the container.
Note that all 389ds data is stored in the `389ds-data` volume. So if you want
to start over with a fresh installation, delete both that volume as well as the
container.
#### Minio / RS
#### Minio / remoteStorage
If you want to run remoteStorage accounts locally, you will have to create the
respective bucket first:
respective bucket first. With the `minio` container running (run by default
when using Docker Compose), follow these steps:
* `docker compose up web redis minio liquor-cabinet`
* Head to http://localhost:9001 and log in with user `minioadmin`, password

View File

@@ -0,0 +1,5 @@
<% if @image_url %>
<%= image_tag @image_url, class: "h-full w-full" %>
<% else %>
<%= render partial: "icons/remotestorage", locals: { custom_class: "h-full w-full p-0.5 text-gray-200" } %>
<% end %>

View File

@@ -0,0 +1,21 @@
# frozen_string_literal: true
module AppCatalog
class WebAppIconComponent < ViewComponent::Base
def initialize(web_app:)
if web_app&.icon&.attached?
@image_url = image_url_for(web_app.icon)
elsif web_app&.apple_touch_icon&.attached?
@image_url = image_url_for(web_app.apple_touch_icon)
end
end
def image_url_for(attachment)
if Setting.s3_enabled?
s3_image_url(attachment)
else
Rails.application.routes.url_helpers.rails_blob_path(attachment, only_path: true)
end
end
end
end

View File

@@ -2,13 +2,21 @@
<div class="relative inline-block">
<div role="button" tabindex="0" data-dropdown-target="button"
class="inline-block select-none">
<% if @size == :large %>
<span class="appearance-none flex items-center inline-block">
<span class="p-2 bg-gray-50 hover:bg-gray-100 rounded-full">
<%= render partial: "icons/kebab-menu", locals: {
custom_class: "inline text-gray-500 h-6 w-6"
} %>
<%= render partial: "icons/#{@icon_name}",
locals: { custom_class: "inline text-gray-500 h-6 w-6" } %>
</span>
</span>
<% elsif @size == :small %>
<span class="appearance-none flex items-center inline-block">
<span class="text-gray-500 hover:text-blue-600">
<%= render partial: "icons/#{@icon_name}",
locals: { custom_class: "inline h-4 w-4" } %>
</span>
</span>
<% end %>
</div>
<div data-dropdown-target="menu"
data-transition-enter="transition ease-out duration-200"

View File

@@ -1,5 +1,8 @@
# frozen_string_literal: true
class DropdownComponent < ViewComponent::Base
def initialize(size: :large, icon_name: "kebap-menu")
@size = size.to_sym
@icon_name = icon_name
end
end

View File

@@ -5,7 +5,9 @@
} : nil do %>
<div class="flex flex-col">
<label class="font-bold mb-1"><%= @title %></label>
<% if @description.present? %>
<p class="text-gray-500"><%= @descripton %></p>
<% end %>
</div>
<div class="relative ml-4 inline-flex flex-shrink-0">
<%= render FormElements::ToggleComponent.new(

View File

@@ -3,7 +3,7 @@
module FormElements
class FieldsetToggleComponent < ViewComponent::Base
def initialize(tag: "li", form: nil, attribute: nil, field_name: nil,
enabled: false, input_enabled: true, title:, description:)
enabled: false, input_enabled: true, title:, description: nil)
@tag = tag
@form = form
@attribute = attribute

View File

@@ -18,9 +18,11 @@
<div class="m-1 bg-white rounded shadow">
<div class="p-8">
<%= content %>
<% if @show_close_button %>
<div class="flex justify-end items-center flex-wrap mt-6">
<button class="btn-md btn-blue" data-action="click->modal#close:prevent">Close</button>
</div>
<% end %>
</div>
</div>
</div>

View File

@@ -1,2 +1,5 @@
class ModalComponent < ViewComponent::Base
def initialize(show_close_button: true)
@show_close_button = show_close_button
end
end

View File

@@ -1,16 +1,10 @@
<div class="flex items-center gap-4">
<div class="h-16 w-16 flex-none">
<% if @web_app.icon.attached? %>
<%= image_tag s3_image_url(@web_app.icon), class: "h-full w-full" %>
<% elsif @web_app.apple_touch_icon.attached? %>
<%= image_tag s3_image_url(@web_app.apple_touch_icon), class: "h-full w-full" %>
<% else %>
<%= render partial: "icons/remotestorage", locals: { custom_class: "h-full w-full p-0.5 text-gray-200" } %>
<% end %>
<%= render AppCatalog::WebAppIconComponent.new(web_app: @web_app) %>
</div>
<div class="flex-grow">
<h4 class="mb-1 text-lg font-bold">
<%= @web_app.name %>
<%= @web_app&.name || @auth.app_name %>
</h4>
<p class="text-sm text-gray-500">
<%= @auth.client_id %>

View File

@@ -1,8 +1,8 @@
class Admin::Settings::RegistrationsController < Admin::SettingsController
def index
def show
end
def create
def update
update_settings
redirect_to admin_settings_registrations_path, flash: {

View File

@@ -1,19 +1,32 @@
class Admin::Settings::ServicesController < Admin::SettingsController
def index
@service = params[:s]
before_action :set_service, only: [:show, :update]
if @service.blank?
redirect_to admin_settings_services_path(params: { s: "btcpay" })
end
def index
redirect_to admin_settings_service_path("btcpay")
end
def create
service = params.require(:service)
def show
end
def update
update_settings
redirect_to admin_settings_services_path(params: { s: service }), flash: {
redirect_to admin_settings_service_path(@service), flash: {
success: "Settings saved"
}
end
private
def set_subsection
@subsection = "services"
end
def set_service
@service = params[:service]
if @service.blank?
redirect_to admin_settings_services_path and return
end
end
end

View File

@@ -20,7 +20,7 @@ class Admin::SettingsController < Admin::BaseController
end
if @errors.any?
render :index and return
render :show and return
end
changed_keys.each do |key|

View File

@@ -1,11 +1,11 @@
class Admin::UsersController < Admin::BaseController
before_action :set_user, only: [:show]
before_action :set_user, except: [:index]
before_action :set_current_section
# GET /admin/users
def index
ldap = LdapService.new
@ou = params[:ou] || Setting.primary_domain
@orgs = ldap.fetch_organizations
@ou = Setting.primary_domain
@pagy, @users = pagy(User.where(ou: @ou).order(cn: :asc))
@stats = {
@@ -14,6 +14,7 @@ class Admin::UsersController < Admin::BaseController
}
end
# GET /admin/users/:username
def show
if Setting.lndhub_admin_enabled?
@lndhub_user = @user.lndhub_user
@@ -24,11 +25,35 @@ class Admin::UsersController < Admin::BaseController
@avatar = LdapManager::FetchAvatar.call(cn: @user.cn)
end
# POST /admin/users/:username/invitations
def create_invitations
amount = params[:amount].to_i
notify_user = ActiveRecord::Type::Boolean.new.cast(params[:notify_user])
CreateInvitations.call(user: @user, amount: amount, notify: notify_user)
redirect_to admin_user_path(@user.cn), flash: {
success: "Added #{amount} invitations to #{@user.cn}'s account"
}
end
# DELETE /admin/users/:username/invitations
def delete_invitations
invitations = @user.invitations.unused
amount = invitations.count
invitations.destroy_all
redirect_to admin_user_path(@user.cn), flash: {
success: "Removed #{amount} invitations from #{@user.cn}'s account"
}
end
private
def set_user
address = params[:address].split("@")
@user = User.where(cn: address.first, ou: address.last).first
@user = User.find_by(cn: params[:username], ou: Setting.primary_domain)
http_status :not_found unless @user
end
def set_current_section

View File

@@ -1,7 +1,7 @@
class AppCatalog::WebApp < ApplicationRecord
store :metadata, coder: JSON
has_many :remote_storage_authorizations
has_many :remote_storage_authorizations, dependent: :destroy
has_one_attached :icon
has_one_attached :apple_touch_icon

View File

@@ -15,6 +15,9 @@ class Setting < RailsSettings::Base
field :redis_url, type: :string,
default: ENV["REDIS_URL"] || "redis://localhost:6379/0"
field :s3_enabled, type: :boolean,
default: ENV["S3_ENABLED"] && ENV["S3_ENABLED"].to_s != "false"
#
# Registrations
#

View File

@@ -18,6 +18,10 @@ module AppCatalogManager
@app.metadata[prop] = metadata.send(prop) if prop
end
@app.save!
# TODO move icon downloads to separate, async job
if icon = metadata.select_icon(sizes: "256x256") ||
icon = metadata.select_icon(sizes: "192x192")
attach_remote_image(:icon, icon)
@@ -27,8 +31,6 @@ module AppCatalogManager
if apple_touch_icon = metadata.select_icon(purpose: "apple-touch-icon")
attach_remote_image(:apple_touch_icon, apple_touch_icon)
end
@app.save!
rescue Manifique::Error => e
msg = "Fetching web app manifest failed for #{e.url}: #{e.type}"
Rails.logger.warn(msg)
@@ -42,14 +44,19 @@ module AppCatalogManager
else
download_url = "#{@app.url}/#{icon["src"].gsub(/^\//,'')}"
end
filename = "#{attachment_name}.png"
key = "web_apps/#{@app.id}/icons/#{attachment_name}.png"
filename = "#{attachment_name}-#{Time.now.to_i}.png"
key = "web_apps/#{@app.id}/icons/#{filename}"
begin
tempfile = Down.download(download_url)
@app.send(attachment_name).attach(key: key, io: tempfile, filename: filename)
rescue Down::NotFound
Rails.logger.warn "Icon download failed: NotFound error for #{download_url}"
msg = "Download of \"#{attachment_name}\" failed: NotFound error for #{download_url}"
Rails.logger.warn(msg)
Sentry.capture_message(msg)
rescue => e
Rails.logger.warn "Saving attachment \"#{attachment_name}\" failed: \"#{e.message}\""
Sentry.capture_exception(e) if Setting.sentry_enabled?
end
end
end

View File

@@ -1,7 +1,7 @@
<%= render HeaderComponent.new(title: "Settings") %>
<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/admin_sidenav_settings') do %>
<%= form_for(Setting.new, url: admin_settings_registrations_path) do |f| %>
<%= form_for(Setting.new, url: admin_settings_registrations_path, method: :put) do |f| %>
<section>
<h3>Registrations</h3>

View File

@@ -1,9 +1,7 @@
<%= render HeaderComponent.new(title: "Settings") %>
<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/admin_sidenav_settings') do %>
<%= form_for(Setting.new, url: admin_settings_services_path) do |f| %>
<%= hidden_field_tag :service, @service %>
<%= form_for(Setting.new, url: admin_settings_service_path(@service), method: :put) do |f| %>
<% if @errors && @errors.any? %>
<section>
<%= render partial: "admin/settings/errors", locals: { errors: @errors } %>

View File

@@ -0,0 +1,21 @@
<h3>Add new invitations to <%= @user.cn %>'s account</h3>
<%= form_with(url: invitations_admin_user_path, method: :post) do |form| %>
<ul role="list">
<%= render FormElements::FieldsetComponent.new(
positioning: :horizontal,
title: "Amount"
) do %>
<%= form.select :amount, options_for_select([
["3", "3"], ["5", "5"], ["10", "10"], ["20", "20"]
]) %>
<% end %>
<%= render FormElements::FieldsetToggleComponent.new(
field_name: "notify_user",
enabled: true,
title: "Notify user via email"
) %>
</ul>
<p class="pt-6 border-t border-gray-200 text-right">
<%= form.submit 'Add', class: "btn-md btn-blue w-full" %>
</p>
<% end %>

View File

@@ -1,4 +1,4 @@
<%= render HeaderComponent.new(title: "Users: #{@ou}") %>
<%= render HeaderComponent.new(title: "Users") %>
<%= render MainSimpleComponent.new do %>
<section>
@@ -16,19 +16,6 @@
<% end %>
</section>
<% if @orgs.length > 1 %>
<section>
<h3 class="hidden">Domains</h3>
<ul>
<% @orgs.each do |org| %>
<li class="inline-block">
<%= link_to org[:ou], admin_users_path(ou: org[:ou]), class: "ks-text-link" %>
</li>
<% end %>
</ul>
</section>
<% end %>
<section>
<table class="divided mb-8">
<thead>
@@ -36,13 +23,12 @@
<th>UID</th>
<th>Status</th>
<th>Roles</th>
<!-- <th>Password</th> -->
</tr>
</thead>
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= link_to(user.cn, admin_user_path(user.address), class: 'ks-text-link') %></td>
<td><%= link_to(user.cn, admin_user_path(user.cn), class: 'ks-text-link') %></td>
<td><%= user.confirmed_at.nil? ? badge("pending", :yellow) : "" %></td>
<td><%= user.is_admin? ? badge("admin", :red) : "" %></td>
</tr>

View File

@@ -1,4 +1,4 @@
<%= render HeaderComponent.new(title: "User: #{@user.address}") %>
<%= render HeaderComponent.new(title: "User: #{@user.cn}") %>
<%= render MainSimpleComponent.new do %>
<div class="mb-12 sm:flex sm:flex-row sm:gap-x-8">
@@ -42,8 +42,34 @@
</tr>
<tr>
<th>Invitations available</th>
<td>
<%= @user.invitations.count %>
<td data-controller="modal" data-action="keydown.esc->modal#close">
<div class="flex justify-between">
<span>
<%= @user.invitations.count %>
</span>
<span>
<button id="add-invitations" data-action="click->modal#open">
<%= render partial: "icons/plus-circle", locals: {
custom_class: "text-green-600 hover:text-green-500 -mt-2 -mb-1 h-6 w-6 inline-block"
} %>
</button>
<% if @user.invitations.unused.count > 0 %>
<%= link_to invitations_admin_user_path(@user.cn),
id: "remove-invitations", data: {
turbo_method: :delete,
turbo_confirm: "Delete all of #{@user.cn}'s available invitations?"
} do %>
<%= render partial: "icons/x-circle", locals: {
custom_class: "text-red-600 hover:text-red-500 -mt-2 -mb-1 h-6 w-6 inline-block"
} %>
<% end %>
<% end %>
</span>
</div>
<%= render ModalComponent.new(show_close_button: false) do %>
<%= render partial: "admin/users/create_invitations",
locals: { user: @user } %>
<% end %>
</td>
</tr>
<tr>

View File

@@ -1,6 +1,7 @@
<%
# TODO remove when https://github.com/hotwired/turbo/issues/203 is fixed
enable_turbo = !session[:user_return_to] || !session[:user_return_to].match?('/discourse/connect')
enable_turbo = session[:user_return_to].blank? ||
['/discourse/connect', '/rs/oauth'].none? { |s| session[:user_return_to].match(s) }
%>
<%= render HeaderCompactComponent.new(title: "Log in") %>

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus-circle <%= custom_class %>"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="16"></line><line x1="8" y1="12" x2="16" y2="12"></line></svg>

Before

Width:  |  Height:  |  Size: 351 B

After

Width:  |  Height:  |  Size: 372 B

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" class="<%= custom_class %>" clip-rule="evenodd" fill-rule="evenodd" image-rendering="optimizeQuality" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" version="1.1" viewBox="0 0 250 249.9" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
<svg width="24" height="24" class="icon-remotestorage <%= custom_class %>" clip-rule="evenodd" fill-rule="evenodd" image-rendering="optimizeQuality" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" version="1.1" viewBox="0 0 250 249.9" xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(-66.822 -.16484)">
<polygon id="polygon1" fill="currentColor" transform="matrix(.29308 0 0 .29308 83.528 -.028385)" points="228 181 370 100 511 181 652 263 370 425 87 263 87 263 0 213 0 213 0 311 0 378 0 427 0 476 86 525 185 582 370 689 554 582 653 525 653 590 653 592 370 754 0 542 0 640 185 747 370 853 554 747 739 640 739 525 739 476 739 427 739 378 653 427 370 589 86 427 86 361 185 418 370 524 554 418 653 361 739 311 739 213 554 107 370 0 185 107 58 180 144 230"/>
</g>

Before

Width:  |  Height:  |  Size: 848 B

After

Width:  |  Height:  |  Size: 867 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x-circle"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x-circle <%= custom_class %>"><circle cx="12" cy="12" r="10"></circle><line x1="15" y1="9" x2="9" y2="15"></line><line x1="9" y1="9" x2="15" y2="15"></line></svg>

Before

Width:  |  Height:  |  Size: 346 B

After

Width:  |  Height:  |  Size: 367 B

View File

@@ -31,6 +31,7 @@
<% end %>
<% end %>
<% if Flipper.enabled?(:avatar_upload, current_user) %>
<label class="block">
<p class="font-bold mb-1">
Avatar
@@ -56,6 +57,7 @@
</div>
</div>
</label>
<% end %>
<p class="mt-8 pt-6 border-t border-gray-200 text-right">
<%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %>

View File

@@ -4,9 +4,9 @@
) %>
<%= render SidenavLinkComponent.new(
name: "Services", path: admin_settings_services_path, icon: "grid",
active: current_page?(admin_settings_services_path)
active: controller_name == "services"
) %>
<% if current_page?(admin_settings_services_path) %>
<% if controller_name == "services" %>
<%= render partial: "shared/admin_sidenav_settings_services" %>
<% end %>
<%= render SidenavLinkComponent.new(

View File

@@ -1,77 +1,77 @@
<%= render SidenavLinkComponent.new(
level: 2,
name: "BTCPay",
path: admin_settings_services_path(params: { s: "btcpay" }),
path: admin_settings_service_path("btcpay"),
text_icon: Setting.btcpay_enabled? ? "◉" : "○",
active: current_page?(admin_settings_services_path(params: { s: "btcpay" })),
active: current_page?(admin_settings_service_path("btcpay")),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "Discourse",
path: admin_settings_services_path(params: { s: "discourse" }),
path: admin_settings_service_path("discourse"),
text_icon: Setting.discourse_enabled? ? "◉" : "○",
active: current_page?(admin_settings_services_path(params: { s: "discourse" })),
active: current_page?(admin_settings_service_path("discourse")),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "Drone CI",
path: admin_settings_services_path(params: { s: "droneci" }),
path: admin_settings_service_path("droneci"),
text_icon: Setting.droneci_enabled? ? "◉" : "○",
active: current_page?(admin_settings_services_path(params: { s: "droneci" })),
active: current_page?(admin_settings_service_path("droneci")),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "E-Mail",
path: admin_settings_services_path(params: { s: "email" }),
path: admin_settings_service_path("email"),
text_icon: Setting.email_enabled? ? "◉" : "○",
active: current_page?(admin_settings_services_path(params: { s: "email" })),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "ejabberd",
path: admin_settings_services_path(params: { s: "ejabberd" }),
path: admin_settings_service_path("ejabberd"),
text_icon: Setting.ejabberd_enabled? ? "◉" : "○",
active: current_page?(admin_settings_services_path(params: { s: "ejabberd" })),
active: current_page?(admin_settings_service_path("ejabberd")),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "Gitea",
path: admin_settings_services_path(params: { s: "gitea" }),
path: admin_settings_service_path("gitea"),
text_icon: Setting.gitea_enabled? ? "◉" : "○",
active: current_page?(admin_settings_services_path(params: { s: "gitea" })),
active: current_page?(admin_settings_service_path("gitea")),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "LNDHub",
path: admin_settings_services_path(params: { s: "lndhub" }),
path: admin_settings_service_path("lndhub"),
text_icon: Setting.lndhub_enabled? ? "◉" : "○",
active: current_page?(admin_settings_services_path(params: { s: "lndhub" })),
active: current_page?(admin_settings_service_path("lndhub")),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "Mastodon",
path: admin_settings_services_path(params: { s: "mastodon" }),
path: admin_settings_service_path("mastodon"),
text_icon: Setting.mastodon_enabled? ? "◉" : "○",
active: current_page?(admin_settings_services_path(params: { s: "mastodon" })),
active: current_page?(admin_settings_service_path("mastodon")),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "MediaWiki",
path: admin_settings_services_path(params: { s: "mediawiki" }),
path: admin_settings_service_path("mediawiki"),
text_icon: Setting.mediawiki_enabled? ? "◉" : "○",
active: current_page?(admin_settings_services_path(params: { s: "mediawiki" })),
active: current_page?(admin_settings_service_path("mediawiki")),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "Nostr",
path: admin_settings_services_path(params: { s: "nostr" }),
path: admin_settings_service_path("nostr"),
text_icon: Setting.nostr_enabled? ? "◉" : "○",
active: current_page?(admin_settings_services_path(params: { s: "nostr" })),
active: current_page?(admin_settings_service_path("nostr")),
) %>
<%= render SidenavLinkComponent.new(
level: 2,
name: "RemoteStorage",
path: admin_settings_services_path(params: { s: "remotestorage" }),
path: admin_settings_service_path("remotestorage"),
text_icon: Setting.remotestorage_enabled? ? "◉" : "○",
active: current_page?(admin_settings_services_path(params: { s: "remotestorage" })),
active: current_page?(admin_settings_service_path("remotestorage")),
) %>

View File

@@ -71,7 +71,7 @@ Rails.application.configure do
# Allow requests from any IP
config.web_console.permissions = '0.0.0.0/0'
if ENV["S3_ENABLED"]
if ENV["S3_ENABLED"] && ENV["S3_ENABLED"].to_s != "false"
config.active_storage.service = :s3
else
config.active_storage.service = :local

View File

@@ -110,7 +110,7 @@ Rails.application.configure do
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
config.action_mailer.raise_delivery_errors = true
if ENV["S3_ENABLED"]
if ENV["S3_ENABLED"] && ENV["S3_ENABLED"].to_s != "false"
config.active_storage.service = :s3
else
config.active_storage.service = :local

View File

@@ -52,10 +52,9 @@ Rails.application.configure do
config.active_job.queue_adapter = :test
if ENV["S3_ENABLED"]
if ENV["S3_ENABLED"] && ENV["S3_ENABLED"].to_s != "false"
config.active_storage.service = :s3
else
# Store attachments on the local disk (in ./tmp)
config.active_storage.service = :test
config.active_storage.service = :local
end
end

View File

@@ -73,9 +73,19 @@ Rails.application.routes.draw do
namespace :admin do
root to: 'dashboard#index'
resources 'users', param: 'address', only: ['index', 'show'], constraints: { address: /.*/ }
resources 'users', param: 'username', only: ['index', 'show'] do
member do
post 'invitations', to: 'users#create_invitations'
delete 'invitations', to: 'users#delete_invitations'
end
end
# post 'users/:username/invitations', to: 'users#create_invitations'
get 'invitations', to: 'invitations#index'
resources :donations
get 'lightning', to: 'lightning#index'
namespace :app_catalog do
@@ -83,8 +93,8 @@ Rails.application.routes.draw do
end
namespace :settings do
resources 'registrations', only: ['index', 'create']
resources 'services', only: ['index', 'create']
resource 'registrations', only: ['show', 'update']
resources 'services', param: 'service', only: ['index', 'show', 'update']
end
end

View File

@@ -1,12 +1,12 @@
local:
service: Disk
root: <%= Rails.root.join("storage") %>
root: <%= ENV["ACTIVE_STORAGE_PATH"] || Rails.root.join("storage") %>
test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>
<% if ENV["S3_ENABLED"] %>
<% if ENV["S3_ENABLED"] && ENV["S3_ENABLED"].to_s != "false" %>
s3:
service: S3
endpoint: <%= ENV["S3_ENDPOINT"] %>

View File

@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2024_02_07_080515) do
ActiveRecord::Schema[7.1].define(version: 2024_02_16_124640) do
create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
@@ -50,12 +50,17 @@ ActiveRecord::Schema[7.1].define(version: 2024_02_07_080515) do
create_table "donations", force: :cascade do |t|
t.integer "user_id"
t.integer "amount_sats"
t.integer "amount_eur"
t.integer "amount_usd"
t.integer "fiat_amount"
t.string "public_name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "paid_at", precision: nil
t.string "fiat_currency", default: "USD"
t.string "donation_method"
t.string "payment_method"
t.string "btcpay_invoice_id"
t.string "payment_status"
t.index ["payment_status"], name: "index_donations_on_payment_status"
t.index ["user_id"], name: "index_donations_on_user_id"
end

View File

@@ -3,10 +3,10 @@ require 'sidekiq/testing'
ldap = LdapService.new
Sidekiq::Testing.inline! do
CreateAccount.call(
CreateAccount.call(account: {
username: "admin", domain: "kosmos.org", email: "admin@example.com",
password: "admin is admin", confirmed: true
)
})
ldap.add_attribute "cn=admin,ou=kosmos.org,cn=users,dc=kosmos,dc=org", :admin, "true"
@@ -15,9 +15,9 @@ Sidekiq::Testing.inline! do
email = Faker::Internet.unique.email
next if username.length < 3
CreateAccount.call(
CreateAccount.call(account: {
username: username, domain: "kosmos.org", email: email,
password: "user is user", confirmed: true
)
})
end
end

View File

@@ -2,7 +2,7 @@ services:
ldap:
image: 4teamwork/389ds:latest
volumes:
- ./tmp/389ds:/data
- 389ds-data:/data
networks:
- external_network
- internal_network
@@ -16,11 +16,12 @@ services:
restart: always
image: redis:7-alpine
networks:
- external_network
- internal_network
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
volumes:
- ./tmp/redis:/data
- redis-data:/data
web:
build: .
@@ -42,8 +43,10 @@ services:
LDAP_ADMIN_PASSWORD: passthebutter
LDAP_USE_TLS: "false"
REDIS_URL: redis://redis:6379/0
ACTIVE_STORAGE_PATH: "/akkounts/tmp/attachments"
RS_REDIS_URL: redis://redis:6379/1
RS_STORAGE_URL: "http://localhost:4567"
S3_ENABLED: false
depends_on:
- ldap
- redis
@@ -67,6 +70,7 @@ services:
REDIS_URL: redis://redis:6379/0
RS_REDIS_URL: redis://redis:6379/1
RS_STORAGE_URL: "http://localhost:4567"
S3_ENABLED: false
depends_on:
- ldap
- redis
@@ -81,10 +85,10 @@ services:
- "9000:9000"
- "9001:9001"
volumes:
- ./tmp/minio:/data
- minio-data:/data
liquor-cabinet:
image: gitea.kosmos.org/5apps/liquor-cabinet:2.0.0-beta.2
image: gitea.kosmos.org/5apps/liquor-cabinet:2.0.0-rc.1
networks:
- external_network
- internal_network
@@ -116,3 +120,11 @@ networks:
external_network:
internal_network:
internal: true
volumes:
389ds-data:
driver: local
minio-data:
driver: local
redis-data:
driver: local

View File

@@ -11,7 +11,7 @@
"postcss-preset-env": "^7.8.3",
"tailwindcss": "^3.2.4"
},
"version": "0.8.1",
"version": "0.9.0",
"scripts": {
"build:css:tailwind": "tailwindcss --postcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css",
"build:css": "yarn run build:css:tailwind"

View File

@@ -0,0 +1,11 @@
require "rails_helper"
RSpec.describe AppCatalog::WebAppIconComponent, type: :component do
describe "No web app given" do
it "renders the default icon" do
expect(
render_inline(described_class.new(web_app: nil)) {}.to_html
).to include("icon-remotestorage")
end
end
end

View File

@@ -23,35 +23,35 @@ RSpec.describe 'Admin/global settings', type: :feature do
scenario "Opening service settings shows page for first service" do
visit admin_settings_services_path
expect(current_url).to eq(admin_settings_services_url(params: { s: "btcpay" }))
expect(current_url).to eq(admin_settings_service_url("btcpay"))
end
scenario "View service settings" do
visit admin_settings_services_path(params: { s: "ejabberd" })
visit admin_settings_service_path("ejabberd")
expect(page).to have_content("Enable ejabberd integration")
expect(page).to have_field("API URL", with: "http://xmpp.example.com/api")
end
scenario "Disable a service integration" do
visit admin_settings_services_path(params: { s: "ejabberd" })
visit admin_settings_service_path("ejabberd")
expect(page).to have_checked_field("setting[ejabberd_enabled]")
uncheck "setting[ejabberd_enabled]"
click_button "Save"
expect(current_url).to eq(admin_settings_services_url(params: { s: "ejabberd" }))
expect(current_url).to eq(admin_settings_service_url("ejabberd"))
expect(page).to_not have_checked_field("setting[ejabberd_enabled]")
expect(page).to_not have_field("API URL", disabled: true)
end
scenario "Resettable fields" do
visit admin_settings_services_path(params: { s: "ejabberd" })
visit admin_settings_service_path("ejabberd")
expect(page).to have_field("API URL", with: "http://xmpp.example.com/api")
expect(page).to_not have_css('input#setting_ejabberd_api_url+button')
Setting.ejabberd_api_url = "http://example.com/foo"
visit admin_settings_services_path(params: { s: "ejabberd" })
visit admin_settings_service_path("ejabberd")
expect(page).to have_field("API URL", with: "http://example.com/foo")
expect(page).to have_css('input#setting_ejabberd_api_url+button')
end

View File

@@ -0,0 +1,55 @@
require "rails_helper"
RSpec.describe "Admin: User management", type: :feature do
let(:admin) { create :user }
let(:user) { create :user, id: 2, cn: "alfred", email: "alfred@example.com" }
before do
user.save!
allow(Devise::LDAP::Adapter).to receive(:get_ldap_param)
.with(admin.cn, :admin).and_return(["true"])
allow(Devise::LDAP::Adapter).to receive(:get_ldap_param)
.with(user.cn, :admin).and_return(nil)
allow_any_instance_of(User).to receive(:ldap_entry)
.and_return({ uid: user.cn, mail: user.email, display_name: "Freddy" })
allow_any_instance_of(LdapManager::FetchAvatar).to receive(:call)
.and_return(nil)
login_as admin, :scope => :user
end
describe "User details page" do
before do
visit admin_user_path("alfred")
end
it "shows the user info" do
within "h1" do
expect(page).to have_content("User: alfred")
end
expect(page).to have_content("alfred@example.com")
end
end
scenario 'Add invitations to account' do
visit admin_user_path("alfred")
find("#add-invitations").click
select "5", :from => "amount"
uncheck "notify_user"
click_button "Add"
expect(user.invitations.count).to eq(5)
end
scenario 'Remove invitations from account' do
3.times { Invitation.create(user: user) }
expect(user.invitations.count).to eq(3)
visit admin_user_path("alfred")
find("#remove-invitations").click
expect(user.invitations.count).to eq(0)
end
end

View File

@@ -12,6 +12,8 @@ RSpec.describe 'Profile settings', type: :feature do
uid: user.cn, ou: user.ou, display_name: "Mark"
})
allow_any_instance_of(User).to receive(:avatar).and_return(avatar_base64)
Flipper.enable "avatar_upload"
end
feature "Update display name" do

View File

@@ -15,7 +15,7 @@ RSpec.describe "WebFinger", type: :request do
res = JSON.parse(response.body)
rs_link = res["links"].find {|l| l["rel"] == "http://tools.ietf.org/id/draft-dejong-remotestorage"}
expect(rs_link["href"]).to eql("https://storage.kosmos.org/tony")
expect(rs_link["href"]).to eql("#{Setting.rs_storage_url}/tony")
oauth_url = rs_link["properties"]["http://tools.ietf.org/html/rfc6749#section-4.2"]
expect(oauth_url).to eql("http://www.example.com/rs/oauth/tony")