14 Commits

Author SHA1 Message Date
9866cd0404 WIP Edit nostr profile 2024-12-24 15:25:37 +01:00
10d29b6fab Reorder things in UI 2024-10-30 13:46:28 +01:00
6f8f60a9e2 Add timeout for fetching latest event 2024-10-30 13:45:56 +01:00
c1b4665706 Conditional
All checks were successful
continuous-integration/drone/push Build is passing
2024-10-18 18:58:09 +02:00
5447150d4d Formatting
All checks were successful
continuous-integration/drone/push Build is passing
2024-10-18 18:51:29 +02:00
bf26703b2d Remove client-side nostr discovery
All checks were successful
continuous-integration/drone/push Build is passing
2024-10-18 18:30:04 +02:00
21c6264ea9 Use rails logger
All checks were successful
continuous-integration/drone/push Build is passing
2024-10-18 18:21:24 +02:00
79ef9fa6d5 WIP Refactor stuff
All checks were successful
continuous-integration/drone/push Build is passing
2024-10-18 18:16:09 +02:00
04a9061663 WIP Render nostr profile and relay status with Ruby
All checks were successful
continuous-integration/drone/push Build is passing
2024-10-18 15:29:43 +02:00
5283f6fce7 WIP fetch relays and profile with ruby 2024-10-16 13:32:15 +02:00
a08a4746f7 Fetch user relays, synchronously 2024-10-12 12:45:50 +02:00
9e3652479b Add global setting and default for discovery relays 2024-10-12 12:45:12 +02:00
011386fb8d WIP Nostr onboarding
All checks were successful
continuous-integration/drone/push Build is passing
2024-10-10 23:37:59 +02:00
4d77f5d38c Add nostrify lib 2024-10-10 23:37:41 +02:00
166 changed files with 1707 additions and 3387 deletions

View File

@@ -1,23 +1,6 @@
# PRIMARY_DOMAIN=kosmos.org # PRIMARY_DOMAIN=kosmos.org
# AKKOUNTS_DOMAIN=accounts.example.com # 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_SERVER=smtp.example.com
# SMTP_PORT=587 # SMTP_PORT=587
# SMTP_LOGIN=accounts # SMTP_LOGIN=accounts
@@ -37,12 +20,8 @@
# LDAP_HOST=localhost # LDAP_HOST=localhost
# LDAP_PORT=389 # 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_ADMIN_PASSWORD=passthebutter
# LDAP_SUFFIX="dc=kosmos,dc=org" # LDAP_SUFFIX='dc=kosmos,dc=org'
# REDIS_URL='redis://localhost:6379/1' # REDIS_URL='redis://localhost:6379/1'

View File

@@ -1,9 +1,6 @@
PRIMARY_DOMAIN=kosmos.org PRIMARY_DOMAIN=kosmos.org
AKKOUNTS_DOMAIN=accounts.kosmos.org AKKOUNTS_DOMAIN=accounts.kosmos.org
ENCRYPTION_PRIMARY_KEY=YhNLBgCFMAzw5dV3gISxnGrhNDMQwRdn
ENCRYPTION_KEY_DERIVATION_SALT=h28g16MRZ1sghF2jTCos1DiLZXUswinR
REDIS_URL='redis://localhost:6379/0' REDIS_URL='redis://localhost:6379/0'
BTCPAY_PUBLIC_URL='https://btcpay.example.com' BTCPAY_PUBLIC_URL='https://btcpay.example.com'
@@ -24,8 +21,7 @@ LNDHUB_PUBLIC_KEY='024cd3be18617f39cf645851e3ba63f51fc13f0bb09e3bb25e6fd4de55648
NOSTR_PRIVATE_KEY='7c3ef7e448505f0615137af38569d01807d3b05b5005d5ecf8aaafcd40323cea' NOSTR_PRIVATE_KEY='7c3ef7e448505f0615137af38569d01807d3b05b5005d5ecf8aaafcd40323cea'
NOSTR_PUBLIC_KEY='bdd76ce2934b2f591f9fad2ebe9da18f20d2921de527494ba00eeaa0a0efadcf' NOSTR_PUBLIC_KEY='bdd76ce2934b2f591f9fad2ebe9da18f20d2921de527494ba00eeaa0a0efadcf'
RS_REDIS_URL='redis://localhost:6379/1'
RS_STORAGE_URL='https://storage.kosmos.org' 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' WEBHOOKS_ALLOWED_IPS='10.1.1.23'

4
.gitignore vendored
View File

