36 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
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
35 changed files with 298 additions and 151 deletions

View File

@@ -1,14 +1,14 @@
PRIMARY_DOMAIN=kosmos.org # PRIMARY_DOMAIN=kosmos.org
AKKOUNTS_DOMAIN=accounts.example.com # AKKOUNTS_DOMAIN=accounts.example.com
SMTP_SERVER=smtp.example.com # SMTP_SERVER=smtp.example.com
SMTP_PORT=587 # SMTP_PORT=587
SMTP_LOGIN=accounts # SMTP_LOGIN=accounts
SMTP_PASSWORD=123abc # SMTP_PASSWORD=123abc
SMTP_FROM_ADDRESS=accounts@example.com # SMTP_FROM_ADDRESS=accounts@example.com
SMTP_DOMAIN=example.com # SMTP_DOMAIN=example.com
SMTP_AUTH_METHOD=plain # SMTP_AUTH_METHOD=plain
SMTP_ENABLE_STARTTLS=auto # SMTP_ENABLE_STARTTLS=auto
# S3_ENABLED=true # S3_ENABLED=true
# S3_ENDPOINT=https://s3.kosmos.org # S3_ENDPOINT=https://s3.kosmos.org
@@ -18,47 +18,47 @@ SMTP_ENABLE_STARTTLS=auto
# S3_ACCESS_KEY=123456abcdefg # S3_ACCESS_KEY=123456abcdefg
# S3_SECRET_KEY=123456789123456789123456789 # S3_SECRET_KEY=123456789123456789123456789
LDAP_HOST=localhost # LDAP_HOST=localhost
LDAP_PORT=389 # LDAP_PORT=389
LDAP_ADMIN_PASSWORD=passthebutter # LDAP_ADMIN_PASSWORD=passthebutter
LDAP_SUFFIX='dc=kosmos,dc=org' # 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 # Service Integrations
# #
BTCPAY_API_URL='http://localhost:23001/api/v1' # BTCPAY_API_URL='http://localhost:23001/api/v1'
BTCPAY_STORE_ID='' # BTCPAY_STORE_ID=''
BTCPAY_AUTH_TOKEN='' # BTCPAY_AUTH_TOKEN=''
DISCOURSE_PUBLIC_URL='https://community.kosmos.org' # DISCOURSE_PUBLIC_URL='https://community.kosmos.org'
DISCOURSE_CONNECT_SECRET='discourse_connect_ftw' # 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_ADMIN_URL='https://xmpp.kosmos.org/admin'
EJABBERD_API_URL='https://xmpp.kosmos.org/api' # 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_API_URL='http://localhost:3023'
LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org' # LNDHUB_PUBLIC_URL='https://lndhub.kosmos.org'
LNDHUB_PUBLIC_KEY='0123d3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946' # LNDHUB_PUBLIC_KEY='0123d3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de556486d946'
LNDHUB_ADMIN_UI=true # LNDHUB_ADMIN_UI=true
LNDHUB_ADMIN_TOKEN=123456789 # LNDHUB_ADMIN_TOKEN=123456789
LNDHUB_PG_HOST=localhost # LNDHUB_PG_HOST=localhost
LNDHUB_PG_PORT=5432 # LNDHUB_PG_PORT=5432
LNDHUB_PG_DATABASE=lndhub # LNDHUB_PG_DATABASE=lndhub
LNDHUB_PG_USERNAME=lndhub # LNDHUB_PG_USERNAME=lndhub
LNDHUB_PG_PASSWORD='' # 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_STORAGE_URL='https://storage.kosmos.org'
RS_REDIS_URL='redis://localhost:6379/2' # RS_REDIS_URL='redis://localhost:6379/2'

View File

