Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d12c63db26
|
||
|
|
e6a9ef84ce
|
||
|
|
b7e91344a0
|
||
|
|
0f07e32781
|
||
|
|
1311b5ed6a
|
||
|
|
12f82061e8
|
||
|
|
a07b4369ab
|
||
|
|
2605c06807
|
||
|
|
1db768fb15
|
||
|
|
8a7403df32
|
||
|
|
f0295fef7a
|
||
|
|
090affd304
|
||
|
|
bafddd436b
|
||
|
|
560f193c4b
|
||
|
|
8aabbad5bb
|
||
|
|
ba8d21eb7a
|
||
|
|
53df455d53
|
||
|
|
9f1af3a9aa
|
||
|
|
1d09008ce2
|
||
|
|
57c5317c38
|
||
|
|
41bd920060
|
||
|
|
0815fa6040
|
||
|
|
af0e99aa50
|
||
|
|
f05eec5255
|
||
|
|
66ca2dc6b0
|
||
|
|
800183e9da
|
+61
@@ -0,0 +1,61 @@
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: CI build
|
||||
|
||||
steps:
|
||||
- name: restore-cache
|
||||
image: drillster/drone-volume-cache
|
||||
volumes:
|
||||
- name: cache
|
||||
path: /cache
|
||||
settings:
|
||||
restore: true
|
||||
mount:
|
||||
- ./vendor/cache
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- name: rspec
|
||||
image: gitea.kosmos.org/kosmos/akkounts-ci:0.9.1
|
||||
environment:
|
||||
RAILS_ENV: test
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
RS_REDIS_URL: redis://redis:6379/1
|
||||
commands:
|
||||
- bundle config unset deployment
|
||||
- bundle config set cache_all 'true'
|
||||
- bundle config set cache_path 'vendor/cache'
|
||||
- bundle config set with 'development test'
|
||||
- bundle install --jobs=3 --retry=3
|
||||
- bundle exec rails db:create
|
||||
- bundle exec rails db:migrate
|
||||
- yarn install
|
||||
- rake css:build
|
||||
- bundle exec rspec
|
||||
- name: rebuild-cache
|
||||
image: drillster/drone-volume-cache
|
||||
volumes:
|
||||
- name: cache
|
||||
path: /cache
|
||||
settings:
|
||||
rebuild: true
|
||||
mount:
|
||||
- ./vendor/cache
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
|
||||
services:
|
||||
- name: redis
|
||||
image: redis
|
||||
|
||||
volumes:
|
||||
- name: cache
|
||||
host:
|
||||
path: /var/lib/drone/tmp
|
||||
---
|
||||
kind: signature
|
||||
hmac: f9a8cf97f6596625721365f6238f6f298aa5a7a4de10c3fb61c57202ae9d1ee1
|
||||
|
||||
...
|
||||
+1
-23
@@ -1,23 +1,6 @@
|
||||
# PRIMARY_DOMAIN=kosmos.org
|
||||
# AKKOUNTS_DOMAIN=accounts.example.com
|
||||
|
||||
# Generate this using `rails secret`
|
||||
# SECRET_KEY_BASE=
|
||||
|
||||
# Generate these using `rails db:encryption:init`
|
||||
# (Optional, needed for LndHub integration)
|
||||
# ENCRYPTION_PRIMARY_KEY=
|
||||
# ENCRYPTION_KEY_DERIVATION_SALT=
|
||||
|
||||
# The default backend is SQLite
|
||||
# DB_ADAPTER=postgresql
|
||||
# PG_HOST=localhost
|
||||
# PG_PORT=5432
|
||||
# PG_DATABASE=akkounts
|
||||
# PG_DATABASE_QUEUE=akkounts_queue
|
||||
# PG_USERNAME=akkounts
|
||||
# PG_PASSWORD=
|
||||
|
||||
# SMTP_SERVER=smtp.example.com
|
||||
# SMTP_PORT=587
|
||||
# SMTP_LOGIN=accounts
|
||||
@@ -37,12 +20,8 @@
|
||||
|
||||
# LDAP_HOST=localhost
|
||||
# LDAP_PORT=389
|
||||
# LDAP_USE_TLS=false
|
||||
# LDAP_UID_ATTR=cn
|
||||
# LDAP_BASE="ou=kosmos.org,cn=users,dc=kosmos,dc=org"
|
||||
# LDAP_ADMIN_USER="cn=Directory Manager"
|
||||
# LDAP_ADMIN_PASSWORD=passthebutter
|
||||
# LDAP_SUFFIX="dc=kosmos,dc=org"
|
||||
# LDAP_SUFFIX='dc=kosmos,dc=org'
|
||||
|
||||
# REDIS_URL='redis://localhost:6379/1'
|
||||
|
||||
@@ -87,7 +66,6 @@
|
||||
# NOSTR_PRIVATE_KEY='123456abcdef...'
|
||||
# NOSTR_PUBLIC_KEY='123456abcdef...'
|
||||
# NOSTR_RELAY_URL='wss://nostr.kosmos.org'
|
||||
# NOSTR_BLOSSOM_SERVER_URL='https://blossom.kosmos.org'
|
||||
|
||||
# RS_STORAGE_URL='https://storage.kosmos.org'
|
||||
# RS_REDIS_URL='redis://localhost:6379/2'
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
PRIMARY_DOMAIN=kosmos.org
|
||||
AKKOUNTS_DOMAIN=accounts.kosmos.org
|
||||
|
||||
ENCRYPTION_PRIMARY_KEY=YhNLBgCFMAzw5dV3gISxnGrhNDMQwRdn
|
||||
ENCRYPTION_KEY_DERIVATION_SALT=h28g16MRZ1sghF2jTCos1DiLZXUswinR
|
||||
|
||||
REDIS_URL='redis://localhost:6379/0'
|
||||
|
||||
BTCPAY_PUBLIC_URL='https://btcpay.example.com'
|
||||
@@ -24,8 +21,7 @@ LNDHUB_PUBLIC_KEY='024cd3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de55648
|
||||
NOSTR_PRIVATE_KEY='7c3ef7e448505f0615137af38569d01807d3b05b5005d5ecf8aaafcd40323cea'
|
||||
NOSTR_PUBLIC_KEY='bdd76ce2934b2f591f9fad2ebe9da18f20d2921de527494ba00eeaa0a0efadcf'
|
||||
|
||||
RS_REDIS_URL='redis://localhost:6379/1'
|
||||
RS_STORAGE_URL='https://storage.kosmos.org'
|
||||
RS_AKKOUNTS_DOMAIN=localhost
|
||||
RS_REDIS_URL='redis://localhost:6379/1'
|
||||
|
||||
WEBHOOKS_ALLOWED_IPS='10.1.1.23'
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
container: gitea.kosmos.org/kosmos/akkounts-ci:0.11.1
|
||||
services:
|
||||
redis:
|
||||
image: redis
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
env:
|
||||
RAILS_ENV: test
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
RS_REDIS_URL: redis://redis:6379/1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Restore bundle cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: vendor/bundle
|
||||
key: ${{ runner.os }}-ruby-3.3.12-${{ hashFiles('Gemfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-ruby-3.3.12-
|
||||
|
||||
- name: Install Ruby dependencies
|
||||
run: |
|
||||
bundle config unset deployment
|
||||
bundle config set cache_all 'true'
|
||||
bundle config set cache_path 'vendor/cache'
|
||||
bundle config set path 'vendor/bundle'
|
||||
bundle config set with 'development test'
|
||||
bundle install --jobs=3 --retry=3
|
||||
|
||||
- name: Prepare database
|
||||
run: |
|
||||
bundle exec rails db:create
|
||||
bundle exec rails db:migrate
|
||||
|
||||
- name: Install JS dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Build CSS
|
||||
run: |
|
||||
bundle exec rake css:build
|
||||
test -s app/assets/builds/application.css
|
||||
|
||||
- name: Run tests
|
||||
run: bundle exec rspec
|
||||
@@ -9,5 +9,3 @@ jobs:
|
||||
steps:
|
||||
- name: Release Drafter
|
||||
uses: https://github.com/raucao/gitea-release-drafter@dev
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
+3
-3
@@ -34,6 +34,9 @@
|
||||
/public/packs
|
||||
/public/packs-test
|
||||
/node_modules
|
||||
/yarn-error.log
|
||||
yarn-debug.log*
|
||||
.yarn-integrity
|
||||
|
||||
# Ignore local dotenv config file
|
||||
.env
|
||||
@@ -44,6 +47,3 @@ dump.rdb
|
||||
|
||||
/app/assets/builds/*
|
||||
!/app/assets/builds/.keep
|
||||
|
||||
# Ignore generated ctags
|
||||
*.tags
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
3.3.12
|
||||
3.3.0
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
# AGENTS.md
|
||||
|
||||
Akkounts is a Rails 8 monolith for managing Kosmos/LDAP user accounts.
|
||||
It authenticates against an LDAP directory via Devise and integrates with
|
||||
ejabberd, Discourse, Mastodon, remoteStorage, Nostr, LNDHub, and BTCPay.
|
||||
|
||||
## Development environment
|
||||
|
||||
Development runs in Docker Compose — run all commands against the `web` container.
|
||||
Start services: `docker compose up` (web, ldap, redis, minio, liquor-cabinet, strfry).
|
||||
The `web` service runs `bin/dev` (foreman: Puma + Tailwind CSS watcher) and embeds
|
||||
Solid Queue workers (`SOLID_QUEUE_IN_PUMA=true`).
|
||||
|
||||
First-time LDAP setup (after creating the 389ds backend once):
|
||||
`docker compose exec ldap dsconf localhost backend create --suffix="dc=kosmos,dc=org" --be_name="dev"`
|
||||
then `docker compose run web bin/rails ldap:setup`.
|
||||
|
||||
## Common commands (prefix with `docker compose exec web`)
|
||||
|
||||
- `bin/rspec` — run the test suite (CI runs `bundle exec rspec`)
|
||||
- `bin/rails db:prepare` — create/migrate DB (also `db:setup`, `db:migrate`)
|
||||
- `bin/rails ldap:setup` — reset LDAP dir and seed dev entries
|
||||
- `bin/rails css:build` — build Tailwind CSS (auto-watched in dev)
|
||||
- `bin/rails lndhub:generate_wallets`, `invitations:generate_for_all_users[N]`
|
||||
- `bin/jobs` — Solid Queue CLI
|
||||
|
||||
Tests use the test env explicitly: `docker compose exec -e RAILS_ENV=test web bin/rspec`.
|
||||
There is no enforced linter — a `bin/rubocop` binstub exists but no `.rubocop.yml`
|
||||
(rubocop is a transitive gem, not in the Gemfile); CI does not lint.
|
||||
|
||||
## Stack & conventions
|
||||
|
||||
- **Assets/views:** ERB + ViewComponent (`app/components/`), Tailwind built via Bun +
|
||||
importmap (`config/importmap.rb`); pin JS deps with `bin/importmap pin <pkg> --download`.
|
||||
- **Auth:** Devise + `devise_ldap_authenticatable`; `authentication_keys = [:cn]`,
|
||||
custom SSHA512 password builder, admin bind. Admin-only mounts (`/jobs` MissionControl,
|
||||
`/flipper` Flipper UI) are gated by `authenticate :user, ->(u) { u.is_admin? }`.
|
||||
- **Services:** `app/services/` uses `ApplicationService.call(**args)` with namespaced
|
||||
`*ManagerService` bases (e.g. `UserManagerService`) and verb-named operations
|
||||
(`UserManager::CreateAccount`, `NostrManager::PublishEvent`, `BtcpayManager::*`).
|
||||
- **Models:** `User` is central; `Setting` uses rails-settings-cached with one concern per
|
||||
service in `app/models/concerns/settings/`. LNDHub models connect to a separate DB
|
||||
(`LndhubBase` `establish_connection :lndhub`). LDAP is accessed via `LdapService`/
|
||||
`LdapManagerService`, not ActiveRecord.
|
||||
- **Jobs:** `app/jobs/` inherit `ApplicationJob < ActiveJob::Base`; backend is **Solid Queue**
|
||||
(`config.active_job.queue_adapter = :solid_queue`, separate `queue` DB in prod).
|
||||
`config/recurring.yml` has no active scheduled jobs. Ignore `config/sidekiq.yml` — it's
|
||||
stale (sidekiq is not in the Gemfile; the README mention is outdated).
|
||||
- **Config:** env-var driven (`.env.example`/`.env.development`/`.env.test`); `SERVICES`
|
||||
constant is loaded from `config/services.yml` in `config/initializers/service_details.rb`.
|
||||
Custom LDAP schema attributes are applied from `schemas/ldap/*.ldif` by `lib/tasks/ldap.rake`.
|
||||
|
||||
## Testing conventions (`spec/`)
|
||||
|
||||
`spec/rails_helper.rb` includes FactoryBot, Devise `ControllerHelpers` (controller specs),
|
||||
Warden `Test::Helpers`, DatabaseCleaner (transactional fixtures **off**), ViewComponent
|
||||
`TestHelpers` + Capybara matchers, and `ActiveJob::TestHelper` for `type: :job`.
|
||||
|
||||
- **Feature specs** (`spec/features/`, `type: :feature`): Capybara **rack-test** driver (no JS).
|
||||
Log in with Warden: `login_as user, scope: :user`; stub admin with
|
||||
`allow(Devise::LDAP::Adapter).to receive(:get_ldap_param).with(user.cn, :admin).and_return(["true"])`.
|
||||
- **Services** (`spec/services/`) are tagged `type: :model`; exercise `#call`/`service.send(:private)`
|
||||
and assert on DB rows / `enqueued_jobs`.
|
||||
- **Request/component/mailer/job/model/helper** specs in matching `spec/` subdirs.
|
||||
Fixtures live in `spec/fixtures/`; factories in `spec/factories/`.
|
||||
|
||||
## Notes & gotchas
|
||||
|
||||
- Generators (`config/application.rb`): ERB templates, RSpec, no stylesheets, factory_bot
|
||||
with `_factory` suffix dir `spec/factories`.
|
||||
- `gitno/` and `extras/strfry/` are scratch/infra (Deno Nostr relay policies) — not app code; ignore.
|
||||
- Don't add code comments unless asked (matches repo style); `frozen_string_literal` is inconsistent.
|
||||
- Default dev login: username `admin` / password `admin is admin`.
|
||||
+7
-6
@@ -1,21 +1,22 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM ruby:3.3.12
|
||||
FROM ruby:3.3.4
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
RUN apt-get update -qq && apt-get install -y --no-install-recommends curl unzip \
|
||||
RUN apt-get update -qq && apt-get install -y --no-install-recommends curl \
|
||||
ldap-utils tini libvips
|
||||
|
||||
RUN curl -fsSL https://bun.sh/install | bash -s -- bun-v1.3.14
|
||||
ENV PATH="/root/.bun/bin:$PATH"
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
|
||||
RUN apt-get update && apt-get install -y nodejs
|
||||
|
||||
WORKDIR /akkounts
|
||||
|
||||
COPY ["Gemfile", "Gemfile.lock", "package.json", "bun.lock", "./"]
|
||||
COPY ["Gemfile", "Gemfile.lock", "package.json", "./"]
|
||||
|
||||
RUN bundle install
|
||||
RUN gem install foreman
|
||||
RUN bun install
|
||||
RUN npm install -g yarn
|
||||
RUN yarn install
|
||||
|
||||
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||
EXPOSE 3000
|
||||
|
||||
@@ -2,13 +2,13 @@ source 'https://rubygems.org'
|
||||
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
||||
|
||||
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
|
||||
gem 'rails', '~> 8.1'
|
||||
gem 'rails', '~> 7.1'
|
||||
# Use Puma as the app server
|
||||
gem 'puma', '~> 8.0'
|
||||
gem 'puma', '~> 4.1'
|
||||
# View components
|
||||
gem "view_component"
|
||||
# Asset bundler
|
||||
gem 'propshaft'
|
||||
# Separate dependency since Rails 7.0
|
||||
gem 'sprockets-rails'
|
||||
# Allows custom JS build tasks to integrate with the asset pipeline
|
||||
gem 'cssbundling-rails'
|
||||
# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
|
||||
@@ -18,21 +18,25 @@ gem "turbo-rails"
|
||||
# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
|
||||
gem "stimulus-rails"
|
||||
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
|
||||
gem 'jbuilder', '~> 2.15'
|
||||
gem 'jbuilder', '~> 2.7'
|
||||
# Use Redis adapter to run Action Cable in production
|
||||
# gem 'redis', '~> 4.0'
|
||||
# Use Active Model has_secure_password
|
||||
gem 'bcrypt', '~> 3.1'
|
||||
|
||||
# Configuration
|
||||
gem 'dotenv-rails'
|
||||
|
||||
# Security
|
||||
gem 'lockbox'
|
||||
|
||||
# Authentication
|
||||
gem 'warden'
|
||||
gem 'devise', '~> 5.0'
|
||||
gem 'devise', '~> 4.9.0'
|
||||
gem 'devise_ldap_authenticatable'
|
||||
gem 'net-ldap'
|
||||
|
||||
# Utilities
|
||||
gem 'aasm'
|
||||
gem "image_processing", "~> 1.12.2"
|
||||
gem "rqrcode", "~> 2.0"
|
||||
gem 'rails-settings-cached', '~> 2.8.3'
|
||||
@@ -40,9 +44,6 @@ gem 'pagy', '~> 6.0', '>= 6.0.2'
|
||||
gem 'flipper'
|
||||
gem 'flipper-active_record'
|
||||
gem 'flipper-ui'
|
||||
gem 'gpgme', '~> 2.0.24'
|
||||
gem 'zbase32', '~> 0.1.1'
|
||||
gem 'kramdown'
|
||||
|
||||
# HTTP requests
|
||||
gem 'faraday'
|
||||
@@ -50,8 +51,8 @@ gem 'down'
|
||||
gem 'aws-sdk-s3', require: false
|
||||
|
||||
# Background/scheduled jobs
|
||||
gem 'solid_queue'
|
||||
gem "mission_control-jobs"
|
||||
gem 'sidekiq', '< 7'
|
||||
gem 'sidekiq-scheduler'
|
||||
|
||||
# Monitoring
|
||||
gem "sentry-ruby"
|
||||
@@ -62,18 +63,17 @@ gem 'discourse_api'
|
||||
gem "lnurl"
|
||||
gem 'manifique', '~> 1.1.0'
|
||||
gem 'nostr', '~> 0.6.0'
|
||||
gem "redis", "~> 5.4"
|
||||
|
||||
group :development, :test do
|
||||
# Use sqlite3 as the database for Active Record
|
||||
gem 'sqlite3', '>= 2.1'
|
||||
gem 'sqlite3', '~> 1.7.2'
|
||||
gem 'rspec-rails'
|
||||
gem 'rails-controller-testing'
|
||||
end
|
||||
|
||||
group :development do
|
||||
# Access an interactive console on exception pages or by calling 'console' anywhere in the code.
|
||||
gem 'web-console', '~> 4.3'
|
||||
gem 'web-console', '~> 4.2'
|
||||
gem 'listen', '~> 3.2'
|
||||
gem 'letter_opener'
|
||||
gem 'letter_opener_web'
|
||||
|
||||
+287
-337
@@ -1,114 +1,110 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
aasm (5.5.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
action_text-trix (2.1.19)
|
||||
railties
|
||||
actioncable (8.1.3.1)
|
||||
actionpack (= 8.1.3.1)
|
||||
activesupport (= 8.1.3.1)
|
||||
actioncable (7.1.3)
|
||||
actionpack (= 7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
zeitwerk (~> 2.6)
|
||||
actionmailbox (8.1.3.1)
|
||||
actionpack (= 8.1.3.1)
|
||||
activejob (= 8.1.3.1)
|
||||
activerecord (= 8.1.3.1)
|
||||
activestorage (= 8.1.3.1)
|
||||
activesupport (= 8.1.3.1)
|
||||
mail (>= 2.8.0)
|
||||
actionmailer (8.1.3.1)
|
||||
actionpack (= 8.1.3.1)
|
||||
actionview (= 8.1.3.1)
|
||||
activejob (= 8.1.3.1)
|
||||
activesupport (= 8.1.3.1)
|
||||
mail (>= 2.8.0)
|
||||
actionmailbox (7.1.3)
|
||||
actionpack (= 7.1.3)
|
||||
activejob (= 7.1.3)
|
||||
activerecord (= 7.1.3)
|
||||
activestorage (= 7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
mail (>= 2.7.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
actionmailer (7.1.3)
|
||||
actionpack (= 7.1.3)
|
||||
actionview (= 7.1.3)
|
||||
activejob (= 7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
rails-dom-testing (~> 2.2)
|
||||
actionpack (8.1.3.1)
|
||||
actionview (= 8.1.3.1)
|
||||
activesupport (= 8.1.3.1)
|
||||
actionpack (7.1.3)
|
||||
actionview (= 7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
nokogiri (>= 1.8.5)
|
||||
racc
|
||||
rack (>= 2.2.4)
|
||||
rack-session (>= 1.0.1)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
useragent (~> 0.16)
|
||||
actiontext (8.1.3.1)
|
||||
action_text-trix (~> 2.1.15)
|
||||
actionpack (= 8.1.3.1)
|
||||
activerecord (= 8.1.3.1)
|
||||
activestorage (= 8.1.3.1)
|
||||
activesupport (= 8.1.3.1)
|
||||
actiontext (7.1.3)
|
||||
actionpack (= 7.1.3)
|
||||
activerecord (= 7.1.3)
|
||||
activestorage (= 7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (8.1.3.1)
|
||||
activesupport (= 8.1.3.1)
|
||||
actionview (7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.11)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
activejob (8.1.3.1)
|
||||
activesupport (= 8.1.3.1)
|
||||
activejob (7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (8.1.3.1)
|
||||
activesupport (= 8.1.3.1)
|
||||
activerecord (8.1.3.1)
|
||||
activemodel (= 8.1.3.1)
|
||||
activesupport (= 8.1.3.1)
|
||||
activemodel (7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
activerecord (7.1.3)
|
||||
activemodel (= 7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
timeout (>= 0.4.0)
|
||||
activestorage (8.1.3.1)
|
||||
actionpack (= 8.1.3.1)
|
||||
activejob (= 8.1.3.1)
|
||||
activerecord (= 8.1.3.1)
|
||||
activesupport (= 8.1.3.1)
|
||||
activestorage (7.1.3)
|
||||
actionpack (= 7.1.3)
|
||||
activejob (= 7.1.3)
|
||||
activerecord (= 7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
marcel (~> 1.0)
|
||||
activesupport (8.1.3.1)
|
||||
activesupport (7.1.3)
|
||||
base64
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
connection_pool (>= 2.2.5)
|
||||
drb
|
||||
i18n (>= 1.6, < 2)
|
||||
json
|
||||
logger (>= 1.4.2)
|
||||
minitest (>= 5.1)
|
||||
securerandom (>= 0.3)
|
||||
tzinfo (~> 2.0, >= 2.0.5)
|
||||
uri (>= 0.13.1)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
ast (2.4.3)
|
||||
aws-eventstream (1.3.2)
|
||||
aws-partitions (1.1092.0)
|
||||
aws-sdk-core (3.222.2)
|
||||
mutex_m
|
||||
tzinfo (~> 2.0)
|
||||
addressable (2.8.6)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
ast (2.4.2)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.886.0)
|
||||
aws-sdk-core (3.191.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
base64
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.8)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
logger
|
||||
aws-sdk-kms (1.99.0)
|
||||
aws-sdk-core (~> 3, >= 3.216.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.183.0)
|
||||
aws-sdk-core (~> 3, >= 3.216.0)
|
||||
aws-sdk-kms (1.77.0)
|
||||
aws-sdk-core (~> 3, >= 3.191.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.143.0)
|
||||
aws-sdk-core (~> 3, >= 3.191.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.11.0)
|
||||
aws-sigv4 (~> 1.8)
|
||||
aws-sigv4 (1.8.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
backport (1.2.0)
|
||||
base64 (0.3.0)
|
||||
bcrypt (3.1.22)
|
||||
bech32 (1.5.0)
|
||||
base64 (0.2.0)
|
||||
bcrypt (3.1.20)
|
||||
bech32 (1.4.2)
|
||||
thor (>= 1.1.0)
|
||||
benchmark (0.5.0)
|
||||
bigdecimal (4.1.2)
|
||||
benchmark (0.3.0)
|
||||
bigdecimal (3.1.6)
|
||||
bindex (0.8.1)
|
||||
bip-schnorr (0.7.0)
|
||||
ecdsa_ext (~> 0.5.0)
|
||||
builder (3.3.0)
|
||||
builder (3.2.4)
|
||||
capybara (3.40.0)
|
||||
addressable
|
||||
matrix
|
||||
@@ -118,145 +114,133 @@ GEM
|
||||
rack-test (>= 0.6.3)
|
||||
regexp_parser (>= 1.5, < 3.0)
|
||||
xpath (~> 3.2)
|
||||
childprocess (5.1.0)
|
||||
logger (~> 1.5)
|
||||
chunky_png (1.4.0)
|
||||
concurrent-ruby (1.3.8)
|
||||
connection_pool (3.0.2)
|
||||
crack (1.0.0)
|
||||
concurrent-ruby (1.2.3)
|
||||
connection_pool (2.4.1)
|
||||
crack (0.4.6)
|
||||
bigdecimal
|
||||
rexml
|
||||
crass (1.0.7)
|
||||
cssbundling-rails (1.4.3)
|
||||
crass (1.0.6)
|
||||
cssbundling-rails (1.4.0)
|
||||
railties (>= 6.0.0)
|
||||
database_cleaner (2.1.0)
|
||||
database_cleaner (2.0.2)
|
||||
database_cleaner-active_record (>= 2, < 3)
|
||||
database_cleaner-active_record (2.2.0)
|
||||
database_cleaner-active_record (2.1.0)
|
||||
activerecord (>= 5.a)
|
||||
database_cleaner-core (~> 2.0.0)
|
||||
database_cleaner-core (2.0.1)
|
||||
date (3.5.1)
|
||||
devise (5.0.4)
|
||||
date (3.3.4)
|
||||
devise (4.9.3)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 7.0)
|
||||
railties (>= 4.1.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
devise_ldap_authenticatable (0.8.7)
|
||||
devise (>= 3.4.1)
|
||||
net-ldap (>= 0.16.0)
|
||||
diff-lcs (1.6.2)
|
||||
diff-lcs (1.5.1)
|
||||
discourse_api (2.0.1)
|
||||
faraday (~> 2.7)
|
||||
faraday-follow_redirects
|
||||
faraday-multipart
|
||||
rack (>= 1.6)
|
||||
dotenv (3.1.8)
|
||||
dotenv-rails (3.1.8)
|
||||
dotenv (= 3.1.8)
|
||||
railties (>= 6.1)
|
||||
down (5.4.2)
|
||||
dotenv (2.8.1)
|
||||
dotenv-rails (2.8.1)
|
||||
dotenv (= 2.8.1)
|
||||
railties (>= 3.2)
|
||||
down (5.4.1)
|
||||
addressable (~> 2.8)
|
||||
drb (2.2.3)
|
||||
drb (2.2.0)
|
||||
ruby2_keywords
|
||||
e2mmap (0.1.0)
|
||||
ecdsa (1.2.0)
|
||||
ecdsa_ext (0.5.1)
|
||||
ecdsa (~> 1.2.0)
|
||||
erb (6.0.7)
|
||||
erubi (1.13.1)
|
||||
et-orbi (1.4.1)
|
||||
erubi (1.12.0)
|
||||
et-orbi (1.2.7)
|
||||
tzinfo
|
||||
event_emitter (0.2.6)
|
||||
eventmachine (1.2.7)
|
||||
factory_bot (6.5.1)
|
||||
activesupport (>= 6.1.0)
|
||||
factory_bot_rails (6.4.4)
|
||||
factory_bot (~> 6.5)
|
||||
factory_bot (6.4.6)
|
||||
activesupport (>= 5.0.0)
|
||||
factory_bot_rails (6.4.3)
|
||||
factory_bot (~> 6.4)
|
||||
railties (>= 5.0.0)
|
||||
faker (3.5.1)
|
||||
faker (3.2.3)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (2.9.2)
|
||||
faraday (2.9.0)
|
||||
faraday-net_http (>= 2.0, < 3.2)
|
||||
faraday-follow_redirects (0.3.0)
|
||||
faraday (>= 1, < 3)
|
||||
faraday-multipart (1.1.0)
|
||||
multipart-post (~> 2.0)
|
||||
faraday-net_http (3.1.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (3.1.0)
|
||||
net-http
|
||||
faye-websocket (0.11.3)
|
||||
eventmachine (>= 0.12.0)
|
||||
websocket-driver (>= 0.5.1)
|
||||
ffi (1.17.2)
|
||||
ffi (1.17.2-arm64-darwin)
|
||||
ffi (1.17.2-x86_64-linux-gnu)
|
||||
flipper (1.3.4)
|
||||
ffi (1.16.3)
|
||||
flipper (1.2.2)
|
||||
concurrent-ruby (< 2)
|
||||
flipper-active_record (1.3.4)
|
||||
activerecord (>= 4.2, < 9)
|
||||
flipper (~> 1.3.4)
|
||||
flipper-ui (1.3.4)
|
||||
flipper-active_record (1.2.2)
|
||||
activerecord (>= 4.2, < 8)
|
||||
flipper (~> 1.2.2)
|
||||
flipper-ui (1.2.2)
|
||||
erubi (>= 1.0.0, < 2.0.0)
|
||||
flipper (~> 1.3.4)
|
||||
flipper (~> 1.2.2)
|
||||
rack (>= 1.4, < 4)
|
||||
rack-protection (>= 1.5.3, < 5.0.0)
|
||||
rack-session (>= 1.0.2, < 3.0.0)
|
||||
sanitize (< 8)
|
||||
fugit (1.13.0)
|
||||
et-orbi (~> 1.4)
|
||||
rack-protection (>= 1.5.3, <= 4.0.0)
|
||||
sanitize (< 7)
|
||||
fugit (1.9.0)
|
||||
et-orbi (~> 1, >= 1.2.7)
|
||||
raabro (~> 1.4)
|
||||
globalid (1.4.0)
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
gpgme (2.0.24)
|
||||
mini_portile2 (~> 2.7)
|
||||
hashdiff (1.1.2)
|
||||
i18n (1.15.2)
|
||||
hashdiff (1.1.0)
|
||||
i18n (1.14.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
image_processing (1.12.2)
|
||||
mini_magick (>= 4.9.5, < 5)
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
importmap-rails (2.2.3)
|
||||
importmap-rails (2.0.1)
|
||||
actionpack (>= 6.0.0)
|
||||
activesupport (>= 6.0.0)
|
||||
railties (>= 6.0.0)
|
||||
io-console (0.9.2)
|
||||
irb (1.18.0)
|
||||
pp (>= 0.6.0)
|
||||
prism (>= 1.3.0)
|
||||
rdoc (>= 4.0.0)
|
||||
io-console (0.7.2)
|
||||
irb (1.11.1)
|
||||
rdoc
|
||||
reline (>= 0.4.2)
|
||||
jaro_winkler (1.6.0)
|
||||
jbuilder (2.15.1)
|
||||
actionview (>= 7.0.0)
|
||||
activesupport (>= 7.0.0)
|
||||
jaro_winkler (1.5.6)
|
||||
jbuilder (2.11.5)
|
||||
actionview (>= 5.0.0)
|
||||
activesupport (>= 5.0.0)
|
||||
jmespath (1.6.2)
|
||||
json (2.21.2)
|
||||
kramdown (2.5.1)
|
||||
rexml (>= 3.3.9)
|
||||
json (2.7.1)
|
||||
kramdown (2.4.0)
|
||||
rexml
|
||||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
language_server-protocol (3.17.0.4)
|
||||
launchy (3.1.1)
|
||||
language_server-protocol (3.17.0.3)
|
||||
launchy (2.5.2)
|
||||
addressable (~> 2.8)
|
||||
childprocess (~> 5.0)
|
||||
logger (~> 1.6)
|
||||
letter_opener (1.10.0)
|
||||
launchy (>= 2.2, < 4)
|
||||
letter_opener_web (3.0.0)
|
||||
actionmailer (>= 6.1)
|
||||
letter_opener (~> 1.9)
|
||||
railties (>= 6.1)
|
||||
letter_opener (1.8.1)
|
||||
launchy (>= 2.2, < 3)
|
||||
letter_opener_web (2.0.0)
|
||||
actionmailer (>= 5.2)
|
||||
letter_opener (~> 1.7)
|
||||
railties (>= 5.2)
|
||||
rexml
|
||||
lint_roller (1.1.0)
|
||||
listen (3.9.0)
|
||||
listen (3.8.0)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
lnurl (1.1.1)
|
||||
lnurl (1.1.0)
|
||||
bech32 (~> 1.1)
|
||||
logger (1.7.0)
|
||||
loofah (2.25.2)
|
||||
lockbox (1.3.2)
|
||||
loofah (2.22.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
mail (2.9.1)
|
||||
logger
|
||||
mail (2.8.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
net-imap
|
||||
net-pop
|
||||
@@ -265,28 +249,18 @@ GEM
|
||||
faraday (~> 2.9.0)
|
||||
faraday-follow_redirects (= 0.3.0)
|
||||
nokogiri (~> 1.16.0)
|
||||
marcel (1.2.1)
|
||||
marcel (1.0.2)
|
||||
matrix (0.4.2)
|
||||
mini_magick (4.13.2)
|
||||
method_source (1.0.0)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.9)
|
||||
minitest (6.0.6)
|
||||
drb (~> 2.0)
|
||||
prism (~> 1.5)
|
||||
mission_control-jobs (1.1.0)
|
||||
actioncable (>= 7.1)
|
||||
actionpack (>= 7.1)
|
||||
activejob (>= 7.1)
|
||||
activerecord (>= 7.1)
|
||||
importmap-rails (>= 1.2.1)
|
||||
irb (~> 1.13)
|
||||
railties (>= 7.1)
|
||||
stimulus-rails
|
||||
turbo-rails
|
||||
multipart-post (2.4.1)
|
||||
net-http (0.6.0)
|
||||
mini_portile2 (2.8.5)
|
||||
minitest (5.21.2)
|
||||
multipart-post (2.3.0)
|
||||
mutex_m (0.2.0)
|
||||
net-http (0.4.1)
|
||||
uri
|
||||
net-imap (0.6.6)
|
||||
net-imap (0.4.9.1)
|
||||
date
|
||||
net-protocol
|
||||
net-ldap (0.19.0)
|
||||
@@ -294,15 +268,15 @@ GEM
|
||||
net-protocol
|
||||
net-protocol (0.2.2)
|
||||
timeout
|
||||
net-smtp (0.5.1)
|
||||
net-smtp (0.4.0.1)
|
||||
net-protocol
|
||||
nio4r (2.7.5)
|
||||
nokogiri (1.16.8)
|
||||
nio4r (2.7.0)
|
||||
nokogiri (1.16.0)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.16.8-arm64-darwin)
|
||||
nokogiri (1.16.0-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.16.8-x86_64-linux)
|
||||
nokogiri (1.16.0-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
nostr (0.6.0)
|
||||
bech32 (~> 1.4)
|
||||
@@ -311,225 +285,205 @@ GEM
|
||||
event_emitter (~> 0.2)
|
||||
faye-websocket (~> 0.11)
|
||||
json (~> 2.6)
|
||||
observer (0.1.2)
|
||||
orm_adapter (0.5.0)
|
||||
ostruct (0.6.1)
|
||||
pagy (6.5.0)
|
||||
parallel (1.27.0)
|
||||
parser (3.3.8.0)
|
||||
pagy (6.4.3)
|
||||
parallel (1.24.0)
|
||||
parser (3.3.0.5)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
pg (1.5.9)
|
||||
pp (0.6.4)
|
||||
prettyprint
|
||||
prettyprint (0.2.0)
|
||||
prism (1.9.0)
|
||||
propshaft (1.3.2)
|
||||
actionpack (>= 7.0.0)
|
||||
activesupport (>= 7.0.0)
|
||||
rack
|
||||
psych (5.4.0)
|
||||
date
|
||||
pg (1.5.4)
|
||||
psych (5.1.2)
|
||||
stringio
|
||||
public_suffix (6.0.1)
|
||||
puma (8.0.2)
|
||||
public_suffix (5.0.4)
|
||||
puma (4.3.12)
|
||||
nio4r (~> 2.0)
|
||||
raabro (1.5.0)
|
||||
racc (1.8.1)
|
||||
rack (2.2.23)
|
||||
raabro (1.4.0)
|
||||
racc (1.7.3)
|
||||
rack (2.2.8)
|
||||
rack-protection (3.2.0)
|
||||
base64 (>= 0.1.0)
|
||||
rack (~> 2.2, >= 2.2.4)
|
||||
rack-session (1.0.2)
|
||||
rack (< 3)
|
||||
rack-test (2.2.0)
|
||||
rack-test (2.1.0)
|
||||
rack (>= 1.3)
|
||||
rackup (1.0.1)
|
||||
rackup (1.0.0)
|
||||
rack (< 3)
|
||||
webrick
|
||||
rails (8.1.3.1)
|
||||
actioncable (= 8.1.3.1)
|
||||
actionmailbox (= 8.1.3.1)
|
||||
actionmailer (= 8.1.3.1)
|
||||
actionpack (= 8.1.3.1)
|
||||
actiontext (= 8.1.3.1)
|
||||
actionview (= 8.1.3.1)
|
||||
activejob (= 8.1.3.1)
|
||||
activemodel (= 8.1.3.1)
|
||||
activerecord (= 8.1.3.1)
|
||||
activestorage (= 8.1.3.1)
|
||||
activesupport (= 8.1.3.1)
|
||||
rails (7.1.3)
|
||||
actioncable (= 7.1.3)
|
||||
actionmailbox (= 7.1.3)
|
||||
actionmailer (= 7.1.3)
|
||||
actionpack (= 7.1.3)
|
||||
actiontext (= 7.1.3)
|
||||
actionview (= 7.1.3)
|
||||
activejob (= 7.1.3)
|
||||
activemodel (= 7.1.3)
|
||||
activerecord (= 7.1.3)
|
||||
activestorage (= 7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 8.1.3.1)
|
||||
railties (= 7.1.3)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
actionview (>= 5.0.1.rc1)
|
||||
activesupport (>= 5.0.1.rc1)
|
||||
rails-dom-testing (2.3.0)
|
||||
rails-dom-testing (2.2.0)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.7.1)
|
||||
loofah (~> 2.25, >= 2.25.2)
|
||||
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
||||
rails-html-sanitizer (1.6.0)
|
||||
loofah (~> 2.21)
|
||||
nokogiri (~> 1.14)
|
||||
rails-settings-cached (2.8.3)
|
||||
activerecord (>= 5.0.0)
|
||||
railties (>= 5.0.0)
|
||||
railties (8.1.3.1)
|
||||
actionpack (= 8.1.3.1)
|
||||
activesupport (= 8.1.3.1)
|
||||
irb (~> 1.13)
|
||||
railties (7.1.3)
|
||||
actionpack (= 7.1.3)
|
||||
activesupport (= 7.1.3)
|
||||
irb
|
||||
rackup (>= 1.0.0)
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0, >= 1.2.2)
|
||||
tsort (>= 0.2)
|
||||
zeitwerk (~> 2.6)
|
||||
rainbow (3.1.1)
|
||||
rake (13.4.2)
|
||||
rake (13.1.0)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.11.1)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rbs (3.9.2)
|
||||
logger
|
||||
rdoc (7.2.0)
|
||||
erb
|
||||
rbs (2.8.4)
|
||||
rdoc (6.6.2)
|
||||
psych (>= 4.0.0)
|
||||
tsort
|
||||
redis (5.4.0)
|
||||
redis-client (>= 0.22.0)
|
||||
redis-client (0.24.0)
|
||||
connection_pool
|
||||
regexp_parser (2.10.0)
|
||||
reline (0.7.0)
|
||||
redis (4.8.1)
|
||||
regexp_parser (2.9.0)
|
||||
reline (0.4.2)
|
||||
io-console (~> 0.5)
|
||||
responders (3.2.0)
|
||||
actionpack (>= 7.0)
|
||||
railties (>= 7.0)
|
||||
reverse_markdown (3.0.0)
|
||||
responders (3.1.1)
|
||||
actionpack (>= 5.2)
|
||||
railties (>= 5.2)
|
||||
reverse_markdown (2.1.1)
|
||||
nokogiri
|
||||
rexml (3.4.1)
|
||||
rexml (3.2.6)
|
||||
rqrcode (2.2.0)
|
||||
chunky_png (~> 1.0)
|
||||
rqrcode_core (~> 1.0)
|
||||
rqrcode_core (1.2.0)
|
||||
rspec-core (3.13.6)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-expectations (3.13.5)
|
||||
rspec-core (3.12.2)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-expectations (3.12.3)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-mocks (3.13.8)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-mocks (3.12.6)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-rails (8.0.4)
|
||||
actionpack (>= 7.2)
|
||||
activesupport (>= 7.2)
|
||||
railties (>= 7.2)
|
||||
rspec-core (>= 3.13.0, < 5.0.0)
|
||||
rspec-expectations (>= 3.13.0, < 5.0.0)
|
||||
rspec-mocks (>= 3.13.0, < 5.0.0)
|
||||
rspec-support (>= 3.13.0, < 5.0.0)
|
||||
rspec-support (3.13.7)
|
||||
rubocop (1.75.3)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-rails (6.1.1)
|
||||
actionpack (>= 6.1)
|
||||
activesupport (>= 6.1)
|
||||
railties (>= 6.1)
|
||||
rspec-core (~> 3.12)
|
||||
rspec-expectations (~> 3.12)
|
||||
rspec-mocks (~> 3.12)
|
||||
rspec-support (~> 3.12)
|
||||
rspec-support (3.12.1)
|
||||
rubocop (1.60.2)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
lint_roller (~> 1.1.0)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.3.0.2)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 2.9.3, < 3.0)
|
||||
rubocop-ast (>= 1.44.0, < 2.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.30.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 4.0)
|
||||
rubocop-ast (1.44.1)
|
||||
parser (>= 3.3.7.2)
|
||||
prism (~> 1.4)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.30.0)
|
||||
parser (>= 3.2.1.0)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby-vips (2.2.3)
|
||||
ruby-vips (2.2.0)
|
||||
ffi (~> 1.12)
|
||||
logger
|
||||
sanitize (7.0.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
rufus-scheduler (3.9.1)
|
||||
fugit (~> 1.1, >= 1.1.6)
|
||||
sanitize (6.1.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.16.8)
|
||||
securerandom (0.4.1)
|
||||
sentry-rails (5.23.0)
|
||||
nokogiri (>= 1.12.0)
|
||||
sentry-rails (5.16.1)
|
||||
railties (>= 5.0)
|
||||
sentry-ruby (~> 5.23.0)
|
||||
sentry-ruby (5.23.0)
|
||||
bigdecimal
|
||||
sentry-ruby (~> 5.16.1)
|
||||
sentry-ruby (5.16.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
solargraph (0.54.2)
|
||||
sidekiq (6.5.12)
|
||||
connection_pool (>= 2.2.5, < 3)
|
||||
rack (~> 2.0)
|
||||
redis (>= 4.5.0, < 5)
|
||||
sidekiq-scheduler (5.0.3)
|
||||
rufus-scheduler (~> 3.2)
|
||||
sidekiq (>= 6, < 8)
|
||||
tilt (>= 1.4.0)
|
||||
solargraph (0.50.0)
|
||||
backport (~> 1.2)
|
||||
benchmark (~> 0.4)
|
||||
benchmark
|
||||
bundler (~> 2.0)
|
||||
diff-lcs (~> 1.4)
|
||||
jaro_winkler (~> 1.6)
|
||||
e2mmap
|
||||
jaro_winkler (~> 1.5)
|
||||
kramdown (~> 2.3)
|
||||
kramdown-parser-gfm (~> 1.1)
|
||||
logger (~> 1.6)
|
||||
observer (~> 0.1)
|
||||
ostruct (~> 0.6)
|
||||
parser (~> 3.0)
|
||||
rbs (~> 3.3)
|
||||
reverse_markdown (~> 3.0)
|
||||
rbs (~> 2.0)
|
||||
reverse_markdown (~> 2.0)
|
||||
rubocop (~> 1.38)
|
||||
thor (~> 1.0)
|
||||
tilt (~> 2.0)
|
||||
yard (~> 0.9, >= 0.9.24)
|
||||
yard-solargraph (~> 0.1)
|
||||
solid_queue (1.6.0)
|
||||
activejob (>= 7.1)
|
||||
activerecord (>= 7.1)
|
||||
concurrent-ruby (>= 1.3.1)
|
||||
fugit (~> 1.11)
|
||||
railties (>= 7.1)
|
||||
thor (>= 1.3.1)
|
||||
sqlite3 (2.6.0)
|
||||
sprockets (4.2.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (>= 2.2.4, < 4)
|
||||
sprockets-rails (3.4.2)
|
||||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
sprockets (>= 3.0.0)
|
||||
sqlite3 (1.7.2)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
sqlite3 (2.6.0-arm64-darwin)
|
||||
sqlite3 (2.6.0-x86_64-linux-gnu)
|
||||
stimulus-rails (1.3.4)
|
||||
sqlite3 (1.7.2-arm64-darwin)
|
||||
sqlite3 (1.7.2-x86_64-linux)
|
||||
stimulus-rails (1.3.3)
|
||||
railties (>= 6.0.0)
|
||||
stringio (3.1.0)
|
||||
thor (1.3.0)
|
||||
tilt (2.3.0)
|
||||
timeout (0.4.1)
|
||||
turbo-rails (1.5.0)
|
||||
actionpack (>= 6.0.0)
|
||||
activejob (>= 6.0.0)
|
||||
railties (>= 6.0.0)
|
||||
stringio (3.2.0)
|
||||
thor (1.5.0)
|
||||
tilt (2.6.0)
|
||||
timeout (0.6.1)
|
||||
tsort (0.2.0)
|
||||
turbo-rails (2.0.23)
|
||||
actionpack (>= 7.1.0)
|
||||
railties (>= 7.1.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
unicode-display_width (3.1.4)
|
||||
unicode-emoji (~> 4.0, >= 4.0.4)
|
||||
unicode-emoji (4.0.4)
|
||||
uri (1.1.1)
|
||||
useragent (0.16.11)
|
||||
view_component (4.12.0)
|
||||
actionview (>= 7.1.0)
|
||||
activesupport (>= 7.1.0)
|
||||
concurrent-ruby (~> 1)
|
||||
unicode-display_width (2.5.0)
|
||||
uri (0.13.0)
|
||||
view_component (3.10.0)
|
||||
activesupport (>= 5.2.0, < 8.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
method_source (~> 1.0)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
web-console (4.3.0)
|
||||
actionview (>= 8.0.0)
|
||||
web-console (4.2.1)
|
||||
actionview (>= 6.0.0)
|
||||
activemodel (>= 6.0.0)
|
||||
bindex (>= 0.4.0)
|
||||
railties (>= 8.0.0)
|
||||
webmock (3.25.1)
|
||||
railties (>= 6.0.0)
|
||||
webmock (3.19.1)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webrick (1.9.2)
|
||||
websocket-driver (0.8.2)
|
||||
base64
|
||||
webrick (1.8.1)
|
||||
websocket-driver (0.7.6)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
yard (0.9.37)
|
||||
yard-solargraph (0.1.0)
|
||||
yard (~> 0.9)
|
||||
zbase32 (0.1.1)
|
||||
zeitwerk (2.8.3)
|
||||
yard (0.9.34)
|
||||
zeitwerk (2.6.12)
|
||||
|
||||
PLATFORMS
|
||||
arm64-darwin-22
|
||||
@@ -537,13 +491,12 @@ PLATFORMS
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
aasm
|
||||
aws-sdk-s3
|
||||
bcrypt (~> 3.1)
|
||||
capybara
|
||||
cssbundling-rails
|
||||
database_cleaner
|
||||
devise (~> 5.0)
|
||||
devise (~> 4.9.0)
|
||||
devise_ldap_authenticatable
|
||||
discourse_api
|
||||
dotenv-rails
|
||||
@@ -554,42 +507,39 @@ DEPENDENCIES
|
||||
flipper
|
||||
flipper-active_record
|
||||
flipper-ui
|
||||
gpgme (~> 2.0.24)
|
||||
image_processing (~> 1.12.2)
|
||||
importmap-rails
|
||||
jbuilder (~> 2.15)
|
||||
kramdown
|
||||
jbuilder (~> 2.7)
|
||||
letter_opener
|
||||
letter_opener_web
|
||||
listen (~> 3.2)
|
||||
lnurl
|
||||
lockbox
|
||||
manifique (~> 1.1.0)
|
||||
mission_control-jobs
|
||||
net-ldap
|
||||
nostr (~> 0.6.0)
|
||||
pagy (~> 6.0, >= 6.0.2)
|
||||
pg (~> 1.5)
|
||||
propshaft
|
||||
puma (~> 8.0)
|
||||
rails (~> 8.1)
|
||||
puma (~> 4.1)
|
||||
rails (~> 7.1)
|
||||
rails-controller-testing
|
||||
rails-settings-cached (~> 2.8.3)
|
||||
redis (~> 5.4)
|
||||
rqrcode (~> 2.0)
|
||||
rspec-rails
|
||||
sentry-rails
|
||||
sentry-ruby
|
||||
sidekiq (< 7)
|
||||
sidekiq-scheduler
|
||||
solargraph
|
||||
solid_queue
|
||||
sqlite3 (>= 2.1)
|
||||
sprockets-rails
|
||||
sqlite3 (~> 1.7.2)
|
||||
stimulus-rails
|
||||
turbo-rails
|
||||
tzinfo-data
|
||||
view_component
|
||||
warden
|
||||
web-console (~> 4.3)
|
||||
web-console (~> 4.2)
|
||||
webmock
|
||||
zbase32 (~> 0.1.1)
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.5
|
||||
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
web: bin/rails server -b 0.0.0.0 -p 3000
|
||||
css: bun run build:css:tailwind --watch
|
||||
css: yarn build:css --watch
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[](https://gitea.kosmos.org/kosmos/akkounts/actions)
|
||||
[](https://drone.kosmos.org/kosmos/akkounts)
|
||||
|
||||
# Akkounts
|
||||
|
||||
@@ -36,7 +36,7 @@ run web`._
|
||||
Installing dependencies:
|
||||
|
||||
bundle install
|
||||
bun install
|
||||
yarn install
|
||||
|
||||
Migrating the local database (after schema changes):
|
||||
|
||||
@@ -57,7 +57,7 @@ Running the test suite:
|
||||
Running the test suite with Docker Compose requires overriding the Rails
|
||||
environment:
|
||||
|
||||
docker-compose exec -e "RAILS_ENV=test" web rspec
|
||||
docker-compose run -e "RAILS_ENV=test" web rspec
|
||||
|
||||
### Docker Compose
|
||||
|
||||
@@ -128,7 +128,6 @@ command:
|
||||
|
||||
### Front-end
|
||||
|
||||
* [Icons](https://feathericons.com)
|
||||
* [Tailwind CSS](https://tailwindcss.com/)
|
||||
* [Sass](https://sass-lang.com/documentation)
|
||||
* [Stimulus](https://stimulus.hotwired.dev/handbook/)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
//= link_tree ../images
|
||||
//= link_tree ../../javascript .js
|
||||
//= link_tree ../builds
|
||||
//= link_tree ../../../vendor/javascript .js
|
||||
@@ -7,7 +7,7 @@
|
||||
@import "components/buttons";
|
||||
@import "components/dashboard_services";
|
||||
@import "components/forms";
|
||||
@import "components/links";
|
||||
@import "components/notifications";
|
||||
@import "components/pagination";
|
||||
@import "components/rich_text";
|
||||
@import "components/tables";
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
body {
|
||||
@apply leading-none bg-cover bg-fixed;
|
||||
background-image: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(13,79,153,0.8) 100%), url('/img/bg-1.jpg');
|
||||
color: black;
|
||||
}
|
||||
|
||||
body#admin {
|
||||
@@ -33,10 +32,6 @@
|
||||
@apply pt-8 sm:pt-12;
|
||||
}
|
||||
|
||||
main section h3:not(:first-child) {
|
||||
@apply mt-8;
|
||||
}
|
||||
|
||||
main section:first-of-type {
|
||||
@apply pt-0;
|
||||
}
|
||||
@@ -60,11 +55,4 @@
|
||||
main ul li {
|
||||
@apply leading-6;
|
||||
}
|
||||
|
||||
main a:not(nav > *) {
|
||||
@apply text-blue-600;
|
||||
&:hover { @apply underline; }
|
||||
&:visited { @apply text-indigo-600; }
|
||||
&:active { @apply text-red-600; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
@layer components {
|
||||
.btn-text-dark { @apply text-black; }
|
||||
.btn-text-dark:hover { @apply text-black no-underline; }
|
||||
.btn-text-dark:visited { @apply text-black; }
|
||||
.btn-text-dark:active { @apply text-black; }
|
||||
.btn-text-light { @apply text-white; }
|
||||
.btn-text-light:hover { @apply text-white no-underline; }
|
||||
.btn-text-light:visited { @apply text-white; }
|
||||
.btn-text-light:active { @apply text-white; }
|
||||
|
||||
.btn {
|
||||
@apply btn-text-dark;
|
||||
@apply inline-block font-semibold rounded-md leading-none cursor-pointer text-center
|
||||
transition-colors duration-75 focus:outline-none focus:ring-4;
|
||||
}
|
||||
@@ -38,20 +28,17 @@
|
||||
}
|
||||
|
||||
.btn-blue {
|
||||
@apply btn-text-light;
|
||||
@apply bg-blue-500 hover:bg-blue-600
|
||||
@apply bg-blue-500 hover:bg-blue-600 text-white
|
||||
focus:ring-blue-400 focus:ring-opacity-75;
|
||||
}
|
||||
|
||||
.btn-emerald {
|
||||
@apply btn-text-light;
|
||||
@apply bg-emerald-500 hover:bg-emerald-600
|
||||
@apply bg-emerald-500 hover:bg-emerald-600 text-white
|
||||
focus:ring-emerald-400 focus:ring-opacity-75;
|
||||
}
|
||||
|
||||
.btn-red {
|
||||
@apply btn-text-light;
|
||||
@apply bg-red-600 hover:bg-red-700
|
||||
@apply bg-red-600 hover:bg-red-700 text-white
|
||||
focus:ring-red-500 focus:ring-opacity-75;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
@layer components {
|
||||
.ks-text-link {
|
||||
@apply text-blue-600;
|
||||
&:hover { @apply underline; }
|
||||
&:visited { @apply text-indigo-600; }
|
||||
&:active { @apply text-red-600; }
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@
|
||||
.pagy-nav .page a, .page.gap {
|
||||
@apply bg-white border-gray-300 text-gray-500 hover:bg-gray-100 relative
|
||||
inline-flex items-center border px-4 py-2 text-sm font-medium
|
||||
no-underline focus:z-20;
|
||||
focus:z-20;
|
||||
}
|
||||
|
||||
.pagy-nav .page.active {
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
@layer base {
|
||||
main {
|
||||
.rich-text {
|
||||
ul {
|
||||
@apply list-disc list-inside;
|
||||
|
||||
li {
|
||||
@apply my-2;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
@apply list-decimal list-inside;
|
||||
|
||||
li {
|
||||
@apply my-2;
|
||||
}
|
||||
}
|
||||
|
||||
li > ul,
|
||||
li > ol {
|
||||
@apply ps-6 my-1;
|
||||
}
|
||||
|
||||
hr {
|
||||
@apply my-8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
module AppCatalog
|
||||
class WebAppIconComponent < ViewComponent::Base
|
||||
include ApplicationHelper
|
||||
|
||||
def initialize(web_app:)
|
||||
if web_app&.icon&.attached?
|
||||
@image_url = image_url_for(web_app.icon)
|
||||
@@ -11,5 +9,13 @@ module AppCatalog
|
||||
@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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<%= link_to @href, class: @class, target: @target, data: {
|
||||
<%= link_to @href, class: @class, data: {
|
||||
'dropdown-target': "menuItem",
|
||||
'action': "keydown.up->dropdown#previousItem:prevent keydown.down->dropdown#nextItem:prevent"
|
||||
} do %>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class DropdownLinkComponent < ViewComponent::Base
|
||||
def initialize(href:, open_in_new_tab: false, separator: false, add_class: nil)
|
||||
def initialize(href:, separator: false, add_class: nil)
|
||||
@href = href
|
||||
@target = open_in_new_tab ? "_blank" : nil
|
||||
@class = class_str(separator, add_class)
|
||||
end
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<div class="inline-block text-left" data-controller="modal" data-action="keydown.esc->modal#close">
|
||||
<button class="btn-md btn-outline text-red-600" data-action="click->modal#open" title="Edit">
|
||||
<%= content || "Edit" %>
|
||||
</button>
|
||||
|
||||
<%= render ModalComponent.new(show_close_button: false) do %>
|
||||
<%= form_with model: [:admin, @editable_content],
|
||||
html: { autocomplete: "off" } do |form| %>
|
||||
<%= form.hidden_field :redirect_to, value: @redirect_to %>
|
||||
<p class="mb-2">
|
||||
<%= form.label :content, @editable_content.key.capitalize, class: 'font-bold' %>
|
||||
</p>
|
||||
<% if @editable_content.rich_text %>
|
||||
<p>
|
||||
<%= form.textarea :content, class: "md:w-[56rem] md:h-[28rem]" %>
|
||||
</p>
|
||||
<p class="text-right">
|
||||
<%= form.submit "Save", class: "ml-2 btn-md btn-blue" %>
|
||||
</p>
|
||||
<% else %>
|
||||
<p class="">
|
||||
<%= form.text_field :content, class: "w-80" %>
|
||||
</p>
|
||||
<p>
|
||||
<%= form.submit "Save", class: "btn-md btn-blue w-full" %>
|
||||
</p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -1,6 +0,0 @@
|
||||
class EditContentButtonComponent < ViewComponent::Base
|
||||
def initialize(context:, key:, rich_text: false, redirect_to: nil)
|
||||
@editable_content = EditableContent.find_or_create_by(context:, key:, rich_text:)
|
||||
@redirect_to = redirect_to
|
||||
end
|
||||
end
|
||||
@@ -1,11 +0,0 @@
|
||||
<% if @editable_content.has_content? %>
|
||||
<% if @editable_content.rich_text %>
|
||||
<div class="rich-text">
|
||||
<%= helpers.markdown_to_html @editable_content.content %>
|
||||
</div>
|
||||
<% else %>
|
||||
<%= @editable_content.content %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= @default %>
|
||||
<% end %>
|
||||
@@ -1,6 +0,0 @@
|
||||
class EditableContentComponent < ViewComponent::Base
|
||||
def initialize(context:, key:, rich_text: false, default: nil)
|
||||
@editable_content = EditableContent.find_or_create_by(context:, key:, rich_text:)
|
||||
@default = default
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,4 @@
|
||||
<main class="w-full max-w-xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<main class="w-full max-w-xl mx-auto pb-12 px-4 sm:px-6 lg:px-8">
|
||||
<div class="bg-white rounded-lg shadow px-6 sm:px-12 py-8 sm:py-12">
|
||||
<%= content %>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<main class="w-full max-w-6xl mx-auto px-4 md:px-6 lg:px-8">
|
||||
<main class="w-full max-w-6xl mx-auto pb-12 px-4 md:px-6 lg:px-8">
|
||||
<div class="md:min-h-[50vh] bg-white rounded-lg shadow px-6 sm:px-12 py-8 sm:py-12">
|
||||
<%= content %>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<main class="w-full max-w-6xl mx-auto px-4 md:px-6 lg:px-8">
|
||||
<main class="w-full max-w-6xl mx-auto pb-12 px-4 md:px-6 lg:px-8">
|
||||
<div class="bg-white rounded-lg shadow">
|
||||
<div class="md:min-h-[50vh] divide-y divide-gray-200 lg:grid lg:grid-cols-12 lg:divide-y-0 lg:divide-x">
|
||||
<aside class="py-6 sm:py-8 lg:col-span-3">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<main class="w-full max-w-6xl mx-auto px-4 md:px-6 lg:px-8">
|
||||
<main class="w-full max-w-6xl mx-auto pb-12 px-4 md:px-6 lg:px-8">
|
||||
<div class="md:min-h-[50vh] bg-white rounded-lg shadow">
|
||||
<div class="px-6 sm:px-12 pt-2 sm:pt-4">
|
||||
<%= render partial: @tabnav_partial %>
|
||||
|
||||
@@ -12,8 +12,7 @@
|
||||
</div>
|
||||
<%= render DropdownComponent.new do %>
|
||||
<%= render DropdownLinkComponent.new(
|
||||
href: launch_app_services_storage_rs_auth_url(@auth),
|
||||
open_in_new_tab: true
|
||||
href: launch_app_services_storage_rs_auth_url(@auth)
|
||||
) do %>
|
||||
Launch app
|
||||
<% end %>
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<div class="w-[72vw] md:w-[500px]">
|
||||
<header class="absolute z-10 h-36 sm:h-44 inset-x-1 top-1 rounded-t
|
||||
bg-cover bg-center bg-gray-50"
|
||||
style="background-image: url('<%= @profile["banner"]%>');">
|
||||
<div class="inline-block z-20 size-28 sm:size-32 ml-4 mt-16 sm:mt-20">
|
||||
<% if @profile["picture"].present? %>
|
||||
<img src="<%= @profile["picture"] %>"
|
||||
class="inline-block size:28 sm:size-32 rounded-full border-2 border-white" />
|
||||
<% else %>
|
||||
<span class="inline-block size:28 sm:size-32 overflow-hidden rounded-full border-2 border-white bg-gray-100">
|
||||
<svg class="size-full text-gray-300" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</header>
|
||||
<main class="mt-44 sm:mt-52">
|
||||
<%= form_for(@user, url: setting_path(:nostr), html: { :method => :put }) do |f| %>
|
||||
<%= render FormElements::FieldsetComponent.new(tag: "div", title: "Display name") do %>
|
||||
<%= f.text_field :display_name, value: @display_name, class: "w-full sm:w-3/5" %>
|
||||
<% if @validation_errors.present? && @validation_errors[:display_name].present? %>
|
||||
<p class="error-msg mt-2"><%= @validation_errors[:display_name].first %></p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= render FormElements::FieldsetComponent.new(tag: "div", title: "Nostr address (NIP-05)") do %>
|
||||
<%= f.text_field :nip05_address, value: @profile["nip05"], class: "w-full sm:w-3/5" %>
|
||||
<% if @validation_errors.present? && @validation_errors[:nip05_address].present? %>
|
||||
<p class="error-msg mt-2"><%= @validation_errors[:nip05_address].first %></p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= render FormElements::FieldsetComponent.new(tag: "div", title: "Ligtning address for Zaps") do %>
|
||||
<%= f.text_field :lud16_address, value: @profile["lud16"], class: "w-full sm:w-3/5" %>
|
||||
<% if @validation_errors.present? && @validation_errors[:lud16_address].present? %>
|
||||
<p class="error-msg mt-2"><%= @validation_errors[:lud16_address].first %></p>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</main>
|
||||
<footer>
|
||||
<%# <%= @profile.inspect %>
|
||||
<%# <%= @profile_event.inspect %>
|
||||
</footer>
|
||||
</div>
|
||||
@@ -1,17 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Settings
|
||||
class NostrEditProfileComponent < ViewComponent::Base
|
||||
def initialize(user:, profile_event:)
|
||||
@user = user
|
||||
@profile_event = profile_event
|
||||
@profile = {}
|
||||
@display_name = nil
|
||||
|
||||
if profile_event.present? && profile_event["content"].present?
|
||||
@profile = JSON.parse(profile_event["content"]) rescue {}
|
||||
@display_name = @profile["display_name"] || @profile["displayName"]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,45 +0,0 @@
|
||||
<div class="w-[72vw] md:w-[500px]">
|
||||
<h3 class="mb-6">Edit relays</h3>
|
||||
<%= form_with url: "#", local: true,
|
||||
html: { data: { controller: "relay-list-editor" } } do |f| %>
|
||||
<ul data-relay-list-editor-target="list" class="mb-4 space-y-2">
|
||||
<% @relays.each do |relay| %>
|
||||
<li class="flex gap-x-2 items-center">
|
||||
<input type="text" name="relay_urls[]" value="<%= relay[:url] %>" class="grow" />
|
||||
<select name="relay_markers[]" class="shrink-0 w-32">
|
||||
<option value=""<%= %( selected="selected") if relay[:marker].nil? %>>Read & write</option>
|
||||
<option value="read"<%= %( selected="selected") if relay[:marker] == "read" %>>Read only</option>
|
||||
<option value="write"<%= %( selected="selected") if relay[:marker] == "write" %>>Write only</option>
|
||||
</select>
|
||||
<button type="button" class="btn-md btn-icon btn-outline text-red-700 shrink-0"
|
||||
data-action="click->relay-list-editor#remove" title="Remove relay">
|
||||
<%= render partial: "icons/trash-2", locals: { custom_class: "h-4 w-4" } %>
|
||||
</button>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
<template data-relay-list-editor-target="template">
|
||||
<li class="flex gap-x-2 items-center">
|
||||
<input type="text" name="relay_urls[]" value="" class="grow" />
|
||||
<select name="relay_markers[]" class="shrink-0 w-32">
|
||||
<option value="">Read & write</option>
|
||||
<option value="read">Read only</option>
|
||||
<option value="write">Write only</option>
|
||||
</select>
|
||||
<button type="button" class="btn-md btn-icon btn-outline text-red-700 shrink-0"
|
||||
data-action="click->relay-list-editor#remove" title="Remove relay">
|
||||
<%= render partial: "icons/trash-2", locals: { custom_class: "h-4 w-4" } %>
|
||||
</button>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<button type="button" class="btn-md btn-gray w-full mb-6"
|
||||
data-action="click->relay-list-editor#add">
|
||||
<%= render partial: "icons/plus", locals: { custom_class: "h-4 w-4 inline mr-1" } %>
|
||||
Add relay
|
||||
</button>
|
||||
|
||||
<%= f.submit "Save relay list", class: "btn-md btn-blue w-full" %>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -1,31 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Settings
|
||||
class NostrEditRelaysComponent < ViewComponent::Base
|
||||
def initialize(nip65_event: nil)
|
||||
@nip65_event = nip65_event
|
||||
@relays = build_relays
|
||||
ensure_site_relay
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_relays
|
||||
if @nip65_event.present?
|
||||
@nip65_event["tags"]
|
||||
.select { |t| t[0] == "r" }
|
||||
.map { |t| { url: t[1], marker: t[2] } }
|
||||
else
|
||||
Setting.nostr_discovery_relays.map { |url| { url: url, marker: nil } }
|
||||
end
|
||||
end
|
||||
|
||||
def ensure_site_relay
|
||||
site_relay_url = Setting.nostr_relay_url
|
||||
return if site_relay_url.blank?
|
||||
|
||||
@relays.reject! { |r| r[:url] == site_relay_url }
|
||||
@relays.unshift({ url: site_relay_url, marker: nil })
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,21 +0,0 @@
|
||||
<% @statuses.each do |status| %>
|
||||
<%= render StatusTextComponent.new(
|
||||
text: status[:text],
|
||||
icon_name: status[:icon_name],
|
||||
icon_color: status[:icon_color]
|
||||
) %>
|
||||
<% end %>
|
||||
|
||||
<% if @status == 1 %>
|
||||
<p class="mt-8">
|
||||
<button class="btn-md btn-blue">
|
||||
Edit my profile
|
||||
</button>
|
||||
</p>
|
||||
<% elsif @status == 2 %>
|
||||
<p class="mt-8">
|
||||
<button class="btn-md btn-blue">
|
||||
Create my profile
|
||||
</button>
|
||||
</p>
|
||||
<% end %>
|
||||
@@ -1,53 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Settings
|
||||
class NostrProfileStatusComponent < ViewComponent::Base
|
||||
def initialize(profile_event:, user_address:)
|
||||
@statuses = []
|
||||
|
||||
if profile_event.present?
|
||||
profile = JSON.parse(profile_event["content"])
|
||||
|
||||
@statuses.push({
|
||||
text: "You have a public Nostr profile",
|
||||
icon_name: "check-circle",
|
||||
icon_color: "emerald-500"
|
||||
})
|
||||
|
||||
if profile["nip05"].present? && profile["nip05"] == user_address
|
||||
@statuses.push({
|
||||
text: "Your profile's Nostr address is set to <strong>#{ user_address }</strong>",
|
||||
icon_name: "check-circle",
|
||||
icon_color: "emerald-500"
|
||||
})
|
||||
else
|
||||
@statuses.push({
|
||||
text: "Your profile's Nostr address is not set to <strong>#{ user_address }</strong> yet",
|
||||
icon_name: "alert-octagon",
|
||||
icon_color: "amber-500"
|
||||
})
|
||||
end
|
||||
|
||||
if profile["lud16"].present? && profile["lud16"] == user_address
|
||||
@statuses.push({
|
||||
text: "Your profile's Lightning address is set to <strong>#{ user_address }</strong>",
|
||||
icon_name: "check-circle",
|
||||
icon_color: "emerald-500"
|
||||
})
|
||||
else
|
||||
@statuses.push({
|
||||
text: "Your profile's Lightning address is not set to <strong>#{ user_address }</strong> yet",
|
||||
icon_name: "alert-octagon",
|
||||
icon_color: "amber-500"
|
||||
})
|
||||
end
|
||||
else
|
||||
@statuses.push({
|
||||
text: "We could not find a profile for your public key",
|
||||
icon_name: "alert-octagon",
|
||||
icon_color: "amber-500"
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,18 +0,0 @@
|
||||
<% @statuses.each do |status| %>
|
||||
<%= render StatusTextComponent.new(
|
||||
text: status[:text],
|
||||
icon_name: status[:icon_name],
|
||||
icon_color: status[:icon_color]
|
||||
) %>
|
||||
<% end %>
|
||||
|
||||
<div class="mt-8" data-controller="modal" data-action="keydown.esc->modal#close">
|
||||
<button data-action="click->modal#open" class="btn-md btn-blue w-full sm:w-auto">
|
||||
<%= @button_text %>
|
||||
</button>
|
||||
<%= render ModalComponent.new(show_close_button: false) do %>
|
||||
<%= render Settings::NostrEditRelaysComponent.new(
|
||||
nip65_event: @nip65_event
|
||||
) %>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -1,48 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Settings
|
||||
class NostrRelayStatusComponent < ViewComponent::Base
|
||||
def initialize(nip65_event:, relay_url: Setting.nostr_relay_url)
|
||||
@nip65_event = nip65_event
|
||||
@relay_url = relay_url
|
||||
@relay_host = relay_url.present? ? URI.parse(relay_url).host : nil
|
||||
@statuses = []
|
||||
|
||||
if nip65_event.present?
|
||||
@statuses.push({
|
||||
text: "You have a relay list configured",
|
||||
icon_name: "check-circle",
|
||||
icon_color: "emerald-500"
|
||||
})
|
||||
|
||||
if relay_urls(nip65_event).include?(@relay_url)
|
||||
@statuses.push({
|
||||
text: "The <strong>#{@relay_host}</strong> relay is part of your relay list",
|
||||
icon_name: "check-circle",
|
||||
icon_color: "emerald-500"
|
||||
})
|
||||
else
|
||||
@statuses.push({
|
||||
text: "The <strong>#{@relay_host}</strong> relay is missing from your relay list",
|
||||
icon_name: "alert-octagon",
|
||||
icon_color: "amber-500"
|
||||
})
|
||||
end
|
||||
else
|
||||
@statuses.push({
|
||||
text: "We could not find a relay list for your public key",
|
||||
icon_name: "alert-octagon",
|
||||
icon_color: "amber-500"
|
||||
})
|
||||
end
|
||||
|
||||
@button_text = (nip65_event.present? ? "Edit relays" : "Set up relays")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def relay_urls(nip65_event)
|
||||
nip65_event["tags"].select { |t| t[0] == "r" }.map { |t| t[1] }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -29,7 +29,7 @@ class SidenavLinkComponent < ViewComponent::Base
|
||||
|
||||
def class_names_icon(path)
|
||||
if @active
|
||||
"text-teal-600 group-hover:text-teal-600 flex-shrink-0 -ml-1 mr-3 h-6 w-6"
|
||||
"text-teal-500 group-hover:text-teal-500 flex-shrink-0 -ml-1 mr-3 h-6 w-6"
|
||||
elsif @disabled
|
||||
"text-gray-300 group-hover:text-gray-300 flex-shrink-0 -ml-1 mr-3 h-6 w-6"
|
||||
else
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<p class="flex gap-x-4 items-center">
|
||||
<span class="inline-block h-6 w-6 grow-0 text-<%= @icon_color %>">
|
||||
<%= render "icons/#{@icon_name}" %>
|
||||
</span>
|
||||
<span>
|
||||
<%= raw @text %>
|
||||
</span>
|
||||
</p>
|
||||
@@ -1,7 +0,0 @@
|
||||
class StatusTextComponent < ViewComponent::Base
|
||||
def initialize(text:, icon_name:, icon_color:)
|
||||
@text = text
|
||||
@icon_name = icon_name
|
||||
@icon_color = icon_color
|
||||
end
|
||||
end
|
||||
@@ -4,22 +4,11 @@ class Admin::DonationsController < Admin::BaseController
|
||||
|
||||
# GET /donations
|
||||
def index
|
||||
@username = params[:username].presence
|
||||
|
||||
pending_scope = Donation.incomplete.joins(:user).order('paid_at desc')
|
||||
completed_scope = Donation.completed.joins(:user).order('paid_at desc')
|
||||
|
||||
if @username
|
||||
pending_scope = pending_scope.where(users: { cn: @username })
|
||||
completed_scope = completed_scope.where(users: { cn: @username })
|
||||
end
|
||||
|
||||
@pending_donations = pending_scope
|
||||
@pagy, @donations = pagy(completed_scope)
|
||||
@pagy, @donations = pagy(Donation.completed.order('paid_at desc'))
|
||||
|
||||
@stats = {
|
||||
overall_sats: completed_scope.sum("amount_sats"),
|
||||
donor_count: completed_scope.distinct.count(:user_id)
|
||||
overall_sats: @donations.sum("amount_sats"),
|
||||
donor_count: Donation.completed.count(:user_id)
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
class Admin::EditableContentsController < Admin::BaseController
|
||||
before_action :set_content, only: [:show, :edit, :update]
|
||||
before_action :set_current_section, only: [:index, :show, :edit]
|
||||
|
||||
def index
|
||||
@path = params[:path].presence
|
||||
scope = EditableContent.order(path: :asc)
|
||||
scope = scope.where(path: @path) if @path
|
||||
@pagy, @contents = pagy(scope)
|
||||
end
|
||||
|
||||
def show
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
def update
|
||||
return_to = params[:editable_content][:redirect_to].presence
|
||||
|
||||
if @editable_content.update(content_params)
|
||||
if return_to
|
||||
redirect_to return_to
|
||||
else
|
||||
render status: :ok
|
||||
end
|
||||
else
|
||||
render :edit, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_content
|
||||
@editable_content = EditableContent.find(params[:id])
|
||||
end
|
||||
|
||||
def content_params
|
||||
params.require(:editable_content).permit(:path, :key, :lang, :content, :rich_text)
|
||||
end
|
||||
|
||||
def set_current_section
|
||||
@current_section = :content
|
||||
end
|
||||
end
|
||||
@@ -1,28 +1,12 @@
|
||||
class Admin::InvitationsController < Admin::BaseController
|
||||
before_action :set_current_section
|
||||
|
||||
def index
|
||||
@username = params[:username].presence
|
||||
accepted_scope = Invitation.used.order('used_at desc')
|
||||
unused_scope = Invitation.unused
|
||||
|
||||
if @username
|
||||
accepted_scope = accepted_scope.joins(:user).where(users: { cn: @username })
|
||||
unused_scope = unused_scope.joins(:user).where(users: { cn: @username })
|
||||
end
|
||||
|
||||
@pagy, @invitations_used = pagy(accepted_scope)
|
||||
@current_section = :invitations
|
||||
@pagy, @invitations_used = pagy(Invitation.used.order('used_at desc'))
|
||||
|
||||
@stats = {
|
||||
available: unused_scope.count,
|
||||
accepted: accepted_scope.count,
|
||||
users_with_referrals: accepted_scope.distinct.count(:user_id)
|
||||
available: Invitation.unused.count,
|
||||
accepted: @invitations_used.length,
|
||||
users_with_referrals: Invitation.used.distinct.count(:user_id)
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_current_section
|
||||
@current_section = :invitations
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,7 +4,7 @@ class Admin::LightningController < Admin::BaseController
|
||||
def index
|
||||
@current_section = :lightning
|
||||
|
||||
@users = User.pluck(:cn, :ou, :lndhub_username)
|
||||
@users = User.pluck(:cn, :ou, :ln_account)
|
||||
@accounts = LndhubAccount.with_balances.order(balance: :desc).to_a
|
||||
|
||||
@ln = {}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
class Admin::Settings::MembershipController < Admin::SettingsController
|
||||
def show
|
||||
end
|
||||
|
||||
def update
|
||||
update_settings
|
||||
|
||||
redirect_to admin_settings_membership_path, flash: {
|
||||
success: "Settings saved"
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def setting_params
|
||||
params.require(:setting).permit([
|
||||
:member_status_contributor,
|
||||
:member_status_sustainer,
|
||||
:user_index_show_contributors,
|
||||
:user_index_show_sustainers
|
||||
])
|
||||
end
|
||||
end
|
||||
@@ -9,12 +9,4 @@ class Admin::Settings::RegistrationsController < Admin::SettingsController
|
||||
success: "Settings saved"
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def setting_params
|
||||
params.require(:setting).permit([
|
||||
:reserved_usernames, default_services: []
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,12 +9,11 @@ class Admin::SettingsController < Admin::BaseController
|
||||
changed_keys = []
|
||||
|
||||
setting_params.keys.each do |key|
|
||||
next if clean_param(key).nil? ||
|
||||
(Setting.send(key).to_s == clean_param(key))
|
||||
|
||||
next if setting_params[key].nil? ||
|
||||
(Setting.send(key).to_s == setting_params[key].strip)
|
||||
changed_keys.push(key)
|
||||
setting = Setting.new(var: key)
|
||||
setting.value = clean_param(key)
|
||||
setting.value = setting_params[key].strip
|
||||
unless setting.valid?
|
||||
@errors.merge!(setting.errors)
|
||||
end
|
||||
@@ -25,7 +24,7 @@ class Admin::SettingsController < Admin::BaseController
|
||||
end
|
||||
|
||||
changed_keys.each do |key|
|
||||
Setting.send("#{key}=", clean_param(key))
|
||||
Setting.send("#{key}=", setting_params[key].strip)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -38,12 +37,4 @@ class Admin::SettingsController < Admin::BaseController
|
||||
def setting_params
|
||||
params.require(:setting).permit(Setting.editable_keys.map(&:to_sym))
|
||||
end
|
||||
|
||||
def clean_param(key)
|
||||
if Setting.get_field(key)[:type] == :string
|
||||
setting_params[key].strip
|
||||
else
|
||||
setting_params[key]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,41 +4,25 @@ class Admin::UsersController < Admin::BaseController
|
||||
|
||||
# GET /admin/users
|
||||
def index
|
||||
ldap = LdapService.new
|
||||
ou = Setting.primary_domain
|
||||
@show_contributors = Setting.user_index_show_contributors
|
||||
@show_sustainers = Setting.user_index_show_sustainers
|
||||
|
||||
@contributors = ldap.search_users(:memberStatus, :contributor, :cn) if @show_contributors
|
||||
@sustainers = ldap.search_users(:memberStatus, :sustainer, :cn) if @show_sustainers
|
||||
@admins = ldap.search_users(:admin, true, :cn)
|
||||
|
||||
@username = params[:username].presence
|
||||
users_scope = User.where(ou: ou).order(cn: :asc)
|
||||
users_scope = users_scope.where("cn LIKE ?", "%#{User.sanitize_sql_like(@username.downcase)}%") if @username
|
||||
@pagy, @users = pagy(users_scope)
|
||||
ldap = LdapService.new
|
||||
@ou = Setting.primary_domain
|
||||
@pagy, @users = pagy(User.where(ou: @ou).order(cn: :asc))
|
||||
|
||||
@stats = {
|
||||
users_confirmed: User.where(ou: ou).confirmed.count,
|
||||
users_pending: User.where(ou: ou).pending.count
|
||||
users_confirmed: User.where(ou: @ou).confirmed.count,
|
||||
users_pending: User.where(ou: @ou).pending.count
|
||||
}
|
||||
@stats[:users_contributing] = @contributors.size if @show_contributors
|
||||
@stats[:users_paying] = @sustainers.size if @show_sustainers
|
||||
end
|
||||
|
||||
# GET /admin/users/:username
|
||||
def show
|
||||
@invitees = @user.invitees
|
||||
@recent_invitees = @user.invitees.order(created_at: :desc).limit(5)
|
||||
@more_invitees = (@invitees - @recent_invitees).count
|
||||
|
||||
if Setting.lndhub_admin_enabled?
|
||||
@lndhub_user = @user.lndhub_user
|
||||
end
|
||||
|
||||
@services_enabled = @user.services_enabled
|
||||
|
||||
@ldap_avatar = LdapManager::FetchAvatar.call(cn: @user.cn)
|
||||
@avatar = LdapManager::FetchAvatar.call(cn: @user.cn)
|
||||
end
|
||||
|
||||
# POST /admin/users/:username/invitations
|
||||
@@ -46,7 +30,7 @@ class Admin::UsersController < Admin::BaseController
|
||||
amount = params[:amount].to_i
|
||||
notify_user = ActiveRecord::Type::Boolean.new.cast(params[:notify_user])
|
||||
|
||||
UserManager::CreateInvitations.call(user: @user, amount: amount, notify: 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"
|
||||
|
||||
@@ -39,7 +39,7 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
|
||||
def after_sign_in_path_for(user)
|
||||
session.delete(:user_return_to) || root_path
|
||||
session[:user_return_to] || root_path
|
||||
end
|
||||
|
||||
def lndhub_authenticate(options={})
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
class AvatarsController < ApplicationController
|
||||
def show
|
||||
if user = User.find_by(cn: params[:username])
|
||||
http_status :not_found and return unless user.avatar.attached?
|
||||
|
||||
sha256_hash = params[:hash]
|
||||
format = params[:format]&.to_sym || :png
|
||||
# size = params[:size]&.to_sym || :original
|
||||
|
||||
unless user.avatar.filename.to_s == "#{sha256_hash}.#{format}"
|
||||
http_status :not_found and return
|
||||
end
|
||||
|
||||
# TODO See note for avatar_variant in user model
|
||||
# blob = if size == :original
|
||||
# user.avatar.blob
|
||||
# else
|
||||
# user.avatar_variant(size: size)&.blob
|
||||
# end
|
||||
|
||||
data = user.avatar.blob.download
|
||||
send_data data, type: "image/#{format}", disposition: "inline"
|
||||
else
|
||||
http_status :not_found
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -11,7 +11,7 @@ class Contributions::DonationsController < ApplicationController
|
||||
def index
|
||||
@current_section = :contributions
|
||||
@donations_completed = current_user.donations.completed.order('paid_at desc')
|
||||
@donations_processing = current_user.donations.processing.order('created_at desc')
|
||||
@donations_pending = current_user.donations.processing.order('created_at desc')
|
||||
|
||||
if Setting.lndhub_enabled?
|
||||
begin
|
||||
@@ -81,11 +81,14 @@ class Contributions::DonationsController < ApplicationController
|
||||
|
||||
case invoice["status"]
|
||||
when "Settled"
|
||||
@donation.complete!
|
||||
@donation.paid_at = DateTime.now
|
||||
@donation.payment_status = "settled"
|
||||
@donation.save!
|
||||
flash_message = { success: "Thank you!" }
|
||||
when "Processing"
|
||||
unless @donation.processing?
|
||||
@donation.start_processing!
|
||||
@donation.payment_status = "processing"
|
||||
@donation.save!
|
||||
flash_message = { success: "Thank you! We will send you an email when the payment is confirmed." }
|
||||
BtcpayCheckDonationJob.set(wait: 20.seconds).perform_later(@donation)
|
||||
end
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
class Contributions::OtherController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
before_action :set_content_editing
|
||||
|
||||
# GET /contributions/other
|
||||
def index
|
||||
@current_section = :contributions
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_content_editing
|
||||
return unless params[:edit] && current_user.is_admin?
|
||||
@edit_content = true
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
class Contributions::ProjectsController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
|
||||
# GET /contributions
|
||||
def index
|
||||
@current_section = :contributions
|
||||
end
|
||||
end
|
||||
@@ -55,12 +55,7 @@ class Devise::PasswordsController < DeviseController
|
||||
|
||||
protected
|
||||
def after_resetting_password_path_for(resource)
|
||||
session.delete(:user_return_to)
|
||||
if Devise.sign_in_after_reset_password
|
||||
root_path
|
||||
else
|
||||
new_session_path(resource_name)
|
||||
end
|
||||
Devise.sign_in_after_reset_password ? after_sign_in_path_for(resource) : new_session_path(resource_name)
|
||||
end
|
||||
|
||||
# The path used after sending reset password instructions
|
||||
|
||||
@@ -8,9 +8,6 @@ class Discourse::SsoController < ApplicationController
|
||||
sso.email = current_user.email
|
||||
sso.username = current_user.cn
|
||||
sso.name = current_user.display_name
|
||||
if current_user.avatar.attached?
|
||||
sso.avatar_url = helpers.image_url_for(current_user.avatar)
|
||||
end
|
||||
sso.admin = current_user.is_admin?
|
||||
sso.sso_secret = secret
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class LnurlpayController < ApplicationController
|
||||
before_action :check_service_available
|
||||
before_action :find_user
|
||||
before_action :set_cors_access_control_headers
|
||||
before_action :set_cors_access_control_headers, only: [:invoice]
|
||||
|
||||
MIN_SATS = 10
|
||||
MAX_SATS = 1_000_000
|
||||
@@ -37,7 +37,7 @@ class LnurlpayController < ApplicationController
|
||||
pubkey: Setting.lndhub_public_key,
|
||||
customData: [{
|
||||
customKey: "696969",
|
||||
customValue: @user.lndhub_username
|
||||
customValue: @user.ln_account
|
||||
}]
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
class PagesController < ApplicationController
|
||||
def privacy
|
||||
@current_section = :privacy
|
||||
end
|
||||
|
||||
def tos
|
||||
@current_section = :tos
|
||||
end
|
||||
end
|
||||
@@ -1,5 +1,4 @@
|
||||
class Rs::OauthController < ApplicationController
|
||||
prepend_before_action :assert_redirect_uri, only: [:new, :create]
|
||||
before_action :require_signed_in_with_username, only: :new
|
||||
before_action :authenticate_user!, only: :create
|
||||
|
||||
@@ -17,6 +16,8 @@ class Rs::OauthController < ApplicationController
|
||||
["In 1 month", 1.month.from_now],
|
||||
["In 1 day", 1.day.from_now]]
|
||||
|
||||
http_status :bad_request and return unless @redirect_uri.present?
|
||||
|
||||
unless current_user == @user
|
||||
sign_out :user
|
||||
|
||||
@@ -63,6 +64,8 @@ class Rs::OauthController < ApplicationController
|
||||
state = params[:state].presence
|
||||
expire_at = params[:expire_at].presence
|
||||
|
||||
http_status :bad_request and return unless redirect_uri.present?
|
||||
|
||||
if permissions.empty?
|
||||
redirect_to(url_with_state("#{redirect_uri}#error=invalid_scope", state),
|
||||
allow_other_host: true) and return
|
||||
@@ -94,10 +97,6 @@ class Rs::OauthController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def assert_redirect_uri
|
||||
http_status :bad_request unless params[:redirect_uri].present?
|
||||
end
|
||||
|
||||
def require_signed_in_with_username
|
||||
unless user_signed_in?
|
||||
session[:user_return_to] = request.url
|
||||
|
||||
@@ -3,7 +3,7 @@ class Services::ChatController < Services::BaseController
|
||||
before_action :require_service_available
|
||||
|
||||
def show
|
||||
@service_enabled = current_user.service_enabled?(:ejabberd)
|
||||
@service_enabled = current_user.service_enabled?(:xmpp)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -9,7 +9,7 @@ class Services::LightningController < ApplicationController
|
||||
before_action :lndhub_fetch_balance
|
||||
|
||||
def index
|
||||
@wallet_setup_url = "lndhub://#{current_user.lndhub_username}:#{current_user.lndhub_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
|
||||
@wallet_setup_url = "lndhub://#{current_user.ln_account}:#{current_user.ln_password}@#{ENV['LNDHUB_PUBLIC_URL']}"
|
||||
end
|
||||
|
||||
def transactions
|
||||
|
||||
@@ -23,11 +23,7 @@ class Services::RsAuthsController < Services::BaseController
|
||||
end
|
||||
|
||||
def launch_app
|
||||
user_address = Rails.env.development? ?
|
||||
"#{current_user.cn}@localhost:3000" :
|
||||
current_user.address
|
||||
|
||||
launch_url = "#{@auth.launch_url}#remotestorage=#{user_address}"
|
||||
launch_url = "#{@auth.launch_url}#remotestorage=#{current_user.address}&access_token=#{@auth.token}"
|
||||
|
||||
redirect_to launch_url, allow_other_host: true
|
||||
end
|
||||
|
||||
@@ -4,13 +4,8 @@ require "bcrypt"
|
||||
class SettingsController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
before_action :set_main_nav_section
|
||||
before_action :set_settings_section, only: [
|
||||
:show, :update, :update_email, :reset_email_password
|
||||
]
|
||||
before_action :set_user, only: [
|
||||
:show, :update, :update_email, :reset_email_password,
|
||||
:fetch_nostr_metadata
|
||||
]
|
||||
before_action :set_settings_section, only: [:show, :update, :update_email, :reset_email_password]
|
||||
before_action :set_user, only: [:show, :update, :update_email, :reset_email_password]
|
||||
|
||||
def index
|
||||
redirect_to setting_path(:profile)
|
||||
@@ -26,12 +21,10 @@ class SettingsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
# PUT /settings/:section
|
||||
def update
|
||||
@user.preferences.merge!(user_params[:preferences] || {})
|
||||
@user.display_name = user_params[:display_name]
|
||||
@user.avatar_new = user_params[:avatar_new]
|
||||
@user.pgp_pubkey = user_params[:pgp_pubkey]
|
||||
@user.avatar_new = user_params[:avatar]
|
||||
|
||||
if @user.save
|
||||
if @user.display_name && (@user.display_name != @user.ldap_entry[:display_name])
|
||||
@@ -39,16 +32,7 @@ class SettingsController < ApplicationController
|
||||
end
|
||||
|
||||
if @user.avatar_new.present?
|
||||
if store_user_avatar
|
||||
UserManager::UpdateAvatar.call(user: @user)
|
||||
else
|
||||
@validation_errors = @user.errors
|
||||
render :show, status: :unprocessable_entity and return
|
||||
end
|
||||
end
|
||||
|
||||
if @user.pgp_pubkey && (@user.pgp_pubkey != @user.ldap_entry[:pgp_key])
|
||||
UserManager::UpdatePgpKey.call(user: @user)
|
||||
LdapManager::UpdateAvatar.call(dn: @user.dn, file: @user.avatar_new)
|
||||
end
|
||||
|
||||
redirect_to setting_path(@settings_section), flash: {
|
||||
@@ -60,7 +44,6 @@ class SettingsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
# POST /settings/update_email
|
||||
def update_email
|
||||
if @user.valid_ldap_authentication?(security_params[:current_password])
|
||||
if @user.update email: email_params[:email]
|
||||
@@ -78,7 +61,6 @@ class SettingsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
# POST /settings/reset_email_password
|
||||
def reset_email_password
|
||||
@user.current_password = security_params[:current_password]
|
||||
|
||||
@@ -101,7 +83,6 @@ class SettingsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
# POST /settings/reset_password
|
||||
def reset_password
|
||||
current_user.send_reset_password_instructions
|
||||
sign_out current_user
|
||||
@@ -109,7 +90,6 @@ class SettingsController < ApplicationController
|
||||
redirect_to check_your_email_path, notice: msg
|
||||
end
|
||||
|
||||
# POST /settings/set_nostr_pubkey
|
||||
def set_nostr_pubkey
|
||||
signed_event = Nostr::Event.new(**nostr_event_from_params)
|
||||
|
||||
@@ -148,63 +128,12 @@ class SettingsController < ApplicationController
|
||||
}
|
||||
end
|
||||
|
||||
def fetch_nostr_metadata
|
||||
return render plain: "no pubkey", status: :bad_request if @user.nostr_pubkey.blank?
|
||||
|
||||
cache_key = "nostr_metadata:#{@user.id}"
|
||||
|
||||
case params[:step]
|
||||
when "relays"
|
||||
nip65_event = NostrManager::DiscoverUserRelays.call(pubkey: @user.nostr_pubkey)
|
||||
write_relays = nip65_event ? extract_write_relays(nip65_event) : []
|
||||
|
||||
if nip65_event && write_relays.any? && stale?(nip65_event)
|
||||
auth_event = NostrManager::DiscoverUserRelays.call(
|
||||
pubkey: @user.nostr_pubkey, relays: write_relays
|
||||
)
|
||||
nip65_event = auth_event if auth_event
|
||||
write_relays = extract_write_relays(nip65_event) if auth_event
|
||||
end
|
||||
|
||||
Rails.cache.write("#{cache_key}:nip65", nip65_event, expires_in: 5.minutes)
|
||||
Rails.cache.write("#{cache_key}:write_relays", write_relays, expires_in: 5.minutes)
|
||||
render json: { next: "profile" }
|
||||
|
||||
when "profile"
|
||||
write_relays = Rails.cache.read("#{cache_key}:write_relays") || []
|
||||
relays = write_relays.any? ? write_relays : nil
|
||||
profile = NostrManager::DiscoverUserProfile.call(
|
||||
pubkey: @user.nostr_pubkey, relays: relays
|
||||
)
|
||||
Rails.cache.write("#{cache_key}:profile", profile, expires_in: 5.minutes)
|
||||
render json: { next: "render" }
|
||||
|
||||
when "render", nil
|
||||
@nip65_event = Rails.cache.read("#{cache_key}:nip65")
|
||||
@profile = Rails.cache.read("#{cache_key}:profile")
|
||||
render partial: "nostr_metadata", formats: :html
|
||||
|
||||
else
|
||||
render plain: "invalid step", status: :bad_request
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_main_nav_section
|
||||
@current_section = :settings
|
||||
end
|
||||
|
||||
def extract_write_relays(nip65_event)
|
||||
nip65_event["tags"]
|
||||
.select { |t| t[0] == "r" && t[2] != "read" }
|
||||
.map { |t| t[1] }
|
||||
end
|
||||
|
||||
def stale?(event, threshold = 30.minutes)
|
||||
Time.at(event["created_at"]) < threshold.ago
|
||||
end
|
||||
|
||||
def set_settings_section
|
||||
@settings_section = params[:section]
|
||||
allowed_sections = [
|
||||
@@ -223,8 +152,7 @@ class SettingsController < ApplicationController
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(
|
||||
:display_name, :avatar_new, :pgp_pubkey,
|
||||
preferences: UserPreferences.pref_keys
|
||||
:display_name, :avatar, preferences: UserPreferences.pref_keys
|
||||
)
|
||||
end
|
||||
|
||||
@@ -245,30 +173,4 @@ class SettingsController < ApplicationController
|
||||
salt = BCrypt::Engine.generate_salt
|
||||
BCrypt::Engine.hash_secret(password, salt)
|
||||
end
|
||||
|
||||
def store_user_avatar
|
||||
io = @user.avatar_new.tempfile
|
||||
img_data = UserManager::ProcessAvatar.call(io: io)
|
||||
if img_data.blank?
|
||||
@user.errors.add(:avatar, "failed to process file")
|
||||
false
|
||||
end
|
||||
tempfile = Tempfile.create
|
||||
tempfile.binmode
|
||||
tempfile.write(img_data)
|
||||
tempfile.rewind
|
||||
|
||||
hash = Digest::SHA256.hexdigest(img_data)
|
||||
ext = @user.avatar_new.content_type == "image/png" ? "png" : "jpg"
|
||||
filename = "#{hash}.#{ext}"
|
||||
|
||||
if filename == @user.avatar.filename.to_s
|
||||
@user.errors.add(:avatar, "must be a new file/picture")
|
||||
false
|
||||
else
|
||||
key = "users/#{@user.cn}/avatars/#{filename}"
|
||||
@user.avatar.attach io: tempfile, key: key, filename: filename
|
||||
@user.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -96,7 +96,7 @@ class SignupController < ApplicationController
|
||||
session[:new_user] = nil
|
||||
session[:validation_error] = nil
|
||||
|
||||
UserManager::CreateAccount.call(account: {
|
||||
CreateAccount.call(account: {
|
||||
username: @user.cn,
|
||||
domain: Setting.primary_domain,
|
||||
email: @user.email,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
class TurboController < ApplicationController
|
||||
class Responder < ActionController::Responder
|
||||
def to_turbo_stream
|
||||
controller.render(options.merge(formats: :html))
|
||||
rescue ActionView::MissingTemplate => error
|
||||
if get?
|
||||
raise error
|
||||
elsif has_errors? && default_action
|
||||
render rendering_options.merge(formats: :html, status: :unprocessable_entity)
|
||||
else
|
||||
redirect_to navigation_location
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.responder = Responder
|
||||
respond_to :html, :turbo_stream
|
||||
end
|
||||
@@ -6,7 +6,7 @@ class Users::DeviseController < ApplicationController
|
||||
if get?
|
||||
raise error
|
||||
elsif has_errors? && default_action
|
||||
render error_rendering_options.merge(formats: :html, status: :unprocessable_entity)
|
||||
render rendering_options.merge(formats: :html, status: :unprocessable_entity)
|
||||
else
|
||||
redirect_to navigation_location
|
||||
end
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
class WebKeyDirectoryController < WellKnownController
|
||||
before_action :allow_cross_origin_requests
|
||||
|
||||
# /.well-known/openpgpkey/hu/:hashed_username(.txt)?l=username
|
||||
def show
|
||||
if params[:l].blank?
|
||||
# TODO store hashed username in db if existing implementations trigger
|
||||
# this a lot
|
||||
msg = "WKD request with \"l\" param omitted for hu: #{params[:hashed_username]}"
|
||||
Sentry.capture_message(msg) if Setting.sentry_enabled?
|
||||
http_status :bad_request and return
|
||||
end
|
||||
|
||||
@user = User.find_by(cn: params[:l].downcase)
|
||||
|
||||
if @user.nil? ||
|
||||
@user.pgp_pubkey.blank? ||
|
||||
!@user.pgp_pubkey_contains_user_address?
|
||||
http_status :not_found and return
|
||||
end
|
||||
|
||||
if params[:hashed_username] != @user.wkd_hash
|
||||
http_status :unprocessable_entity and return
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.text do
|
||||
response.headers['Content-Type'] = 'text/plain'
|
||||
render plain: @user.pgp_pubkey
|
||||
end
|
||||
|
||||
format.any do
|
||||
key = @user.gnupg_key.export
|
||||
send_data key, filename: "#{@user.wkd_hash}.pem",
|
||||
type: "application/octet-stream"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def policy
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
@@ -33,10 +33,6 @@ class WebfingerController < WellKnownController
|
||||
links: []
|
||||
}
|
||||
|
||||
if @user.avatar.attached?
|
||||
jrd[:links] += avatar_link
|
||||
end
|
||||
|
||||
if Setting.mastodon_enabled && @user.service_enabled?(:mastodon)
|
||||
# https://docs.joinmastodon.org/spec/webfinger/
|
||||
jrd[:aliases] += mastodon_aliases
|
||||
@@ -51,16 +47,6 @@ class WebfingerController < WellKnownController
|
||||
jrd
|
||||
end
|
||||
|
||||
def avatar_link
|
||||
[
|
||||
{
|
||||
rel: "http://webfinger.net/rel/avatar",
|
||||
type: @user.avatar.content_type,
|
||||
href: helpers.image_url_for(@user.avatar)
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
def mastodon_aliases
|
||||
[
|
||||
"#{Setting.mastodon_public_url}/@#{@user.cn}",
|
||||
@@ -88,7 +74,7 @@ class WebfingerController < WellKnownController
|
||||
end
|
||||
|
||||
def remotestorage_link
|
||||
auth_url = new_rs_oauth_url(@username, host: Setting.rs_accounts_domain)
|
||||
auth_url = new_rs_oauth_url(@username)
|
||||
storage_url = "#{Setting.rs_storage_url}/#{@username}"
|
||||
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ class WebhooksController < ApplicationController
|
||||
before_action :process_payload
|
||||
|
||||
def lndhub
|
||||
@user = User.find_by!(lndhub_username: @payload[:user_login])
|
||||
@user = User.find_by!(ln_account: @payload[:user_login])
|
||||
|
||||
if @zap = @user.zaps.find_by(payment_request: @payload[:payment_request])
|
||||
settled_at = Time.parse(@payload[:settled_at])
|
||||
|
||||
@@ -10,14 +10,8 @@ class WellKnownController < ApplicationController
|
||||
relay_url = Setting.nostr_relay_url.presence
|
||||
|
||||
if params[:name] == "_"
|
||||
if domain == Setting.primary_domain
|
||||
# pubkey for the primary domain without a username (e.g. kosmos.org)
|
||||
res = { names: { "_": Setting.nostr_public_key_primary_domain.presence || Setting.nostr_public_key } }
|
||||
else
|
||||
# pubkey for the akkounts domain without a username (e.g. accounts.kosmos.org)
|
||||
res = { names: { "_": Setting.nostr_public_key } }
|
||||
end
|
||||
|
||||
# pubkey for the primary domain without a username (e.g. kosmos.org)
|
||||
res = { names: { "_": Setting.nostr_public_key } }
|
||||
res[:relays] = { "_" => [ relay_url ] } if relay_url
|
||||
else
|
||||
@user = User.where(cn: params[:name], ou: domain).first
|
||||
|
||||
@@ -14,25 +14,4 @@ module ApplicationHelper
|
||||
def badge(text, color)
|
||||
tag.span text, class: "inline-flex items-center rounded-full bg-#{color}-100 px-2.5 py-0.5 text-xs font-medium text-#{color}-800"
|
||||
end
|
||||
|
||||
def markdown_to_html(string)
|
||||
raw Kramdown::Document.new(string, {
|
||||
input: "GFM"
|
||||
}).to_html
|
||||
end
|
||||
|
||||
def image_url_for(attachment)
|
||||
return s3_image_url(attachment) if Setting.s3_enabled?
|
||||
|
||||
if attachment.record.is_a?(User) && attachment.name == "avatar"
|
||||
hash, format = attachment.blob.filename.to_s.split(".", 2)
|
||||
user_avatar_url(
|
||||
username: attachment.record.cn,
|
||||
hash: hash,
|
||||
format: format
|
||||
)
|
||||
else
|
||||
Rails.application.routes.url_helpers.rails_blob_path(attachment, only_path: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
module DashboardHelper
|
||||
end
|
||||
@@ -0,0 +1,2 @@
|
||||
module DonationsHelper
|
||||
end
|
||||
@@ -0,0 +1,2 @@
|
||||
module InvitationsHelper
|
||||
end
|
||||
@@ -0,0 +1,2 @@
|
||||
module LnurlpayHelper
|
||||
end
|
||||
@@ -1,12 +0,0 @@
|
||||
module ServicesHelper
|
||||
|
||||
def service_human_name(key, category = :external)
|
||||
SERVICES[category][key][:name] || key.to_s
|
||||
end
|
||||
|
||||
def service_display_name(key, category = :external)
|
||||
SERVICES[category][key][:display_name] ||
|
||||
service_human_name(key, category)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,2 @@
|
||||
module SettingsHelper
|
||||
end
|
||||
@@ -0,0 +1,2 @@
|
||||
module SignupHelper
|
||||
end
|
||||
@@ -0,0 +1,2 @@
|
||||
module UsersHelper
|
||||
end
|
||||
@@ -0,0 +1,2 @@
|
||||
module WalletHelper
|
||||
end
|
||||
@@ -0,0 +1,2 @@
|
||||
module WelcomeHelper
|
||||
end
|
||||
@@ -1,50 +0,0 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = [ "status", "loading", "content" ]
|
||||
static values = { url: String }
|
||||
|
||||
connect () {
|
||||
this.runStep("relays")
|
||||
}
|
||||
|
||||
async runStep (step) {
|
||||
const messages = {
|
||||
relays: "Looking up your relay list…",
|
||||
profile: "Fetching your profile…",
|
||||
blossom: "Fetching your media server list…",
|
||||
render: null
|
||||
}
|
||||
|
||||
if (messages[step]) {
|
||||
this.statusTarget.textContent = messages[step]
|
||||
}
|
||||
|
||||
try {
|
||||
const headers = (step === "render")
|
||||
? { "Accept": "text/html" }
|
||||
: { "Accept": "application/json" }
|
||||
|
||||
const res = await fetch(`${this.urlValue}?step=${step}`, { headers })
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP ${res.status}`)
|
||||
}
|
||||
|
||||
if (step === "render") {
|
||||
const html = await res.text()
|
||||
this.loadingTarget.classList.add("hidden")
|
||||
this.contentTarget.classList.remove("hidden")
|
||||
this.contentTarget.innerHTML = html
|
||||
} else {
|
||||
const data = await res.json()
|
||||
if (data.next) {
|
||||
this.runStep(data.next)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Nostr metadata fetch failed:", error.message)
|
||||
this.statusTarget.textContent = "Something went wrong. Please reload."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = [ "list", "template" ]
|
||||
|
||||
add (event) {
|
||||
event.preventDefault()
|
||||
const row = this.templateTarget.content.cloneNode(true)
|
||||
this.listTarget.appendChild(row)
|
||||
}
|
||||
|
||||
remove (event) {
|
||||
event.preventDefault()
|
||||
const row = event.target.closest("li")
|
||||
row.remove()
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,9 @@ class BtcpayCheckDonationJob < ApplicationJob
|
||||
|
||||
case invoice["status"]
|
||||
when "Settled"
|
||||
donation.complete!
|
||||
donation.paid_at = DateTime.now
|
||||
donation.payment_status = "settled"
|
||||
donation.save!
|
||||
|
||||
NotificationMailer.with(user: donation.user)
|
||||
.bitcoin_donation_confirmed
|
||||
|
||||
@@ -4,7 +4,7 @@ class CreateLdapUserJob < ApplicationJob
|
||||
def perform(username:, domain:, email:, hashed_pw:, confirmed: false)
|
||||
dn = "cn=#{username},ou=#{domain},cn=users,dc=kosmos,dc=org"
|
||||
attr = {
|
||||
objectclass: ["top", "account", "person", "inetOrgPerson", "extensibleObject"],
|
||||
objectclass: ["top", "account", "person", "extensibleObject"],
|
||||
cn: username,
|
||||
sn: username,
|
||||
uid: username,
|
||||
|
||||
@@ -2,12 +2,12 @@ class CreateLndhubAccountJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(user)
|
||||
return if user.lndhub_username.present? && user.lndhub_password.present?
|
||||
return if user.ln_account.present? && user.ln_password.present?
|
||||
|
||||
lndhub = LndhubV2.new
|
||||
credentials = lndhub.create_account
|
||||
|
||||
user.update! lndhub_username: credentials["login"],
|
||||
lndhub_password: credentials["password"]
|
||||
user.update! ln_account: credentials["login"],
|
||||
ln_password: credentials["password"]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,6 +3,8 @@ class RemoteStorageExpireAuthorizationJob < ApplicationJob
|
||||
|
||||
def perform(rs_auth_id)
|
||||
rs_auth = RemoteStorageAuthorization.find rs_auth_id
|
||||
return unless rs_auth.expire_at.nil? || rs_auth.expire_at <= DateTime.now
|
||||
|
||||
rs_auth.destroy!
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,6 +2,21 @@ class XmppExchangeContactsJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(inviter, invitee)
|
||||
EjabberdManager::ExchangeContacts.call(inviter:, invitee:)
|
||||
return unless inviter.service_enabled?(:xmpp) &&
|
||||
invitee.service_enabled?(:xmpp) &&
|
||||
inviter.preferences[:xmpp_exchange_contacts_with_invitees]
|
||||
|
||||
ejabberd = EjabberdApiClient.new
|
||||
|
||||
ejabberd.add_rosteritem({
|
||||
"localuser": invitee.cn, "localhost": invitee.ou,
|
||||
"user": inviter.cn, "host": inviter.ou,
|
||||
"nick": inviter.cn, "group": Setting.ejabberd_buddy_roster, "subs": "both"
|
||||
})
|
||||
ejabberd.add_rosteritem({
|
||||
"localuser": inviter.cn, "localhost": inviter.ou,
|
||||
"user": invitee.cn, "host": invitee.ou,
|
||||
"nick": invitee.cn, "group": Setting.ejabberd_buddy_roster, "subs": "both"
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,6 +2,7 @@ class XmppSendMessageJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(payload)
|
||||
EjabberdManager::SendMessage.call(payload:)
|
||||
ejabberd = EjabberdApiClient.new
|
||||
ejabberd.send_message payload
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
class XmppSetAvatarJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(user:, overwrite: false)
|
||||
EjabberdManager::SetAvatar.call(user:, overwrite:)
|
||||
end
|
||||
end
|
||||
@@ -2,6 +2,25 @@ class XmppSetDefaultBookmarksJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(user)
|
||||
EjabberdManager::SetDefaultBookmarks.call(user:)
|
||||
return unless Setting.xmpp_default_rooms.any?
|
||||
@user = user
|
||||
ejabberd = EjabberdApiClient.new
|
||||
ejabberd.private_set user, storage_content
|
||||
end
|
||||
|
||||
def storage_content
|
||||
bookmarks = ""
|
||||
Setting.xmpp_default_rooms.each do |r|
|
||||
bookmarks << conference_element(
|
||||
jid: r[/<(.+)>/, 1], name: r[/^(.+)\s/, 1], nick: @user.cn,
|
||||
autojoin: Setting.xmpp_autojoin_default_rooms
|
||||
)
|
||||
end
|
||||
|
||||
"<storage xmlns='storage:bookmarks'>#{bookmarks}</storage>"
|
||||
end
|
||||
|
||||
def conference_element(jid:, name:, autojoin: false, nick:)
|
||||
"<conference jid='#{jid}' name='#{name}' autojoin='#{autojoin.to_s}'><nick>#{nick}</nick></conference>"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,90 +1,3 @@
|
||||
class ApplicationMailer < ActionMailer::Base
|
||||
default Rails.application.config.action_mailer.default_options
|
||||
layout 'mailer'
|
||||
|
||||
private
|
||||
|
||||
def send_mail
|
||||
@template ||= "#{self.class.name.underscore}/#{caller[0][/`([^']*)'/, 1]}"
|
||||
headers['Message-ID'] = message_id
|
||||
|
||||
if @user.pgp_pubkey.present?
|
||||
mail(to: @user.email, subject: "...", content_type: pgp_content_type) do |format|
|
||||
format.text { render plain: pgp_content }
|
||||
end
|
||||
else
|
||||
mail(to: @user.email, subject: @subject) do |format|
|
||||
format.text { render @template }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def from_address
|
||||
self.class.default[:from]
|
||||
end
|
||||
|
||||
def from_domain
|
||||
Mail::Address.new(from_address).domain
|
||||
end
|
||||
|
||||
def message_id
|
||||
@message_id ||= "#{SecureRandom.uuid}@#{from_domain}"
|
||||
end
|
||||
|
||||
def boundary
|
||||
@boundary ||= SecureRandom.hex(8)
|
||||
end
|
||||
|
||||
def pgp_content_type
|
||||
"multipart/encrypted; protocol=\"application/pgp-encrypted\"; boundary=\"------------#{boundary}\""
|
||||
end
|
||||
|
||||
def pgp_nested_content
|
||||
message_content = render_to_string(template: @template)
|
||||
message_content_base64 = Base64.encode64(message_content)
|
||||
nested_boundary = SecureRandom.hex(8)
|
||||
|
||||
<<~NESTED_CONTENT
|
||||
Content-Type: multipart/mixed; boundary="------------#{nested_boundary}"; protected-headers="v1"
|
||||
Subject: #{@subject}
|
||||
From: <#{from_address}>
|
||||
To: #{@user.display_name || @user.cn} <#{@user.email}>
|
||||
Message-ID: <#{message_id}>
|
||||
|
||||
--------------#{nested_boundary}
|
||||
Content-Type: text/plain; charset=UTF-8; format=flowed
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
#{message_content_base64}
|
||||
|
||||
--------------#{nested_boundary}--
|
||||
NESTED_CONTENT
|
||||
end
|
||||
|
||||
def pgp_content
|
||||
encrypted_content = UserManager::PgpEncrypt.call(user: @user, text: pgp_nested_content)
|
||||
encrypted_base64 = Base64.encode64(encrypted_content.to_s)
|
||||
|
||||
<<~EMAIL_CONTENT
|
||||
This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)
|
||||
--------------#{boundary}
|
||||
Content-Type: application/pgp-encrypted
|
||||
Content-Description: PGP/MIME version identification
|
||||
|
||||
Version: 1
|
||||
|
||||
--------------#{boundary}
|
||||
Content-Type: application/octet-stream; name="encrypted.asc"
|
||||
Content-Description: OpenPGP encrypted message
|
||||
Content-Disposition: inline; filename="encrypted.asc"
|
||||
|
||||
-----BEGIN PGP MESSAGE-----
|
||||
|
||||
#{encrypted_base64}
|
||||
|
||||
-----END PGP MESSAGE-----
|
||||
|
||||
--------------#{boundary}--
|
||||
EMAIL_CONTENT
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,6 +18,6 @@ class CustomMailer < ApplicationMailer
|
||||
@user = params[:user]
|
||||
@subject = params[:subject]
|
||||
@body = params[:body]
|
||||
send_mail
|
||||
mail(to: @user.email, subject: @subject)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@ class NotificationMailer < ApplicationMailer
|
||||
@user = params[:user]
|
||||
@amount_sats = params[:amount_sats]
|
||||
@subject = "Sats received"
|
||||
send_mail
|
||||
mail to: @user.email, subject: @subject
|
||||
end
|
||||
|
||||
def remotestorage_auth_created
|
||||
@@ -15,19 +15,19 @@ class NotificationMailer < ApplicationMailer
|
||||
"#{access} #{directory}"
|
||||
end
|
||||
@subject = "New app connected to your storage"
|
||||
send_mail
|
||||
mail to: @user.email, subject: @subject
|
||||
end
|
||||
|
||||
def new_invitations_available
|
||||
@user = params[:user]
|
||||
@subject = "New invitations added to your account"
|
||||
send_mail
|
||||
mail to: @user.email, subject: @subject
|
||||
end
|
||||
|
||||
def bitcoin_donation_confirmed
|
||||
@user = params[:user]
|
||||
@donation = params[:donation]
|
||||
@subject = "Donation confirmed"
|
||||
send_mail
|
||||
mail to: @user.email, subject: @subject
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
module Settings
|
||||
module BtcpaySettings
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
field :btcpay_api_url, type: :string,
|
||||
default: ENV["BTCPAY_API_URL"].presence
|
||||
|
||||
field :btcpay_enabled, type: :boolean,
|
||||
default: ENV["BTCPAY_API_URL"].present?
|
||||
|
||||
field :btcpay_public_url, type: :string,
|
||||
default: ENV["BTCPAY_PUBLIC_URL"].presence
|
||||
|
||||
field :btcpay_store_id, type: :string,
|
||||
default: ENV["BTCPAY_STORE_ID"].presence
|
||||
|
||||
field :btcpay_auth_token, type: :string,
|
||||
default: ENV["BTCPAY_AUTH_TOKEN"].presence
|
||||
|
||||
field :btcpay_publish_wallet_balances, type: :boolean, default: true
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,16 +0,0 @@
|
||||
module Settings
|
||||
module DiscourseSettings
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
field :discourse_public_url, type: :string,
|
||||
default: ENV["DISCOURSE_PUBLIC_URL"].presence
|
||||
|
||||
field :discourse_enabled, type: :boolean,
|
||||
default: ENV["DISCOURSE_PUBLIC_URL"].present?
|
||||
|
||||
field :discourse_connect_secret, type: :string,
|
||||
default: ENV["DISCOURSE_CONNECT_SECRET"].presence
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,13 +0,0 @@
|
||||
module Settings
|
||||
module DroneCiSettings
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
field :droneci_public_url, type: :string,
|
||||
default: ENV["DRONECI_PUBLIC_URL"].presence
|
||||
|
||||
field :droneci_enabled, type: :boolean,
|
||||
default: ENV["DRONECI_PUBLIC_URL"].present?
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,19 +0,0 @@
|
||||
module Settings
|
||||
module EjabberdSettings
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
field :ejabberd_enabled, type: :boolean,
|
||||
default: ENV["EJABBERD_API_URL"].present?
|
||||
|
||||
field :ejabberd_api_url, type: :string,
|
||||
default: ENV["EJABBERD_API_URL"].presence
|
||||
|
||||
field :ejabberd_admin_url, type: :string,
|
||||
default: ENV["EJABBERD_ADMIN_URL"].presence
|
||||
|
||||
field :ejabberd_buddy_roster, type: :string,
|
||||
default: "Buddies"
|
||||
end
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user