@@ -37,7 +37,6 @@
/yarn-error.log /yarn-error.log
yarn-debug.log* yarn-debug.log*
.yarn-integrity .yarn-integrity
bun.lock
# Ignore local dotenv config file # Ignore local dotenv config file
.env .env
@@ -48,6 +47,3 @@ dump.rdb
/app/assets/builds/* /app/assets/builds/*
!/app/assets/builds/.keep !/app/assets/builds/.keep
# Ignore generated ctags
*.tags

22
Gemfile
View File

@@ -2,13 +2,13 @@ source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" } git_source(:github) { |repo| "https://github.com/#{repo}.git" }
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 8.0' gem 'rails', '~> 7.1'
# Use Puma as the app server # Use Puma as the app server
gem 'puma', '~> 6.6' gem 'puma', '~> 4.1'
# View components # View components
gem "view_component" gem "view_component"
# Asset bundler # Separate dependency since Rails 7.0
gem 'propshaft' gem 'sprockets-rails'
# Allows custom JS build tasks to integrate with the asset pipeline # Allows custom JS build tasks to integrate with the asset pipeline
gem 'cssbundling-rails' gem 'cssbundling-rails'
# Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails] # Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]
@@ -19,12 +19,17 @@ gem "turbo-rails"
gem "stimulus-rails" gem "stimulus-rails"
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.7' gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use Active Model has_secure_password # Use Active Model has_secure_password
gem 'bcrypt', '~> 3.1' gem 'bcrypt', '~> 3.1'
# Configuration # Configuration
gem 'dotenv-rails' gem 'dotenv-rails'
# Security
gem 'lockbox'
# Authentication # Authentication
gem 'warden' gem 'warden'
gem 'devise', '~> 4.9.0' gem 'devise', '~> 4.9.0'
@@ -39,8 +44,6 @@ gem 'pagy', '~> 6.0', '>= 6.0.2'
gem 'flipper' gem 'flipper'
gem 'flipper-active_record' gem 'flipper-active_record'
gem 'flipper-ui' gem 'flipper-ui'
gem 'gpgme', '~> 2.0.24'
gem 'zbase32', '~> 0.1.1'
# HTTP requests # HTTP requests
gem 'faraday' gem 'faraday'
@@ -48,8 +51,8 @@ gem 'down'
gem 'aws-sdk-s3', require: false gem 'aws-sdk-s3', require: false
# Background/scheduled jobs # Background/scheduled jobs
gem 'solid_queue' gem 'sidekiq', '< 7'
gem "mission_control-jobs" gem 'sidekiq-scheduler'
# Monitoring # Monitoring
gem "sentry-ruby" gem "sentry-ruby"
@@ -60,11 +63,10 @@ gem 'discourse_api'
gem "lnurl" gem "lnurl"
gem 'manifique', '~> 1.1.0' gem 'manifique', '~> 1.1.0'
gem 'nostr', '~> 0.6.0' gem 'nostr', '~> 0.6.0'
gem "redis", "~> 5.4"
group :development, :test do group :development, :test do
# Use sqlite3 as the database for Active Record # Use sqlite3 as the database for Active Record
gem 'sqlite3', '>= 2.1' gem 'sqlite3', '~> 1.7.2'
gem 'rspec-rails' gem 'rspec-rails'
gem 'rails-controller-testing' gem 'rails-controller-testing'
end end

View File

@@ -1,109 +1,110 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (8.0.2) actioncable (7.1.3)
actionpack (= 8.0.2) actionpack (= 7.1.3)
activesupport (= 8.0.2) activesupport (= 7.1.3)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
actionmailbox (8.0.2) actionmailbox (7.1.3)
actionpack (= 8.0.2) actionpack (= 7.1.3)
activejob (= 8.0.2) activejob (= 7.1.3)
activerecord (= 8.0.2) activerecord (= 7.1.3)
activestorage (= 8.0.2) activestorage (= 7.1.3)
activesupport (= 8.0.2) activesupport (= 7.1.3)
mail (>= 2.8.0) mail (>= 2.7.1)
actionmailer (8.0.2) net-imap
actionpack (= 8.0.2) net-pop
actionview (= 8.0.2) net-smtp
activejob (= 8.0.2) actionmailer (7.1.3)
activesupport (= 8.0.2) actionpack (= 7.1.3)
mail (>= 2.8.0) 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) rails-dom-testing (~> 2.2)
actionpack (8.0.2) actionpack (7.1.3)
actionview (= 8.0.2) actionview (= 7.1.3)
activesupport (= 8.0.2) activesupport (= 7.1.3)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4) rack (>= 2.2.4)
rack-session (>= 1.0.1) rack-session (>= 1.0.1)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
useragent (~> 0.16) actiontext (7.1.3)
actiontext (8.0.2) actionpack (= 7.1.3)
actionpack (= 8.0.2) activerecord (= 7.1.3)
activerecord (= 8.0.2) activestorage (= 7.1.3)
activestorage (= 8.0.2) activesupport (= 7.1.3)
activesupport (= 8.0.2)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (8.0.2) actionview (7.1.3)
activesupport (= 8.0.2) activesupport (= 7.1.3)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.11) erubi (~> 1.11)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
activejob (8.0.2) activejob (7.1.3)
activesupport (= 8.0.2) activesupport (= 7.1.3)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (8.0.2) activemodel (7.1.3)
activesupport (= 8.0.2) activesupport (= 7.1.3)
activerecord (8.0.2) activerecord (7.1.3)
activemodel (= 8.0.2) activemodel (= 7.1.3)
activesupport (= 8.0.2) activesupport (= 7.1.3)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (8.0.2) activestorage (7.1.3)
actionpack (= 8.0.2) actionpack (= 7.1.3)
activejob (= 8.0.2) activejob (= 7.1.3)
activerecord (= 8.0.2) activerecord (= 7.1.3)
activesupport (= 8.0.2) activesupport (= 7.1.3)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (8.0.2) activesupport (7.1.3)
base64 base64
benchmark (>= 0.3)
bigdecimal bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1) concurrent-ruby (~> 1.0, >= 1.0.2)
connection_pool (>= 2.2.5) connection_pool (>= 2.2.5)
drb drb
i18n (>= 1.6, < 2) i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1) minitest (>= 5.1)
securerandom (>= 0.3) mutex_m
tzinfo (~> 2.0, >= 2.0.5) tzinfo (~> 2.0)
uri (>= 0.13.1) addressable (2.8.6)
addressable (2.8.7) public_suffix (>= 2.0.2, < 6.0)
public_suffix (>= 2.0.2, < 7.0) ast (2.4.2)
ast (2.4.3) aws-eventstream (1.3.0)
aws-eventstream (1.3.2) aws-partitions (1.886.0)
aws-partitions (1.1092.0) aws-sdk-core (3.191.0)
aws-sdk-core (3.222.2)
aws-eventstream (~> 1, >= 1.3.0) aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0) aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.9) aws-sigv4 (~> 1.8)
base64
jmespath (~> 1, >= 1.6.1) jmespath (~> 1, >= 1.6.1)
logger aws-sdk-kms (1.77.0)
aws-sdk-kms (1.99.0) aws-sdk-core (~> 3, >= 3.191.0)
aws-sdk-core (~> 3, >= 3.216.0) aws-sigv4 (~> 1.1)
aws-sigv4 (~> 1.5) aws-sdk-s3 (1.143.0)
aws-sdk-s3 (1.183.0) aws-sdk-core (~> 3, >= 3.191.0)
aws-sdk-core (~> 3, >= 3.216.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5) aws-sigv4 (~> 1.8)
aws-sigv4 (1.11.0) aws-sigv4 (1.8.0)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
backport (1.2.0) backport (1.2.0)
base64 (0.2.0) base64 (0.2.0)
bcrypt (3.1.20) bcrypt (3.1.20)
bech32 (1.5.0) bech32 (1.4.2)
thor (>= 1.1.0) thor (>= 1.1.0)
benchmark (0.4.0) benchmark (0.3.0)
bigdecimal (3.1.9) bigdecimal (3.1.6)
bindex (0.8.1) bindex (0.8.1)
bip-schnorr (0.7.0) bip-schnorr (0.7.0)
ecdsa_ext (~> 0.5.0) ecdsa_ext (~> 0.5.0)
builder (3.3.0) builder (3.2.4)
capybara (3.40.0) capybara (3.40.0)
addressable addressable
matrix matrix
@@ -113,25 +114,23 @@ GEM
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0) regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2) xpath (~> 3.2)
childprocess (5.1.0)
logger (~> 1.5)
chunky_png (1.4.0) chunky_png (1.4.0)
concurrent-ruby (1.3.4) concurrent-ruby (1.2.3)
connection_pool (2.5.2) connection_pool (2.4.1)
crack (1.0.0) crack (0.4.6)
bigdecimal bigdecimal
rexml rexml
crass (1.0.6) crass (1.0.6)
cssbundling-rails (1.4.3) cssbundling-rails (1.4.0)
railties (>= 6.0.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, < 3)
database_cleaner-active_record (2.2.0) database_cleaner-active_record (2.1.0)
activerecord (>= 5.a) activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0) database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1) database_cleaner-core (2.0.1)
date (3.4.1) date (3.3.4)
devise (4.9.4) devise (4.9.3)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
railties (>= 4.1.0) railties (>= 4.1.0)
@@ -140,112 +139,105 @@ GEM
devise_ldap_authenticatable (0.8.7) devise_ldap_authenticatable (0.8.7)
devise (>= 3.4.1) devise (>= 3.4.1)
net-ldap (>= 0.16.0) net-ldap (>= 0.16.0)
diff-lcs (1.6.1) diff-lcs (1.5.1)
discourse_api (2.0.1) discourse_api (2.0.1)
faraday (~> 2.7) faraday (~> 2.7)
faraday-follow_redirects faraday-follow_redirects
faraday-multipart faraday-multipart
rack (>= 1.6) rack (>= 1.6)
dotenv (3.1.8) dotenv (2.8.1)
dotenv-rails (3.1.8) dotenv-rails (2.8.1)
dotenv (= 3.1.8) dotenv (= 2.8.1)
railties (>= 6.1) railties (>= 3.2)
down (5.4.2) down (5.4.1)
addressable (~> 2.8) addressable (~> 2.8)
drb (2.2.1) drb (2.2.0)
ruby2_keywords
e2mmap (0.1.0)
ecdsa (1.2.0) ecdsa (1.2.0)
ecdsa_ext (0.5.1) ecdsa_ext (0.5.1)
ecdsa (~> 1.2.0) ecdsa (~> 1.2.0)
erubi (1.13.1) erubi (1.12.0)
et-orbi (1.2.11) et-orbi (1.2.7)
tzinfo tzinfo
event_emitter (0.2.6) event_emitter (0.2.6)
eventmachine (1.2.7) eventmachine (1.2.7)
factory_bot (6.5.1) factory_bot (6.4.6)
activesupport (>= 6.1.0) activesupport (>= 5.0.0)
factory_bot_rails (6.4.4) factory_bot_rails (6.4.3)
factory_bot (~> 6.5) factory_bot (~> 6.4)
railties (>= 5.0.0) railties (>= 5.0.0)
faker (3.5.1) faker (3.2.3)
i18n (>= 1.8.11, < 2) i18n (>= 1.8.11, < 2)
faraday (2.9.2) faraday (2.9.0)
faraday-net_http (>= 2.0, < 3.2) faraday-net_http (>= 2.0, < 3.2)
faraday-follow_redirects (0.3.0) faraday-follow_redirects (0.3.0)
faraday (>= 1, < 3) faraday (>= 1, < 3)
faraday-multipart (1.1.0) faraday-multipart (1.0.4)
multipart-post (~> 2.0) multipart-post (~> 2)
faraday-net_http (3.1.1) faraday-net_http (3.1.0)
net-http net-http
faye-websocket (0.11.3) faye-websocket (0.11.3)
eventmachine (>= 0.12.0) eventmachine (>= 0.12.0)
websocket-driver (>= 0.5.1) websocket-driver (>= 0.5.1)
ffi (1.17.2) ffi (1.16.3)
ffi (1.17.2-arm64-darwin) flipper (1.2.2)
ffi (1.17.2-x86_64-linux-gnu)
flipper (1.3.4)
concurrent-ruby (< 2) concurrent-ruby (< 2)
flipper-active_record (1.3.4) flipper-active_record (1.2.2)
activerecord (>= 4.2, < 9) activerecord (>= 4.2, < 8)
flipper (~> 1.3.4) flipper (~> 1.2.2)
flipper-ui (1.3.4) flipper-ui (1.2.2)
erubi (>= 1.0.0, < 2.0.0) erubi (>= 1.0.0, < 2.0.0)
flipper (~> 1.3.4) flipper (~> 1.2.2)
rack (>= 1.4, < 4) rack (>= 1.4, < 4)
rack-protection (>= 1.5.3, < 5.0.0) rack-protection (>= 1.5.3, <= 4.0.0)
rack-session (>= 1.0.2, < 3.0.0) sanitize (< 7)
sanitize (< 8) fugit (1.9.0)
fugit (1.11.1) et-orbi (~> 1, >= 1.2.7)
et-orbi (~> 1, >= 1.2.11)
raabro (~> 1.4) raabro (~> 1.4)
globalid (1.2.1) globalid (1.2.1)
activesupport (>= 6.1) activesupport (>= 6.1)
gpgme (2.0.24) hashdiff (1.1.0)
mini_portile2 (~> 2.7) i18n (1.14.1)
hashdiff (1.1.2)
i18n (1.14.7)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
image_processing (1.12.2) image_processing (1.12.2)
mini_magick (>= 4.9.5, < 5) mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3) ruby-vips (>= 2.0.17, < 3)
importmap-rails (2.1.0) importmap-rails (2.0.1)
actionpack (>= 6.0.0) actionpack (>= 6.0.0)
activesupport (>= 6.0.0) activesupport (>= 6.0.0)
railties (>= 6.0.0) railties (>= 6.0.0)
io-console (0.8.0) io-console (0.7.2)
irb (1.15.2) irb (1.11.1)
pp (>= 0.6.0) rdoc
rdoc (>= 4.0.0)
reline (>= 0.4.2) reline (>= 0.4.2)
jaro_winkler (1.6.0) jaro_winkler (1.5.6)
jbuilder (2.13.0) jbuilder (2.11.5)
actionview (>= 5.0.0) actionview (>= 5.0.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
jmespath (1.6.2) jmespath (1.6.2)
json (2.11.3) json (2.7.1)
kramdown (2.5.1) kramdown (2.4.0)
rexml (>= 3.3.9) rexml
kramdown-parser-gfm (1.1.0) kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0) kramdown (~> 2.0)
language_server-protocol (3.17.0.4) language_server-protocol (3.17.0.3)
launchy (3.1.1) launchy (2.5.2)
addressable (~> 2.8) addressable (~> 2.8)
childprocess (~> 5.0) letter_opener (1.8.1)
logger (~> 1.6) launchy (>= 2.2, < 3)
letter_opener (1.10.0) letter_opener_web (2.0.0)
launchy (>= 2.2, < 4) actionmailer (>= 5.2)
letter_opener_web (3.0.0) letter_opener (~> 1.7)
actionmailer (>= 6.1) railties (>= 5.2)
letter_opener (~> 1.9)
railties (>= 6.1)
rexml rexml
lint_roller (1.1.0) listen (3.8.0)
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10) rb-inotify (~> 0.9, >= 0.9.10)
lnurl (1.1.1) lnurl (1.1.0)
bech32 (~> 1.1) bech32 (~> 1.1)
logger (1.7.0) lockbox (1.3.2)
loofah (2.24.0) loofah (2.22.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.12.0)
mail (2.8.1) mail (2.8.1)
@@ -257,27 +249,18 @@ GEM
faraday (~> 2.9.0) faraday (~> 2.9.0)
faraday-follow_redirects (= 0.3.0) faraday-follow_redirects (= 0.3.0)
nokogiri (~> 1.16.0) nokogiri (~> 1.16.0)
marcel (1.0.4) marcel (1.0.2)
matrix (0.4.2) matrix (0.4.2)
method_source (1.1.0) method_source (1.0.0)
mini_magick (4.13.2) mini_magick (4.12.0)
mini_mime (1.1.5) mini_mime (1.1.5)
mini_portile2 (2.8.8) mini_portile2 (2.8.5)
minitest (5.25.5) minitest (5.21.2)
mission_control-jobs (1.0.2) multipart-post (2.3.0)
actioncable (>= 7.1) mutex_m (0.2.0)
actionpack (>= 7.1) net-http (0.4.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)
uri uri
net-imap (0.5.7) net-imap (0.4.9.1)
date date
net-protocol net-protocol
net-ldap (0.19.0) net-ldap (0.19.0)
@@ -285,15 +268,15 @@ GEM
net-protocol net-protocol
net-protocol (0.2.2) net-protocol (0.2.2)
timeout timeout
net-smtp (0.5.1) net-smtp (0.4.0.1)
net-protocol net-protocol
nio4r (2.7.4) nio4r (2.7.0)
nokogiri (1.16.8) nokogiri (1.16.0)
mini_portile2 (~> 2.8.2) mini_portile2 (~> 2.8.2)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.16.8-arm64-darwin) nokogiri (1.16.0-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.16.8-x86_64-linux) nokogiri (1.16.0-x86_64-linux)
racc (~> 1.4) racc (~> 1.4)
nostr (0.6.0) nostr (0.6.0)
bech32 (~> 1.4) bech32 (~> 1.4)
@@ -302,57 +285,45 @@ GEM
event_emitter (~> 0.2) event_emitter (~> 0.2)
faye-websocket (~> 0.11) faye-websocket (~> 0.11)
json (~> 2.6) json (~> 2.6)
observer (0.1.2)
orm_adapter (0.5.0) orm_adapter (0.5.0)
ostruct (0.6.1) pagy (6.4.3)
pagy (6.5.0) parallel (1.24.0)
parallel (1.27.0) parser (3.3.0.5)
parser (3.3.8.0)
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
pg (1.5.9) pg (1.5.4)
pp (0.6.2) psych (5.1.2)
prettyprint
prettyprint (0.2.0)
prism (1.4.0)
propshaft (1.1.0)
actionpack (>= 7.0.0)
activesupport (>= 7.0.0)
rack
railties (>= 7.0.0)
psych (5.2.3)
date
stringio stringio
public_suffix (6.0.1) public_suffix (5.0.4)
puma (6.6.0) puma (4.3.12)
nio4r (~> 2.0) nio4r (~> 2.0)
raabro (1.4.0) raabro (1.4.0)
racc (1.8.1) racc (1.7.3)
rack (2.2.13) rack (2.2.8)
rack-protection (3.2.0) rack-protection (3.2.0)
base64 (>= 0.1.0) base64 (>= 0.1.0)
rack (~> 2.2, >= 2.2.4) rack (~> 2.2, >= 2.2.4)
rack-session (1.0.2) rack-session (1.0.2)
rack (< 3) rack (< 3)
rack-test (2.2.0) rack-test (2.1.0)
rack (>= 1.3) rack (>= 1.3)
rackup (1.0.1) rackup (1.0.0)
rack (< 3) rack (< 3)
webrick webrick
rails (8.0.2) rails (7.1.3)
actioncable (= 8.0.2) actioncable (= 7.1.3)
actionmailbox (= 8.0.2) actionmailbox (= 7.1.3)
actionmailer (= 8.0.2) actionmailer (= 7.1.3)
actionpack (= 8.0.2) actionpack (= 7.1.3)
actiontext (= 8.0.2) actiontext (= 7.1.3)
actionview (= 8.0.2) actionview (= 7.1.3)
activejob (= 8.0.2) activejob (= 7.1.3)
activemodel (= 8.0.2) activemodel (= 7.1.3)
activerecord (= 8.0.2) activerecord (= 7.1.3)
activestorage (= 8.0.2) activestorage (= 7.1.3)
activesupport (= 8.0.2) activesupport (= 7.1.3)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 8.0.2) railties (= 7.1.3)
rails-controller-testing (1.0.5) rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1) actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1)
@@ -361,140 +332,138 @@ GEM
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
minitest minitest
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.6.2) rails-html-sanitizer (1.6.0)
loofah (~> 2.21) loofah (~> 2.21)
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) nokogiri (~> 1.14)
rails-settings-cached (2.8.3) rails-settings-cached (2.8.3)
activerecord (>= 5.0.0) activerecord (>= 5.0.0)
railties (>= 5.0.0) railties (>= 5.0.0)
railties (8.0.2) railties (7.1.3)
actionpack (= 8.0.2) actionpack (= 7.1.3)
activesupport (= 8.0.2) activesupport (= 7.1.3)
irb (~> 1.13) irb
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
thor (~> 1.0, >= 1.2.2) thor (~> 1.0, >= 1.2.2)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
rainbow (3.1.1) rainbow (3.1.1)
rake (13.2.1) rake (13.1.0)
rb-fsevent (0.11.2) rb-fsevent (0.11.2)
rb-inotify (0.11.1) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
rbs (3.9.2) rbs (2.8.4)
logger rdoc (6.6.2)
rdoc (6.13.1)
psych (>= 4.0.0) psych (>= 4.0.0)
redis (5.4.0) redis (4.8.1)
redis-client (>= 0.22.0) regexp_parser (2.9.0)
redis-client (0.24.0) reline (0.4.2)
connection_pool
regexp_parser (2.10.0)
reline (0.6.1)
io-console (~> 0.5) io-console (~> 0.5)
responders (3.1.1) responders (3.1.1)
actionpack (>= 5.2) actionpack (>= 5.2)
railties (>= 5.2) railties (>= 5.2)
reverse_markdown (3.0.0) reverse_markdown (2.1.1)
nokogiri nokogiri
rexml (3.4.1) rexml (3.2.6)
rqrcode (2.2.0) rqrcode (2.2.0)
chunky_png (~> 1.0) chunky_png (~> 1.0)
rqrcode_core (~> 1.0) rqrcode_core (~> 1.0)
rqrcode_core (1.2.0) rqrcode_core (1.2.0)
rspec-core (3.13.3) rspec-core (3.12.2)
rspec-support (~> 3.13.0) rspec-support (~> 3.12.0)
rspec-expectations (3.13.3) rspec-expectations (3.12.3)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.12.0)
rspec-mocks (3.13.2) rspec-mocks (3.12.6)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.12.0)
rspec-rails (7.1.1) rspec-rails (6.1.1)
actionpack (>= 7.0) actionpack (>= 6.1)
activesupport (>= 7.0) activesupport (>= 6.1)
railties (>= 7.0) railties (>= 6.1)
rspec-core (~> 3.13) rspec-core (~> 3.12)
rspec-expectations (~> 3.13) rspec-expectations (~> 3.12)
rspec-mocks (~> 3.13) rspec-mocks (~> 3.12)
rspec-support (~> 3.13) rspec-support (~> 3.12)
rspec-support (3.13.2) rspec-support (3.12.1)
rubocop (1.75.3) rubocop (1.60.2)
json (~> 2.3) json (~> 2.3)
language_server-protocol (~> 3.17.0.2) language_server-protocol (>= 3.17.0)
lint_roller (~> 1.1.0)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.3.0.2) parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0) regexp_parser (>= 1.8, < 3.0)
rubocop-ast (>= 1.44.0, < 2.0) rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.30.0, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0) unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.44.1) rubocop-ast (1.30.0)
parser (>= 3.3.7.2) parser (>= 3.2.1.0)
prism (~> 1.4)
ruby-progressbar (1.13.0) ruby-progressbar (1.13.0)
ruby-vips (2.2.3) ruby-vips (2.2.0)
ffi (~> 1.12) ffi (~> 1.12)
logger ruby2_keywords (0.0.5)
sanitize (7.0.0) rufus-scheduler (3.9.1)
fugit (~> 1.1, >= 1.1.6)
sanitize (6.1.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.16.8) nokogiri (>= 1.12.0)
securerandom (0.4.1) sentry-rails (5.16.1)
sentry-rails (5.23.0)
railties (>= 5.0) railties (>= 5.0)
sentry-ruby (~> 5.23.0) sentry-ruby (~> 5.16.1)
sentry-ruby (5.23.0) sentry-ruby (5.16.1)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2) 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) backport (~> 1.2)
benchmark (~> 0.4) benchmark
bundler (~> 2.0) bundler (~> 2.0)
diff-lcs (~> 1.4) diff-lcs (~> 1.4)
jaro_winkler (~> 1.6) e2mmap
jaro_winkler (~> 1.5)
kramdown (~> 2.3) kramdown (~> 2.3)
kramdown-parser-gfm (~> 1.1) kramdown-parser-gfm (~> 1.1)
logger (~> 1.6)
observer (~> 0.1)
ostruct (~> 0.6)
parser (~> 3.0) parser (~> 3.0)
rbs (~> 3.3) rbs (~> 2.0)
reverse_markdown (~> 3.0) reverse_markdown (~> 2.0)
rubocop (~> 1.38) rubocop (~> 1.38)
thor (~> 1.0) thor (~> 1.0)
tilt (~> 2.0) tilt (~> 2.0)
yard (~> 0.9, >= 0.9.24) yard (~> 0.9, >= 0.9.24)
yard-solargraph (~> 0.1) sprockets (4.2.1)
solid_queue (1.1.5) concurrent-ruby (~> 1.0)
activejob (>= 7.1) rack (>= 2.2.4, < 4)
activerecord (>= 7.1) sprockets-rails (3.4.2)
concurrent-ruby (>= 1.3.1) actionpack (>= 5.2)
fugit (~> 1.11.0) activesupport (>= 5.2)
railties (>= 7.1) sprockets (>= 3.0.0)
thor (~> 1.3.1) sqlite3 (1.7.2)
sqlite3 (2.6.0)
mini_portile2 (~> 2.8.0) mini_portile2 (~> 2.8.0)
sqlite3 (2.6.0-arm64-darwin) sqlite3 (1.7.2-arm64-darwin)
sqlite3 (2.6.0-x86_64-linux-gnu) sqlite3 (1.7.2-x86_64-linux)
stimulus-rails (1.3.4) 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) railties (>= 6.0.0)
stringio (3.1.7)
thor (1.3.2)
tilt (2.6.0)
timeout (0.4.3)
turbo-rails (2.0.13)
actionpack (>= 7.1.0)
railties (>= 7.1.0)
tzinfo (2.0.6) tzinfo (2.0.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
unicode-display_width (3.1.4) unicode-display_width (2.5.0)
unicode-emoji (~> 4.0, >= 4.0.4) uri (0.13.0)
unicode-emoji (4.0.4) view_component (3.10.0)
uri (1.0.3) activesupport (>= 5.2.0, < 8.0)
useragent (0.16.11) concurrent-ruby (~> 1.0)
view_component (3.22.0)
activesupport (>= 5.2.0, < 8.1)
concurrent-ruby (= 1.3.4)
method_source (~> 1.0) method_source (~> 1.0)
warden (1.2.9) warden (1.2.9)
rack (>= 2.0.9) rack (>= 2.0.9)
@@ -503,22 +472,18 @@ GEM
activemodel (>= 6.0.0) activemodel (>= 6.0.0)
bindex (>= 0.4.0) bindex (>= 0.4.0)
railties (>= 6.0.0) railties (>= 6.0.0)
webmock (3.25.1) webmock (3.19.1)
addressable (>= 2.8.0) addressable (>= 2.8.0)
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0) hashdiff (>= 0.4.0, < 2.0.0)
webrick (1.9.1) webrick (1.8.1)
websocket-driver (0.7.7) websocket-driver (0.7.6)
base64
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
yard (0.9.37) yard (0.9.34)
yard-solargraph (0.1.0) zeitwerk (2.6.12)
yard (~> 0.9)
zbase32 (0.1.1)
zeitwerk (2.7.2)
PLATFORMS PLATFORMS
arm64-darwin-22 arm64-darwin-22
@@ -542,7 +507,6 @@ DEPENDENCIES
flipper flipper
flipper-active_record flipper-active_record
flipper-ui flipper-ui
gpgme (~> 2.0.24)
image_processing (~> 1.12.2) image_processing (~> 1.12.2)
importmap-rails importmap-rails
jbuilder (~> 2.7) jbuilder (~> 2.7)
@@ -550,25 +514,25 @@ DEPENDENCIES
letter_opener_web letter_opener_web
listen (~> 3.2) listen (~> 3.2)
lnurl lnurl
lockbox
manifique (~> 1.1.0) manifique (~> 1.1.0)
mission_control-jobs
net-ldap net-ldap
nostr (~> 0.6.0) nostr (~> 0.6.0)
pagy (~> 6.0, >= 6.0.2) pagy (~> 6.0, >= 6.0.2)
pg (~> 1.5) pg (~> 1.5)
propshaft puma (~> 4.1)
puma (~> 6.6) rails (~> 7.1)
rails (~> 8.0)
rails-controller-testing rails-controller-testing
rails-settings-cached (~> 2.8.3) rails-settings-cached (~> 2.8.3)
redis (~> 5.4)
rqrcode (~> 2.0) rqrcode (~> 2.0)
rspec-rails rspec-rails
sentry-rails sentry-rails
sentry-ruby sentry-ruby
sidekiq (< 7)
sidekiq-scheduler
solargraph solargraph
solid_queue sprockets-rails
sqlite3 (>= 2.1) sqlite3 (~> 1.7.2)
stimulus-rails stimulus-rails
turbo-rails turbo-rails
tzinfo-data tzinfo-data
@@ -576,7 +540,6 @@ DEPENDENCIES
warden warden
web-console (~> 4.2) web-console (~> 4.2)
webmock webmock
zbase32 (~> 0.1.1)
BUNDLED WITH BUNDLED WITH
2.5.5 2.5.5

View File

@@ -57,7 +57,7 @@ Running the test suite:
Running the test suite with Docker Compose requires overriding the Rails Running the test suite with Docker Compose requires overriding the Rails
environment: environment:
docker-compose exec -e "RAILS_ENV=test" web rspec docker-compose run -e "RAILS_ENV=test" web rspec
### Docker Compose ### Docker Compose

View File

@@ -0,0 +1,4 @@
//= link_tree ../images
//= link_tree ../../javascript .js
//= link_tree ../builds
//= link_tree ../../../vendor/javascript .js

View File

@@ -2,8 +2,6 @@
module AppCatalog module AppCatalog
class WebAppIconComponent < ViewComponent::Base class WebAppIconComponent < ViewComponent::Base
include ApplicationHelper
def initialize(web_app:) def initialize(web_app:)
if web_app&.icon&.attached? if web_app&.icon&.attached?
@image_url = image_url_for(web_app.icon) @image_url = image_url_for(web_app.icon)
@@ -11,5 +9,13 @@ module AppCatalog
@image_url = image_url_for(web_app.apple_touch_icon) @image_url = image_url_for(web_app.apple_touch_icon)
end end
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
end end

View File

@@ -1,4 +1,4 @@
<%= link_to @href, class: @class, target: @target, data: { <%= link_to @href, class: @class, data: {
'dropdown-target': "menuItem", 'dropdown-target': "menuItem",
'action': "keydown.up->dropdown#previousItem:prevent keydown.down->dropdown#nextItem:prevent" 'action': "keydown.up->dropdown#previousItem:prevent keydown.down->dropdown#nextItem:prevent"
} do %> } do %>

View File

@@ -1,9 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class DropdownLinkComponent < ViewComponent::Base 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 @href = href
@target = open_in_new_tab ? "_blank" : nil
@class = class_str(separator, add_class) @class = class_str(separator, add_class)
end end

View File

@@ -12,8 +12,7 @@
</div> </div>
<%= render DropdownComponent.new do %> <%= render DropdownComponent.new do %>
<%= render DropdownLinkComponent.new( <%= render DropdownLinkComponent.new(
href: launch_app_services_storage_rs_auth_url(@auth), href: launch_app_services_storage_rs_auth_url(@auth)
open_in_new_tab: true
) do %> ) do %>
Launch app Launch app
<% end %> <% end %>

View File

@@ -0,0 +1,44 @@
<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>

View File

@@ -0,0 +1,28 @@
# frozen_string_literal: true
module Settings
class NostrEditProfileComponent < ViewComponent::Base
def initialize(user:, profile_event:)
if profile_event.present?
@user = user
@profile_event = profile_event
@profile = JSON.parse(profile_event["content"])
@display_name = @profile["display_name"] || @profile["displayName"]
if @profile["nip05"].present? && @profile["nip05"] == @user.address
# "Your profile's Nostr address is set to <strong>#{ user_address }</strong>"
else
# "Your profile's Nostr address is not set to <strong>#{ user_address }</strong> yet"
end
if @profile["lud16"].present? && @profile["lud16"] == @user.address
# "Your profile's Lightning address is set to <strong>#{ user_address }</strong>"
else
# "Your profile's Lightning address is not set to <strong>#{ user_address }</strong> yet"
end
else
# "We could not find a profile for your public key"
end
end
end
end

View File

@@ -0,0 +1,21 @@
<% @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 %>

View File

@@ -0,0 +1,53 @@
# 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

View File

@@ -0,0 +1,18 @@
<%= render StatusTextComponent.new(
text: @text,
icon_name: @icon_name,
icon_color: @icon_color) %>
<% if @status == 1 %>
<p class="mt-8">
<button class="btn-md btn-blue">
Add the relay to my list
</button>
</p>
<% elsif @status == 2 %>
<p class="mt-8">
<button class="btn-md btn-blue">
Set up default relays
</button>
</p>
<% end %>

View File

@@ -0,0 +1,34 @@
# frozen_string_literal: true
module Settings
class NostrRelayStatusComponent < ViewComponent::Base
def initialize(nip65_event:)
if nip65_event.present?
if relay_urls(nip65_event).any? { |r| r.include?("wss://nostr.kosmos.org") }
@text = "You have a relay list, and the Kosmos relay is part of it"
@icon_name = "check-circle"
@icon_color = "emerald-500"
@status = 0
else
@text = "The Kosmos relay is missing from your relay list"
@icon_name = "alert-octagon"
@icon_color = "amber-500"
@status = 1
end
else
@text = "We could not find a relay list for your public key"
@icon_name = "alert-octagon"
@icon_color = "amber-500"
@status = 2
end
end
private
def relay_urls(nip65_event)
nip65_event["tags"].select{ |t| t[0] == "r" }.map{ |t| t[1] }
# @inbox_relay_urls = relay_tags&.select{ |t| t[2] == "read" }&.map{ |t| t[1] }
# @outbox_relay_urls = relay_tags&.select{ |t| t[2] != "read" }&.map{ |t| t[1] }
end
end
end

View File

@@ -0,0 +1,8 @@
<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>

View File

@@ -0,0 +1,7 @@
class StatusTextComponent < ViewComponent::Base
def initialize(text:, icon_name:, icon_color:)
@text = text
@icon_name = icon_name
@icon_color = icon_color
end
end

View File

@@ -4,7 +4,7 @@ class Admin::LightningController < Admin::BaseController
def index def index
@current_section = :lightning @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 @accounts = LndhubAccount.with_balances.order(balance: :desc).to_a
@ln = {} @ln = {}

View File

@@ -22,7 +22,7 @@ class Admin::UsersController < Admin::BaseController
@services_enabled = @user.services_enabled @services_enabled = @user.services_enabled
@ldap_avatar = LdapManager::FetchAvatar.call(cn: @user.cn) @avatar = LdapManager::FetchAvatar.call(cn: @user.cn)
end end
# POST /admin/users/:username/invitations # POST /admin/users/:username/invitations
@@ -30,7 +30,7 @@ class Admin::UsersController < Admin::BaseController
amount = params[:amount].to_i amount = params[:amount].to_i
notify_user = ActiveRecord::Type::Boolean.new.cast(params[:notify_user]) 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: { redirect_to admin_user_path(@user.cn), flash: {
success: "Added #{amount} invitations to #{@user.cn}'s account" success: "Added #{amount} invitations to #{@user.cn}'s account"

View File

@@ -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

View File

@@ -8,9 +8,6 @@ class Discourse::SsoController < ApplicationController
sso.email = current_user.email sso.email = current_user.email
sso.username = current_user.cn sso.username = current_user.cn
sso.name = current_user.display_name 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.admin = current_user.is_admin?
sso.sso_secret = secret sso.sso_secret = secret

View File

@@ -37,7 +37,7 @@ class LnurlpayController < ApplicationController
pubkey: Setting.lndhub_public_key, pubkey: Setting.lndhub_public_key,
customData: [{ customData: [{
customKey: "696969", customKey: "696969",
customValue: @user.lndhub_username customValue: @user.ln_account
}] }]
} }
end end

View File

@@ -9,7 +9,7 @@ class Services::LightningController < ApplicationController
before_action :lndhub_fetch_balance before_action :lndhub_fetch_balance
def index 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 end
def transactions def transactions

View File

@@ -23,11 +23,7 @@ class Services::RsAuthsController < Services::BaseController
end end
def launch_app def launch_app
user_address = Rails.env.development? ? launch_url = "#{@auth.launch_url}#remotestorage=#{current_user.address}&access_token=#{@auth.token}"
"#{current_user.cn}@localhost:3000" :
current_user.address
launch_url = "#{@auth.launch_url}#remotestorage=#{user_address}"
redirect_to launch_url, allow_other_host: true redirect_to launch_url, allow_other_host: true
end end

View File

@@ -4,8 +4,13 @@ require "bcrypt"
class SettingsController < ApplicationController class SettingsController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_main_nav_section before_action :set_main_nav_section
before_action :set_settings_section, only: [:show, :update, :update_email, :reset_email_password] before_action :set_settings_section, only: [
before_action :set_user, only: [:show, :update, :update_email, :reset_email_password] :show, :update, :update_email, :reset_email_password
]
before_action :set_user, only: [
:show, :update, :update_email, :reset_email_password,
:fetch_nostr_user_metadata
]
def index def index
redirect_to setting_path(:profile) redirect_to setting_path(:profile)
@@ -21,12 +26,10 @@ class SettingsController < ApplicationController
end end
end end
# PUT /settings/:section
def update def update
@user.preferences.merge!(user_params[:preferences] || {}) @user.preferences.merge!(user_params[:preferences] || {})
@user.display_name = user_params[:display_name] @user.display_name = user_params[:display_name]
@user.avatar_new = user_params[:avatar_new] @user.avatar_new = user_params[:avatar]
@user.pgp_pubkey = user_params[:pgp_pubkey]
if @user.save if @user.save
if @user.display_name && (@user.display_name != @user.ldap_entry[:display_name]) if @user.display_name && (@user.display_name != @user.ldap_entry[:display_name])
@@ -34,16 +37,7 @@ class SettingsController < ApplicationController
end end
if @user.avatar_new.present? if @user.avatar_new.present?
if store_user_avatar LdapManager::UpdateAvatar.call(dn: @user.dn, file: @user.avatar_new)
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)
end end
redirect_to setting_path(@settings_section), flash: { redirect_to setting_path(@settings_section), flash: {
@@ -55,7 +49,6 @@ class SettingsController < ApplicationController
end end
end end
# POST /settings/update_email
def update_email def update_email
if @user.valid_ldap_authentication?(security_params[:current_password]) if @user.valid_ldap_authentication?(security_params[:current_password])
if @user.update email: email_params[:email] if @user.update email: email_params[:email]
@@ -73,7 +66,6 @@ class SettingsController < ApplicationController
end end
end end
# POST /settings/reset_email_password
def reset_email_password def reset_email_password
@user.current_password = security_params[:current_password] @user.current_password = security_params[:current_password]
@@ -96,7 +88,6 @@ class SettingsController < ApplicationController
end end
end end
# POST /settings/reset_password
def reset_password def reset_password
current_user.send_reset_password_instructions current_user.send_reset_password_instructions
sign_out current_user sign_out current_user
@@ -104,7 +95,6 @@ class SettingsController < ApplicationController
redirect_to check_your_email_path, notice: msg redirect_to check_your_email_path, notice: msg
end end
# POST /settings/set_nostr_pubkey
def set_nostr_pubkey def set_nostr_pubkey
signed_event = Nostr::Event.new(**nostr_event_from_params) signed_event = Nostr::Event.new(**nostr_event_from_params)
@@ -143,6 +133,28 @@ class SettingsController < ApplicationController
} }
end end
def fetch_nostr_user_metadata
if @user.nostr_pubkey.present?
outbox_relay_urls = nil
# if @nip65_event = NostrManager::DiscoverUserRelays.call(pubkey: @user.nostr_pubkey)
# relay_tags = @nip65_event["tags"].select{ |t| t[0] == "r" }
# outbox_relay_urls = relay_tags&.select{ |t| t[2] != "read" }&.map{ |t| t[1] }
# end
# @profile = NostrManager::DiscoverUserProfile.call(
# pubkey: @user.nostr_pubkey,
# relays: outbox_relay_urls
# )
@profile = {"content"=>"{\"name\":\"jimmy\",\"picture\":\"https://storage.kosmos.org/jimmy/public/shares/241028-1117-tony.jpg\",\"banner\":\"https://storage.kosmos.org/raucao/public/shares/240604-1517-1500x500.jpg\",\"nip05\":\"jimmy@kosmos.org\",\"lud16\":\"jimmy@kosmos.org\",\"pubkey\":\"07e188a1ff87ce171d517b8ed2bb7a31b1d3453a0db3b15379ec07b724d232f3\",\"display_name\":\"Jimmy\",\"displayName\":\"Jimmy\",\"about\":\"I don't exist. Follow at your own peril.\"}", "created_at"=>1730114246, "id"=>"6b15b1308a61ee837bd3b50319978314650e435891c259f4ea499f819f35a4f6", "kind"=>0, "pubkey"=>"07e188a1ff87ce171d517b8ed2bb7a31b1d3453a0db3b15379ec07b724d232f3", "sig"=>"4f681f4b95646bbf88a6eae9ca92c0f2ce5effecfa017556a23490f91a99243aedf81d956ee2466ed64fecb9a03b6b89cd80ff116df0178830977e203867d7ae", "tags"=>[]}
# @profile = {"content"=>"{\"name\":\"jimmy\",\"nip05\":\"jimmy@kosmos.org\",\"lud16\":\"jimmy@kosmos.org\",\"pubkey\":\"07e188a1ff87ce171d517b8ed2bb7a31b1d3453a0db3b15379ec07b724d232f3\",\"display_name\":\"Jimmy\",\"displayName\":\"Jimmy\",\"about\":\"I don't exist. Follow at your own peril.\"}", "created_at"=>1730114246, "id"=>"6b15b1308a61ee837bd3b50319978314650e435891c259f4ea499f819f35a4f6", "kind"=>0, "pubkey"=>"07e188a1ff87ce171d517b8ed2bb7a31b1d3453a0db3b15379ec07b724d232f3", "sig"=>"4f681f4b95646bbf88a6eae9ca92c0f2ce5effecfa017556a23490f91a99243aedf81d956ee2466ed64fecb9a03b6b89cd80ff116df0178830977e203867d7ae", "tags"=>[]}
else
@relays, @profile = [nil, nil]
end
render partial: 'nostr_user_metadata'
end
private private
def set_main_nav_section def set_main_nav_section
@@ -167,8 +179,7 @@ class SettingsController < ApplicationController
def user_params def user_params
params.require(:user).permit( params.require(:user).permit(
:display_name, :avatar_new, :pgp_pubkey, :display_name, :avatar, preferences: UserPreferences.pref_keys
preferences: UserPreferences.pref_keys
) )
end end
@@ -189,30 +200,4 @@ class SettingsController < ApplicationController
salt = BCrypt::Engine.generate_salt salt = BCrypt::Engine.generate_salt
BCrypt::Engine.hash_secret(password, salt) BCrypt::Engine.hash_secret(password, salt)
end 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 end

View File

@@ -96,7 +96,7 @@ class SignupController < ApplicationController
session[:new_user] = nil session[:new_user] = nil
session[:validation_error] = nil session[:validation_error] = nil
UserManager::CreateAccount.call(account: { CreateAccount.call(account: {
username: @user.cn, username: @user.cn,
domain: Setting.primary_domain, domain: Setting.primary_domain,
email: @user.email, email: @user.email,

View File

@@ -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

View File

@@ -33,10 +33,6 @@ class WebfingerController < WellKnownController
links: [] links: []
} }
if @user.avatar.attached?
jrd[:links] += avatar_link
end
if Setting.mastodon_enabled && @user.service_enabled?(:mastodon) if Setting.mastodon_enabled && @user.service_enabled?(:mastodon)
# https://docs.joinmastodon.org/spec/webfinger/ # https://docs.joinmastodon.org/spec/webfinger/
jrd[:aliases] += mastodon_aliases jrd[:aliases] += mastodon_aliases
@@ -51,16 +47,6 @@ class WebfingerController < WellKnownController
jrd jrd
end 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 def mastodon_aliases
[ [
"#{Setting.mastodon_public_url}/@#{@user.cn}", "#{Setting.mastodon_public_url}/@#{@user.cn}",
@@ -88,7 +74,7 @@ class WebfingerController < WellKnownController
end end
def remotestorage_link def remotestorage_link
auth_url = new_rs_oauth_url(@username, host: Setting.rs_accounts_domain) auth_url = new_rs_oauth_url(@username, host: Setting.accounts_domain)
storage_url = "#{Setting.rs_storage_url}/#{@username}" storage_url = "#{Setting.rs_storage_url}/#{@username}"
{ {

View File

@@ -5,7 +5,7 @@ class WebhooksController < ApplicationController
before_action :process_payload before_action :process_payload
def lndhub 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]) if @zap = @user.zaps.find_by(payment_request: @payload[:payment_request])
settled_at = Time.parse(@payload[:settled_at]) settled_at = Time.parse(@payload[:settled_at])

View File

@@ -14,19 +14,4 @@ module ApplicationHelper
def badge(text, color) 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" 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 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 end

View File

@@ -4,7 +4,7 @@ class CreateLdapUserJob < ApplicationJob
def perform(username:, domain:, email:, hashed_pw:, confirmed: false) def perform(username:, domain:, email:, hashed_pw:, confirmed: false)
dn = "cn=#{username},ou=#{domain},cn=users,dc=kosmos,dc=org" dn = "cn=#{username},ou=#{domain},cn=users,dc=kosmos,dc=org"
attr = { attr = {
objectclass: ["top", "account", "person", "inetOrgPerson", "extensibleObject"], objectclass: ["top", "account", "person", "extensibleObject"],
cn: username, cn: username,
sn: username, sn: username,
uid: username, uid: username,

View File

@@ -2,12 +2,12 @@ class CreateLndhubAccountJob < ApplicationJob
queue_as :default queue_as :default
def perform(user) 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 lndhub = LndhubV2.new
credentials = lndhub.create_account credentials = lndhub.create_account
user.update! lndhub_username: credentials["login"], user.update! ln_account: credentials["login"],
lndhub_password: credentials["password"] ln_password: credentials["password"]
end end
end end

View File

@@ -3,6 +3,8 @@ class RemoteStorageExpireAuthorizationJob < ApplicationJob
def perform(rs_auth_id) def perform(rs_auth_id)
rs_auth = RemoteStorageAuthorization.find 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! rs_auth.destroy!
end end
end end

View File

@@ -1,97 +0,0 @@
require 'digest'
require "image_processing/vips"
class XmppSetAvatarJob < ApplicationJob
queue_as :default
def perform(user:, overwrite: false)
return if Rails.env.development?
@user = user
unless overwrite
current_avatar = get_current_avatar
Rails.logger.info { "User #{user.cn} already has an avatar set" }
return if current_avatar.present?
end
Rails.logger.debug { "Setting XMPP avatar for user #{user.cn}" }
stanzas = build_xep0084_stanzas
stanzas.each do |stanza|
payload = { from: @user.address, to: @user.address, stanza: stanza }
res = ejabberd.send_stanza payload
raise res.inspect if res.status != 200
end
end
private
def ejabberd
@ejabberd ||= EjabberdApiClient.new
end
def get_current_avatar
res = ejabberd.get_vcard2 @user, "PHOTO", "BINVAL"
if res.status == 200
# VCARD PHOTO/BINVAL prop exists
res.body
elsif res.status == 400
# VCARD or PHOTO/BINVAL prop does not exist
nil
else
# Unexpected error, let job fail
raise res.inspect
end
end
def process_avatar
@user.avatar.blob.open do |file|
processed = ImageProcessing::Vips
.source(file)
.resize_to_fill(256, 256)
.convert("png")
.call
processed.read
end
end
# See https://xmpp.org/extensions/xep-0084.html
def build_xep0084_stanzas
img_data = process_avatar
sha1_hash = Digest::SHA1.hexdigest(img_data)
base64_data = Base64.strict_encode64(img_data)
[
"""
<iq type='set' from='#{@user.address}' id='avatar-data-#{rand(101)}'>
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
<publish node='urn:xmpp:avatar:data'>
<item id='#{sha1_hash}'>
<data xmlns='urn:xmpp:avatar:data'>#{base64_data}</data>
</item>
</publish>
</pubsub>
</iq>
""".strip,
"""
<iq type='set' from='#{@user.address}' id='avatar-metadata-#{rand(101)}'>
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
<publish node='urn:xmpp:avatar:metadata'>
<item id='#{sha1_hash}'>
<metadata xmlns='urn:xmpp:avatar:metadata'>
<info bytes='#{img_data.size}'
id='#{sha1_hash}'
height='256'
type='image/png'
width='256'/>
</metadata>
</item>
</publish>
</pubsub>
</iq>
""".strip,
]
end
end

View File

@@ -1,90 +1,3 @@
class ApplicationMailer < ActionMailer::Base class ApplicationMailer < ActionMailer::Base
default Rails.application.config.action_mailer.default_options
layout 'mailer' 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 end

View File

@@ -18,6 +18,6 @@ class CustomMailer < ApplicationMailer
@user = params[:user] @user = params[:user]
@subject = params[:subject] @subject = params[:subject]
@body = params[:body] @body = params[:body]
send_mail mail(to: @user.email, subject: @subject)
end end
end end

View File

@@ -3,7 +3,7 @@ class NotificationMailer < ApplicationMailer
@user = params[:user] @user = params[:user]
@amount_sats = params[:amount_sats] @amount_sats = params[:amount_sats]
@subject = "Sats received" @subject = "Sats received"
send_mail mail to: @user.email, subject: @subject
end end
def remotestorage_auth_created def remotestorage_auth_created
@@ -15,19 +15,19 @@ class NotificationMailer < ApplicationMailer
"#{access} #{directory}" "#{access} #{directory}"
end end
@subject = "New app connected to your storage" @subject = "New app connected to your storage"
send_mail mail to: @user.email, subject: @subject
end end
def new_invitations_available def new_invitations_available
@user = params[:user] @user = params[:user]
@subject = "New invitations added to your account" @subject = "New invitations added to your account"
send_mail mail to: @user.email, subject: @subject
end end
def bitcoin_donation_confirmed def bitcoin_donation_confirmed
@user = params[:user] @user = params[:user]
@donation = params[:donation] @donation = params[:donation]
@subject = "Donation confirmed" @subject = "Donation confirmed"
send_mail mail to: @user.email, subject: @subject
end end
end end

View File

@@ -11,9 +11,6 @@ module Settings
field :mastodon_address_domain, type: :string, field :mastodon_address_domain, type: :string,
default: ENV["MASTODON_ADDRESS_DOMAIN"].presence || self.primary_domain default: ENV["MASTODON_ADDRESS_DOMAIN"].presence || self.primary_domain
field :mastodon_auth_token, type: :string,
default: ENV["MASTODON_AUTH_TOKEN"].presence
end end
end end
end end

View File

@@ -20,6 +20,19 @@ module Settings
field :nostr_zaps_relay_limit, type: :integer, field :nostr_zaps_relay_limit, type: :integer,
default: 12 default: 12
field :nostr_discovery_relays, type: :array, default: %w[
wss://nostr.kosmos.org
wss://purplepag.es
wss://relay.nostr.band
wss://njump.me
wss://relay.damus.io
]
def self.nostr_relay_url_http
self.nostr_relay_url.gsub(/^ws:/, "http:")
.gsub(/^wss:/, "https:")
end
end end
end end
end end

View File

@@ -6,9 +6,6 @@ module Settings
field :remotestorage_enabled, type: :boolean, field :remotestorage_enabled, type: :boolean,
default: ENV["RS_STORAGE_URL"].present? default: ENV["RS_STORAGE_URL"].present?
field :rs_accounts_domain, type: :string,
default: ENV["RS_AKKOUNTS_DOMAIN"] || ENV["AKKOUNTS_DOMAIN"]
field :rs_storage_url, type: :string, field :rs_storage_url, type: :string,
default: ENV["RS_STORAGE_URL"].presence default: ENV["RS_STORAGE_URL"].presence

View File

@@ -6,7 +6,7 @@ class LndhubUser < LndhubBase
foreign_key: "user_id" foreign_key: "user_id"
belongs_to :user, class_name: "User", belongs_to :user, class_name: "User",
primary_key: "lndhub_username", primary_key: "ln_account",
foreign_key: "login" foreign_key: "login"
def balance def balance

View File

@@ -2,7 +2,7 @@ class RemoteStorageAuthorization < ApplicationRecord
belongs_to :user belongs_to :user
belongs_to :web_app, class_name: "AppCatalog::WebApp", optional: true belongs_to :web_app, class_name: "AppCatalog::WebApp", optional: true
serialize :permissions, coder: YAML unless Rails.env.production? serialize :permissions unless Rails.env.production?
validates_presence_of :permissions validates_presence_of :permissions
validates_presence_of :client_id validates_presence_of :client_id
@@ -69,19 +69,11 @@ class RemoteStorageAuthorization < ApplicationRecord
end end
def remove_token_expiry_job def remove_token_expiry_job
job_class = RemoteStorageExpireAuthorizationJob queue = Sidekiq::Queue.new(RemoteStorageExpireAuthorizationJob.queue_name)
job_args = [id] queue.each do |job|
next unless job.display_class == "RemoteStorageExpireAuthorizationJob"
query = SolidQueue::Job.where(class_name: job_class.to_s) job.delete if job.display_args == [id]
end
case ActiveRecord::Base.connection.adapter_name.downcase
when /sqlite/
query.where("json_extract(arguments, '$.arguments') = ?", job_args.to_json)
when /postgres/
query.where("CAST(arguments AS jsonb)->>'arguments' = ?", job_args.to_json)
else
raise "Unsupported database adapter"
end.destroy_all
end end
def find_or_create_web_app def find_or_create_web_app

View File

@@ -3,10 +3,9 @@ require 'nostr'
class User < ApplicationRecord class User < ApplicationRecord
include EmailValidatable include EmailValidatable
attr_accessor :current_password
attr_accessor :display_name attr_accessor :display_name
attr_accessor :avatar_new attr_accessor :avatar_new
attr_accessor :pgp_pubkey attr_accessor :current_password
serialize :preferences, coder: UserPreferences serialize :preferences, coder: UserPreferences
@@ -23,16 +22,10 @@ class User < ApplicationRecord
has_many :zaps has_many :zaps
has_one :lndhub_user, class_name: "LndhubUser", inverse_of: "user", has_one :lndhub_user, class_name: "LndhubUser", inverse_of: "user",
primary_key: "lndhub_username", foreign_key: "login" primary_key: "ln_account", foreign_key: "login"
has_many :accounts, through: :lndhub_user has_many :accounts, through: :lndhub_user
#
# Attachments
#
has_one_attached :avatar
# #
# Validations # Validations
# #
@@ -56,11 +49,8 @@ class User < ApplicationRecord
validates_length_of :display_name, minimum: 3, maximum: 35, allow_blank: true, validates_length_of :display_name, minimum: 3, maximum: 35, allow_blank: true,
if: -> { defined?(@display_name) } if: -> { defined?(@display_name) }
validate :acceptable_avatar validate :acceptable_avatar
validate :acceptable_pgp_key_format, if: -> { defined?(@pgp_pubkey) && @pgp_pubkey.present? }
# #
# Scopes # Scopes
# #
@@ -73,7 +63,7 @@ class User < ApplicationRecord
# Encrypted database columns # Encrypted database columns
# #
encrypts :lndhub_password has_encrypted :ln_login, :ln_password
# Include default devise modules. Others available are: # Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
@@ -84,10 +74,6 @@ class User < ApplicationRecord
:timeoutable, :timeoutable,
:rememberable :rememberable
#
# Methods
#
def ldap_before_save def ldap_before_save
self.email = Devise::LDAP::Adapter.get_ldap_param(self.cn, "mail").first self.email = Devise::LDAP::Adapter.get_ldap_param(self.cn, "mail").first
self.ou = dn.split(',') self.ou = dn.split(',')
@@ -170,20 +156,6 @@ class User < ApplicationRecord
@display_name ||= ldap_entry[:display_name] @display_name ||= ldap_entry[:display_name]
end end
# TODO Variant keys are currently broken for some reason
# (They use the same key as the main blob, when it should be
# "/variants/#{key)"
# def avatar_variant(size: :medium)
# dimensions = case size
# when :large then [400, 400]
# when :medium then [256, 256]
# when :small then [64, 64]
# else [256, 256]
# end
# format = avatar.content_type == "image/png" ? :png : :jpeg
# avatar.variant(resize_to_fill: dimensions, format: format)
# end
def nostr_pubkey def nostr_pubkey
@nostr_pubkey ||= ldap_entry[:nostr_key] @nostr_pubkey ||= ldap_entry[:nostr_key]
end end
@@ -193,22 +165,8 @@ class User < ApplicationRecord
Nostr::PublicKey.new(nostr_pubkey).to_bech32 Nostr::PublicKey.new(nostr_pubkey).to_bech32
end end
def pgp_pubkey def avatar
@pgp_pubkey ||= ldap_entry[:pgp_key] @avatar_base64 ||= LdapManager::FetchAvatar.call(cn: cn)
end
def gnupg_key
return nil unless pgp_pubkey.present?
GPGME::Key.import(pgp_pubkey)
GPGME::Key.get(pgp_fpr)
end
def pgp_pubkey_contains_user_address?
gnupg_key.uids.map(&:email).include?(address)
end
def wkd_hash
ZBase32.encode(Digest::SHA1.digest(cn))
end end
def services_enabled def services_enabled
@@ -248,7 +206,7 @@ class User < ApplicationRecord
return unless avatar_new.present? return unless avatar_new.present?
if avatar_new.size > 1.megabyte if avatar_new.size > 1.megabyte
errors.add(:avatar, "must be less than 1MB file size") errors.add(:avatar, "file size is too large")
end end
acceptable_types = ["image/jpeg", "image/png"] acceptable_types = ["image/jpeg", "image/png"]
@@ -256,10 +214,4 @@ class User < ApplicationRecord
errors.add(:avatar, "must be a JPEG or PNG file") errors.add(:avatar, "must be a JPEG or PNG file")
end end
end end
def acceptable_pgp_key_format
unless GPGME::Key.valid?(pgp_pubkey)
errors.add(:pgp_pubkey, 'is not a valid armored PGP public key block')
end
end
end end

View File

@@ -1,22 +1,36 @@
# #
# API Docs: https://docs.btcpayserver.org/API/Greenfield/v1/ # API Docs: https://docs.btcpayserver.org/API/Greenfield/v1/
# #
class BtcpayManagerService < RestApiService class BtcpayManagerService < ApplicationService
private private
def base_url def base_url
@base_url ||= "#{Setting.btcpay_api_url}/stores/#{Setting.btcpay_store_id}" @base_url ||= "#{Setting.btcpay_api_url}/stores/#{Setting.btcpay_store_id}"
end end
def auth_token def auth_token
@auth_token ||= Setting.btcpay_auth_token @auth_token ||= Setting.btcpay_auth_token
end end
def headers def headers
{ {
"Content-Type" => "application/json", "Content-Type" => "application/json",
"Accept" => "application/json", "Accept" => "application/json",
"Authorization" => "token #{auth_token}" "Authorization" => "token #{auth_token}"
} }
end end
def endpoint_url(path)
"#{base_url}/#{path.gsub(/^\//, '')}"
end
def get(path, params = {})
res = Faraday.get endpoint_url(path), params, headers
JSON.parse(res.body)
end
def post(path, payload)
res = Faraday.post endpoint_url(path), payload.to_json, headers
JSON.parse(res.body)
end
end end

View File

@@ -0,0 +1,54 @@
class CreateAccount < ApplicationService
def initialize(account:)
@username = account[:username]
@domain = account[:ou] || Setting.primary_domain
@email = account[:email]
@password = account[:password]
@invitation = account[:invitation]
@confirmed = account[:confirmed]
end
def call
user = create_user_in_database
add_ldap_document
create_lndhub_account(user) if Setting.lndhub_enabled
if @invitation.present?
update_invitation(user.id)
end
end
private
def create_user_in_database
User.create!(
cn: @username,
ou: @domain,
email: @email,
password: @password,
password_confirmation: @password,
confirmed_at: @confirmed ? DateTime.now : nil
)
end
def update_invitation(user_id)
@invitation.update! invited_user_id: user_id, used_at: DateTime.now
end
def add_ldap_document
hashed_pw = Devise.ldap_auth_password_builder.call(@password)
CreateLdapUserJob.perform_later(
username: @username,
domain: @domain,
email: @email,
hashed_pw: hashed_pw,
confirmed: @confirmed
)
end
def create_lndhub_account(user)
#TODO enable in development when we have a local lndhub (mock?) API
return if Rails.env.development?
CreateLndhubAccountJob.perform_later(user)
end
end

View File

@@ -0,0 +1,17 @@
class CreateInvitations < ApplicationService
def initialize(user:, amount:, notify: true)
@user = user
@amount = amount
@notify = notify
end
def call
@amount.times do
Invitation.create(user: @user)
end
if @notify
NotificationMailer.with(user: @user).new_invitations_available.deliver_later
end
end
end

View File

@@ -4,13 +4,15 @@ class EjabberdApiClient
end end
def post(endpoint, payload) def post(endpoint, payload)
Faraday.post "#{@base_url}/#{endpoint}", payload.to_json, res = Faraday.post("#{@base_url}/#{endpoint}", payload.to_json,
"Content-Type" => "application/json" "Content-Type" => "application/json")
end
# if res.status != 200
# API endpoints Rails.logger.error "[ejabberd] API request failed:"
# Rails.logger.error res.body
#TODO Send custom event to Sentry
end
end
def add_rosteritem(payload) def add_rosteritem(payload)
post "add_rosteritem", payload post "add_rosteritem", payload
@@ -20,31 +22,8 @@ class EjabberdApiClient
post "send_message", payload post "send_message", payload
end end
def send_stanza(payload)
post "send_stanza", payload
end
def get_vcard2(user, name, subname)
payload = {
user: user.cn, host: user.ou,
name: name, subname: subname
}
post "get_vcard2", payload
end
def private_get(user, element_name, namespace)
payload = {
user: user.cn, host: user.ou,
element: element_name, ns: namespace
}
post "private_get", payload
end
def private_set(user, content) def private_set(user, content)
payload = { payload = { user: user.cn, host: user.ou, element: content }
user: user.cn, host: user.ou,
element: content
}
post "private_set", payload post "private_set", payload
end end
end end

View File

@@ -5,12 +5,12 @@ module LdapManager
end end
def call def call
treebase = ldap_config["base"] treebase = ldap_config["base"]
attributes = %w{ jpegPhoto } attributes = %w{ jpegPhoto }
filter = Net::LDAP::Filter.eq("cn", @cn) filter = Net::LDAP::Filter.eq("cn", @cn)
entry = client.search(base: treebase, filter: filter, attributes: attributes).first entry = client.search(base: treebase, filter: filter, attributes: attributes).first
entry[:jpegPhoto].present? ? entry.jpegPhoto.first : nil entry.try(:jpegPhoto) ? entry.jpegPhoto.first : nil
end end
end end
end end

View File

@@ -2,41 +2,26 @@ require "image_processing/vips"
module LdapManager module LdapManager
class UpdateAvatar < LdapManagerService class UpdateAvatar < LdapManagerService
def initialize(user:) def initialize(dn:, file:)
@user = user @dn = dn
@dn = user.dn @img_data = process(file)
end end
def call def call
unless @user.avatar.attached? replace_attribute @dn, :jpegPhoto, @img_data
Rails.logger.error { "Cannot store empty jpegPhoto for user #{@user.cn}" }
return false
end
img_data = @user.avatar.blob.download
jpg_data = process_avatar
Rails.logger.debug { "Storing new jpegPhoto for user #{@user.cn} in LDAP" }
result = replace_attribute(@dn, :jpegPhoto, jpg_data)
result == 0
end end
private private
def process_avatar def process(file)
@user.avatar.blob.open do |file| processed = ImageProcessing::Vips
processed = ImageProcessing::Vips .resize_to_fill(512, 512)
.source(file) .source(file)
.resize_to_fill(256, 256) .convert("jpeg")
.convert("jpeg") .saver(strip: true)
.saver(strip: true) .call
.call
processed.read Base64.strict_encode64 processed.read
end
rescue Vips::Error => e
Sentry.capture_exception(e) if Setting.sentry_enabled?
Rails.logger.error { "Image processing failed for LDAP avatar: #{e.message}" }
nil
end end
end end
end end

View File

@@ -6,11 +6,7 @@ module LdapManager
end end
def call def call
if @display_name.present? replace_attribute @dn, :displayName, @display_name
replace_attribute @dn, :displayName, @display_name
else
delete_attribute @dn, :displayName
end
end end
end end
end end

View File

@@ -1,16 +0,0 @@
module LdapManager
class UpdatePgpKey < LdapManagerService
def initialize(dn:, pubkey:)
@dn = dn
@pubkey = pubkey
end
def call
if @pubkey.present?
replace_attribute @dn, :pgpKey, @pubkey
else
delete_attribute @dn, :pgpKey
end
end
end
end

View File

@@ -58,7 +58,7 @@ class LdapService < ApplicationService
attributes = %w[ attributes = %w[
dn cn uid mail displayName admin serviceEnabled dn cn uid mail displayName admin serviceEnabled
mailRoutingAddress mailpassword nostrKey pgpKey mailRoutingAddress mailpassword nostrKey
] ]
filter = Net::LDAP::Filter.eq("uid", args[:uid] || "*") filter = Net::LDAP::Filter.eq("uid", args[:uid] || "*")
@@ -73,8 +73,7 @@ class LdapService < ApplicationService
services_enabled: e.try(:serviceEnabled), services_enabled: e.try(:serviceEnabled),
email_maildrop: e.try(:mailRoutingAddress), email_maildrop: e.try(:mailRoutingAddress),
email_password: e.try(:mailpassword), email_password: e.try(:mailpassword),
nostr_key: e.try(:nostrKey) ? e.nostrKey.first : nil, nostr_key: e.try(:nostrKey) ? e.nostrKey.first : nil
pgp_key: e.try(:pgpKey) ? e.pgpKey.first : nil
} }
end end
end end
@@ -102,7 +101,7 @@ class LdapService < ApplicationService
dn = "ou=#{ou},cn=users,#{ldap_suffix}" dn = "ou=#{ou},cn=users,#{ldap_suffix}"
aci = <<-EOS aci = <<-EOS
(target="ldap:///cn=*,ou=#{ou},cn=users,#{ldap_suffix}")(targetattr="cn || sn || uid || userPassword || mail || mailRoutingAddress || serviceEnabled || nostrKey || pgpKey || nsRole || objectClass") (version 3.0; acl "service-#{ou.gsub(".", "-")}-read-search"; allow (read,search) userdn="ldap:///uid=service,ou=#{ou},cn=applications,#{ldap_suffix}";) (target="ldap:///cn=*,ou=#{ou},cn=users,#{ldap_suffix}")(targetattr="cn || sn || uid || userPassword || mail || mailRoutingAddress || serviceEnabled || nostrKey || nsRole || objectClass") (version 3.0; acl "service-#{ou.gsub(".", "-")}-read-search"; allow (read,search) userdn="ldap:///uid=service,ou=#{ou},cn=applications,#{ldap_suffix}";)
EOS EOS
attrs = { attrs = {

View File

@@ -33,10 +33,7 @@ class Lndhub < ApplicationService
end end
def authenticate(user) def authenticate(user)
credentials = post "auth?type=auth", { credentials = post "auth?type=auth", { login: user.ln_account, password: user.ln_password }
login: user.lndhub_username,
password: user.lndhub_password
}
self.auth_token = credentials["access_token"] self.auth_token = credentials["access_token"]
self.auth_token self.auth_token
end end

View File

@@ -1,12 +0,0 @@
module MastodonManager
class FetchUser < MastodonManagerService
def initialize(mastodon_id:)
@mastodon_id = mastodon_id
end
def call
user = get "v1/admin/accounts/#{@mastodon_id}"
user.with_indifferent_access
end
end
end

View File

@@ -1,14 +0,0 @@
module MastodonManager
class FindUser < MastodonManagerService
def initialize(username:)
@username = username
end
def call
users = get "v2/admin/accounts?username=#{@username}&origin=local"
users = users.map { |u| u.with_indifferent_access }
# Results may contain partial matches
users.find { |u| u.dig(:username).downcase == @username.downcase }
end
end
end

View File

@@ -1,64 +0,0 @@
module MastodonManager
class SyncAccountProfiles < MastodonManagerService
def initialize(direction: "down", overwrite: false, user: nil)
@direction = direction
@overwrite = overwrite
@user = user
if @direction != "down"
raise NotImplementedError
end
end
def call
if @user
Rails.logger.debug { "Syncing account profile for user #{@user.cn} (direction: #{@direction}, overwrite: #{@overwrite})"}
users = User.where(cn: @user.cn)
else
Rails.logger.debug { "Syncing account profiles (direction: #{@direction}, overwrite: #{@overwrite})"}
users = User
end
users.find_each do |user|
if user.mastodon_id.blank?
mastodon_user = MastodonManager::FindUser.call username: user.cn
if mastodon_user
Rails.logger.debug { "Setting mastodon_id for user #{user.cn}" }
user.update! mastodon_id: mastodon_user.dig(:account, :id).to_i
else
Rails.logger.debug { "No Mastodon user found for username #{user.cn}" }
next
end
end
next if user.avatar.attached? && user.display_name.present?
unless mastodon_user
Rails.logger.debug { "Fetching Mastodon account with ID #{user.mastodon_id} for #{user.cn}" }
mastodon_user = MastodonManager::FetchUser.call mastodon_id: user.mastodon_id
end
if user.display_name.blank?
if mastodon_display_name = mastodon_user.dig(:account, :display_name)
Rails.logger.debug { "Setting display name for user #{user.cn} from Mastodon" }
LdapManager::UpdateDisplayName.call(
dn: user.dn, display_name: mastodon_display_name
)
end
end
if !user.avatar.attached?
if avatar_url = mastodon_user.dig(:account, :avatar_static)
Rails.logger.debug { "Importing Mastodon avatar for user #{user.cn}" }
UserManager::ImportRemoteAvatar.call(
user: user, avatar_url: avatar_url
)
end
end
rescue => e
Sentry.capture_exception(e) if Setting.sentry_enabled?
Rails.logger.error e
end
end
end
end

View File

@@ -1,22 +0,0 @@
#
# API Docs: https://docs.joinmastodon.org/methods/
#
class MastodonManagerService < RestApiService
private
def base_url
@base_url ||= "#{Setting.mastodon_public_url}/api"
end
def auth_token
@auth_token ||= Setting.mastodon_auth_token
end
def headers
{
"Content-Type" => "application/json",
"Accept" => "application/json",
"Authorization" => "Bearer #{auth_token}"
}
end
end

View File

@@ -0,0 +1,21 @@
module NostrManager
class DiscoverUserProfile < NostrManagerService
def initialize(pubkey:, relays: nil)
@pubkey = pubkey
@relays = relays.present? ? relays : Setting.nostr_discovery_relays
end
def call
filter = Nostr::Filter.new(
authors: [@pubkey],
kinds: [0],
limit: 1,
)
NostrManager::FetchLatestEvent.call(
relays: @relays,
filter: filter
)
end
end
end

View File

@@ -0,0 +1,21 @@
module NostrManager
class DiscoverUserRelays < NostrManagerService
def initialize(pubkey:)
@pubkey = pubkey
@relays = Setting.nostr_discovery_relays
end
def call
filter = Nostr::Filter.new(
authors: [@pubkey],
kinds: [10002],
limit: 1,
)
NostrManager::FetchLatestEvent.call(
relays: @relays,
filter: filter
)
end
end
end

View File

@@ -0,0 +1,59 @@
module NostrManager
class FetchEvent < NostrManagerService
TIMEOUT = 10
def initialize(filter:, relay_url:)
@filter = filter
@relay = new_relay(relay_url)
@client = Nostr::Client.new
end
def call
filter, client, relay = @filter, @client, @relay
event = nil
mutex = Mutex.new
received_event = ConditionVariable.new
log_prefix = "[nostr][#{@relay.name}]"
thread = Thread.new do
client.on :connect do
client.subscribe(filter: filter)
end
client.on :error do |e|
Rails.logger.info "#{log_prefix} Error: #{e}"
Thread.current.exit
end
client.on :message do |m|
msg = JSON.parse(m) rescue nil
if msg && msg[0] == "EVENT" && msg[2]
Rails.logger.debug "#{log_prefix} Event received: #{msg[2]["id"]}"
mutex.synchronize do
event = msg[2]
received_event.signal
end
elsif msg && msg[0] == "EOSE"
Thread.current.exit
end
end
client.connect relay
end
begin
Timeout.timeout(TIMEOUT) do
mutex.synchronize do
received_event.wait(mutex) if event.nil?
end
end
rescue Timeout::Error
Rails.logger.debug "#{log_prefix} Timeout: No event received within #{TIMEOUT} seconds"
ensure
thread.exit
end
event
end
end
end

View File

@@ -0,0 +1,44 @@
module NostrManager
class FetchLatestEvent < NostrManagerService
TIMEOUT = 20
def initialize(relays:, filter:, max_events: 2)
@relays = relays
@filter = filter
@max_events = max_events
end
def call
received_events = 0
events = []
begin
Timeout.timeout(TIMEOUT) do
@relays.each do |url|
event = NostrManager::FetchEvent.call(filter: @filter, relay_url: url)
if event.present?
events << event if events.none? { |e| e["id"] == event["id"] }
received_events += 1
end
if received_events >= @max_events
Rails.logger.debug "Found #{@max_events} events, ending the search"
break
end
end
events.min_by { |e| e["created_at"] }
end
rescue Timeout::Error
if events.size == 1
Rails.logger.debug "[nostr] Timeout: only found 1 event within #{TIMEOUT} seconds for filter: #{@filter.inspect}"
events.first
else
Rails.logger.debug "[nostr] Timeout: no events found within #{TIMEOUT} seconds for filter: #{@filter.inspect}"
nil
end
end
end
end
end

View File

@@ -19,28 +19,28 @@ module NostrManager
thread = Thread.new do thread = Thread.new do
client.on :connect do client.on :connect do
puts "#{log_prefix} Publishing #{event.id}..." Rails.logger.debug "#{log_prefix} Publishing #{event.id}..."
client.publish event client.publish event
end end
client.on :error do |e| client.on :error do |e|
puts "#{log_prefix} Error: #{e}" Rails.logger.debug "#{log_prefix} Error: #{e}"
puts "#{log_prefix} Closing thread..." Rails.logger.debug "#{log_prefix} Closing thread..."
thread.exit thread.exit
end end
client.on :message do |m| client.on :message do |m|
puts "#{log_prefix} Message: #{m}" Rails.logger.debug "#{log_prefix} Message: #{m}"
msg = JSON.parse(m) rescue [] msg = JSON.parse(m) rescue []
if msg[0] == "OK" && msg[1] == event.id && msg[2] if msg[0] == "OK" && msg[1] == event.id && msg[2]
puts "#{log_prefix} Event published. Closing thread..." Rails.logger.debug "#{log_prefix} Event published. Closing thread..."
else else
puts "#{log_prefix} Unexpected message from relay. Closing thread..." Rails.logger.debug "#{log_prefix} Unexpected message from relay. Closing thread..."
end end
thread.exit thread.exit
end end
puts "#{log_prefix} Connecting to #{relay.url}..." Rails.logger.debug "#{log_prefix} Connecting to #{relay.url}..."
client.connect relay client.connect relay
end end

View File

@@ -3,6 +3,7 @@ require "nostr"
class NostrManagerService < ApplicationService class NostrManagerService < ApplicationService
def parse_tags(tags) def parse_tags(tags)
out = {} out = {}
# TODO support more than 1 item for each tag type
tags.each do |tag| tags.each do |tag|
out[tag[0].to_sym] = tag[1, tag.length] out[tag[0].to_sym] = tag[1, tag.length]
end end
@@ -19,4 +20,8 @@ class NostrManagerService < ApplicationService
def site_user def site_user
Nostr::User.new(keypair: site_keypair) Nostr::User.new(keypair: site_keypair)
end end
def new_relay(url)
Nostr::Relay.new(url: url, name: URI.parse(url).host)
end
end end

View File

@@ -1,27 +0,0 @@
class RestApiService < ApplicationService
private
def base_url
raise NotImplementedError
end
def headers
raise NotImplementedError
end
def endpoint_url(path)
"#{base_url}/#{path.gsub(/^\//, '')}"
end
def get(path, params = {})
res = Faraday.get endpoint_url(path), params, headers
# TODO handle unsuccessful responses with no valid JSON body
JSON.parse(res.body)
end
def post(path, payload)
res = Faraday.post endpoint_url(path), payload.to_json, headers
# TODO handle unsuccessful responses with no valid JSON body
JSON.parse(res.body)
end
end

View File

@@ -1,56 +0,0 @@
module UserManager
class CreateAccount < UserManagerService
def initialize(account:)
@username = account[:username]
@domain = account[:ou] || Setting.primary_domain
@email = account[:email]
@password = account[:password]
@invitation = account[:invitation]
@confirmed = account[:confirmed]
end
def call
user = create_user_in_database
add_ldap_document
create_lndhub_account(user) if Setting.lndhub_enabled
if @invitation.present?
update_invitation(user.id)
end
end
private
def create_user_in_database
User.create!(
cn: @username,
ou: @domain,
email: @email,
password: @password,
password_confirmation: @password,
confirmed_at: @confirmed ? DateTime.now : nil
)
end
def update_invitation(user_id)
@invitation.update! invited_user_id: user_id, used_at: DateTime.now
end
def add_ldap_document
hashed_pw = Devise.ldap_auth_password_builder.call(@password)
CreateLdapUserJob.perform_later(
username: @username,
domain: @domain,
email: @email,
hashed_pw: hashed_pw,
confirmed: @confirmed
)
end
def create_lndhub_account(user)
#TODO enable in development when we have a local lndhub (mock?) API
return if Rails.env.development?
CreateLndhubAccountJob.perform_later(user)
end
end
end

View File

@@ -1,19 +0,0 @@
module UserManager
class CreateInvitations < UserManagerService
def initialize(user:, amount:, notify: true)
@user = user
@amount = amount
@notify = notify
end
def call
@amount.times do
Invitation.create(user: @user)
end
if @notify
NotificationMailer.with(user: @user).new_invitations_available.deliver_later
end
end
end
end

View File

@@ -1,42 +0,0 @@
module UserManager
class ImportRemoteAvatar < UserManagerService
def initialize(user:, avatar_url:)
@user = user
@avatar_url = avatar_url
end
def call
if import_remote_avatar
UserManager::UpdateAvatar.call(user: @user)
end
end
private
def import_remote_avatar
tempfile = Down.download(@avatar_url)
content_type = tempfile.content_type
unless %w[image/jpeg image/png].include?(content_type)
Rails.logger.warn { "Wrong content type of remote avatar for user #{user.cn}: '#{content_type}'" }
return false
end
img_data = UserManager::ProcessAvatar.call(io: tempfile)
tempfile = Tempfile.create
tempfile.binmode
tempfile.write(img_data)
tempfile.rewind
hash = Digest::SHA256.hexdigest(img_data)
ext = content_type == "image/png" ? "png" : "jpg"
filename = "#{hash}.#{ext}"
key = "users/#{@user.cn}/avatars/#{filename}"
@user.avatar.attach io: tempfile, key: key, filename: filename
rescue => e
Sentry.capture_exception(e) if Setting.sentry_enabled?
Rails.logger.warn "Importing remote avatar failed: \"#{e.message}\""
false
end
end
end

View File

@@ -1,19 +0,0 @@
require 'gpgme'
module UserManager
class PgpEncrypt < UserManagerService
def initialize(user:, text:)
@user = user
@text = text
end
def call
crypto = GPGME::Crypto.new
crypto.encrypt(
@text,
recipients: @user.gnupg_key,
always_trust: true
)
end
end
end

View File

@@ -1,21 +0,0 @@
module UserManager
class ProcessAvatar < UserManagerService
def initialize(io:)
@io = io
end
def call
processed = ImageProcessing::Vips
.source(@io)
.resize_to_fill(400, 400)
.saver(strip: true)
.call
@io.rewind
processed.read
rescue Vips::Error => e
Sentry.capture_exception(e) if Setting.sentry_enabled?
Rails.logger.warn { "Image processing failed for avatar: #{e.message}" }
nil
end
end
end

View File

@@ -1,15 +0,0 @@
module UserManager
class UpdateAvatar < UserManagerService
def initialize(user:)
@user = user
end
def call
LdapManager::UpdateAvatar.call(user: @user)
if Setting.ejabberd_enabled?
XmppSetAvatarJob.perform_later(user: @user)
end
end
end
end

View File

@@ -1,24 +0,0 @@
module UserManager
class UpdatePgpKey < UserManagerService
def initialize(user:)
@user = user
end
def call
if @user.pgp_pubkey.blank?
@user.update! pgp_fpr: nil
else
result = GPGME::Key.import(@user.pgp_pubkey)
if result.imports.present?
@user.update! pgp_fpr: result.imports.first.fpr
else
# TODO notify Sentry, user
raise "Failed to import OpenPGP pubkey"
end
end
LdapManager::UpdatePgpKey.call(dn: @user.dn, pubkey: @user.pgp_pubkey)
end
end
end

View File

@@ -1,2 +0,0 @@
class UserManagerService < ApplicationService
end

View File

@@ -16,10 +16,5 @@
key: :mastodon_address_domain, key: :mastodon_address_domain,
title: "User address domain" title: "User address domain"
) %> ) %>
<%= render FormElements::FieldsetResettableSettingComponent.new(
key: :mastodon_auth_token,
type: :password,
title: "API auth token"
) %>
<% end %> <% end %>
</ul> </ul>

View File

@@ -31,13 +31,28 @@
) %> ) %>
</ul> </ul>
</section> </section>
<section> <section>
<h3>Zaps</h3> <h3>Zaps</h3>
<ul role="list"> <ul role="list">
<%= render FormElements::FieldsetResettableSettingComponent.new( <%= render FormElements::FieldsetResettableSettingComponent.new(
key: :nostr_zaps_relay_limit, key: :nostr_zaps_relay_limit,
title: "Relay limit", title: "Relay limit",
description: "The maximum number of relays to publish zap receipts to" description: "The maximum number of sender-defined relays to try to publish zap receipts to"
) %> ) %>
</ul> </ul>
</section>
<section>
<h3>Onboarding</h3>
<ul role="list">
<%= render FormElements::FieldsetComponent.new(
title: "Discovery relays",
description: "Used to discover a user's published relay list and/or profile"
) do %>
<%= f.text_area :nostr_discovery_relays,
value: Setting.nostr_discovery_relays.join("\n"),
class: "h-44 w-80" %>
<% end %>
</ul>
<% end %> <% end %>

View File

@@ -89,75 +89,13 @@
</section> </section>
<section class="sm:flex-1 sm:pt-0"> <section class="sm:flex-1 sm:pt-0">
<h3>Avatar</h3> <% if @avatar.present? %>
<% if @user.avatar.attached? %> <h3>LDAP<h3>
<table class="divided"> <p>
<tbody> <img src="data:image/jpeg;base64,<%= @avatar %>" class="h-48 w-48" />
<tr> </p>
<th class="align-top">Image</th>
<td class="align-top">
<%= image_tag image_url_for(@user.avatar), class: "h-20 w-20 rounded-lg" %>
</td>
</tr>
<tr>
<th>Content type</th>
<td>
<%= @user.avatar.content_type %>
</td>
</tr>
<tr>
<th>Size</th>
<td>
<%= number_to_human_size(@user.avatar.blob.byte_size) %>
</td>
</tr>
</tbody>
</table>
<% else %>
<p class="text-gray-500">No avatar uploaded</p>
<% end %> <% end %>
<!-- <h3>Actions</h3> -->
<h3 class="mt-12">LDAP</h3>
<table class="divided">
<tbody>
<tr>
<th>Avatar</th>
<td>
<% if @ldap_avatar.present? %>
JPEG size: <%= number_to_human_size(@ldap_avatar.size) %>
<% else %>
&mdash;
<% end %>
</td>
</tr>
<tr>
<th>Display name</th>
<td><%= @user.display_name || "—" %></td>
</tr>
<tr>
<th class="align-top">PGP key</th>
<td class="align-top leading-5">
<% if @user.pgp_pubkey.present? %>
<span class="font-mono" title="<%= @user.pgp_fpr %>">
<% if @user.pgp_pubkey_contains_user_address? %>
<%= link_to wkd_key_url(hashed_username: @user.wkd_hash, l: @user.cn, format: :txt),
class: "ks-text-link", target: "_blank" do %>
<%= "#{@user.pgp_fpr[0, 8]}…#{@user.pgp_fpr[-8..-1]}" %>
<% end %>
<% else %>
<%= "#{@user.pgp_fpr[0, 8]}…#{@user.pgp_fpr[-8..-1]}" %>
<% end %>
</span><br />
<% @user.gnupg_key.uids.each do |uid| %>
<%= uid.uid %><br />
<% end %>
<% else %>
&mdash;
<% end %>
</td>
</tr>
</tbody>
</table>
</section> </section>
</div> </div>
@@ -304,7 +242,7 @@
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td><%= @user.lndhub_username %></td> <td><%= @user.ln_account %></td>
<td><%= number_with_delimiter @lndhub_user.balance %> sats</td> <td><%= number_with_delimiter @lndhub_user.balance %> sats</td>
<td><%= number_with_delimiter @lndhub_user.sum_incoming %> sats</td> <td><%= number_with_delimiter @lndhub_user.sum_incoming %> sats</td>
<td><%= number_with_delimiter @lndhub_user.sum_outgoing %> sats</td> <td><%= number_with_delimiter @lndhub_user.sum_outgoing %> sats</td>
@@ -313,7 +251,7 @@
</tbody> </tbody>
</table> </table>
<% else %> <% else %>
<p>No LndHub user found for account <strong class="font-mono"><%= @user.lndhub_username %></strong>. <p>No LndHub user found for account <strong class="font-mono"><%= @user.ln_account %></strong>.
<% end %> <% end %>
</section> </section>
<% end %> <% end %>

View File

@@ -14,9 +14,8 @@
<p class="mb-6"> <p class="mb-6">
In order to connect an app to your storage account, give it your address: In order to connect an app to your storage account, give it your address:
</p> </p>
<p data-controller="clipboard" class="flex gap-1 sm:w-2/5"> <p data-controller="clipboard" class="flex items-center gap-1 sm:w-2/5">
<img src="/img/logos/icon_remotestorage.svg" <img src="/img/logos/icon_remotestorage.svg" class="inline-block h-6 w-6 mr-1">
class="inline-block h-6 w-6 mr-1 self-center">
<input type="text" id="user_address" class="grow" <input type="text" id="user_address" class="grow"
value=<%= current_user.address %> disabled="disabled" value=<%= current_user.address %> disabled="disabled"
data-clipboard-target="source" /> data-clipboard-target="source" />

View File

@@ -1,6 +1,6 @@
<%= tag.section data: { <%= tag.section data: {
controller: "settings--account--email", controller: "settings--account--email",
"settings--account--email-validation-failed-value": @validation_errors&.[](:email)&.present? "settings--account--email-validation-failed-value": @validation_errors.present?
} do %> } do %>
<h3>E-Mail</h3> <h3>E-Mail</h3>
<%= form_for(@user, url: update_email_settings_path, method: "post") do |f| %> <%= form_for(@user, url: update_email_settings_path, method: "post") do |f| %>
@@ -23,7 +23,7 @@
</span> </span>
</button> </button>
</p> </p>
<% if @validation_errors&.[](:email)&.present? %> <% if @validation_errors.present? && @validation_errors[:email].present? %>
<p class="error-msg"><%= @validation_errors[:email].first %></p> <p class="error-msg"><%= @validation_errors[:email].first %></p>
<% end %> <% end %>
<div class="initial-hidden"> <div class="initial-hidden">
@@ -41,35 +41,10 @@
<% end %> <% end %>
<section> <section>
<h3>Password</h3> <h3>Password</h3>
<p class="mb-6">Use the following button to request an email with a password reset link:</p> <p class="mb-8">Use the following button to request an email with a password reset link:</p>
<%= form_with(url: reset_password_settings_path, method: :post) do %> <%= form_with(url: reset_password_settings_path, method: :post) do %>
<p> <p>
<%= submit_tag("Send me a password reset link", class: 'btn-md btn-gray w-full sm:w-auto') %> <%= submit_tag("Send me a password reset link", class: 'btn-md btn-gray w-full sm:w-auto') %>
</p> </p>
<% end %> <% end %>
</section> </section>
<%= form_for(@user, url: setting_path(:account), html: { :method => :put }) do |f| %>
<section class="!pt-8 sm:!pt-12">
<h3>OpenPGP</h3>
<ul role="list">
<% example_link = link_to "example", "https://accounts.kosmos.org/.well-known/openpgpkey/hu/yuca4ky39mhwkjo78qb8zjgbfj1hg3yf.txt?l=jimmy",
target: "_blank", class: "text-gray-500 underline" %>
<%= render FormElements::FieldsetComponent.new(
title: "Public key",
description: raw("Your OpenPGP public key in ASCII Armor format (#{example_link})")
) do %>
<%= f.text_area :pgp_pubkey,
value: @user.pgp_pubkey,
class: "h-24 w-full" %>
<% if @validation_errors&.[](:pgp_pubkey)&.present? %>
<p class="error-msg">This <%= @validation_errors[:pgp_pubkey].first %></p>
<% end %>
<% end %>
</ul>
</section>
<section>
<p class="pt-6 border-t border-gray-200 text-right">
<%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %>
</p>
</section>
<% end %>

View File

@@ -5,7 +5,7 @@
<h3>E-Mail Password</h3> <h3>E-Mail Password</h3>
<%= form_for(@user, url: reset_email_password_settings_path, method: "post") do |f| %> <%= form_for(@user, url: reset_email_password_settings_path, method: "post") do |f| %>
<%= hidden_field_tag :section, "email" %> <%= hidden_field_tag :section, "email" %>
<p class="mb-6"> <p class="mb-8">
Use the following button to generate a new email password: Use the following button to generate a new email password:
</p> </p>
<p class="hidden initial-visible"> <p class="hidden initial-visible">

View File

@@ -1,47 +1,43 @@
<section> <div data-controller="settings--nostr-pubkey"
<h3>Nostr</h3> data-settings--nostr-pubkey-user-address-value="<%= current_user.address %>"
<h4 class="mb-0">Public Key</h4> data-settings--nostr-pubkey-site-value="<%= Setting.accounts_domain %>"
<div data-controller="settings--nostr-pubkey" data-settings--nostr-pubkey-shared-secret-value="<%= session[:shared_secret] %>"
data-settings--nostr-pubkey-user-address-value="<%= current_user.address %>" data-settings--nostr-pubkey-pubkey-hex-value="<%= current_user.nostr_pubkey %>">
data-settings--nostr-pubkey-site-value="<%= Setting.accounts_domain %>" <section class="mb-8 sm:mb-12">
data-settings--nostr-pubkey-shared-secret-value="<%= session[:shared_secret] %>" <h3>Nostr</h3>
data-settings--nostr-pubkey-pubkey-hex-value="<%= current_user.nostr_pubkey %>"> <h4 class="mb-0">
Public Key
<p class="<%= current_user.nostr_pubkey.present? ? '' : 'hidden' %> mt-2 flex gap-1"> </h4>
<p class="<%= current_user.nostr_pubkey.present? ? '' : 'hidden' %> mt-2 flex gap-x-1">
<input type="text" value="<%= current_user.nostr_pubkey_bech32 %>" disabled <input type="text" value="<%= current_user.nostr_pubkey_bech32 %>" disabled
data-settings--nostr-pubkey-target="pubkeyBech32Input" data-settings--nostr-pubkey-target="pubkeyBech32Input"
name="nostr_public_key" class="relative grow" /> name="nostr_public_key" class="w-full" />
<%= link_to nostr_pubkey_settings_path, <%= link_to nostr_pubkey_settings_path,
class: 'btn-md btn-outline text-red-700 relative shrink-0', class: 'btn-md btn-outline relative grow-0 shrink-0 text-red-700',
data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } do %> data: { turbo_method: :delete, turbo_confirm: 'Are you sure?' } do %>
Remove Remove
<% end %> <% end %>
</p> </p>
<% if current_user.nostr_pubkey.present? %> <% if current_user.nostr_pubkey.present? %>
<div class="rounded-md bg-blue-50 p-4"> <!-- <div> -->
<div class="flex"> <!-- Pubkey present -->
<div class="flex-shrink-0"> <!-- </div> -->
<svg class="h-5 w-5 text-blue-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3 flex-1">
<p class="text-sm text-blue-800">
Your user address <strong><%= current_user.address %></strong> is
also a Nostr address now. Use your favorite Nostr app, or for
example <a href="http://metadata.nostr.com" target="_blank"
class="underline">metadata.nostr.com</a>, to add this
<strong>NIP-05</strong> address to your public profile.
</p>
</div>
</div>
</div>
<% else %> <% else %>
<p class="my-4"> <p class="my-4">
If you use any apps on the Nostr network, you can verify your public key Verify your Nostr public key with us in order to enable Nostr-specific
with us in order to enable Nostr-specific features for your account. features for your account:
</p> </p>
<ul class="list-disc list-inside">
<li>Log in with Nostr (no password needed)</li>
<li>Verified Nostr address</li>
<% if Setting.lndhub_enabled? %>
<li>Receive zaps in your Lightning account</li>
<% end %>
<% if Setting.nostr_relay_url.present? %>
<li>Publish notes on <%= link_to "our relay", Setting.nostr_relay_url_http, class: "ks-text-link", target: "_blank" %></li>
<% end %>
</ul>
<% end %> <% end %>
<div data-settings--nostr-pubkey-target="noExtension" <div data-settings--nostr-pubkey-target="noExtension"
@@ -58,8 +54,8 @@
</h3> </h3>
<div class="mt-2 mb-0 text-sm text-blue-800"> <div class="mt-2 mb-0 text-sm text-blue-800">
<p> <p>
We recommend Alby, which you can also use for your Lightning We recommend Alby, which you can also use a wallet for your
Wallet. Lightning account.
</p> </p>
</div> </div>
<div class="mt-4"> <div class="mt-4">
@@ -86,5 +82,11 @@
</button> </button>
</p> </p>
<% end %> <% end %>
</div> </section>
</section>
<% if current_user.nostr_pubkey.present? %>
<%= turbo_frame_tag "nostr_user_metadata", src: nostr_user_metadata_settings_path do %>
<p>Loading...</p>
<% end %>
<% end %>
</div>

View File

@@ -0,0 +1,27 @@
<%= turbo_frame_tag "nostr_user_metadata" do %>
<section>
<h3>Relays</h3>
<%= render Settings::NostrRelayStatusComponent.new(
nip65_event: @nip65_event
) %>
</section>
<section>
<h3>Profile</h3>
<%= render Settings::NostrProfileStatusComponent.new(
profile_event: @profile,
user_address: current_user.address
) %>
<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">
Edit profile
</button>
<%= render ModalComponent.new(show_close_button: false) do %>
<%= render Settings::NostrEditProfileComponent.new(
user: current_user,
profile_event: @profile
) %>
<% end %>
</div>
</section>
<% end %>

View File

@@ -20,7 +20,7 @@
</button> </button>
</p> </p>
<p class="text-sm text-gray-500"> <p class="text-sm text-gray-500">
Your account's address on the Internet Your user address for Chat and Lightning Network.
</p> </p>
</div> </div>
<%= form_for(@user, url: setting_path(:profile), html: { :method => :put }) do |f| %> <%= form_for(@user, url: setting_path(:profile), html: { :method => :put }) do |f| %>
@@ -31,19 +31,23 @@
<% end %> <% end %>
<% end %> <% end %>
<% if Flipper.enabled?(:avatar_upload, current_user) %>
<label class="block"> <label class="block">
<p class="font-bold mb-1">Avatar</p> <p class="font-bold mb-1">
<p class="text-gray-500">Default profile picture</p> Avatar
</p>
<p class="text-gray-500">
Default profile picture
</p>
<div class="flex items-center gap-6"> <div class="flex items-center gap-6">
<% if @user.avatar.attached? %> <% if current_user.avatar.present? %>
<p class="flex-none"> <p class="flex-none">
<%= image_tag image_url_for(@user.avatar), class: "h-24 w-24 rounded-lg" %> <%= image_tag "data:image/jpeg;base64,#{current_user.avatar}", class: "h-24 w-24 rounded-lg" %>
</p> </p>
<% end %> <% end %>
<div class="grow"> <div class="grow">
<p class="mb-2"> <p class="mb-2">
<%= f.file_field :avatar_new, accept: "image/jpeg,image/png" %> <%= f.file_field :avatar, class: "" %>
</p>
<p class="text-sm text-gray-500"> <p class="text-sm text-gray-500">
JPEG or PNG image, not larger than 1 megabyte JPEG or PNG image, not larger than 1 megabyte
</p> </p>
@@ -53,6 +57,7 @@
</div> </div>
</div> </div>
</label> </label>
<% end %>
<p class="mt-8 pt-6 border-t border-gray-200 text-right"> <p class="mt-8 pt-6 border-t border-gray-200 text-right">
<%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %> <%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %>

View File

@@ -19,12 +19,6 @@
active: @settings_section.to_s == "email" active: @settings_section.to_s == "email"
) %> ) %>
<% end %> <% end %>
<% if Setting.lndhub_enabled %>
<%= render SidenavLinkComponent.new(
name: "Lightning", path: setting_path(:lightning), icon: "zap",
active: @settings_section.to_s == "lightning"
) %>
<% end %>
<% if Setting.remotestorage_enabled? && <% if Setting.remotestorage_enabled? &&
Flipper.enabled?(:remotestorage, current_user) %> Flipper.enabled?(:remotestorage, current_user) %>
<%= render SidenavLinkComponent.new( <%= render SidenavLinkComponent.new(
@@ -32,6 +26,12 @@
active: @settings_section.to_s == "remotestorage" active: @settings_section.to_s == "remotestorage"
) %> ) %>
<% end %> <% end %>
<% if Setting.lndhub_enabled %>
<%= render SidenavLinkComponent.new(
name: "Lightning", path: setting_path(:lightning), icon: "zap",
active: @settings_section.to_s == "lightning"
) %>
<% end %>
<% if Setting.nostr_enabled %> <% if Setting.nostr_enabled %>
<%= render SidenavLinkComponent.new( <%= render SidenavLinkComponent.new(
name: "Nostr", path: setting_path(:nostr), icon: "nostrich-head", name: "Nostr", path: setting_path(:nostr), icon: "nostrich-head",

View File

@@ -0,0 +1,3 @@
<div>
<%= profile.inspect %>
</div>

View File

@@ -1,6 +0,0 @@
#!/usr/bin/env ruby
require_relative "../config/environment"
require "solid_queue/cli"
SolidQueue::Cli.start(ARGV)

View File

@@ -1,4 +1,9 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
APP_PATH = File.expand_path("../config/application", __dir__) begin
require_relative "../config/boot" load File.expand_path('../spring', __FILE__)
require "rails/commands" rescue LoadError => e
raise unless e.message.include?('spring')
end
APP_PATH = File.expand_path('../config/application', __dir__)
require_relative '../config/boot'
require 'rails/commands'

View File

@@ -1,4 +1,9 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
require_relative "../config/boot" begin
require "rake" load File.expand_path('../spring', __FILE__)
rescue LoadError => e
raise unless e.message.include?('spring')
end
require_relative '../config/boot'
require 'rake'
Rake.application.run Rake.application.run

View File

@@ -1,8 +0,0 @@
#!/usr/bin/env ruby
require "rubygems"
require "bundler/setup"
# explicit rubocop config increases performance slightly while avoiding config confusion.
ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__))
load Gem.bin_path("rubocop", "rubocop")

View File

@@ -1,34 +1,36 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
require "fileutils" require 'fileutils'
APP_ROOT = File.expand_path("..", __dir__) # path to your application root.
APP_ROOT = File.expand_path('..', __dir__)
def system!(*args) def system!(*args)
system(*args, exception: true) system(*args) || abort("\n== Command #{args} failed ==")
end end
FileUtils.chdir APP_ROOT do FileUtils.chdir APP_ROOT do
# This script is a way to set up or update your development environment automatically. # This script is a way to setup or update your development environment automatically.
# This script is idempotent, so that you can run it at any time and get an expectable outcome. # This script is idempotent, so that you can run it at anytime and get an expectable outcome.
# Add necessary setup steps to this file. # Add necessary setup steps to this file.
puts "== Installing dependencies ==" puts '== Installing dependencies =='
system("bundle check") || system!("bundle install") system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')
# Install JavaScript dependencies
# system('bin/yarn')
# puts "\n== Copying sample files ==" # puts "\n== Copying sample files =="
# unless File.exist?("config/database.yml") # unless File.exist?('config/database.yml')
# FileUtils.cp "config/database.yml.sample", "config/database.yml" # FileUtils.cp 'config/database.yml.sample', 'config/database.yml'
# end # end
puts "\n== Preparing database ==" puts "\n== Preparing database =="
system! "bin/rails db:prepare" system! 'bin/rails db:prepare'
puts "\n== Removing old logs and tempfiles ==" puts "\n== Removing old logs and tempfiles =="
system! "bin/rails log:clear tmp:clear" system! 'bin/rails log:clear tmp:clear'
unless ARGV.include?("--skip-server") puts "\n== Restarting application server =="
puts "\n== Starting development server ==" system! 'bin/rails restart'
STDOUT.flush # flush the output before exec(2) so that it displays
exec "bin/dev"
end
end end

View File

@@ -1,4 +1,4 @@
require_relative "boot" require_relative 'boot'
require "rails" require "rails"
# Pick the frameworks you want: # Pick the frameworks you want:
@@ -12,6 +12,7 @@ require "action_mailbox/engine"
# require "action_text/engine" # require "action_text/engine"
require "action_view/railtie" require "action_view/railtie"
require "action_cable/engine" require "action_cable/engine"
require "sprockets/railtie"
# require "rails/test_unit/railtie" # require "rails/test_unit/railtie"
# Require the gems listed in Gemfile, including any gems # Require the gems listed in Gemfile, including any gems
@@ -21,20 +22,12 @@ Bundler.require(*Rails.groups)
module Akkounts module Akkounts
class Application < Rails::Application class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version. # Initialize configuration defaults for originally generated Rails version.
config.load_defaults 8.0 config.load_defaults 7.0
# Please, add to the `ignore` list any other `lib` subdirectories that do # Settings in config/environments/* take precedence over those specified here.
# not contain `.rb` files, or that should not be reloaded or eager loaded. # Application configuration can go into files in config/initializers
# Common ones are `templates`, `generators`, or `middleware`, for example. # -- all .rb files in that directory are automatically loaded after loading
config.autoload_lib(ignore: %w[assets tasks]) # the framework and any gems in your application.
# Configuration for the application, engines, and railties goes here.
#
# These settings can be overridden in specific environments using the files
# in config/environments, which are processed later.
#
# config.time_zone = "Central Time (US & Canada)"
# config.eager_load_paths << Rails.root.join("extras")
# Don't generate system test files. # Don't generate system test files.
config.generators.system_tests = nil config.generators.system_tests = nil
@@ -47,15 +40,7 @@ module Akkounts
g.stylesheets false g.stylesheets false
end end
config.active_job.queue_adapter = :solid_queue config.active_job.queue_adapter = :sidekiq
config.mission_control.jobs.http_basic_auth_enabled = false
config.action_mailer.deliver_later_queue_name = nil # use "default" queue config.action_mailer.deliver_later_queue_name = nil # use "default" queue
# The default includes webp, which requires webp support everywhere
config.active_storage.web_image_content_types = %w[image/png image/jpeg image/gif]
config.active_record.encryption.primary_key = ENV["ENCRYPTION_PRIMARY_KEY"]
config.active_record.encryption.key_derivation_salt = ENV["ENCRYPTION_KEY_DERIVATION_SALT"]
end end
end end

View File

@@ -0,0 +1 @@
tmI5vm7qZhaigr52jEBVWkRdj+EE+9OmPh3vWXC7kA/OHuuucpr7SodychuMkQDPLM0BLk88LFsqvRIR+mqnLWpRC+P9aeUFE6ohxSWzcAd7Y4sgxUD8zpCRPndrwTw0hxXXj1WZSYeWn4BoAB34aV+gYen2MajZF3a95hJGtS5yjgWxvLVkQQKqRDfykkfX6fCS0BPo5X7sT7m4xwCATD/D4219wajm5W3TIdkriHtwt28ZLspaRWA5e0UkzKf8+/Gaj2CrW7UWcvew8R93zQ5RA2/Sp3sDTVN+kLz9I9Q095lQC0ywCAEFYHeKmc2tjrzqRaAAWu06xmWLqGIg21G+A/UU9lUJOkIpxQACWoOfS2IoXR1nXhgXMopkz3aCBXDxKw554v4H2QyOceOsuRf2C685ibMqzQkKMmJ4tcbiOJL77DUc08JTjB8Dq4Ohr8sMzXbV/hATevjYoRP0XarLekqhLv90ZLuIVY16DwB0CzACeNBKeKbeLqJF51upRRWgi+gTbYpV04yUwnXdyssF8mydWocgihrTryBi8F6PsuhBGcaYdP+0yibnGxDCC4x2rupbBfMj2OIX7pYzgtIHB3Eo954Y+bCoggqbE/Qrb9VVXNMgtKgLt8EGWU2tg6wl9QicitIq87uLDAade93zTn6rmcKPywjMDo6jbVIs653ZdUhiKdHGdpnJccbgQ/iLSPB1umNnCeaEX5jM+K9zBvl7ZMCdSk1YIQ==--ekKumqLiSlVJNwMe--K/ecXmmMT1x+WnIXMbHBDw==

View File

@@ -0,0 +1 @@
6b101c9addbfa5f959b5859f756bc9d7

View File

@@ -0,0 +1 @@
vqH5By5qFLImVjdlWj+7FwGg8APKnr/AEd7WqekG7L0vNA32WGBpwS1uGzs02LIcATRwGj8DyJxiBOB/w9z8cwoO+t6Woi5hAnOSCQwFWKLT0dZq7jgtT8pxK0Yu/Nf91PEFN1rc/8ZFy2KKVpbtMbMPyivT38e/ctBZD/lHrWkndvLXYvFVhqWjUnDOGbhwl/U0RZgqBBjvlm3B0JkQfiN8VXPlCJL2Cd8kd0+MpRCRTgtcxA==--OdVXnDP7OhzJxCsP--+8SI6IFIeXyDxXb+WpqhIQ==

View File

@@ -1,37 +1,21 @@
default: &default default: &default
adapter: <%= ENV["DB_ADAPTER"] || "sqlite3" %> adapter: sqlite3
pool: <%= ENV["DB_POOL"] || ENV['MAX_THREADS'] || 5 %> pool: <%= ENV["DB_POOL"] || ENV['MAX_THREADS'] || 5 %>
timeout: 5000 timeout: 5000
<% if ENV["DB_ADAPTER"] == "postgresql" %>
host: <%= ENV["PG_HOST"] || 'localhost' %>
port: <%= ENV["PG_PORT"] || 5432 %>
username: <%= ENV["PG_USERNAME"] || 'akkounts' %>
password: <%= ENV["PG_PASSWORD"] %>
<% end %>
<% if ENV["LNDHUB_PG_HOST"].present? %>
lndhub: &lndhub
adapter: postgresql
database_tasks: false
host: <%= ENV["LNDHUB_PG_HOST"] %>
port: <%= ENV["LNDHUB_PG_PORT"] || 5432 %>
database: <%= ENV["LNDHUB_PG_DATABASE"] || 'lndhub' %>
username: <%= ENV["LNDHUB_PG_USERNAME"] || 'lndhub' %>
password: <%= ENV["LNDHUB_PG_PASSWORD"] %>
<% end %>
development: development:
primary: primary:
<<: *default <<: *default
database: <%= ENV["DB_ADAPTER"] == "postgresql" ? ENV["PG_DATABASE"] : "db/development.sqlite3" %> database: db/development.sqlite3
queue:
<<: *default
database: <%= ENV["DB_ADAPTER"] == "postgresql" ? ENV["PG_DATABASE_QUEUE"] : "db/development_queue.sqlite3" %>
migrations_paths: db/queue_migrate
<% if ENV["LNDHUB_PG_HOST"].present? %>
lndhub: lndhub:
<<: *lndhub <<: *default
<% end %> adapter: postgresql
database_tasks: false
host: <%= ENV["LNDHUB_PG_HOST"] || 'localhost' %>
port: <%= ENV["LNDHUB_PG_PORT"] || 5432 %>
database: <%= ENV["LNDHUB_PG_DATABASE"] || 'lndhub' %>
username: <%= ENV["LNDHUB_PG_USERNAME"] || 'lndhub' %>
password: <%= ENV["LNDHUB_PG_PASSWORD"] %>
# Warning: The database defined as "test" will be erased and # Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake". # re-generated from your development database when you run "rake".
@@ -48,12 +32,18 @@ test:
production: production:
primary: primary:
<<: *default <<: *default
database: <%= ENV["DB_ADAPTER"] == "postgresql" ? ENV["PG_DATABASE"] : "db/production.sqlite3" %> adapter: postgresql
queue: database: akkounts
<<: *default port: 5432
database: <%= ENV["DB_ADAPTER"] == "postgresql" ? ENV["PG_DATABASE_QUEUE"] : "db/production_queue.sqlite3" %> host: <%= Rails.application.credentials.postgres[:host] rescue nil %>
migrations_paths: db/queue_migrate username: <%= Rails.application.credentials.postgres[:username] rescue nil %>
<% if ENV["LNDHUB_PG_HOST"].present? %> password: <%= Rails.application.credentials.postgres[:password] rescue nil %>
lndhub: lndhub:
<<: *lndhub <<: *default
<% end %> adapter: postgresql
database_tasks: false
host: <%= ENV["LNDHUB_PG_HOST"] || 'localhost' %>
port: <%= ENV["LNDHUB_PG_PORT"] || 5432 %>
database: <%= ENV["LNDHUB_PG_DATABASE"] || 'lndhub' %>
username: <%= ENV["LNDHUB_PG_USERNAME"] || 'lndhub' %>
password: <%= ENV["LNDHUB_PG_PASSWORD"] %>

View File

@@ -1,5 +1,5 @@
# Load the Rails application. # Load the Rails application.
require_relative "application" require_relative 'application'
# Initialize the Rails application. # Initialize the Rails application.
Rails.application.initialize! Rails.application.initialize!

View File

@@ -1,10 +1,10 @@
require "active_support/core_ext/integer/time"
Rails.application.configure do Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb. # Settings specified here will take precedence over those in config/application.rb.
# Make code changes take effect immediately without server restart. # In the development environment your application's code is reloaded on
config.enable_reloading = true # every request. This slows down response time but is perfect for development
# since you don't have to restart the web server when you make code changes.
config.cache_classes = false
# Do not eager load code on boot. # Do not eager load code on boot.
config.eager_load = false config.eager_load = false
@@ -12,15 +12,16 @@ Rails.application.configure do
# Show full error reports. # Show full error reports.
config.consider_all_requests_local = true config.consider_all_requests_local = true
# Enable server timing. # Enable/disable caching. By default caching is disabled.
config.server_timing = true # Run rails dev:cache to toggle caching.
if Rails.root.join('tmp', 'caching-dev.txt').exist?
# Enable/disable Action Controller caching. By default Action Controller caching is disabled.
# Run rails dev:cache to toggle Action Controller caching.
if Rails.root.join("tmp/caching-dev.txt").exist?
config.action_controller.perform_caching = true config.action_controller.perform_caching = true
config.action_controller.enable_fragment_cache_logging = true config.action_controller.enable_fragment_cache_logging = true
config.public_file_server.headers = { "cache-control" => "public, max-age=#{2.days.to_i}" }
config.cache_store = :memory_store
config.public_file_server.headers = {
'Cache-Control' => "public, max-age=#{2.days.to_i}"
}
else else
config.action_controller.perform_caching = false config.action_controller.perform_caching = false
@@ -30,63 +31,42 @@ Rails.application.configure do
# Don't care if the mailer can't send. # Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false config.action_mailer.raise_delivery_errors = false
# Make template changes take effect immediately.
config.action_mailer.perform_caching = false config.action_mailer.perform_caching = false
# Print deprecation notices to the Rails logger. # Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log config.active_support.deprecation = :log
# Raise exceptions for disallowed deprecations.
config.active_support.disallowed_deprecation = :raise
# Tell Active Support which deprecation messages to disallow.
config.active_support.disallowed_deprecation_warnings = []
# Raise an error on page load if there are pending migrations. # Raise an error on page load if there are pending migrations.
config.active_record.migration_error = :page_load config.active_record.migration_error = :page_load
# Highlight code that triggered database queries in logs. # Highlight code that triggered database queries in logs.
config.active_record.verbose_query_logs = true config.active_record.verbose_query_logs = true
# Append comments with runtime information tags to SQL queries in logs. # Debug mode disables concatenation and preprocessing of assets.
config.active_record.query_log_tags_enabled = true # This option may cause significant delays in view rendering with a large
# number of complex assets.
# Highlight code that enqueued background job in logs. config.assets.debug = true
config.active_job.verbose_enqueue_logs = true
# Solid Queue database
config.solid_queue.connects_to = { database: { writing: :queue } }
# Suppress logger output for asset requests. # Suppress logger output for asset requests.
# config.assets.quiet = true config.assets.quiet = true
# Raises error for missing translations. # Raises error for missing translations.
# config.i18n.raise_on_missing_translations = true # config.action_view.raise_on_missing_translations = true
# Annotate rendered view with file names. # Use an evented file watcher to asynchronously detect changes in source code,
config.action_view.annotate_rendered_view_with_filenames = true # routes, locales, etc. This feature depends on the listen gem.
config.file_watcher = ActiveSupport::EventedFileUpdateChecker
config.action_mailer.default_options = {
from: "accounts@localhost"
}
# Don't actually send emails, cache them for viewing via letter opener # Don't actually send emails, cache them for viewing via letter opener
config.action_mailer.delivery_method = :letter_opener config.action_mailer.delivery_method = :letter_opener
# Uncomment if you wish to allow Action Cable access from any origin. # Don't care if the mailer can't send
# config.action_cable.disable_request_forgery_protection = true
# Raise error when a before_action's only/except options reference missing actions.
config.action_controller.raise_on_missing_callback_actions = true
# Notice if the mailer can't send
config.action_mailer.raise_delivery_errors = true config.action_mailer.raise_delivery_errors = true
# Base URL to be used by email template link helpers # Base URL to be used by email template link helpers
config.action_mailer.default_url_options = { config.action_mailer.default_url_options = { host: "localhost:3000", protocol: "http" }
host: "localhost:3000", # TODO port: 3000
protocol: "http"
}
config.action_mailer.default_options = {
from: "accounts@localhost",
message_id: -> { "<#{Mail.random_tag}@localhost>" },
}
# Allow requests from any IP # Allow requests from any IP
config.web_console.permissions = '0.0.0.0/0' config.web_console.permissions = '0.0.0.0/0'

View File

@@ -1,61 +1,61 @@
require "active_support/core_ext/integer/time"
Rails.application.configure do Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb. # Settings specified here will take precedence over those in config/application.rb.
# Code is not reloaded between requests. # Code is not reloaded between requests.
config.enable_reloading = false config.cache_classes = true
# Eager load code on boot for better performance and memory savings (ignored by Rake tasks). # Eager load code on boot. This eager loads most of Rails and
# your application in memory, allowing both threaded web servers
# and those relying on copy on write to perform better.
# Rake tasks automatically ignore this option for performance.
config.eager_load = true config.eager_load = true
# Full error reports are disabled. # Full error reports are disabled and caching is turned on.
config.consider_all_requests_local = false config.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
# or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
# config.require_master_key = true
# Disable serving static files from the `/public` folder by default since # Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this. # Apache or NGINX already handles this.
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
# Turn on fragment caching in view templates. # Compress CSS using a preprocessor.
config.action_controller.perform_caching = true # config.assets.css_compressor = :sass
# Do not fallback to assets pipeline if a precompiled asset is missed. # Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false config.assets.compile = false
# Cache assets for far-future expiry since they are all digest stamped.
config.public_file_server.headers = { "cache-control" => "public, max-age=#{1.year.to_i}" }
# Enable serving of images, stylesheets, and JavaScripts from an asset server. # Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.asset_host = "http://assets.example.com" # config.action_controller.asset_host = 'http://assets.example.com'
# Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
# Assume all access to the app is happening through a SSL-terminating reverse proxy. # Mount Action Cable outside main process or domain.
# config.assume_ssl = true # config.action_cable.mount_path = nil
# config.action_cable.url = 'wss://example.com/cable'
# config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true # config.force_ssl = true
# Skip http-to-https redirect for the default health check endpoint. # Use the lowest log level to ensure availability of diagnostic information
# config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } } # when problems arise.
config.log_level = :debug
# Log to STDOUT with the current request id as a default log tag. # Prepend all log lines with the following tags.
config.log_tags = [ :request_id ] config.log_tags = [ :request_id ]
config.logger = ActiveSupport::TaggedLogging.logger(STDOUT)
# Change to "debug" to log everything (including potentially personally-identifiable information!) # Use a different cache store in production.
config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info")
# Prevent health checks from clogging up the logs.
config.silence_healthcheck_path = "/up"
# Don't log any deprecations.
config.active_support.report_deprecations = false
# Replace the default in-process memory cache store with a durable alternative.
# config.cache_store = :mem_cache_store # config.cache_store = :mem_cache_store
# Solid Queue database # Use a real queuing backend for Active Job (and separate queues per environment).
config.solid_queue.connects_to = { database: { writing: :queue } } # config.active_job.queue_adapter = :resque
# config.active_job.queue_name_prefix = "akkounts_production"
# E-mail settings, adapted from https://github.com/mastodon/mastodon # E-mail settings, adapted from https://github.com/mastodon/mastodon
@@ -63,7 +63,7 @@ Rails.application.configure do
outgoing_email_domain = Mail::Address.new(outgoing_email_address).domain outgoing_email_domain = Mail::Address.new(outgoing_email_address).domain
config.action_mailer.default_url_options = { config.action_mailer.default_url_options = {
host: ENV.fetch('AKKOUNTS_DOMAIN'), host: ENV['AKKOUNTS_DOMAIN'],
protocol: "https", protocol: "https",
} }
@@ -106,10 +106,6 @@ Rails.application.configure do
config.action_mailer.delivery_method = ENV.fetch('SMTP_DELIVERY_METHOD', 'smtp').to_sym config.action_mailer.delivery_method = ENV.fetch('SMTP_DELIVERY_METHOD', 'smtp').to_sym
# Disable caching for Action Mailer templates even if Action Controller
# caching is enabled.
config.action_mailer.perform_caching = false
# Ignore bad email addresses and do not raise email delivery errors. # Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors.
config.action_mailer.raise_delivery_errors = true config.action_mailer.raise_delivery_errors = true
@@ -124,18 +120,43 @@ Rails.application.configure do
# the I18n.default_locale when a translation cannot be found). # the I18n.default_locale when a translation cannot be found).
config.i18n.fallbacks = true config.i18n.fallbacks = true
# Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify
# Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new
# Use a different logger for distributed setups.
# require 'syslog/logger'
# config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
if ENV["RAILS_LOG_TO_STDOUT"].present?
logger = ActiveSupport::Logger.new(STDOUT)
logger.formatter = config.log_formatter
config.logger = ActiveSupport::TaggedLogging.new(logger)
end
# Do not dump schema after migrations. # Do not dump schema after migrations.
config.active_record.dump_schema_after_migration = false config.active_record.dump_schema_after_migration = false
# Only use :id for inspections in production. # Inserts middleware to perform automatic connection switching.
config.active_record.attributes_for_inspect = [ :id ] # The `database_selector` hash is used to pass options to the DatabaseSelector
# middleware. The `delay` is used to determine how long to wait after a write
# Enable DNS rebinding protection and other `Host` header attacks. # to send a subsequent read to the primary.
# config.hosts = [
# "example.com", # Allow requests from example.com
# /.*\.example\.com/ # Allow requests from subdomains like `www.example.com`
# ]
# #
# Skip DNS rebinding protection for the default health check endpoint. # The `database_resolver` class is used by the middleware to determine which
# config.host_authorization = { exclude: ->(request) { request.path == "/up" } } # database is appropriate to use based on the time delay.
#
# The `database_resolver_context` class is used by the middleware to set
# timestamps for the last write to the primary. The resolver uses the context
# class timestamps to determine how long to wait before reading from the
# replica.
#
# By default Rails will store a last write timestamp in the session. The
# DatabaseSelector middleware is designed as such you can define your own
# strategy for connection switching and pass that into the middleware through
# these configuration options.
# config.active_record.database_selector = { delay: 2.seconds }
# config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
# config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
end end

View File

@@ -6,33 +6,31 @@
Rails.application.configure do Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb. # Settings specified here will take precedence over those in config/application.rb.
# While tests run files are not watched, reloading is not necessary. config.cache_classes = false
config.enable_reloading = false config.action_view.cache_template_loading = true
# Eager loading loads your entire application. When running a single test locally, # Do not eager load code on boot. This avoids loading your whole application
# this is usually not necessary, and can slow down your test suite. However, it's # just for the purpose of running a single test. If you are using a tool that
# recommended that you enable it in continuous integration systems to ensure eager # preloads Rails for running tests, you may have to set it to true.
# loading is working properly before deploying your code. config.eager_load = false
config.eager_load = ENV["CI"].present?
# Configure public file server for tests with cache-control for performance. # Configure public file server for tests with Cache-Control for performance.
config.public_file_server.headers = { "cache-control" => "public, max-age=3600" } config.public_file_server.enabled = true
config.public_file_server.headers = {
'Cache-Control' => "public, max-age=#{1.hour.to_i}"
}
# Show full error reports. # Show full error reports and disable caching.
config.consider_all_requests_local = true config.consider_all_requests_local = true
config.action_controller.perform_caching = false config.action_controller.perform_caching = false
config.cache_store = :null_store config.cache_store = :null_store
# Render exception templates for rescuable exceptions and raise for other exceptions. # Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = :rescuable config.action_dispatch.show_exceptions = :none
# Disable request forgery protection in test environment. # Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false config.action_controller.allow_forgery_protection = false
config.active_job.queue_adapter = :test
# Disable caching for Action Mailer templates even if Action Controller
# caching is enabled.
config.action_mailer.perform_caching = false config.action_mailer.perform_caching = false
# Tell Action Mailer not to deliver emails to the real world. # Tell Action Mailer not to deliver emails to the real world.
@@ -40,28 +38,23 @@ Rails.application.configure do
# ActionMailer::Base.deliveries array. # ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test config.action_mailer.delivery_method = :test
config.action_mailer.default_options = { # Print deprecation notices to the stderr.
from: "accounts@kosmos.org", config.active_support.deprecation = :stderr
message_id: -> { "<#{Mail.random_tag}@kosmos.org>" },
} # Raises error for missing translations.
# config.action_view.raise_on_missing_translations = true
config.action_mailer.default_url_options = { config.action_mailer.default_url_options = {
host: "accounts.kosmos.org", host: "accounts.kosmos.org",
protocol: "https" protocol: "https",
from: "accounts@kosmos.org"
} }
# Raises error for missing translations. config.active_job.queue_adapter = :test
# config.i18n.raise_on_missing_translations = true
# Annotate rendered view with file names.
# config.action_view.annotate_rendered_view_with_filenames = true
if ENV["S3_ENABLED"] && ENV["S3_ENABLED"].to_s != "false" if ENV["S3_ENABLED"] && ENV["S3_ENABLED"].to_s != "false"
config.active_storage.service = :s3 config.active_storage.service = :s3
else else
config.active_storage.service = :local config.active_storage.service = :local
end end
# Raise error when a before_action's only/except options reference missing actions.
config.action_controller.raise_on_missing_callback_actions = true
end end

View File

@@ -1,9 +1,14 @@
# Be sure to restart your server when you modify this file. # Be sure to restart your server when you modify this file.
# Version of your assets, change this if you want to expire all your assets. # Version of your assets, change this if you want to expire all your assets.
Rails.application.config.assets.version = "1.0" Rails.application.config.assets.version = '1.0'
# Add additional assets to the asset load path. # Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path # Rails.application.config.assets.paths << Emoji.images_path
# Add Yarn node_modules folder to the asset load path. # Add Yarn node_modules folder to the asset load path.
Rails.application.config.assets.paths << Rails.root.join('node_modules') Rails.application.config.assets.paths << Rails.root.join('node_modules')
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
# Rails.application.config.assets.precompile += %w( admin.js admin.css )

Some files were not shown because too many files have changed in this diff Show More