@@ -1,10 +1,18 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
FROM ruby:3.3.0 FROM debian:bullseye-slim as base
SHELL ["/bin/bash", "-o", "pipefail", "-c"] SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN apt-get update -qq && apt-get install -y --no-install-recommends curl \ # TODO Remove when upstream Ruby works properly on Apple silicon
ldap-utils tini libvips 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 curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt-get update && apt-get install -y nodejs 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 1. Make sure [Docker Compose is installed][1] and Docker is running (included in
Docker Desktop) Docker Desktop)
3. Run `docker compose up` and wait until 389ds announces its successful start 3. Run `docker compose up --build` and wait until all services have started
in the log output (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"` 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` 5. `docker compose run web rails ldap:setup`
6. `docker compose run web rails db:setup` 6. `docker compose run web rails db:setup`
@@ -28,38 +30,44 @@ have the password "user is user".
### Rails app ### Rails app
_Note: when using Docker Compose, prefix the following commands with `docker-compose
run web`._
Installing dependencies: Installing dependencies:
bundle install bundle install
yarn 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 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 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 bundle exec sidekiq -C config/sidekiq.yml
Running all specs: Running the test suite:
bundle exec rspec 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 docker-compose run -e "RAILS_ENV=test" web rspec
an app server for Rails as well as a local 389ds (LDAP) server.
For Rails developers, you probably just want to start the LDAP server: `docker-compose up ldap`, ### Docker Compose
listening on port 389 on your machine.
You can pick and choose your services adding them by name (listed in `docker-compose.yml`) at Services/containers are configured in `docker-compose.yml`.
the end of the docker compose command. eg. `docker compose up ldap redis`
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 #### 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 The setup task will first delete any existing entries in the directory tree
("dc=kosmos,dc=org"), and then create our development entries. ("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 Note that all 389ds data is stored in the `389ds-data` volume. So if you want
with a fresh installation, delete both that directory as well as the container. 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 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` * `docker compose up web redis minio liquor-cabinet`
* Head to http://localhost:9001 and log in with user `minioadmin`, password * 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

@@ -1,16 +1,10 @@
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<div class="h-16 w-16 flex-none"> <div class="h-16 w-16 flex-none">
<% if @web_app.icon.attached? %> <%= render AppCatalog::WebAppIconComponent.new(web_app: @web_app) %>
<%= 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 %>
</div> </div>
<div class="flex-grow"> <div class="flex-grow">
<h4 class="mb-1 text-lg font-bold"> <h4 class="mb-1 text-lg font-bold">
<%= @web_app.name %> <%= @web_app&.name || @auth.app_name %>
</h4> </h4>
<p class="text-sm text-gray-500"> <p class="text-sm text-gray-500">
<%= @auth.client_id %> <%= @auth.client_id %>

View File

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

View File

@@ -1,19 +1,32 @@
class Admin::Settings::ServicesController < Admin::SettingsController class Admin::Settings::ServicesController < Admin::SettingsController
before_action :set_service, only: [:show, :update]
def index def index
@service = params[:s] redirect_to admin_settings_service_path("btcpay")
if @service.blank?
redirect_to admin_settings_services_path(params: { s: "btcpay" })
end
end end
def create def show
service = params.require(:service) end
def update
update_settings update_settings
redirect_to admin_settings_services_path(params: { s: service }), flash: { redirect_to admin_settings_service_path(@service), flash: {
success: "Settings saved" success: "Settings saved"
} }
end 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 end

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
<%= render HeaderComponent.new(title: "Settings") %> <%= render HeaderComponent.new(title: "Settings") %>
<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/admin_sidenav_settings') do %> <%= 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> <section>
<h3>Registrations</h3> <h3>Registrations</h3>

View File

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

View File

@@ -48,13 +48,14 @@
<%= @user.invitations.count %> <%= @user.invitations.count %>
</span> </span>
<span> <span>
<button data-action="click->modal#open"> <button id="add-invitations" data-action="click->modal#open">
<%= render partial: "icons/plus-circle", locals: { <%= 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" custom_class: "text-green-600 hover:text-green-500 -mt-2 -mb-1 h-6 w-6 inline-block"
} %> } %>
</button> </button>
<% if @user.invitations.unused.count > 0 %> <% if @user.invitations.unused.count > 0 %>
<%= link_to invitations_admin_user_path(@user.cn), data: { <%= link_to invitations_admin_user_path(@user.cn),
id: "remove-invitations", data: {
turbo_method: :delete, turbo_method: :delete,
turbo_confirm: "Delete all of #{@user.cn}'s available invitations?" turbo_confirm: "Delete all of #{@user.cn}'s available invitations?"
} do %> } do %>

View File

@@ -1,6 +1,7 @@
<% <%
# TODO remove when https://github.com/hotwired/turbo/issues/203 is fixed # 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") %> <%= 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,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?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)"> <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"/> <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> </g>

Before

Width:  |  Height:  |  Size: 848 B

After

Width:  |  Height:  |  Size: 867 B

View File

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

View File

@@ -4,9 +4,9 @@
) %> ) %>
<%= render SidenavLinkComponent.new( <%= render SidenavLinkComponent.new(
name: "Services", path: admin_settings_services_path, icon: "grid", 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" %> <%= render partial: "shared/admin_sidenav_settings_services" %>
<% end %> <% end %>
<%= render SidenavLinkComponent.new( <%= render SidenavLinkComponent.new(

View File

@@ -1,77 +1,77 @@
<%= render SidenavLinkComponent.new( <%= render SidenavLinkComponent.new(
level: 2, level: 2,
name: "BTCPay", name: "BTCPay",
path: admin_settings_services_path(params: { s: "btcpay" }), path: admin_settings_service_path("btcpay"),
text_icon: Setting.btcpay_enabled? ? "◉" : "○", 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( <%= render SidenavLinkComponent.new(
level: 2, level: 2,
name: "Discourse", name: "Discourse",
path: admin_settings_services_path(params: { s: "discourse" }), path: admin_settings_service_path("discourse"),
text_icon: Setting.discourse_enabled? ? "◉" : "○", 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( <%= render SidenavLinkComponent.new(
level: 2, level: 2,
name: "Drone CI", name: "Drone CI",
path: admin_settings_services_path(params: { s: "droneci" }), path: admin_settings_service_path("droneci"),
text_icon: Setting.droneci_enabled? ? "◉" : "○", 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( <%= render SidenavLinkComponent.new(
level: 2, level: 2,
name: "E-Mail", name: "E-Mail",
path: admin_settings_services_path(params: { s: "email" }), path: admin_settings_service_path("email"),
text_icon: Setting.email_enabled? ? "◉" : "○", text_icon: Setting.email_enabled? ? "◉" : "○",
active: current_page?(admin_settings_services_path(params: { s: "email" })), active: current_page?(admin_settings_services_path(params: { s: "email" })),
) %> ) %>
<%= render SidenavLinkComponent.new( <%= render SidenavLinkComponent.new(
level: 2, level: 2,
name: "ejabberd", name: "ejabberd",
path: admin_settings_services_path(params: { s: "ejabberd" }), path: admin_settings_service_path("ejabberd"),
text_icon: Setting.ejabberd_enabled? ? "◉" : "○", 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( <%= render SidenavLinkComponent.new(
level: 2, level: 2,
name: "Gitea", name: "Gitea",
path: admin_settings_services_path(params: { s: "gitea" }), path: admin_settings_service_path("gitea"),
text_icon: Setting.gitea_enabled? ? "◉" : "○", 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( <%= render SidenavLinkComponent.new(
level: 2, level: 2,
name: "LNDHub", name: "LNDHub",
path: admin_settings_services_path(params: { s: "lndhub" }), path: admin_settings_service_path("lndhub"),
text_icon: Setting.lndhub_enabled? ? "◉" : "○", 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( <%= render SidenavLinkComponent.new(
level: 2, level: 2,
name: "Mastodon", name: "Mastodon",
path: admin_settings_services_path(params: { s: "mastodon" }), path: admin_settings_service_path("mastodon"),
text_icon: Setting.mastodon_enabled? ? "◉" : "○", 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( <%= render SidenavLinkComponent.new(
level: 2, level: 2,
name: "MediaWiki", name: "MediaWiki",
path: admin_settings_services_path(params: { s: "mediawiki" }), path: admin_settings_service_path("mediawiki"),
text_icon: Setting.mediawiki_enabled? ? "◉" : "○", 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( <%= render SidenavLinkComponent.new(
level: 2, level: 2,
name: "Nostr", name: "Nostr",
path: admin_settings_services_path(params: { s: "nostr" }), path: admin_settings_service_path("nostr"),
text_icon: Setting.nostr_enabled? ? "◉" : "○", 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( <%= render SidenavLinkComponent.new(
level: 2, level: 2,
name: "RemoteStorage", name: "RemoteStorage",
path: admin_settings_services_path(params: { s: "remotestorage" }), path: admin_settings_service_path("remotestorage"),
text_icon: Setting.remotestorage_enabled? ? "◉" : "○", 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 # Allow requests from any IP
config.web_console.permissions = '0.0.0.0/0' 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 config.active_storage.service = :s3
else else
config.active_storage.service = :local 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. # Set this to true and configure the email server for immediate delivery to raise delivery errors.
config.action_mailer.raise_delivery_errors = true 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 config.active_storage.service = :s3
else else
config.active_storage.service = :local config.active_storage.service = :local

View File

@@ -52,10 +52,9 @@ Rails.application.configure do
config.active_job.queue_adapter = :test 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 config.active_storage.service = :s3
else else
# Store attachments on the local disk (in ./tmp) config.active_storage.service = :local
config.active_storage.service = :test
end end
end end

View File

@@ -93,8 +93,8 @@ Rails.application.routes.draw do
end end
namespace :settings do namespace :settings do
resources 'registrations', only: ['index', 'create'] resource 'registrations', only: ['show', 'update']
resources 'services', only: ['index', 'create'] resources 'services', param: 'service', only: ['index', 'show', 'update']
end end
end end

View File

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

View File

@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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| create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.string "record_type", 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| create_table "donations", force: :cascade do |t|
t.integer "user_id" t.integer "user_id"
t.integer "amount_sats" t.integer "amount_sats"
t.integer "amount_eur" t.integer "fiat_amount"
t.integer "amount_usd"
t.string "public_name" t.string "public_name"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.datetime "paid_at", precision: nil 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" t.index ["user_id"], name: "index_donations_on_user_id"
end end

View File

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

View File

@@ -2,7 +2,7 @@ services:
ldap: ldap:
image: 4teamwork/389ds:latest image: 4teamwork/389ds:latest
volumes: volumes:
- ./tmp/389ds:/data - 389ds-data:/data
networks: networks:
- external_network - external_network
- internal_network - internal_network
@@ -16,11 +16,12 @@ services:
restart: always restart: always
image: redis:7-alpine image: redis:7-alpine
networks: networks:
- external_network
- internal_network - internal_network
healthcheck: healthcheck:
test: ['CMD', 'redis-cli', 'ping'] test: ['CMD', 'redis-cli', 'ping']
volumes: volumes:
- ./tmp/redis:/data - redis-data:/data
web: web:
build: . build: .
@@ -42,8 +43,10 @@ services:
LDAP_ADMIN_PASSWORD: passthebutter LDAP_ADMIN_PASSWORD: passthebutter
LDAP_USE_TLS: "false" LDAP_USE_TLS: "false"
REDIS_URL: redis://redis:6379/0 REDIS_URL: redis://redis:6379/0
ACTIVE_STORAGE_PATH: "/akkounts/tmp/attachments"
RS_REDIS_URL: redis://redis:6379/1 RS_REDIS_URL: redis://redis:6379/1
RS_STORAGE_URL: "http://localhost:4567" RS_STORAGE_URL: "http://localhost:4567"
S3_ENABLED: false
depends_on: depends_on:
- ldap - ldap
- redis - redis
@@ -67,6 +70,7 @@ services:
REDIS_URL: redis://redis:6379/0 REDIS_URL: redis://redis:6379/0
RS_REDIS_URL: redis://redis:6379/1 RS_REDIS_URL: redis://redis:6379/1
RS_STORAGE_URL: "http://localhost:4567" RS_STORAGE_URL: "http://localhost:4567"
S3_ENABLED: false
depends_on: depends_on:
- ldap - ldap
- redis - redis
@@ -81,10 +85,10 @@ services:
- "9000:9000" - "9000:9000"
- "9001:9001" - "9001:9001"
volumes: volumes:
- ./tmp/minio:/data - minio-data:/data
liquor-cabinet: 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: networks:
- external_network - external_network
- internal_network - internal_network
@@ -116,3 +120,11 @@ networks:
external_network: external_network:
internal_network: internal_network:
internal: true 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", "postcss-preset-env": "^7.8.3",
"tailwindcss": "^3.2.4" "tailwindcss": "^3.2.4"
}, },
"version": "0.8.1", "version": "0.9.0",
"scripts": { "scripts": {
"build:css:tailwind": "tailwindcss --postcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css", "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" "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 scenario "Opening service settings shows page for first service" do
visit admin_settings_services_path 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 end
scenario "View service settings" do 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_content("Enable ejabberd integration")
expect(page).to have_field("API URL", with: "http://xmpp.example.com/api") expect(page).to have_field("API URL", with: "http://xmpp.example.com/api")
end end
scenario "Disable a service integration" do 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]") expect(page).to have_checked_field("setting[ejabberd_enabled]")
uncheck "setting[ejabberd_enabled]" uncheck "setting[ejabberd_enabled]"
click_button "Save" 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_checked_field("setting[ejabberd_enabled]")
expect(page).to_not have_field("API URL", disabled: true) expect(page).to_not have_field("API URL", disabled: true)
end end
scenario "Resettable fields" do 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 have_field("API URL", with: "http://xmpp.example.com/api")
expect(page).to_not have_css('input#setting_ejabberd_api_url+button') expect(page).to_not have_css('input#setting_ejabberd_api_url+button')
Setting.ejabberd_api_url = "http://example.com/foo" 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_field("API URL", with: "http://example.com/foo")
expect(page).to have_css('input#setting_ejabberd_api_url+button') expect(page).to have_css('input#setting_ejabberd_api_url+button')
end 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" uid: user.cn, ou: user.ou, display_name: "Mark"
}) })
allow_any_instance_of(User).to receive(:avatar).and_return(avatar_base64) allow_any_instance_of(User).to receive(:avatar).and_return(avatar_base64)
Flipper.enable "avatar_upload"
end end
feature "Update display name" do feature "Update display name" do

View File

@@ -15,7 +15,7 @@ RSpec.describe "WebFinger", type: :request do
res = JSON.parse(response.body) res = JSON.parse(response.body)
rs_link = res["links"].find {|l| l["rel"] == "http://tools.ietf.org/id/draft-dejong-remotestorage"} 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"] 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") expect(oauth_url).to eql("http://www.example.com/rs/oauth/tony")