mirror of
https://github.com/neocities/neocities.git
synced 2025-04-24 17:22:35 +02:00
Merge branch 'master' into apng
This commit is contained in:
commit
a1d32b67a3
80 changed files with 1381 additions and 1450 deletions
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -36,6 +36,6 @@ jobs:
|
|||
- run: sudo apt-get update && sudo apt-get -y install libimlib2-dev chromium-browser
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 3.1
|
||||
ruby-version: '3.3'
|
||||
bundler-cache: true
|
||||
- run: bundle exec rake
|
||||
|
|
15
Gemfile
15
Gemfile
|
@ -7,15 +7,14 @@ gem 'redis-namespace'
|
|||
gem 'bcrypt'
|
||||
gem 'sinatra-flash', require: 'sinatra/flash'
|
||||
gem 'sinatra-xsendfile', require: 'sinatra/xsendfile'
|
||||
gem 'puma', '5.6.5', require: nil
|
||||
gem 'sidekiq', '~> 7.0.8'
|
||||
gem 'puma', '< 7', require: nil
|
||||
gem 'sidekiq', '~> 7'
|
||||
gem 'mail'
|
||||
gem 'net-smtp'
|
||||
gem 'tilt'
|
||||
gem 'erubis'
|
||||
gem 'stripe' #, source: 'https://code.stripe.com/'
|
||||
gem 'terrapin'
|
||||
gem 'zipruby'
|
||||
gem 'sass', require: nil
|
||||
gem 'dav4rack', git: 'https://github.com/neocities/dav4rack.git', ref: '3ecde122a0b8bcc1d85581dc85ef3a7120b6a8f0'
|
||||
gem 'filesize'
|
||||
|
@ -27,7 +26,6 @@ gem 'paypal-recurring', require: 'paypal/recurring'
|
|||
gem 'geoip'
|
||||
gem 'io-extra', require: 'io/extra'
|
||||
#gem 'rye'
|
||||
gem 'base32'
|
||||
gem 'coveralls_reborn', require: false
|
||||
gem 'sanitize'
|
||||
gem 'will_paginate'
|
||||
|
@ -58,6 +56,13 @@ gem 'rss'
|
|||
gem 'webp-ffi'
|
||||
gem 'rszr'
|
||||
gem 'zip_tricks'
|
||||
gem 'adequate_crypto_address'
|
||||
gem 'twilio-ruby'
|
||||
gem 'phonelib'
|
||||
gem 'dnsbl-client'
|
||||
gem 'minfraud'
|
||||
gem 'image_optimizer' # apt install optipng jpegoptim pngquant
|
||||
gem 'rubyzip', require: 'zip'
|
||||
|
||||
group :development, :test do
|
||||
gem 'pry'
|
||||
|
@ -77,6 +82,7 @@ group :test do
|
|||
gem 'mocha', require: nil
|
||||
gem 'rake', '>= 12.3.3', require: nil
|
||||
gem 'capybara', require: nil #, '2.10.1', require: nil
|
||||
gem 'selenium-webdriver'
|
||||
gem 'rack_session_access', require: nil
|
||||
gem 'webmock', require: nil
|
||||
gem 'stripe-ruby-mock', '~> 3.1.0.rc3', require: 'stripe_mock'
|
||||
|
@ -84,5 +90,4 @@ group :test do
|
|||
gem 'mock_redis'
|
||||
gem 'simplecov', require: nil
|
||||
gem 'm'
|
||||
gem 'apparition', github: 'twalpole/apparition', ref: 'ca86be4d54af835d531dbcd2b86e7b2c77f85f34'
|
||||
end
|
||||
|
|
253
Gemfile.lock
253
Gemfile.lock
|
@ -10,33 +10,34 @@ GIT
|
|||
rack (>= 1.6)
|
||||
uuidtools (~> 2.1.1)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/twalpole/apparition.git
|
||||
revision: ca86be4d54af835d531dbcd2b86e7b2c77f85f34
|
||||
ref: ca86be4d54af835d531dbcd2b86e7b2c77f85f34
|
||||
specs:
|
||||
apparition (0.6.0)
|
||||
capybara (~> 3.13, < 4)
|
||||
websocket-driver (>= 0.6.5)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
acme-client (2.0.11)
|
||||
acme-client (2.0.15)
|
||||
faraday (>= 1.0, < 3.0.0)
|
||||
faraday-retry (~> 1.0)
|
||||
activesupport (7.0.4.3)
|
||||
faraday-retry (>= 1.0, < 3.0.0)
|
||||
activesupport (7.1.2)
|
||||
base64
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
connection_pool (>= 2.2.5)
|
||||
drb
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
mutex_m
|
||||
tzinfo (~> 2.0)
|
||||
addressable (2.8.1)
|
||||
addressable (2.8.6)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
adequate_crypto_address (0.1.9)
|
||||
base58 (~> 0.2)
|
||||
keccak (~> 1.3)
|
||||
ansi (1.5.0)
|
||||
base32 (0.3.4)
|
||||
bcrypt (3.1.18)
|
||||
base58 (0.2.3)
|
||||
base64 (0.2.0)
|
||||
bcrypt (3.1.20)
|
||||
bigdecimal (3.1.5)
|
||||
builder (3.2.4)
|
||||
capybara (3.38.0)
|
||||
capybara (3.39.2)
|
||||
addressable
|
||||
matrix
|
||||
mini_mime (>= 0.1.3)
|
||||
|
@ -46,31 +47,34 @@ GEM
|
|||
regexp_parser (>= 1.5, < 3.0)
|
||||
xpath (~> 3.2)
|
||||
certified (1.0.0)
|
||||
climate_control (0.2.0)
|
||||
climate_control (1.2.0)
|
||||
coderay (1.1.3)
|
||||
concurrent-ruby (1.2.2)
|
||||
connection_pool (2.4.0)
|
||||
coveralls_reborn (0.25.0)
|
||||
simplecov (>= 0.18.1, < 0.22.0)
|
||||
term-ansicolor (~> 1.6)
|
||||
thor (>= 0.20.3, < 2.0)
|
||||
tins (~> 1.16)
|
||||
connection_pool (2.4.1)
|
||||
coveralls_reborn (0.28.0)
|
||||
simplecov (~> 0.22.0)
|
||||
term-ansicolor (~> 1.7)
|
||||
thor (~> 1.2)
|
||||
tins (~> 1.32)
|
||||
crack (0.4.5)
|
||||
rexml
|
||||
crass (1.0.6)
|
||||
dante (0.2.0)
|
||||
date (3.3.4)
|
||||
dnsbl-client (1.1.1)
|
||||
docile (1.4.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
domain_name (0.6.20231109)
|
||||
drb (2.2.0)
|
||||
ruby2_keywords
|
||||
erubis (2.7.0)
|
||||
exifr (1.3.10)
|
||||
fabrication (2.30.0)
|
||||
facter (4.2.13)
|
||||
exifr (1.4.0)
|
||||
fabrication (2.31.0)
|
||||
facter (4.5.1)
|
||||
hocon (~> 1.3)
|
||||
thor (>= 1.0.1, < 2.0)
|
||||
faker (3.0.0)
|
||||
faker (3.2.2)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (1.10.2)
|
||||
faraday (1.10.3)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
|
@ -100,7 +104,7 @@ GEM
|
|||
faraday_middleware (>= 0.9)
|
||||
loofah (>= 2.0)
|
||||
sax-machine (>= 1.0)
|
||||
ffi (1.15.5)
|
||||
ffi (1.16.3)
|
||||
ffi-compiler (1.0.1)
|
||||
ffi (>= 1.0.0)
|
||||
rake
|
||||
|
@ -110,14 +114,14 @@ GEM
|
|||
hashie
|
||||
xmlrpc
|
||||
geoip (1.6.4)
|
||||
hashdiff (1.0.1)
|
||||
hashdiff (1.1.0)
|
||||
hashie (5.0.0)
|
||||
hiredis (0.6.3)
|
||||
hocon (1.3.1)
|
||||
hoe (3.26.0)
|
||||
hocon (1.4.0)
|
||||
hoe (4.1.0)
|
||||
rake (>= 0.8, < 15.0)
|
||||
htmlentities (4.3.4)
|
||||
http (5.1.0)
|
||||
http (5.1.1)
|
||||
addressable (~> 2.8)
|
||||
http-cookie (~> 1.0)
|
||||
http-form_data (~> 2.2)
|
||||
|
@ -126,113 +130,137 @@ GEM
|
|||
http-cookie (1.0.5)
|
||||
domain_name (~> 0.5)
|
||||
http-form_data (2.3.0)
|
||||
i18n (1.12.0)
|
||||
i18n (1.14.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
image_optim (0.31.1)
|
||||
image_optim (0.31.3)
|
||||
exifr (~> 1.2, >= 1.2.2)
|
||||
fspath (~> 3.0)
|
||||
image_size (>= 1.5, < 4)
|
||||
in_threads (~> 1.3)
|
||||
progress (~> 3.0, >= 3.0.1)
|
||||
image_optim_pack (0.9.1.20221104-x86_64-linux)
|
||||
image_optim_pack (0.10.1-x86_64-linux)
|
||||
fspath (>= 2.1, < 4)
|
||||
image_optim (~> 0.19)
|
||||
image_size (3.2.0)
|
||||
image_optimizer (1.9.0)
|
||||
image_size (3.3.0)
|
||||
in_threads (1.6.0)
|
||||
io-extra (1.4.0)
|
||||
ipaddress (0.8.3)
|
||||
json (2.6.2)
|
||||
json (2.7.1)
|
||||
jwt (2.7.1)
|
||||
keccak (1.3.1)
|
||||
llhttp-ffi (0.4.0)
|
||||
ffi-compiler (~> 1.0)
|
||||
rake (~> 13.0)
|
||||
loofah (2.19.1)
|
||||
loofah (2.22.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
m (1.6.0)
|
||||
nokogiri (>= 1.12.0)
|
||||
m (1.6.2)
|
||||
method_source (>= 0.6.7)
|
||||
rake (>= 0.9.2.2)
|
||||
magic (0.2.9)
|
||||
ffi (>= 0.6.3)
|
||||
mail (2.7.1)
|
||||
mail (2.8.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
matrix (0.4.2)
|
||||
maxmind-db (1.1.1)
|
||||
maxmind-db (1.2.0)
|
||||
maxmind-geoip2 (1.2.0)
|
||||
connection_pool (~> 2.2)
|
||||
http (>= 4.3, < 6.0)
|
||||
maxmind-db (~> 1.2)
|
||||
method_source (1.0.0)
|
||||
mime-types (3.4.1)
|
||||
mime-types (3.5.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2022.0105)
|
||||
mini_mime (1.1.2)
|
||||
minitest (5.16.3)
|
||||
minitest-reporters (1.5.0)
|
||||
mime-types-data (3.2023.1205)
|
||||
minfraud (2.3.0)
|
||||
connection_pool (~> 2.2)
|
||||
http (>= 4.3, < 6.0)
|
||||
maxmind-geoip2 (~> 1.2)
|
||||
simpleidn (~> 0.1, >= 0.1.1)
|
||||
mini_mime (1.1.5)
|
||||
minitest (5.20.0)
|
||||
minitest-reporters (1.6.1)
|
||||
ansi
|
||||
builder
|
||||
minitest (>= 5.0)
|
||||
ruby-progressbar
|
||||
mocha (2.0.1)
|
||||
mocha (2.1.0)
|
||||
ruby2_keywords (>= 0.0.5)
|
||||
mock_redis (0.34.0)
|
||||
ruby2_keywords
|
||||
mock_redis (0.41.0)
|
||||
monetize (1.12.0)
|
||||
money (~> 6.12)
|
||||
money (6.16.0)
|
||||
i18n (>= 0.6.4, <= 2)
|
||||
msgpack (1.6.0)
|
||||
msgpack (1.7.2)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.2.3)
|
||||
multipart-post (2.3.0)
|
||||
mustermann (3.0.0)
|
||||
ruby2_keywords (~> 0.0.1)
|
||||
net-protocol (0.1.3)
|
||||
mutex_m (0.2.0)
|
||||
net-imap (0.4.9)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
net-protocol
|
||||
net-protocol (0.2.2)
|
||||
timeout
|
||||
net-smtp (0.3.3)
|
||||
net-smtp (0.4.0)
|
||||
net-protocol
|
||||
netrc (0.11.0)
|
||||
nio4r (2.5.8)
|
||||
nokogiri (1.14.3-x86_64-linux)
|
||||
nio4r (2.7.0)
|
||||
nokogiri (1.16.0-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
ox (2.14.11)
|
||||
ox (2.14.17)
|
||||
paypal-recurring (1.1.0)
|
||||
pg (1.5.3)
|
||||
pg (1.5.4)
|
||||
phonelib (0.8.6)
|
||||
progress (3.6.0)
|
||||
pry (0.14.1)
|
||||
pry (0.14.2)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
public_suffix (5.0.0)
|
||||
puma (5.6.5)
|
||||
public_suffix (5.0.4)
|
||||
puma (6.4.0)
|
||||
nio4r (~> 2.0)
|
||||
racc (1.7.1)
|
||||
rack (2.2.6.4)
|
||||
rack-cache (1.13.0)
|
||||
racc (1.7.3)
|
||||
rack (2.2.8)
|
||||
rack-cache (1.15.0)
|
||||
rack (>= 0.4)
|
||||
rack-protection (3.0.4)
|
||||
rack
|
||||
rack-test (2.0.2)
|
||||
rack-protection (3.2.0)
|
||||
base64 (>= 0.1.0)
|
||||
rack (~> 2.2, >= 2.2.4)
|
||||
rack-test (2.1.0)
|
||||
rack (>= 1.3)
|
||||
rack_session_access (0.2.0)
|
||||
builder (>= 2.0.0)
|
||||
rack (>= 1.0.0)
|
||||
rake (13.0.6)
|
||||
rake (13.1.0)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
redis (4.5.1)
|
||||
redis-client (0.14.1)
|
||||
redis (5.0.8)
|
||||
redis-client (>= 0.17.0)
|
||||
redis-client (0.19.1)
|
||||
connection_pool
|
||||
redis-namespace (1.9.0)
|
||||
redis-namespace (1.11.0)
|
||||
redis (>= 4)
|
||||
regexp_parser (2.6.0)
|
||||
regexp_parser (2.8.3)
|
||||
rest-client (2.1.0)
|
||||
http-accept (>= 1.7.0, < 2.0)
|
||||
http-cookie (>= 1.0.2, < 2.0)
|
||||
mime-types (>= 1.16, < 4.0)
|
||||
netrc (~> 0.8)
|
||||
rexml (3.2.5)
|
||||
rexml (3.2.6)
|
||||
rinku (2.0.6)
|
||||
rss (0.2.9)
|
||||
rss (0.3.0)
|
||||
rexml
|
||||
rszr (1.3.0)
|
||||
ruby-progressbar (1.11.0)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
sanitize (6.0.2)
|
||||
rubyzip (2.3.2)
|
||||
sanitize (6.1.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
sass (3.7.4)
|
||||
|
@ -241,18 +269,23 @@ GEM
|
|||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
sax-machine (1.3.2)
|
||||
sequel (5.68.0)
|
||||
selenium-webdriver (4.16.0)
|
||||
rexml (~> 3.2, >= 3.2.5)
|
||||
rubyzip (>= 1.2.2, < 3.0)
|
||||
websocket (~> 1.0)
|
||||
sequel (5.75.0)
|
||||
bigdecimal
|
||||
sequel_pg (1.17.1)
|
||||
pg (>= 0.18.0, != 1.2.0)
|
||||
sequel (>= 4.38.0)
|
||||
shotgun (0.9.2)
|
||||
rack (>= 1.0)
|
||||
sidekiq (7.0.8)
|
||||
sidekiq (7.2.0)
|
||||
concurrent-ruby (< 2)
|
||||
connection_pool (>= 2.3.0)
|
||||
rack (>= 2.2.4)
|
||||
redis-client (>= 0.11.0)
|
||||
simplecov (0.21.2)
|
||||
redis-client (>= 0.14.0)
|
||||
simplecov (0.22.0)
|
||||
docile (~> 1.1)
|
||||
simplecov-html (~> 0.11)
|
||||
simplecov_json_formatter (~> 0.1)
|
||||
|
@ -260,10 +293,10 @@ GEM
|
|||
simplecov_json_formatter (0.1.4)
|
||||
simpleidn (0.2.1)
|
||||
unf (~> 0.1.4)
|
||||
sinatra (3.0.4)
|
||||
sinatra (3.2.0)
|
||||
mustermann (~> 3.0)
|
||||
rack (~> 2.2, >= 2.2.4)
|
||||
rack-protection (= 3.0.4)
|
||||
rack-protection (= 3.2.0)
|
||||
tilt (~> 2.0)
|
||||
sinatra-flash (0.3.0)
|
||||
sinatra (>= 1.0.0)
|
||||
|
@ -277,39 +310,40 @@ GEM
|
|||
sync (0.5.0)
|
||||
term-ansicolor (1.7.1)
|
||||
tins (~> 1.0)
|
||||
terrapin (0.6.0)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
thor (1.2.1)
|
||||
terrapin (1.0.1)
|
||||
climate_control
|
||||
thor (1.3.0)
|
||||
thread (0.2.2)
|
||||
tilt (2.0.11)
|
||||
timecop (0.9.5)
|
||||
timeout (0.3.0)
|
||||
tins (1.31.1)
|
||||
tilt (2.3.0)
|
||||
timecop (0.9.8)
|
||||
timeout (0.4.1)
|
||||
tins (1.32.1)
|
||||
sync
|
||||
twilio-ruby (6.9.0)
|
||||
faraday (>= 0.9, < 3.0)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
nokogiri (>= 1.6, < 2.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8.2)
|
||||
unf_ext (0.0.9.1)
|
||||
uuidtools (2.1.5)
|
||||
webmock (3.18.1)
|
||||
webmock (3.19.1)
|
||||
addressable (>= 2.8.0)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
webp-ffi (0.3.1)
|
||||
webp-ffi (0.4.0)
|
||||
ffi (>= 1.9.0)
|
||||
ffi-compiler (>= 0.1.2)
|
||||
webrick (1.7.0)
|
||||
websocket-driver (0.7.5)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
will_paginate (3.3.1)
|
||||
xmlrpc (0.3.2)
|
||||
webrick (1.8.1)
|
||||
websocket (1.2.10)
|
||||
will_paginate (4.0.0)
|
||||
xmlrpc (0.3.3)
|
||||
webrick
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
zip_tricks (5.6.0)
|
||||
zipruby (0.3.6)
|
||||
|
||||
PLATFORMS
|
||||
x86_64-linux
|
||||
|
@ -318,13 +352,13 @@ DEPENDENCIES
|
|||
acme-client (~> 2.0.0)
|
||||
activesupport
|
||||
addressable (>= 2.8.0)
|
||||
apparition!
|
||||
base32
|
||||
adequate_crypto_address
|
||||
bcrypt
|
||||
capybara
|
||||
certified
|
||||
coveralls_reborn
|
||||
dav4rack!
|
||||
dnsbl-client
|
||||
erubis
|
||||
fabrication
|
||||
facter
|
||||
|
@ -339,6 +373,7 @@ DEPENDENCIES
|
|||
http
|
||||
image_optim
|
||||
image_optim_pack
|
||||
image_optimizer
|
||||
io-extra
|
||||
ipaddress
|
||||
json (>= 2.3.0)
|
||||
|
@ -346,6 +381,7 @@ DEPENDENCIES
|
|||
magic
|
||||
mail
|
||||
maxmind-db
|
||||
minfraud
|
||||
minitest
|
||||
minitest-reporters
|
||||
mocha
|
||||
|
@ -356,8 +392,9 @@ DEPENDENCIES
|
|||
nokogiri
|
||||
paypal-recurring
|
||||
pg
|
||||
phonelib
|
||||
pry
|
||||
puma (= 5.6.5)
|
||||
puma (< 7)
|
||||
rack-cache
|
||||
rack-test
|
||||
rack_session_access
|
||||
|
@ -368,12 +405,14 @@ DEPENDENCIES
|
|||
rinku
|
||||
rss
|
||||
rszr
|
||||
rubyzip
|
||||
sanitize
|
||||
sass
|
||||
selenium-webdriver
|
||||
sequel
|
||||
sequel_pg
|
||||
shotgun
|
||||
sidekiq (~> 7.0.8)
|
||||
sidekiq (~> 7)
|
||||
simplecov
|
||||
simpleidn
|
||||
sinatra
|
||||
|
@ -385,12 +424,12 @@ DEPENDENCIES
|
|||
thread
|
||||
tilt
|
||||
timecop
|
||||
twilio-ruby
|
||||
webmock
|
||||
webp-ffi
|
||||
will_paginate
|
||||
xmlrpc
|
||||
zip_tricks
|
||||
zipruby
|
||||
|
||||
BUNDLED WITH
|
||||
2.3.10
|
||||
|
|
390
Rakefile
390
Rakefile
|
@ -14,22 +14,6 @@ end
|
|||
|
||||
task :default => :test
|
||||
|
||||
=begin
|
||||
desc "send domain update email"
|
||||
task :send_domain_update_email => [:environment] do
|
||||
Site.exclude(domain: nil).exclude(domain: '').all.each do |site|
|
||||
msg = <<-HERE
|
||||
MESSAGE GOES HERE TEST
|
||||
HERE
|
||||
|
||||
site.send_email(
|
||||
subject: 'SUBJECT GOES HERE',
|
||||
body: msg
|
||||
)
|
||||
end
|
||||
end
|
||||
=end
|
||||
|
||||
desc "prune logs"
|
||||
task :prune_logs => [:environment] do
|
||||
Stat.prune!
|
||||
|
@ -45,7 +29,8 @@ end
|
|||
|
||||
desc 'Update disposable email blacklist'
|
||||
task :update_disposable_email_blacklist => [:environment] do
|
||||
uri = URI.parse('https://raw.githubusercontent.com/martenson/disposable-email-domains/master/disposable_email_blocklist.conf')
|
||||
# Formerly: https://raw.githubusercontent.com/martenson/disposable-email-domains/master/disposable_email_blocklist.conf
|
||||
uri = URI.parse('https://raw.githubusercontent.com/disposable/disposable-email-domains/master/domains.txt')
|
||||
|
||||
File.write(Site::DISPOSABLE_EMAIL_BLACKLIST_PATH, Net::HTTP.get(uri))
|
||||
end
|
||||
|
@ -54,196 +39,33 @@ desc 'Update banned IPs list'
|
|||
task :update_blocked_ips => [:environment] do
|
||||
|
||||
filename = 'listed_ip_365_ipv46'
|
||||
zip_path = "/tmp/#{filename}.zip"
|
||||
|
||||
File.open("/tmp/#{filename}.zip", 'wb') do |file|
|
||||
File.open(zip_path, 'wb') do |file|
|
||||
response = HTTP.get "https://www.stopforumspam.com/downloads/#{filename}.zip"
|
||||
response.body.each do |chunk|
|
||||
file.write chunk
|
||||
end
|
||||
end
|
||||
|
||||
Zip::Archive.open("/tmp/#{filename}.zip") do |ar|
|
||||
ar.fopen("#{filename}.txt") do |f|
|
||||
ips = f.read
|
||||
insert_hashes = []
|
||||
ips.each_line {|ip| insert_hashes << {ip: ip.strip, created_at: Time.now}}
|
||||
ips = nil
|
||||
Zip::File.open(zip_path) do |zip_file|
|
||||
zip_file.each do |entry|
|
||||
if entry.name == "#{filename}.txt"
|
||||
ips = entry.get_input_stream.read
|
||||
insert_hashes = []
|
||||
ips.each_line { |ip| insert_hashes << { ip: ip.strip, created_at: Time.now } }
|
||||
ips = nil
|
||||
|
||||
DB.transaction do
|
||||
DB[:blocked_ips].delete
|
||||
DB[:blocked_ips].multi_insert insert_hashes
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
FileUtils.rm "/tmp/#{filename}.zip"
|
||||
end
|
||||
|
||||
desc 'parse tor exits'
|
||||
task :parse_tor_exits => [:environment] do
|
||||
exit_ips = Net::HTTP.get(URI.parse('https://check.torproject.org/exit-addresses'))
|
||||
|
||||
exit_ips.split("\n").collect {|line|
|
||||
line.match(/ExitAddress (\d+\.\d+\.\d+\.\d+)/)&.captures&.first
|
||||
}.compact
|
||||
|
||||
# ^^ Array of ip addresses of known exit nodes
|
||||
end
|
||||
|
||||
desc 'Compile nginx mapfiles'
|
||||
task :compile_nginx_mapfiles => [:environment] do
|
||||
FileUtils.mkdir_p './files/maps'
|
||||
|
||||
File.open('./files/maps/domains.txt', 'w') do |file|
|
||||
Site.exclude(domain: nil).exclude(domain: '').select(:username,:domain).all.each do |site|
|
||||
file.write ".#{site.values[:domain]} #{site.username};\n"
|
||||
end
|
||||
end
|
||||
|
||||
File.open('./files/maps/supporters.txt', 'w') do |file|
|
||||
Site.select(:username, :domain).exclude(plan_type: 'free').exclude(plan_type: nil).all.each do |parent_site|
|
||||
sites = [parent_site] + parent_site.children
|
||||
sites.each do |site|
|
||||
file.write "#{site.username}.neocities.org 1;\n"
|
||||
unless site.host.match(/\.neocities\.org$/)
|
||||
file.write ".#{site.values[:domain]} 1;\n"
|
||||
# Database transaction
|
||||
DB.transaction do
|
||||
DB[:blocked_ips].delete
|
||||
DB[:blocked_ips].multi_insert insert_hashes
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
File.open('./files/maps/subdomain-to-domain.txt', 'w') do |file|
|
||||
Site.select(:username, :domain).exclude(domain: nil).exclude(domain: '').all.each do |site|
|
||||
file.write "#{site.username}.neocities.org #{site.values[:domain]};\n"
|
||||
end
|
||||
end
|
||||
|
||||
File.open('./files/maps/sandboxed.txt', 'w') do |file|
|
||||
usernames = DB["select username from sites where created_at > ? and parent_site_id is null and (plan_type is null or plan_type='free') and is_banned != 't' and is_deleted != 't'", 2.days.ago].all.collect {|s| s[:username]}.each {|username| file.write "#{username} 1;\n"}
|
||||
end
|
||||
|
||||
# Compile letsencrypt ssl keys
|
||||
sites = DB[%{select username,ssl_key,ssl_cert,domain from sites where ssl_cert is not null and ssl_key is not null and (domain is not null or domain != '') and is_banned != 't' and is_deleted != 't'}].all
|
||||
|
||||
ssl_path = './files/maps/ssl'
|
||||
|
||||
FileUtils.mkdir_p ssl_path
|
||||
|
||||
sites.each do |site|
|
||||
[site[:domain], "www.#{site[:domain]}"].each do |domain|
|
||||
begin
|
||||
key = OpenSSL::PKey::RSA.new site[:ssl_key]
|
||||
crt = OpenSSL::X509::Certificate.new site[:ssl_cert]
|
||||
rescue => e
|
||||
puts "SSL ERROR: #{e.class} #{e.inspect}"
|
||||
next
|
||||
end
|
||||
|
||||
File.open(File.join(ssl_path, "#{domain}.key"), 'wb') {|f| f.write key.to_der}
|
||||
File.open(File.join(ssl_path, "#{domain}.crt"), 'wb') {|f| f.write site[:ssl_cert]}
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
desc 'Produce SSL config package for proxy'
|
||||
task :buildssl => [:environment] do
|
||||
sites = Site.select(:id, :username, :domain, :ssl_key, :ssl_cert).
|
||||
exclude(domain: nil).
|
||||
exclude(ssl_key: nil).
|
||||
exclude(ssl_cert: nil).
|
||||
all
|
||||
|
||||
payload = []
|
||||
|
||||
begin
|
||||
FileUtils.rm './files/sslsites.zip'
|
||||
rescue Errno::ENOENT
|
||||
end
|
||||
|
||||
Zip::Archive.open('./files/sslsites.zip', Zip::CREATE) do |ar|
|
||||
ar.add_dir 'ssl'
|
||||
|
||||
sites.each do |site|
|
||||
ar.add_buffer "ssl/#{site.username}.key", site.ssl_key
|
||||
ar.add_buffer "ssl/#{site.username}.crt", site.ssl_cert
|
||||
payload << {username: site.username, domain: site.domain}
|
||||
end
|
||||
|
||||
ar.add_buffer 'sslsites.json', payload.to_json
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Set existing stripe customers to internal supporter plan'
|
||||
task :primenewstriperunonlyonce => [:environment] do
|
||||
# Site.exclude(stripe_customer_id: nil).all.each do |site|
|
||||
# site.plan_type = 'supporter'
|
||||
# site.save_changes validate: false
|
||||
# end
|
||||
|
||||
Site.exclude(stripe_customer_id: nil).where(plan_type: nil).where(plan_ended: false).all.each do |s|
|
||||
customer = Stripe::Customer.retrieve(s.stripe_customer_id)
|
||||
subscription = customer.subscriptions.first
|
||||
next if subscription.nil?
|
||||
puts "set subscription id to #{subscription.id}"
|
||||
puts "set plan type to #{subscription.plan.id}"
|
||||
s.stripe_subscription_id = subscription.id
|
||||
s.plan_type = subscription.plan.id
|
||||
s.save_changes(validate: false)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
desc 'dedupe tags'
|
||||
task :dedupetags => [:environment] do
|
||||
Tag.all.each do |tag|
|
||||
begin
|
||||
tag.reload
|
||||
rescue Sequel::Error => e
|
||||
next if e.message =~ /Record not found/
|
||||
end
|
||||
|
||||
matching_tags = Tag.exclude(id: tag.id).where(name: tag.name).all
|
||||
|
||||
matching_tags.each do |matching_tag|
|
||||
DB[:sites_tags].where(tag_id: matching_tag.id).update(tag_id: tag.id)
|
||||
matching_tag.delete
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Clean tags'
|
||||
task :cleantags => [:environment] do
|
||||
|
||||
Site.select(:id).all.each do |site|
|
||||
if site.tags.length > 5
|
||||
site.tags.slice(5, site.tags.length).each {|tag| site.remove_tag tag}
|
||||
end
|
||||
end
|
||||
|
||||
empty_tag = Tag.where(name: '').first
|
||||
|
||||
if empty_tag
|
||||
DB[:sites_tags].where(tag_id: empty_tag.id).delete
|
||||
end
|
||||
|
||||
Tag.all.each do |tag|
|
||||
if tag.name.length > Tag::NAME_LENGTH_MAX || tag.name.match(/ /)
|
||||
DB[:sites_tags].where(tag_id: tag.id).delete
|
||||
DB[:tags].where(id: tag.id).delete
|
||||
else
|
||||
tag.update name: tag.name.downcase.strip
|
||||
end
|
||||
end
|
||||
|
||||
Tag.where(name: 'porn').first.update is_nsfw: true
|
||||
end
|
||||
|
||||
desc 'update screenshots'
|
||||
task :update_screenshots => [:environment] do
|
||||
Site.select(:username).where(site_changed: true, is_banned: false, is_crashing: false).filter(~{updated_at: nil}).order(:updated_at.desc).all.each do |site|
|
||||
ScreenshotWorker.perform_async site.username, 'index.html'
|
||||
end
|
||||
FileUtils.rm zip_path
|
||||
end
|
||||
|
||||
desc 'rebuild_thumbnails'
|
||||
|
@ -266,142 +88,11 @@ task :rebuild_thumbnails => [:environment] do
|
|||
end
|
||||
end
|
||||
|
||||
desc 'prime_space_used'
|
||||
task :prime_space_used => [:environment] do
|
||||
Site.select(:id,:username,:space_used).all.each do |s|
|
||||
s.space_used = s.actual_space_used
|
||||
s.save_changes validate: false
|
||||
end
|
||||
end
|
||||
|
||||
desc 'prime site_updated_at'
|
||||
task :prime_site_updated_at => [:environment] do
|
||||
Site.select(:id,:username,:site_updated_at, :updated_at).all.each do |s|
|
||||
s.site_updated_at = s.updated_at
|
||||
s.save_changes validate: false
|
||||
end
|
||||
end
|
||||
|
||||
desc 'prime_site_files'
|
||||
task :prime_site_files => [:environment] do
|
||||
Site.where(is_banned: false).where(is_deleted: false).select(:id, :username).all.each do |site|
|
||||
Dir.glob(File.join(site.files_path, '**/*')).each do |file|
|
||||
path = file.gsub(site.base_files_path, '').sub(/^\//, '')
|
||||
|
||||
site_file = site.site_files_dataset[path: path]
|
||||
|
||||
if site_file.nil?
|
||||
mtime = File.mtime file
|
||||
|
||||
site_file_opts = {
|
||||
path: path,
|
||||
updated_at: mtime,
|
||||
created_at: mtime
|
||||
}
|
||||
|
||||
if File.directory? file
|
||||
site_file_opts.merge! is_directory: true
|
||||
else
|
||||
site_file_opts.merge!(
|
||||
size: File.size(file),
|
||||
sha1_hash: Digest::SHA1.file(file).hexdigest
|
||||
)
|
||||
end
|
||||
|
||||
site.add_site_file site_file_opts
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'dedupe_follows'
|
||||
task :dedupe_follows => [:environment] do
|
||||
follows = Follow.all
|
||||
deduped_follows = Follow.all.uniq {|f| "#{f.site_id}_#{f.actioning_site_id}"}
|
||||
|
||||
follows.each do |follow|
|
||||
unless deduped_follows.include?(follow)
|
||||
puts "deleting dedupe: #{follow.inspect}"
|
||||
follow.delete
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'flush_empty_index_sites'
|
||||
task :flush_empty_index_sites => [:environment] do
|
||||
sites = Site.select(:id).all
|
||||
|
||||
counter = 0
|
||||
|
||||
sites.each do |site|
|
||||
if site.empty_index?
|
||||
counter += 1
|
||||
site.site_changed = false
|
||||
site.save_changes validate: false
|
||||
end
|
||||
end
|
||||
|
||||
puts "#{counter} sites set to not changed."
|
||||
end
|
||||
|
||||
desc 'compute_scores'
|
||||
task :compute_scores => [:environment] do
|
||||
Site.compute_scores
|
||||
end
|
||||
|
||||
=begin
|
||||
desc 'Update screenshots'
|
||||
task :update_screenshots => [:environment] do
|
||||
Site.select(:username).filter(is_banned: false).filter(~{updated_at: nil}).order(:updated_at.desc).all.collect {|s|
|
||||
ScreenshotWorker.perform_async s.username
|
||||
}
|
||||
end
|
||||
=end
|
||||
|
||||
desc 'prime_classifier'
|
||||
task :prime_classifier => [:environment] do
|
||||
Site.select(:id, :username).where(is_banned: false, is_deleted: false).all.each do |site|
|
||||
next if site.site_files_dataset.where(classifier: 'spam').count > 0
|
||||
html_files = site.site_files_dataset.where(path: /\.html$/).all
|
||||
|
||||
html_files.each do |html_file|
|
||||
print "training #{site.username}/#{html_file.path}..."
|
||||
site.train html_file.path
|
||||
print "done.\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'train_spam'
|
||||
task :train_spam => [:environment] do
|
||||
paths = File.read('./spam.txt')
|
||||
|
||||
paths.split("\n").each do |path|
|
||||
username, site_file_path = path.match(/^([a-zA-Z0-9_\-]+)\/(.+)$/i).captures
|
||||
site = Site[username: username]
|
||||
next if site.nil?
|
||||
site_file = site.site_files_dataset.where(path: site_file_path).first
|
||||
next if site_file.nil?
|
||||
site.train site_file_path, :spam
|
||||
site.ban!
|
||||
puts "Deleted #{site_file_path}, banned #{site.username}"
|
||||
end
|
||||
end
|
||||
|
||||
desc 'regenerate_ssl_certs'
|
||||
task :regenerate_ssl_certs => [:environment] do
|
||||
sites = DB[%{select id from sites where (domain is not null or domain != '') and is_banned != 't' and is_deleted != 't'}].all
|
||||
|
||||
seconds = 2
|
||||
|
||||
sites.each do |site|
|
||||
LetsEncryptWorker.perform_in seconds, site[:id]
|
||||
seconds += 10
|
||||
end
|
||||
|
||||
puts "#{sites.length.to_s} records are primed"
|
||||
end
|
||||
|
||||
desc 'renew_ssl_certs'
|
||||
task :renew_ssl_certs => [:environment] do
|
||||
delay = 0
|
||||
|
@ -418,24 +109,6 @@ task :purge_tmp_turds => [:environment] do
|
|||
end
|
||||
end
|
||||
|
||||
desc 'shard_migration'
|
||||
task :shard_migration => [:environment] do
|
||||
#Site.exclude(is_deleted: true).exclude(is_banned: true).select(:username).each do |site|
|
||||
# FileUtils.mkdir_p File.join('public', 'testsites', site.username)
|
||||
#end
|
||||
#exit
|
||||
Dir.chdir('./public/testsites')
|
||||
Dir.glob('*').each do |dir|
|
||||
sharding_dir = Site.sharding_dir(dir)
|
||||
FileUtils.mkdir_p File.join('..', 'newtestsites', sharding_dir)
|
||||
FileUtils.mv dir, File.join('..', 'newtestsites', sharding_dir)
|
||||
end
|
||||
sleep 1
|
||||
FileUtils.rmdir './public/testsites'
|
||||
sleep 1
|
||||
FileUtils.mv './public/newtestsites', './public/testsites'
|
||||
end
|
||||
|
||||
desc 'compute_follow_count_scores'
|
||||
task :compute_follow_count_scores => [:environment] do
|
||||
|
||||
|
@ -449,37 +122,6 @@ task :compute_follow_count_scores => [:environment] do
|
|||
end
|
||||
end
|
||||
|
||||
desc 'prime_redis_proxy_ssl'
|
||||
task :prime_redis_proxy_ssl => [:environment] do
|
||||
site_ids = DB[%{
|
||||
select id from sites where domain is not null and ssl_cert is not null and ssl_key is not null
|
||||
and is_deleted != ? and is_banned != ?
|
||||
}, true, true].all.collect {|site_id| site_id[:id]}
|
||||
|
||||
site_ids.each do |site_id|
|
||||
Site[site_id].store_ssl_in_redis_proxy
|
||||
end
|
||||
end
|
||||
|
||||
desc 'dedupe_site_blocks'
|
||||
task :dedupe_site_blocks => [:environment] do
|
||||
duped_blocks = []
|
||||
block_ids = Block.select(:id).all.collect {|b| b.id}
|
||||
block_ids.each do |block_id|
|
||||
next unless duped_blocks.select {|db| db.id == block_id}.empty?
|
||||
block = Block[block_id]
|
||||
if block
|
||||
blocks = Block.exclude(id: block.id).where(site_id: block.site_id).where(actioning_site_id: block.actioning_site_id).all
|
||||
duped_blocks << blocks
|
||||
duped_blocks.flatten!
|
||||
end
|
||||
end
|
||||
|
||||
duped_blocks.each do |duped_block|
|
||||
duped_block.destroy
|
||||
end
|
||||
end
|
||||
|
||||
desc 'ml_screenshots_list_dump'
|
||||
task :ml_screenshots_list_dump => [:environment] do
|
||||
['phishing', 'spam', 'ham', nil].each do |classifier|
|
||||
|
|
4
Vagrantfile
vendored
4
Vagrantfile
vendored
|
@ -1,12 +1,12 @@
|
|||
VAGRANTFILE_API_VERSION = '2'
|
||||
|
||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
config.vm.box = 'ubuntu/bionic64'
|
||||
config.vm.box = 'ubuntu/jammy64'
|
||||
config.vm.provision :shell, path: './vagrant/development.sh'
|
||||
config.vm.network :forwarded_port, guest: 9292, host: 9292
|
||||
|
||||
config.vm.provider :virtualbox do |vb|
|
||||
vb.customize ['modifyvm', :id, '--memory', '2048']
|
||||
vb.customize ['modifyvm', :id, '--memory', '8192']
|
||||
vb.name = 'neocities'
|
||||
end
|
||||
end
|
||||
|
|
10
app.rb
10
app.rb
|
@ -75,8 +75,10 @@ before do
|
|||
content_type :json
|
||||
elsif request.path.match /^\/webhooks\//
|
||||
# Skips the CSRF/validation check for stripe web hooks
|
||||
elsif email_not_validated? && !(request.path =~ /^\/site\/.+\/confirm_email|^\/settings\/change_email|^\/signout|^\/welcome|^\/supporter/)
|
||||
elsif email_not_validated? && !(request.path =~ /^\/site\/.+\/confirm_email|^\/settings\/change_email|^\/signout|^\/welcome|^\/supporter|^\/signout/)
|
||||
redirect "/site/#{current_site.username}/confirm_email"
|
||||
elsif !email_not_validated? && current_site && current_site.phone_verification_needed? && !(request.path =~ /^\/site\/.+\/confirm_phone|^\/signout/)
|
||||
redirect "/site/#{current_site.username}/confirm_phone"
|
||||
else
|
||||
content_type :html, 'charset' => 'utf-8'
|
||||
redirect '/' if request.post? && !csrf_safe?
|
||||
|
@ -89,9 +91,9 @@ after do
|
|||
end
|
||||
end
|
||||
|
||||
#after do
|
||||
#response.headers['Content-Security-Policy'] = %{block-all-mixed-content; default-src 'self'; connect-src 'self' https://api.stripe.com https://assets.hcaptcha.com; frame-src https://assets.hcaptcha.com https://js.stripe.com; script-src 'self' 'unsafe-inline' https://js.stripe.com https://hcaptcha.com https://assets.hcaptcha.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: }
|
||||
#end
|
||||
after do
|
||||
response.headers['Content-Security-Policy'] = %{default-src 'self' data: blob: 'unsafe-inline'; script-src 'self' blob: 'unsafe-inline' 'unsafe-eval' https://hcaptcha.com https://*.hcaptcha.com https://js.stripe.com; style-src 'self' 'unsafe-inline' https://hcaptcha.com https://*.hcaptcha.com; connect-src 'self' https://hcaptcha.com https://*.hcaptcha.com https://api.stripe.com; frame-src 'self' https://hcaptcha.com https://*.hcaptcha.com https://js.stripe.com} unless self.class.development?
|
||||
end
|
||||
|
||||
not_found do
|
||||
api_not_found if @api
|
||||
|
|
|
@ -250,7 +250,8 @@ post '/admin/banhammer' do
|
|||
StopForumSpamWorker.perform_async(
|
||||
username: site.username,
|
||||
email: site.email,
|
||||
ip: site.ip
|
||||
ip: site.ip,
|
||||
classifier: params[:classifier]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -167,8 +167,7 @@ def api_info_for(site)
|
|||
created_at: site.created_at.rfc2822,
|
||||
last_updated: site.site_updated_at ? site.site_updated_at.rfc2822 : nil,
|
||||
domain: site.domain,
|
||||
tags: site.tags.collect {|t| t.name},
|
||||
latest_ipfs_hash: site.latest_archive ? site.latest_archive.ipfs_hash : nil
|
||||
tags: site.tags.collect {|t| t.name}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
|
|
@ -9,6 +9,10 @@ post '/contact' do
|
|||
@errors << 'Please fill out all fields'
|
||||
end
|
||||
|
||||
if params[:faq_check] == 'no'
|
||||
@errors << 'Please check Frequently Asked Questions before sending a contact message'
|
||||
end
|
||||
|
||||
unless hcaptcha_valid?
|
||||
@errors << 'Captcha was not filled out (or was filled out incorrectly)'
|
||||
end
|
||||
|
|
|
@ -91,6 +91,11 @@ post '/create' do
|
|||
return {result: 'error'}.to_json
|
||||
end
|
||||
|
||||
if defined?(BlackBox.create_disabled?) && BlackBox.create_disabled?(@site, request)
|
||||
flash[:error] = 'Site creation is currently unavailable, please try again later.'
|
||||
return {result: 'error'}.to_json
|
||||
end
|
||||
|
||||
if !@site.valid?
|
||||
flash[:error] = @site.errors.first.last.first
|
||||
return {result: 'error'}.to_json
|
||||
|
@ -98,6 +103,20 @@ post '/create' do
|
|||
end
|
||||
|
||||
@site.email_confirmed = true if self.class.development?
|
||||
@site.phone_verified = true if self.class.development?
|
||||
|
||||
begin
|
||||
@site.phone_verification_required = true if self.class.production? && BlackBox.phone_verification_required?(@site)
|
||||
rescue => e
|
||||
EmailWorker.perform_async({
|
||||
from: 'web@neocities.org',
|
||||
to: 'errors@neocities.org',
|
||||
subject: "[Neocities Error] Phone verification exception",
|
||||
body: "#{e.inspect}\n#{e.backtrace}",
|
||||
no_footer: true
|
||||
})
|
||||
end
|
||||
|
||||
@site.save
|
||||
|
||||
unless education_whitelisted?
|
||||
|
|
|
@ -80,6 +80,8 @@ get '/?' do
|
|||
@blog_feed_html = SimpleCache.get :blog_feed_html
|
||||
end
|
||||
|
||||
@create_disabled = false
|
||||
|
||||
erb :index, layout: :index_layout
|
||||
end
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ the Neocities Cat
|
|||
end
|
||||
end
|
||||
|
||||
flash[:success] = 'If your email was valid (and used by a site), the Neocities Cat will send an e-mail to your account with password reset instructions.'
|
||||
flash[:success] = "We sent an e-mail with password reset instructions. Check your spam folder if you don't see it in your inbox."
|
||||
redirect '/'
|
||||
end
|
||||
|
||||
|
|
|
@ -15,6 +15,13 @@ def require_ownership_for_settings
|
|||
end
|
||||
end
|
||||
|
||||
get '/settings/invoices/?' do
|
||||
require_login
|
||||
@title = 'Invoices'
|
||||
@invoices = parent_site.stripe_customer_id ? Stripe::Invoice.list(customer: parent_site.stripe_customer_id) : []
|
||||
erb :'settings/invoices'
|
||||
end
|
||||
|
||||
get '/settings/:username/?' do |username|
|
||||
# This is for the email_unsubscribe below
|
||||
pass if Site.select(:id).where(username: username).first.nil?
|
||||
|
@ -56,8 +63,7 @@ post '/settings/:username/profile' do
|
|||
|
||||
@site.update(
|
||||
profile_comments_enabled: params[:site][:profile_comments_enabled],
|
||||
profile_enabled: params[:site][:profile_enabled],
|
||||
ipfs_archiving_enabled: params[:site][:ipfs_archiving_enabled]
|
||||
profile_enabled: params[:site][:profile_enabled]
|
||||
)
|
||||
flash[:success] = 'Profile settings changed.'
|
||||
redirect "/settings/#{@site.username}#profile"
|
||||
|
@ -92,8 +98,8 @@ post '/settings/:username/change_name' do
|
|||
}
|
||||
|
||||
old_site.delete_all_thumbnails_and_screenshots
|
||||
old_site.delete_all_cache
|
||||
@site.delete_all_cache
|
||||
old_site.purge_all_cache
|
||||
@site.purge_all_cache
|
||||
@site.regenerate_thumbnails_and_screenshots
|
||||
|
||||
flash[:success] = "Site/user name has been changed. You will need to use this name to login, <b>don't forget it!</b>"
|
||||
|
|
95
app/site.rb
95
app/site.rb
|
@ -38,14 +38,6 @@ get '/site/:username/?' do |username|
|
|||
erb :'site', locals: {site: site, is_current_site: site == current_site}
|
||||
end
|
||||
|
||||
get '/site/:username/archives' do
|
||||
@site = Site[username: params[:username]]
|
||||
not_found if @site.nil? || @site.is_banned || @site.is_deleted || !@site.ipfs_archiving_enabled
|
||||
@title = "Site archives for #{@site.title}"
|
||||
@archives = @site.archives_dataset.limit(300).order(:updated_at.desc).all
|
||||
erb :'site/archives'
|
||||
end
|
||||
|
||||
MAX_STAT_POINTS = 30
|
||||
get '/site/:username/stats' do
|
||||
@default_stat_points = 7
|
||||
|
@ -295,4 +287,91 @@ get '/site/:username/unblock' do |username|
|
|||
|
||||
current_site.unblock! site
|
||||
redirect request.referer
|
||||
end
|
||||
|
||||
get '/site/:username/confirm_phone' do
|
||||
require_login
|
||||
redirect '/' unless current_site.phone_verification_needed?
|
||||
@title = 'Verify your Phone Number'
|
||||
erb :'site/confirm_phone'
|
||||
end
|
||||
|
||||
def restart_phone_verification
|
||||
current_site.phone_verification_sent_at = nil
|
||||
current_site.phone_verification_sid = nil
|
||||
current_site.save_changes validate: false
|
||||
redirect "/site/#{current_site.username}/confirm_phone"
|
||||
end
|
||||
|
||||
post '/site/:username/confirm_phone' do
|
||||
require_login
|
||||
redirect '/' unless current_site.phone_verification_needed?
|
||||
|
||||
if params[:phone_intl]
|
||||
phone = Phonelib.parse params[:phone_intl]
|
||||
|
||||
if !phone.valid?
|
||||
flash[:error] = "Invalid phone number, please try again."
|
||||
redirect "/site/#{current_site.username}/confirm_phone"
|
||||
end
|
||||
|
||||
if phone.types.include?(:premium_rate) || phone.types.include?(:shared_cost)
|
||||
flash[:error] = 'Neocities does not support this type of number, please use another number.'
|
||||
redirect "/site/#{current_site.username}/confirm_phone"
|
||||
end
|
||||
|
||||
current_site.phone_verification_sent_at = Time.now
|
||||
current_site.phone_verification_attempts += 1
|
||||
|
||||
if current_site.phone_verification_attempts > Site::PHONE_VERIFICATION_LOCKOUT_ATTEMPTS
|
||||
flash[:error] = 'You have exceeded the number of phone verification attempts allowed.'
|
||||
redirect "/site/#{current_site.username}/confirm_phone"
|
||||
end
|
||||
|
||||
current_site.save_changes validate: false
|
||||
|
||||
verification = $twilio.verify
|
||||
.v2
|
||||
.services($config['twilio_service_sid'])
|
||||
.verifications
|
||||
.create(to: phone.e164, channel: 'sms')
|
||||
|
||||
current_site.phone_verification_sid = verification.sid
|
||||
current_site.save_changes validate: false
|
||||
|
||||
flash[:success] = 'Validation message sent! Check your phone and enter the code below.'
|
||||
else
|
||||
|
||||
restart_phone_verification if current_site.phone_verification_sent_at < Time.now - Site::PHONE_VERIFICATION_EXPIRATION_TIME
|
||||
minutes_remaining = ((current_site.phone_verification_sent_at - (Time.now - Site::PHONE_VERIFICATION_EXPIRATION_TIME))/60).round
|
||||
|
||||
begin
|
||||
# Check code
|
||||
vc = $twilio.verify
|
||||
.v2
|
||||
.services($config['twilio_service_sid'])
|
||||
.verification_checks
|
||||
.create(verification_sid: current_site.phone_verification_sid, code: params[:code])
|
||||
|
||||
# puts vc.status (pending if failed, approved if it passed)
|
||||
if vc.status == 'approved'
|
||||
current_site.phone_verified = true
|
||||
current_site.save_changes validate: false
|
||||
else
|
||||
flash[:error] = "Code was not correct, please try again. If the phone number you entered was incorrect, you can re-enter the number after #{minutes_remaining} more minutes have passed."
|
||||
end
|
||||
|
||||
rescue Twilio::REST::RestError => e
|
||||
if e.message =~ /60202/
|
||||
flash[:error] = "You have exhausted your check attempts. Please try again in #{minutes_remaining} minutes."
|
||||
elsif e.message =~ /20404/ # Unable to create record
|
||||
restart_phone_verification
|
||||
else
|
||||
raise e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Will redirect to / automagically if phone was verified
|
||||
redirect "/site/#{current_site.username}/confirm_phone"
|
||||
end
|
|
@ -186,12 +186,20 @@ post '/site_files/rename' do
|
|||
redirect "/dashboard#{dir_query}"
|
||||
end
|
||||
|
||||
get '/site_files/:username.zip' do |username|
|
||||
get '/site_files/download' do
|
||||
require_login
|
||||
|
||||
if !current_site.dl_queued_at.nil? && current_site.dl_queued_at > 1.hour.ago
|
||||
flash[:error] = 'Site downloads are currently limited to once per hour, please try again later.'
|
||||
redirect request.referer
|
||||
end
|
||||
|
||||
content_type 'application/zip'
|
||||
attachment "neocities-#{current_site.username}.zip"
|
||||
|
||||
current_site.dl_queued_at = Time.now
|
||||
current_site.save_changes validate: false
|
||||
|
||||
directory_path = current_site.files_path
|
||||
|
||||
stream do |out|
|
||||
|
@ -200,7 +208,7 @@ get '/site_files/:username.zip' do |username|
|
|||
next if File.directory?(file)
|
||||
|
||||
zip_path = file.sub("#{directory_path}/", '')
|
||||
zip.write_deflated_file(zip_path) do |file_writer|
|
||||
zip.write_stored_file(zip_path) do |file_writer|
|
||||
File.open(file, 'rb') do |file|
|
||||
IO.copy_stream(file, file_writer)
|
||||
end
|
||||
|
|
|
@ -20,4 +20,9 @@ cache_control_ips:
|
|||
- 1.2.3.4
|
||||
- 4.5.6.7
|
||||
hcaptcha_site_key: "10000000-ffff-ffff-ffff-000000000001"
|
||||
hcaptcha_secret_key: "0x0000000000000000000000000000000000000000"
|
||||
hcaptcha_secret_key: "0x0000000000000000000000000000000000000000"
|
||||
twilio_account_sid: ACEDERPDERP
|
||||
twilio_auth_token: derpderpderp
|
||||
twilio_service_sid: VADERPDERPDERP
|
||||
minfraud_account_id: 696969420
|
||||
minfraud_license_key: DERPDERPDERP
|
|
@ -18,6 +18,8 @@ development:
|
|||
paypal_api_signature: tonz
|
||||
letsencrypt_key: ./tests/files/letsencrypt.key
|
||||
letsencrypt_endpoint: https://acme-staging.api.letsencrypt.org/
|
||||
minfraud_account_id: 696969420
|
||||
minfraud_license_key: DERPDERPDERP
|
||||
proxy_ips:
|
||||
- 10.0.0.1
|
||||
- 10.0.0.2
|
||||
|
@ -55,3 +57,8 @@ test:
|
|||
cache_control_ips:
|
||||
- 1.2.3.4
|
||||
- 4.5.6.7
|
||||
twilio_account_sid: ACEDERPDERP
|
||||
twilio_auth_token: derpderpderp
|
||||
twilio_service_sid: VADERPDERPDERP
|
||||
minfraud_account_id: 696969420
|
||||
minfraud_license_key: DERPDERPDERP
|
|
@ -1,3 +1,4 @@
|
|||
RubyVM::YJIT.enable
|
||||
ENV['RACK_ENV'] ||= 'development'
|
||||
ENV['TZ'] = 'UTC'
|
||||
DIR_ROOT = File.expand_path File.dirname(__FILE__)
|
||||
|
@ -177,3 +178,11 @@ $image_optim = ImageOptim.new pngout: false, svgo: false
|
|||
Money.locale_backend = nil
|
||||
Money.default_currency = Money::Currency.new("USD")
|
||||
Money.rounding_mode = BigDecimal::ROUND_HALF_UP
|
||||
|
||||
$twilio = Twilio::REST::Client.new $config['twilio_account_sid'], $config['twilio_auth_token']
|
||||
|
||||
Minfraud.configure do |c|
|
||||
c.account_id = $config['minfraud_account_id']
|
||||
c.license_key = $config['minfraud_license_key']
|
||||
c.enable_validation = true
|
||||
end
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
class BitcoinValidator
|
||||
class << self
|
||||
def address_version
|
||||
"00"
|
||||
end
|
||||
|
||||
def p2sh_version
|
||||
"05"
|
||||
end
|
||||
|
||||
def valid_address?(address)
|
||||
hex = decode_base58(address) rescue nil
|
||||
return false unless hex && hex.bytesize == 50
|
||||
return false unless [address_version, p2sh_version].include?(hex[0...2])
|
||||
base58_checksum?(address)
|
||||
end
|
||||
|
||||
def decode_base58(base58_val)
|
||||
s = base58_to_int(base58_val).to_s(16); s = (s.bytesize.odd? ? '0'+s : s)
|
||||
s = '' if s == '00'
|
||||
leading_zero_bytes = (base58_val.match(/^([1]+)/) ? $1 : '').size
|
||||
s = ("00"*leading_zero_bytes) + s if leading_zero_bytes > 0
|
||||
s
|
||||
end
|
||||
|
||||
def base58_checksum?(base58)
|
||||
hex = decode_base58(base58) rescue nil
|
||||
return false unless hex
|
||||
checksum( hex[0...42] ) == hex[-8..-1]
|
||||
end
|
||||
|
||||
def checksum(hex)
|
||||
b = [hex].pack("H*") # unpack hex
|
||||
Digest::SHA256.hexdigest( Digest::SHA256.digest(b) )[0...8]
|
||||
end
|
||||
|
||||
|
||||
def base58_to_int(base58_val)
|
||||
alpha = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||
int_val, base = 0, alpha.size
|
||||
base58_val.reverse.each_char.with_index do |char,index|
|
||||
raise ArgumentError, 'Value not a valid Base58 String.' unless char_index = alpha.index(char)
|
||||
int_val += char_index*(base**index)
|
||||
end
|
||||
int_val
|
||||
end
|
||||
|
||||
end
|
||||
end
|
9
migrations/118_site_dl_queued_at.rb
Normal file
9
migrations/118_site_dl_queued_at.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
Sequel.migration do
|
||||
up {
|
||||
DB.add_column :sites, :dl_queued_at, Time
|
||||
}
|
||||
|
||||
down {
|
||||
DB.drop_column :sites, :dl_queued_at
|
||||
}
|
||||
end
|
15
migrations/119_verify_phone.rb
Normal file
15
migrations/119_verify_phone.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
Sequel.migration do
|
||||
up {
|
||||
DB.add_column :sites, :phone_verification_required, :boolean, default: false
|
||||
DB.add_column :sites, :phone_verified, :boolean, default: false
|
||||
DB.add_column :sites, :phone_verification_sid, :text
|
||||
DB.add_column :sites, :phone_verification_sent_at, :time
|
||||
}
|
||||
|
||||
down {
|
||||
DB.drop_column :sites, :phone_verification_required
|
||||
DB.drop_column :sites, :phone_verified
|
||||
DB.drop_column :sites, :phone_verification_sid
|
||||
DB.drop_column :sites, :phone_verification_sent_at
|
||||
}
|
||||
end
|
11
migrations/120_fix_phone_sent_at.rb
Normal file
11
migrations/120_fix_phone_sent_at.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
Sequel.migration do
|
||||
up {
|
||||
DB.drop_column :sites, :phone_verification_sent_at
|
||||
DB.add_column :sites, :phone_verification_sent_at, Time
|
||||
}
|
||||
|
||||
down {
|
||||
DB.drop_column :sites, :phone_verification_sent_at
|
||||
DB.add_column :sites, :phone_verification_sent_at, :time
|
||||
}
|
||||
end
|
9
migrations/121_phone_verification_attempts.rb
Normal file
9
migrations/121_phone_verification_attempts.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
Sequel.migration do
|
||||
up {
|
||||
DB.add_column :sites, :phone_verification_attempts, :integer, default: 0
|
||||
}
|
||||
|
||||
down {
|
||||
DB.drop_column :sites, :phone_verification_attempts
|
||||
}
|
||||
end
|
17
migrations/122_remove_archives.rb
Normal file
17
migrations/122_remove_archives.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
Sequel.migration do
|
||||
up {
|
||||
DB.drop_table :archives
|
||||
DB.drop_column :sites, :ipfs_archiving_enabled
|
||||
}
|
||||
|
||||
down {
|
||||
DB.create_table! :archives do
|
||||
Integer :site_id, index: true
|
||||
String :ipfs_hash
|
||||
DateTime :updated_at, index: true
|
||||
unique [:site_id, :ipfs_hash]
|
||||
end
|
||||
|
||||
DB.add_column :sites, :ipfs_archiving_enabled, :boolean, default: false
|
||||
}
|
||||
end
|
|
@ -1,39 +0,0 @@
|
|||
require 'base32'
|
||||
|
||||
class Archive < Sequel::Model
|
||||
many_to_one :site
|
||||
set_primary_key [:site_id, :ipfs_hash]
|
||||
unrestrict_primary_key
|
||||
MAXIMUM_ARCHIVES_PER_SITE = 5
|
||||
ARCHIVE_WAIT_TIME = 1.minute
|
||||
|
||||
def before_destroy
|
||||
unpin
|
||||
super
|
||||
end
|
||||
|
||||
def unpin
|
||||
return nil
|
||||
# Not ideal. An SoA version is in progress.
|
||||
if ENV['RACK_ENV'] == 'production' && $config['ipfs_ssh_host'] && $config['ipfs_ssh_user']
|
||||
rbox = Rye::Box.new $config['ipfs_ssh_host'], :user => $config['ipfs_ssh_user']
|
||||
rbox.disable_safe_mode
|
||||
begin
|
||||
response = rbox.execute "ipfs pin rm #{ipfs_hash}"
|
||||
output_array = response
|
||||
rescue => e
|
||||
return true if e.message =~ /indirect pins cannot be removed directly/
|
||||
ensure
|
||||
rbox.disconnect
|
||||
end
|
||||
else
|
||||
line = Terrapin::CommandLine.new('ipfs', 'pin rm :ipfs_hash')
|
||||
response = line.run ipfs_hash: ipfs_hash
|
||||
output_array = response.to_s.split("\n")
|
||||
end
|
||||
end
|
||||
|
||||
def url
|
||||
"https://#{ipfs_hash}.ipfs.neocitiesops.net"
|
||||
end
|
||||
end
|
178
models/site.rb
178
models/site.rb
|
@ -45,11 +45,11 @@ class Site < Sequel::Model
|
|||
}
|
||||
|
||||
VALID_EXTENSIONS = %w{
|
||||
html htm txt text css js jpg jpeg png apng gif svg md markdown eot ttf woff woff2 json geojson csv tsv mf ico pdf asc key pgp xml mid midi manifest otf webapp less sass rss kml dae obj mtl scss webp avif xcf epub gltf bin webmanifest knowl atom opml rdf map gpg resolveHandle
|
||||
html htm txt text css js jpg jpeg png apng gif svg md markdown eot ttf woff woff2 json geojson csv tsv mf ico pdf asc key pgp xml mid midi manifest otf webapp less sass rss kml dae obj mtl scss webp avif xcf epub gltf bin webmanifest knowl atom opml rdf map gpg resolveHandle pls
|
||||
}
|
||||
|
||||
VALID_EDITABLE_EXTENSIONS = %w{
|
||||
html htm txt js css scss md manifest less webmanifest xml json opml rdf svg gpg pgp resolveHandle
|
||||
html htm txt js css scss md manifest less webmanifest xml json opml rdf svg gpg pgp resolveHandle pls
|
||||
}
|
||||
|
||||
MINIMUM_PASSWORD_LENGTH = 5
|
||||
|
@ -141,7 +141,7 @@ class Site < Sequel::Model
|
|||
DISPOSABLE_EMAIL_BLACKLIST_PATH = File.join(DIR_ROOT, 'files', 'disposable_email_blacklist.conf')
|
||||
BANNED_EMAIL_BLACKLIST_PATH = File.join(DIR_ROOT, 'files', 'banned_email_blacklist.conf')
|
||||
|
||||
BLOCK_JERK_THRESHOLD = 4
|
||||
BLOCK_JERK_THRESHOLD = 25
|
||||
MAXIMUM_TAGS = 5
|
||||
MAX_USERNAME_LENGTH = 32.freeze
|
||||
|
||||
|
@ -168,6 +168,9 @@ class Site < Sequel::Model
|
|||
BLACK_BOX_WAIT_TIME = 10.seconds
|
||||
MAX_DISPLAY_FOLLOWS = 56*3
|
||||
|
||||
PHONE_VERIFICATION_EXPIRATION_TIME = 10.minutes
|
||||
PHONE_VERIFICATION_LOCKOUT_ATTEMPTS = 3
|
||||
|
||||
many_to_many :tags
|
||||
|
||||
one_to_many :profile_comments
|
||||
|
@ -204,8 +207,6 @@ class Site < Sequel::Model
|
|||
one_to_many :stat_locations
|
||||
one_to_many :stat_paths
|
||||
|
||||
one_to_many :archives
|
||||
|
||||
def self.supporter_ids
|
||||
parent_supporters = DB[%{SELECT id FROM sites WHERE plan_type IS NOT NULL AND plan_type != 'free'}].all.collect {|s| s[:id]}
|
||||
child_supporters = DB[%{select a.id as id from sites a, sites b where a.parent_site_id is not null and a.parent_site_id=b.id and (a.plan_type != 'free' or b.plan_type != 'free')}].all.collect {|s| s[:id]}
|
||||
|
@ -502,6 +503,7 @@ class Site < Sequel::Model
|
|||
|
||||
def after_destroy
|
||||
update_redis_proxy_record
|
||||
purge_all_cache
|
||||
end
|
||||
|
||||
def undelete!
|
||||
|
@ -514,8 +516,8 @@ class Site < Sequel::Model
|
|||
save_changes
|
||||
}
|
||||
|
||||
delete_all_cache
|
||||
update_redis_proxy_record
|
||||
purge_all_cache
|
||||
true
|
||||
end
|
||||
|
||||
|
@ -541,8 +543,6 @@ class Site < Sequel::Model
|
|||
self.banned_at = Time.now
|
||||
save validate: false
|
||||
destroy
|
||||
|
||||
delete_all_cache
|
||||
end
|
||||
|
||||
def ban_all_sites_on_account!
|
||||
|
@ -628,10 +628,23 @@ class Site < Sequel::Model
|
|||
@blocking_site_ids ||= blockings_dataset.select(:site_id).all.collect {|s| s.site_id}
|
||||
end
|
||||
|
||||
def unfollow_blocked_sites!
|
||||
blockings.each do |blocking|
|
||||
follows.each do |follow|
|
||||
follow.destroy if follow.actioning_site_id == blocking.site_id
|
||||
end
|
||||
|
||||
followings.each do |following|
|
||||
following.destroy if following.site_id == blocking.site_id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def block!(site)
|
||||
block = blockings_dataset.filter(site_id: site.id).first
|
||||
return true if block
|
||||
add_blocking site: site
|
||||
unfollow_blocked_sites!
|
||||
end
|
||||
|
||||
def unblock!(site)
|
||||
|
@ -646,7 +659,7 @@ class Site < Sequel::Model
|
|||
end
|
||||
|
||||
def self.valid_username?(username)
|
||||
!username.empty? && username.match(/^[a-zA-Z0-9_\-]+$/i)
|
||||
!username.empty? && username.match(/^[a-zA-Z0-9][a-zA-Z0-9_\-]+[a-zA-Z0-9]$/i)
|
||||
end
|
||||
|
||||
def self.disposable_email_domains
|
||||
|
@ -767,79 +780,12 @@ class Site < Sequel::Model
|
|||
end
|
||||
end
|
||||
|
||||
def delete_all_cache
|
||||
def purge_all_cache
|
||||
site_files.each do |site_file|
|
||||
delete_cache site_file.path
|
||||
purge_cache site_file.path
|
||||
end
|
||||
end
|
||||
|
||||
def delete_cache(path)
|
||||
purge_cache path
|
||||
end
|
||||
|
||||
#Rye::Cmd.add_command :ipfs
|
||||
|
||||
def add_to_ipfs
|
||||
# Not ideal. An SoA version is in progress.
|
||||
return nil
|
||||
|
||||
if archives_dataset.count > Archive::MAXIMUM_ARCHIVES_PER_SITE
|
||||
archives_dataset.order(:updated_at).first.destroy
|
||||
end
|
||||
|
||||
if $config['ipfs_ssh_host'] && $config['ipfs_ssh_user']
|
||||
rbox = Rye::Box.new $config['ipfs_ssh_host'], user: $config['ipfs_ssh_user']
|
||||
begin
|
||||
cidv0 = rbox.ipfs(:add, :r, :Q, "sites/#{sharding_dir}/#{self.username.gsub(/\/|\.\./, '')}").first
|
||||
cidv1b32 = rbox.ipfs(:cid, :base32, cidv0).first
|
||||
ensure
|
||||
rbox.disconnect
|
||||
end
|
||||
else
|
||||
line = Terrapin::CommandLine.new('ipfs', 'add -r -Q :path')
|
||||
response = line.run(path: files_path).strip
|
||||
line = Terrapin::CommandLine.new('ipfs', 'cid base32 :hash')
|
||||
cidv1b32 = line.run(hash: response).strip
|
||||
end
|
||||
|
||||
cidv1b32
|
||||
end
|
||||
|
||||
def purge_old_archives
|
||||
archives_dataset.order(:updated_at).offset(Archive::MAXIMUM_ARCHIVES_PER_SITE).all.each do |archive|
|
||||
archive.destroy
|
||||
end
|
||||
end
|
||||
|
||||
def archive!
|
||||
ipfs_hash = add_to_ipfs
|
||||
|
||||
archive = archives_dataset.where(ipfs_hash: ipfs_hash).first
|
||||
if archive
|
||||
archive.updated_at = Time.now
|
||||
archive.save_changes
|
||||
else
|
||||
begin
|
||||
add_archive ipfs_hash: ipfs_hash, updated_at: Time.now
|
||||
rescue Sequel::UniqueConstraintViolation
|
||||
# Record already exists, update timestamp
|
||||
archives_dataset.where(ipfs_hash: ipfs_hash).first.update updated_at: Time.now
|
||||
end
|
||||
end
|
||||
|
||||
add_redis_proxy_dnslink
|
||||
end
|
||||
|
||||
def add_redis_proxy_dnslink
|
||||
if host =~ /(.+)\.neocities\.org/ && latest_archive
|
||||
$redis_proxy.hset "dns-#{host}", 'TXT', "dnslink=/ipfs/#{latest_archive.ipfs_hash}"
|
||||
end
|
||||
end
|
||||
|
||||
def latest_archive
|
||||
@latest_archive ||= archives_dataset.order(:updated_at.desc).first
|
||||
end
|
||||
|
||||
def is_directory?(path)
|
||||
File.directory? files_path(path)
|
||||
end
|
||||
|
@ -881,33 +827,6 @@ class Site < Sequel::Model
|
|||
true
|
||||
end
|
||||
|
||||
def files_zip
|
||||
zip_name = "neocities-#{username}"
|
||||
|
||||
tmpfile = Tempfile.new 'neocities-site-zip'
|
||||
tmpfile.close
|
||||
|
||||
begin
|
||||
Zip::Archive.open(tmpfile.path, Zip::CREATE) do |ar|
|
||||
ar.add_dir(zip_name)
|
||||
|
||||
Dir.glob("#{base_files_path}/**/*").each do |path|
|
||||
relative_path = path.gsub(base_files_path+'/', '')
|
||||
if File.directory?(path)
|
||||
ar.add_dir(zip_name+'/'+relative_path)
|
||||
else
|
||||
ar.add_file(zip_name+'/'+relative_path, path) # add_file(<entry name>, <source path>)
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
tmpfile.unlink
|
||||
raise e
|
||||
end
|
||||
|
||||
tmpfile.path
|
||||
end
|
||||
|
||||
def move_files_from(oldusername)
|
||||
FileUtils.mkdir_p self.class.sharding_base_path(username)
|
||||
FileUtils.mkdir_p self.class.sharding_screenshots_path(username)
|
||||
|
@ -987,11 +906,6 @@ class Site < Sequel::Model
|
|||
parent_site_id.nil?
|
||||
end
|
||||
|
||||
# def after_destroy
|
||||
# FileUtils.rm_rf files_path
|
||||
# super
|
||||
# end
|
||||
|
||||
def ssl_installed?
|
||||
!domain.blank? && !ssl_key.blank? && !ssl_cert.blank?
|
||||
end
|
||||
|
@ -1029,6 +943,11 @@ class Site < Sequel::Model
|
|||
$redis_proxy.hset d_www_key, 'ssl_cert', ssl_cert
|
||||
$redis_proxy.hset d_www_key, 'ssl_key', ssl_key
|
||||
end
|
||||
|
||||
if is_deleted
|
||||
$redis_proxy.del d_root_key
|
||||
$redis_proxy.del d_www_key
|
||||
end
|
||||
else
|
||||
$redis_proxy.hdel u_key, 'domain'
|
||||
end
|
||||
|
@ -1039,8 +958,6 @@ class Site < Sequel::Model
|
|||
|
||||
if is_deleted
|
||||
$redis_proxy.del u_key
|
||||
$redis_proxy.del d_root_key
|
||||
$redis_proxy.del d_www_key
|
||||
end
|
||||
|
||||
true
|
||||
|
@ -1070,7 +987,7 @@ class Site < Sequel::Model
|
|||
super
|
||||
|
||||
if !self.class.valid_username?(values[:username])
|
||||
errors.add :username, 'Usernames can only contain letters, numbers, and hyphens.'
|
||||
errors.add :username, 'Usernames can only contain letters, numbers, and hyphens, and cannot start or end with a hyphen.'
|
||||
end
|
||||
|
||||
if !values[:username].blank?
|
||||
|
@ -1120,7 +1037,7 @@ class Site < Sequel::Model
|
|||
errors.add :tipping_paypal, 'A valid PayPal tipping email address is required.'
|
||||
end
|
||||
|
||||
if !values[:tipping_bitcoin].blank? && !BitcoinValidator.valid_address?(values[:tipping_bitcoin])
|
||||
if !values[:tipping_bitcoin].blank? && !AdequateCryptoAddress.valid?(values[:tipping_bitcoin], 'BTC')
|
||||
errors.add :tipping_bitcoin, 'Bitcoin tipping address is not valid.'
|
||||
end
|
||||
|
||||
|
@ -1572,6 +1489,10 @@ class Site < Sequel::Model
|
|||
File.exist? File.join(base_screenshots_path, "#{path}.#{resolution}.webp")
|
||||
end
|
||||
|
||||
def sharing_screenshot_url
|
||||
'https://neocities.org'+base_screenshots_url+'/index.html.jpg'
|
||||
end
|
||||
|
||||
def screenshot_url(path, resolution)
|
||||
path[0] = '' if path[0] == '/'
|
||||
out = ''
|
||||
|
@ -1602,18 +1523,20 @@ class Site < Sequel::Model
|
|||
end
|
||||
|
||||
def to_rss
|
||||
RSS::Maker.make("atom") do |maker|
|
||||
maker.channel.title = title
|
||||
maker.channel.updated = (updated_at ? updated_at : created_at)
|
||||
maker.channel.author = username
|
||||
maker.channel.id = "#{username}.neocities.org"
|
||||
RSS::Maker.make("2.0") do |m|
|
||||
m.channel.title = title
|
||||
m.channel.link = uri
|
||||
m.channel.description = "Site feed for #{title}"
|
||||
m.image.url = sharing_screenshot_url
|
||||
m.image.title = title
|
||||
|
||||
|
||||
latest_events.each do |event|
|
||||
if event.site_change_id
|
||||
maker.items.new_item do |item|
|
||||
item.link = "https://#{host}"
|
||||
item.title = "#{title} has been updated"
|
||||
item.updated = event.site_change.created_at
|
||||
m.items.new_item do |i|
|
||||
i.title = "#{title} has been updated."
|
||||
i.link = "https://neocities.org/site/#{username}?event_id=#{event.id.to_s}"
|
||||
i.pubDate = event.created_at
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1712,10 +1635,6 @@ class Site < Sequel::Model
|
|||
time,
|
||||
self.id
|
||||
].first
|
||||
|
||||
if ipfs_archiving_enabled == true
|
||||
ArchiveWorker.perform_in Archive::ARCHIVE_WAIT_TIME, self.id
|
||||
end
|
||||
end
|
||||
|
||||
reload
|
||||
|
@ -1790,6 +1709,11 @@ class Site < Sequel::Model
|
|||
end
|
||||
end
|
||||
|
||||
def phone_verification_needed?
|
||||
return true if phone_verification_required && !phone_verified
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def store_file(path, uploaded, opts={})
|
||||
|
|
|
@ -105,7 +105,7 @@ class SiteFile < Sequel::Model
|
|||
DB['update sites set space_used=space_used-? where id=?', size, site_id].first
|
||||
end
|
||||
|
||||
site.delete_cache site.files_path(path)
|
||||
site.purge_cache site.files_path(path)
|
||||
SiteChangeFile.filter(site_id: site_id, filename: path).delete
|
||||
end
|
||||
end
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 5.3 KiB |
BIN
public/img/favicon.png
Normal file
BIN
public/img/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
BIN
public/img/neocities-front-screenshot.jpg
Normal file
BIN
public/img/neocities-front-screenshot.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 222 KiB |
70
public/img/plaincat.svg
Normal file
70
public/img/plaincat.svg
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
viewBox="0 0 154 145"
|
||||
width="154.0pt"
|
||||
height="145.0pt"
|
||||
id="svg93"
|
||||
sodipodi:docname="plaincat.svg"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs97" />
|
||||
<sodipodi:namedview
|
||||
id="namedview95"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="pt"
|
||||
showgrid="false"
|
||||
inkscape:zoom="3.0181034"
|
||||
inkscape:cx="205.59269"
|
||||
inkscape:cy="114.8072"
|
||||
inkscape:window-width="2490"
|
||||
inkscape:window-height="1376"
|
||||
inkscape:window-x="70"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg93" />
|
||||
<path
|
||||
d="m 112.83494,2.7962244 c 5.31573,0.7723715 8.39385,4.7591726 11.41519,8.7686886 5.72463,6.621952 9.90454,14.629628 13.80046,22.501003 4.67967,11.914969 11.11988,24.409214 12.68735,37.141988 1.71512,11.108521 2.95318,23.886729 -1.79463,34.438686 -3.63468,8.01904 -9.87046,12.18757 -15.77683,18.28703 -4.14582,3.78235 -9.30253,6.53108 -14.62962,8.21213 -6.29256,2.12403 -12.66463,4.15717 -19.127567,5.64513 -11.222101,1.88549 -21.637759,4.54336 -33.143824,2.74873 C 55.804377,138.65412 44.082501,136.08711 34.9617,130.41927 26.976741,123.54744 21.081729,114.92641 16.810968,105.33992 11.858702,98.27499 9.7346813,90.301385 6.9745879,82.236918 5.9296149,79.033847 4.430306,76.364621 4.0554777,72.957099 3.3739738,66.903069 2.7265442,60.826322 2.1472659,54.749574 2.1359072,45.617415 3.4421245,36.689709 5.3276193,27.773359 c 1.8400614,-0.601995 3.2939376,-1.385725 5.2362257,-0.522488 7.68964,3.509749 11.312973,10.801847 18.582352,14.675063 2.737376,1.601535 5.327093,3.316654 7.825942,5.270299 4.35027,-1.090405 8.382503,-2.862317 12.460173,-4.668304 C 59.030164,38.950031 68.90062,35.974128 78.725641,33.11181 84.64337,31.442124 90.629249,31.328541 96.717356,31.487559 100.05673,23.6389 103.24844,15.847033 107.1898,8.202825 c 1.36302,-2.1353791 2.74874,-5.247583 5.64514,-5.4066006 z"
|
||||
fill="#000000"
|
||||
id="path79"
|
||||
style="stroke-width:1.13584" />
|
||||
<path
|
||||
d="m 114.47053,13.893387 c 8.18942,9.20031 14.28888,20.513282 18.52557,32.042065 2.77146,6.167613 5.31574,12.335228 7.22395,18.832236 -1.91957,2.044514 -3.81642,4.009517 -6.31527,5.338451 -1.59019,1.044973 -3.91866,1.919571 -4.49793,3.884576 -0.31804,1.953645 0.94274,3.305297 2.6465,4.009517 3.43025,-0.567922 6.74689,-1.942288 10.09763,-2.907752 0.19309,2.646508 0.49977,5.383883 0.32938,8.04175 -2.55563,2.964545 -7.82593,3.49839 -9.79094,6.746895 0.1931,4.622871 6.89456,3.70284 9.89318,2.532925 -0.11359,2.146738 -0.10221,4.372985 -0.53385,6.48565 -3.06677,9.16623 -9.46154,14.66369 -16.42425,20.87675 -3.89594,2.95318 -8.32572,4.49793 -12.94858,5.89501 4.14581,-3.94137 7.25802,-8.50744 7.86001,-14.34566 0.22717,-5.25894 0.38619,-10.54061 -4.60015,-13.6528 l -0.26125,-0.49978 -0.72692,-1.13584 c -3.05542,-2.41934 -6.5311,-4.532003 -10.2453,-5.747353 -3.37346,-0.942748 -7.178507,-0.670146 -10.65418,-0.726938 -0.726938,-4.759172 -1.544742,-9.450194 -3.112204,-14.016273 -1.385725,-3.907293 -1.226707,-8.973141 -3.487031,-12.437455 -2.192171,-1.999079 -5.679202,-2.816885 -8.450653,-3.736915 -3.884575,3.305296 -2.055871,10.25664 -0.795089,14.470609 2.703301,6.019956 6.315275,11.710516 9.711438,17.367003 -3.055412,2.737375 -6.91727,4.759172 -8.325713,8.734612 -1.431158,4.00952 -5.270299,8.4393 -4.248043,12.74413 0.511128,3.00999 1.124483,6.15627 2.373908,8.95042 2.555641,3.74828 5.985879,7.03087 9.427477,9.96134 -5.06585,0.24987 -10.120341,0.19308 -15.197548,0.19308 -5.997239,0.11359 -11.846818,-2.01044 -17.480587,-3.89593 2.907751,-2.04451 6.201689,-2.51021 7.223946,-6.20169 0.318035,-2.93047 -2.158098,-3.53246 -4.509287,-3.79371 -4.407063,0.6815 -9.064008,4.47521 -13.425637,4.69102 -3.509747,-1.73783 -6.292557,-5.27031 -9.086724,-7.96224 4.441136,-1.19262 8.734615,-2.62379 12.698698,-5.02041 1.499309,-0.88596 2.521566,-2.22626 3.248505,-3.78236 -0.193094,-3.20307 -3.21443,-4.05495 -5.929089,-3.60061 -5.065849,1.18129 -9.995398,3.02134 -14.993096,4.48657 C 26.874515,103.7611 26.13622,100.1832 24.352949,96.650738 22.56968,92.98197 20.50245,89.574447 19.355251,85.621722 17.265305,78.624944 14.823247,72.286953 14.153101,64.960781 13.142203,56.884955 12.88096,49.229388 13.414804,41.09677 c 5.145359,1.908212 9.086725,6.565159 13.61873,9.745513 2.498849,1.669685 5.33845,5.156716 8.586956,4.168535 7.825941,-1.306217 14.198007,-4.668305 21.739989,-6.724177 9.938605,-2.816885 20.058946,-4.838681 29.940757,-7.894093 4.85004,-1.70376 10.211207,-0.272602 14.902234,-1.98772 2.9759,-2.464776 4.30483,-6.656026 5.75871,-10.108982 2.05588,-4.838681 4.15716,-9.700079 6.50835,-14.402459 z"
|
||||
fill="#f59c32"
|
||||
id="path81"
|
||||
style="stroke-width:1.13584" />
|
||||
<path
|
||||
d="m 78.998242,59.372446 c 2.771451,0.92003 6.258482,1.737836 8.450653,3.736915 2.260324,3.464314 2.101306,8.530162 3.487031,12.437455 1.567462,4.566079 2.385266,9.257101 3.112204,14.016273 3.475673,0.05679 7.28072,-0.21581 10.65418,0.726938 3.7142,1.21535 7.18988,3.328013 10.2453,5.747353 l 0.72692,1.13584 c -4.17989,1.10177 -8.75732,2.21488 -12.93722,0.4657 -3.452953,-1.101765 -5.917732,-3.770995 -8.655105,-6.019959 -1.238065,5.327096 -2.578359,11.029019 -6.12218,15.436079 -3.100846,3.56653 -9.631929,1.59017 -12.096702,5.33844 0.954105,1.19264 1.828703,2.57837 2.998618,3.56655 3.225788,0.42026 6.769611,-0.43162 10.029473,-0.62472 2.317114,3.33937 5.417959,5.98589 9.166239,7.57607 4.543357,1.8287 10.267997,-0.36348 12.539677,-4.66832 1.27213,-3.91865 0.28395,-8.14397 -0.90867,-11.94904 3.82777,-2.11266 6.86047,-3.54383 6.24712,-8.62102 4.98634,3.11219 4.82732,8.39386 4.60015,13.6528 -0.60199,5.83822 -3.7142,10.40429 -7.86001,14.34566 -8.12126,2.57836 -16.99218,5.98589 -25.5337,5.9291 -3.441598,-2.93047 -6.871836,-6.21306 -9.427477,-9.96134 -1.249425,-2.79415 -1.86278,-5.94043 -2.373908,-8.95042 -1.022256,-4.30483 2.816885,-8.73461 4.248043,-12.74413 1.408443,-3.97544 5.270301,-5.997237 8.325713,-8.734612 C 84.518428,85.553571 80.906454,79.863011 78.203153,73.843055 76.942371,69.629086 75.113667,62.677742 78.998242,59.372446 Z"
|
||||
fill="#ffffff"
|
||||
id="path83"
|
||||
style="stroke-width:1.13584" />
|
||||
<path
|
||||
d="m 99.625105,68.833998 c 5.156715,-1.783269 7.905455,4.395703 7.178515,8.450654 -1.5561,5.111283 -10.461091,5.190792 -11.721875,-0.181735 -1.20399,-3.941367 0.885956,-6.951344 4.54336,-8.268919 z"
|
||||
fill="#000000"
|
||||
id="path85"
|
||||
style="stroke-width:1.13584" />
|
||||
<path
|
||||
d="m 67.957871,78.272834 c 1.987722,-0.556563 4.327553,-0.715581 6.201689,0.283959 3.657407,1.476593 5.020416,7.314814 2.158099,10.120341 -2.998621,3.782348 -9.098084,3.589256 -11.540141,-0.726938 -1.033616,-3.464314 -0.36347,-7.939526 3.180353,-9.677362 z"
|
||||
fill="#000000"
|
||||
id="path87"
|
||||
style="stroke-width:1.13584" />
|
||||
<path
|
||||
d="m 94.082205,91.618961 c 2.737373,2.248964 5.202152,4.918194 8.655105,6.019959 4.1799,1.74918 8.75733,0.63607 12.93722,-0.4657 l 0.26125,0.49978 c 0.61335,5.07719 -2.41935,6.50836 -6.24712,8.62102 1.19262,3.80507 2.1808,8.03039 0.90867,11.94904 -2.27168,4.30484 -7.99632,6.49702 -12.539677,4.66832 -3.74828,-1.59018 -6.849125,-4.2367 -9.166239,-7.57607 -3.259862,0.1931 -6.803685,1.04498 -10.029473,0.62472 -1.169915,-0.98818 -2.044513,-2.37391 -2.998618,-3.56655 2.464773,-3.74827 8.995856,-1.77191 12.096702,-5.33844 3.543821,-4.40706 4.884115,-10.108983 6.12218,-15.436079 z"
|
||||
fill="#000000"
|
||||
id="path89"
|
||||
style="stroke-width:1.13584" />
|
||||
<path
|
||||
d="m 99.238919,103.39763 c 1.999081,1.0904 4.225331,1.81734 5.213511,4.03224 1.49931,3.07812 0.88596,7.92816 -2.94183,8.9277 -3.078124,0.28397 -5.508823,-3.18035 -7.632845,-4.98633 2.055871,-2.49887 3.691476,-5.22488 5.361164,-7.97361 z"
|
||||
fill="#ea1d3c"
|
||||
id="path91"
|
||||
style="stroke-width:1.13584" />
|
||||
</svg>
|
After Width: | Height: | Size: 8 KiB |
11
public/js/Chart.min.js
vendored
11
public/js/Chart.min.js
vendored
File diff suppressed because one or more lines are too long
20
public/js/chart.js
Normal file
20
public/js/chart.js
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,13 +1,14 @@
|
|||
require 'facter'
|
||||
|
||||
threads 1, 1
|
||||
#threads 5, 5
|
||||
#threads 1, 1
|
||||
environment 'production'
|
||||
#daemonize
|
||||
pidfile '/var/run/neocities/neocities.pid'
|
||||
stdout_redirect '/var/log/neocities/neocities.stdout.log', '/var/log/neocities/neocities.stderr.log', true
|
||||
quiet
|
||||
workers Facter.value('processors')['count']
|
||||
preload_app!
|
||||
#preload_app!
|
||||
prune_bundler
|
||||
on_worker_boot { DB.disconnect }
|
||||
bind 'unix:/var/run/neocities/neocities.sock?backlog=2048'
|
||||
supported_http_methods Puma::Const::IANA_HTTP_METHODS
|
||||
|
|
|
@ -2,6 +2,8 @@ require_relative './environment.rb'
|
|||
|
||||
describe '/admin' do
|
||||
include Capybara::DSL
|
||||
include Capybara::Minitest::Assertions
|
||||
|
||||
|
||||
before do
|
||||
Capybara.reset_sessions!
|
||||
|
|
|
@ -2,6 +2,7 @@ require_relative './environment.rb'
|
|||
|
||||
describe '/browse' do
|
||||
include Capybara::DSL
|
||||
include Capybara::Minitest::Assertions
|
||||
|
||||
=begin
|
||||
describe 'as admin' do
|
||||
|
|
|
@ -4,8 +4,8 @@ describe 'dashboard' do
|
|||
describe 'create directory' do
|
||||
|
||||
describe 'logged in' do
|
||||
|
||||
include Capybara::DSL
|
||||
include Capybara::Minitest::Assertions
|
||||
|
||||
before do
|
||||
Capybara.reset_sessions!
|
||||
|
|
|
@ -2,6 +2,7 @@ require_relative './environment.rb'
|
|||
|
||||
describe 'signup' do
|
||||
include Capybara::DSL
|
||||
include Capybara::Minitest::Assertions
|
||||
|
||||
def fill_in_valid
|
||||
@site = Fabricate.attributes_for(:site)
|
||||
|
@ -13,7 +14,7 @@ describe 'signup' do
|
|||
end
|
||||
|
||||
before do
|
||||
Capybara.default_driver = :apparition
|
||||
Capybara.default_driver = :selenium_chrome_headless
|
||||
Capybara.reset_sessions!
|
||||
visit '/education'
|
||||
_(page).must_have_content 'Neocities' # Used to force load wait
|
||||
|
|
|
@ -1,38 +1,17 @@
|
|||
require_relative '../environment'
|
||||
|
||||
require 'capybara'
|
||||
require 'capybara/minitest'
|
||||
require 'capybara/minitest/spec'
|
||||
require 'rack_session_access/capybara'
|
||||
require 'capybara/apparition'
|
||||
|
||||
Capybara.app = Sinatra::Application
|
||||
|
||||
include Capybara::Minitest::Assertions
|
||||
Capybara.default_max_wait_time = 5
|
||||
|
||||
#Capybara.register_driver :apparition do |app|
|
||||
# Capybara::Apparition::Driver.new(app, headless: false)
|
||||
#end
|
||||
Capybara.register_driver :selenium_chrome_headless_largewindow do |app|
|
||||
options = ::Selenium::WebDriver::Chrome::Options.new
|
||||
options.add_argument('--headless')
|
||||
options.add_argument('--window-size=1280,800') # Set your desired window size
|
||||
|
||||
=begin
|
||||
def setup
|
||||
Capybara.current_driver = :apparition
|
||||
end
|
||||
|
||||
def teardown
|
||||
Capybara.reset_sessions!
|
||||
Capybara.use_default_driver
|
||||
end
|
||||
=end
|
||||
=begin
|
||||
require 'capybara'
|
||||
require 'capybara/dsl'
|
||||
require 'capybara/poltergeist'
|
||||
require 'rack_session_access/capybara'
|
||||
|
||||
Capybara.app = Sinatra::Application
|
||||
|
||||
def teardown
|
||||
Capybara.reset_sessions!
|
||||
end
|
||||
=end
|
||||
Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
|
||||
end
|
|
@ -2,6 +2,7 @@ require_relative './environment.rb'
|
|||
|
||||
describe '/' do
|
||||
include Capybara::DSL
|
||||
include Capybara::Minitest::Assertions
|
||||
|
||||
describe 'news feed' do
|
||||
before do
|
||||
|
@ -21,7 +22,8 @@ describe '/' do
|
|||
@another_site = Fabricate :site
|
||||
@followed_site.toggle_follow @another_site
|
||||
visit '/'
|
||||
_(find('.news-item', match: :first).text).must_match /#{@followed_site.username} followed #{@another_site.username}/i
|
||||
_(page).must_have_link(@followed_site.title, href: "/site/#{@followed_site.username}")
|
||||
#_(find('.news-item', match: :first).text).must_match /#{@followed_site.username} followed #{@another_site.username}/i
|
||||
end
|
||||
|
||||
it 'loads my activities only' do
|
||||
|
@ -30,7 +32,7 @@ describe '/' do
|
|||
@another_site = Fabricate :site
|
||||
@followed_site.toggle_follow @another_site
|
||||
visit '/?activity=mine'
|
||||
_(find('.news-item').text).must_match //i
|
||||
_(page).must_have_link(@followed_site.title, href: "/site/#{@followed_site.username}")
|
||||
end
|
||||
|
||||
it 'loads a specific event with the id' do
|
||||
|
|
|
@ -2,6 +2,7 @@ require_relative './environment.rb'
|
|||
|
||||
describe '/password_reset' do
|
||||
include Capybara::DSL
|
||||
include Capybara::Minitest::Assertions
|
||||
|
||||
before do
|
||||
Capybara.reset_sessions!
|
||||
|
@ -67,7 +68,7 @@ describe '/password_reset' do
|
|||
fill_in 'email', with: @site.email
|
||||
click_button 'Send Reset Token'
|
||||
|
||||
_(body).must_match /send an e-mail to your account with password reset instructions/
|
||||
_(body).must_match /We sent an e-mail with password reset instructions/
|
||||
_(@site.reload.password_reset_token.blank?).must_equal false
|
||||
_(EmailWorker.jobs.first['args'].first['body']).must_match /#{Rack::Utils.build_query(username: @site.username, token: @site.password_reset_token)}/
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
require_relative '../environment.rb'
|
||||
|
||||
describe 'site/settings' do
|
||||
describe 'email' do
|
||||
include Capybara::DSL
|
||||
include Capybara::DSL
|
||||
include Capybara::Minitest::Assertions
|
||||
|
||||
describe 'email' do
|
||||
before do
|
||||
EmailWorker.jobs.clear
|
||||
@email = "#{SecureRandom.uuid.gsub('-', '')}@exampleedsdfdsf.com"
|
||||
|
@ -84,8 +85,6 @@ describe 'site/settings' do
|
|||
end
|
||||
|
||||
describe 'unsubscribe email' do
|
||||
include Capybara::DSL
|
||||
|
||||
before do
|
||||
@email = "#{SecureRandom.uuid.gsub('-', '')}@exampleedsdfdsf.com"
|
||||
@site = Fabricate :site, email: @email
|
||||
|
@ -127,8 +126,6 @@ describe 'site/settings' do
|
|||
end
|
||||
|
||||
describe 'change password' do
|
||||
include Capybara::DSL
|
||||
|
||||
before do
|
||||
EmailWorker.jobs.clear
|
||||
@site = Fabricate :site, password: 'derpie'
|
||||
|
|
|
@ -1,88 +1,10 @@
|
|||
require_relative '../environment.rb'
|
||||
|
||||
def generate_ssl_certs(opts={})
|
||||
# https://github.com/kyledrake/ruby-openssl-cheat-sheet/blob/master/certificate_authority.rb
|
||||
res = {}
|
||||
|
||||
ca_keypair = OpenSSL::PKey::RSA.new(2048)
|
||||
ca_cert = OpenSSL::X509::Certificate.new
|
||||
ca_cert.not_before = Time.now
|
||||
ca_cert.subject = OpenSSL::X509::Name.new([
|
||||
["C", "US"],
|
||||
["ST", "Oregon"],
|
||||
["L", "Portland"],
|
||||
["CN", "Neocities CA"]
|
||||
])
|
||||
ca_cert.issuer = ca_cert.subject
|
||||
ca_cert.not_after = Time.now + 1000000000 # 40 or so years
|
||||
ca_cert.serial = 1
|
||||
ca_cert.public_key = ca_keypair.public_key
|
||||
ef = OpenSSL::X509::ExtensionFactory.new
|
||||
ef.subject_certificate = ca_cert
|
||||
ef.issuer_certificate = ca_cert
|
||||
# Read more about the various extensions here: http://www.openssl.org/docs/apps/x509v3_config.html
|
||||
ca_cert.add_extension(ef.create_extension("basicConstraints", "CA:TRUE", true))
|
||||
ca_cert.add_extension(ef.create_extension("keyUsage","keyCertSign, cRLSign", true))
|
||||
ca_cert.add_extension(ef.create_extension("subjectKeyIdentifier", "hash", false))
|
||||
ca_cert.add_extension(ef.create_extension("authorityKeyIdentifier", "keyid:always", false))
|
||||
ca_cert.sign(ca_keypair, OpenSSL::Digest::SHA256.new)
|
||||
res[:ca_cert] = ca_cert
|
||||
res[:ca_keypair] = ca_keypair
|
||||
|
||||
ca_cert = OpenSSL::X509::Certificate.new(res[:ca_cert].to_pem)
|
||||
our_cert_keypair = OpenSSL::PKey::RSA.new(2048)
|
||||
our_cert_req = OpenSSL::X509::Request.new
|
||||
our_cert_req.subject = OpenSSL::X509::Name.new([
|
||||
["C", "US"],
|
||||
["ST", "Oregon"],
|
||||
["L", "Portland"],
|
||||
["O", "Neocities User"],
|
||||
["CN", "*.#{opts[:domain]}"]
|
||||
])
|
||||
our_cert_req.public_key = our_cert_keypair.public_key
|
||||
our_cert_req.sign our_cert_keypair, OpenSSL::Digest::SHA1.new
|
||||
our_cert = OpenSSL::X509::Certificate.new
|
||||
our_cert.subject = our_cert_req.subject
|
||||
our_cert.issuer = ca_cert.subject
|
||||
our_cert.not_before = Time.now
|
||||
if opts[:expired]
|
||||
our_cert.not_after = Time.now - 100000000
|
||||
else
|
||||
our_cert.not_after = Time.now + 100000000
|
||||
end
|
||||
our_cert.serial = 123 # Should be an unique number, the CA probably has a database.
|
||||
our_cert.public_key = our_cert_req.public_key
|
||||
# To make the certificate valid for both wildcard and top level domain name, we need an extension.
|
||||
ef = OpenSSL::X509::ExtensionFactory.new
|
||||
ef.subject_certificate = our_cert
|
||||
ef.issuer_certificate = ca_cert
|
||||
our_cert.add_extension(ef.create_extension("subjectAltName", "DNS:#{@domain}, DNS:*.#{@domain}", false))
|
||||
our_cert.sign res[:ca_keypair], OpenSSL::Digest::SHA1.new
|
||||
|
||||
our_cert_tmpfile = Tempfile.new 'our_cert'
|
||||
our_cert_tmpfile.write our_cert.to_pem
|
||||
our_cert_tmpfile.close
|
||||
res[:cert_path] = our_cert_tmpfile.path
|
||||
|
||||
res[:key_path] = '/tmp/nc_test_our_cert_keypair'
|
||||
File.write res[:key_path], our_cert_keypair.to_pem
|
||||
|
||||
res[:cert_intermediate_path] = '/tmp/nc_test_ca_cert'
|
||||
File.write res[:cert_intermediate_path], res[:ca_cert].to_pem
|
||||
|
||||
res[:combined_cert_path] = '/tmp/nc_test_combined_cert'
|
||||
File.write res[:combined_cert_path], "#{File.read(res[:cert_path])}\n#{File.read(res[:cert_intermediate_path])}"
|
||||
|
||||
res[:bad_combined_cert_path] = '/tmp/nc_test_bad_combined_cert'
|
||||
File.write res[:bad_combined_cert_path], "#{File.read(res[:cert_intermediate_path])}\n#{File.read(res[:cert_path])}"
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
describe 'site/settings' do
|
||||
describe 'permissions' do
|
||||
include Capybara::DSL
|
||||
include Capybara::DSL
|
||||
include Capybara::Minitest::Assertions
|
||||
|
||||
describe 'permissions' do
|
||||
before do
|
||||
@parent_site = Fabricate :site
|
||||
@child_site = Fabricate :site, parent_site_id: @parent_site.id
|
||||
|
@ -104,8 +26,6 @@ describe 'site/settings' do
|
|||
end
|
||||
|
||||
describe 'changing username' do
|
||||
include Capybara::DSL
|
||||
|
||||
before do
|
||||
Capybara.reset_sessions!
|
||||
@site = Fabricate :site
|
||||
|
@ -143,142 +63,138 @@ describe 'site/settings' do
|
|||
_(page).must_have_content /You already have this name/
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'api key' do
|
||||
include Capybara::DSL
|
||||
describe 'api key' do
|
||||
before do
|
||||
Capybara.reset_sessions!
|
||||
@site = Fabricate :site
|
||||
@child_site = Fabricate :site, parent_site_id: @site.id
|
||||
page.set_rack_session id: @site.id
|
||||
end
|
||||
|
||||
before do
|
||||
Capybara.reset_sessions!
|
||||
@site = Fabricate :site
|
||||
@child_site = Fabricate :site, parent_site_id: @site.id
|
||||
page.set_rack_session id: @site.id
|
||||
it 'sets api key' do
|
||||
visit "/settings/#{@child_site[:username]}#api_key"
|
||||
_(@site.api_key).must_be_nil
|
||||
_(@child_site.api_key).must_be_nil
|
||||
click_button 'Generate API Key'
|
||||
_(@site.reload.api_key).must_be_nil
|
||||
_(@child_site.reload.api_key).wont_be_nil
|
||||
_(page.body).must_match @child_site.api_key
|
||||
end
|
||||
|
||||
it 'regenerates api key for child site' do
|
||||
visit "/settings/#{@child_site[:username]}#api_key"
|
||||
@child_site.generate_api_key!
|
||||
api_key = @child_site.api_key
|
||||
click_button 'Generate API Key'
|
||||
_(@child_site.reload.api_key).wont_equal api_key
|
||||
end
|
||||
end
|
||||
|
||||
it 'sets api key' do
|
||||
visit "/settings/#{@child_site[:username]}#api_key"
|
||||
_(@site.api_key).must_be_nil
|
||||
_(@child_site.api_key).must_be_nil
|
||||
click_button 'Generate API Key'
|
||||
_(@site.reload.api_key).must_be_nil
|
||||
_(@child_site.reload.api_key).wont_be_nil
|
||||
_(page.body).must_match @child_site.api_key
|
||||
describe 'delete' do
|
||||
before do
|
||||
Capybara.reset_sessions!
|
||||
@site = Fabricate :site
|
||||
page.set_rack_session id: @site.id
|
||||
visit "/settings/#{@site[:username]}#delete"
|
||||
end
|
||||
|
||||
it 'fails for incorrect entered username' do
|
||||
fill_in 'username', with: 'NOPE'
|
||||
click_button 'Delete Site'
|
||||
|
||||
_(page.body).must_match /Site user name and entered user name did not match/i
|
||||
_(@site.reload.is_deleted).must_equal false
|
||||
end
|
||||
|
||||
it 'succeeds' do
|
||||
deleted_reason = 'Penelope left a hairball on my site'
|
||||
|
||||
fill_in 'confirm_username', with: @site.username
|
||||
fill_in 'deleted_reason', with: deleted_reason
|
||||
click_button 'Delete Site'
|
||||
|
||||
@site.reload
|
||||
_(@site.is_deleted).must_equal true
|
||||
_(@site.deleted_reason).must_equal deleted_reason
|
||||
_(page.current_path).must_equal '/'
|
||||
|
||||
_(File.exist?(@site.files_path('./index.html'))).must_equal false
|
||||
_(Dir.exist?(@site.files_path)).must_equal false
|
||||
|
||||
path = File.join Site::DELETED_SITES_ROOT, Site.sharding_dir(@site.username), @site.username
|
||||
_(Dir.exist?(path)).must_equal true
|
||||
_(File.exist?(File.join(path, 'index.html'))).must_equal true
|
||||
|
||||
visit "/site/#{@site.username}"
|
||||
_(page.status_code).must_equal 404
|
||||
end
|
||||
|
||||
it 'stops charging for supporter account' do
|
||||
customer = Stripe::Customer.create(
|
||||
source: $stripe_helper.generate_card_token
|
||||
)
|
||||
|
||||
subscription = customer.subscriptions.create plan: 'supporter'
|
||||
|
||||
@site.update(
|
||||
stripe_customer_id: customer.id,
|
||||
stripe_subscription_id: subscription.id,
|
||||
plan_type: 'supporter'
|
||||
)
|
||||
|
||||
@site.plan_type = subscription.plan.id
|
||||
@site.save_changes
|
||||
|
||||
fill_in 'confirm_username', with: @site.username
|
||||
fill_in 'deleted_reason', with: 'derp'
|
||||
click_button 'Delete Site'
|
||||
|
||||
_(Stripe::Customer.retrieve(@site.stripe_customer_id).subscriptions.count).must_equal 0
|
||||
@site.reload
|
||||
_(@site.stripe_subscription_id).must_be_nil
|
||||
_(@site.is_deleted).must_equal true
|
||||
end
|
||||
|
||||
it 'should fail unless owned by current user' do
|
||||
someone_elses_site = Fabricate :site
|
||||
page.set_rack_session id: @site.id
|
||||
|
||||
page.driver.post "/settings/#{someone_elses_site.username}/delete", {
|
||||
username: someone_elses_site.username,
|
||||
deleted_reason: 'Dade Murphy enters Acid Burns turf'
|
||||
}
|
||||
|
||||
_(page.driver.status_code).must_equal 302
|
||||
_(URI.parse(page.driver.response_headers['Location']).path).must_equal '/'
|
||||
someone_elses_site.reload
|
||||
_(someone_elses_site.is_deleted).must_equal false
|
||||
end
|
||||
|
||||
it 'should not show NSFW tab for admin NSFW flag' do
|
||||
owned_site = Fabricate :site, parent_site_id: @site.id, admin_nsfw: true
|
||||
visit "/settings/#{owned_site.username}"
|
||||
_(page.body).wont_match /18\+/
|
||||
end
|
||||
|
||||
it 'should succeed if you own the site' do
|
||||
owned_site = Fabricate :site, parent_site_id: @site.id
|
||||
visit "/settings/#{owned_site.username}#delete"
|
||||
fill_in 'confirm_username', with: owned_site.username
|
||||
click_button 'Delete Site'
|
||||
|
||||
@site.reload
|
||||
owned_site.reload
|
||||
_(owned_site.is_deleted).must_equal true
|
||||
_(@site.is_deleted).must_equal false
|
||||
|
||||
_(page.current_path).must_equal "/settings"
|
||||
end
|
||||
|
||||
it 'fails to delete parent site if children exist' do
|
||||
owned_site = Fabricate :site, parent_site_id: @site.id
|
||||
visit "/settings/#{@site.username}#delete"
|
||||
_(page.body).must_match /You cannot delete the parent site without deleting the children sites first/i
|
||||
end
|
||||
end
|
||||
|
||||
it 'regenerates api key for child site' do
|
||||
visit "/settings/#{@child_site[:username]}#api_key"
|
||||
@child_site.generate_api_key!
|
||||
api_key = @child_site.api_key
|
||||
click_button 'Generate API Key'
|
||||
_(@child_site.reload.api_key).wont_equal api_key
|
||||
end
|
||||
end
|
||||
|
||||
describe 'delete' do
|
||||
include Capybara::DSL
|
||||
|
||||
before do
|
||||
Capybara.reset_sessions!
|
||||
@site = Fabricate :site
|
||||
page.set_rack_session id: @site.id
|
||||
visit "/settings/#{@site[:username]}#delete"
|
||||
end
|
||||
|
||||
it 'fails for incorrect entered username' do
|
||||
fill_in 'username', with: 'NOPE'
|
||||
click_button 'Delete Site'
|
||||
|
||||
_(page.body).must_match /Site user name and entered user name did not match/i
|
||||
_(@site.reload.is_deleted).must_equal false
|
||||
end
|
||||
|
||||
it 'succeeds' do
|
||||
deleted_reason = 'Penelope left a hairball on my site'
|
||||
|
||||
fill_in 'confirm_username', with: @site.username
|
||||
fill_in 'deleted_reason', with: deleted_reason
|
||||
click_button 'Delete Site'
|
||||
|
||||
@site.reload
|
||||
_(@site.is_deleted).must_equal true
|
||||
_(@site.deleted_reason).must_equal deleted_reason
|
||||
_(page.current_path).must_equal '/'
|
||||
|
||||
_(File.exist?(@site.files_path('./index.html'))).must_equal false
|
||||
_(Dir.exist?(@site.files_path)).must_equal false
|
||||
|
||||
path = File.join Site::DELETED_SITES_ROOT, Site.sharding_dir(@site.username), @site.username
|
||||
_(Dir.exist?(path)).must_equal true
|
||||
_(File.exist?(File.join(path, 'index.html'))).must_equal true
|
||||
|
||||
visit "/site/#{@site.username}"
|
||||
_(page.status_code).must_equal 404
|
||||
end
|
||||
|
||||
it 'stops charging for supporter account' do
|
||||
customer = Stripe::Customer.create(
|
||||
source: $stripe_helper.generate_card_token
|
||||
)
|
||||
|
||||
subscription = customer.subscriptions.create plan: 'supporter'
|
||||
|
||||
@site.update(
|
||||
stripe_customer_id: customer.id,
|
||||
stripe_subscription_id: subscription.id,
|
||||
plan_type: 'supporter'
|
||||
)
|
||||
|
||||
@site.plan_type = subscription.plan.id
|
||||
@site.save_changes
|
||||
|
||||
fill_in 'confirm_username', with: @site.username
|
||||
fill_in 'deleted_reason', with: 'derp'
|
||||
click_button 'Delete Site'
|
||||
|
||||
_(Stripe::Customer.retrieve(@site.stripe_customer_id).subscriptions.count).must_equal 0
|
||||
@site.reload
|
||||
_(@site.stripe_subscription_id).must_be_nil
|
||||
_(@site.is_deleted).must_equal true
|
||||
end
|
||||
|
||||
it 'should fail unless owned by current user' do
|
||||
someone_elses_site = Fabricate :site
|
||||
page.set_rack_session id: @site.id
|
||||
|
||||
page.driver.post "/settings/#{someone_elses_site.username}/delete", {
|
||||
username: someone_elses_site.username,
|
||||
deleted_reason: 'Dade Murphy enters Acid Burns turf'
|
||||
}
|
||||
|
||||
_(page.driver.status_code).must_equal 302
|
||||
_(URI.parse(page.driver.response_headers['Location']).path).must_equal '/'
|
||||
someone_elses_site.reload
|
||||
_(someone_elses_site.is_deleted).must_equal false
|
||||
end
|
||||
|
||||
it 'should not show NSFW tab for admin NSFW flag' do
|
||||
owned_site = Fabricate :site, parent_site_id: @site.id, admin_nsfw: true
|
||||
visit "/settings/#{owned_site.username}"
|
||||
_(page.body).wont_match /18\+/
|
||||
end
|
||||
|
||||
it 'should succeed if you own the site' do
|
||||
owned_site = Fabricate :site, parent_site_id: @site.id
|
||||
visit "/settings/#{owned_site.username}#delete"
|
||||
fill_in 'confirm_username', with: owned_site.username
|
||||
click_button 'Delete Site'
|
||||
|
||||
@site.reload
|
||||
owned_site.reload
|
||||
_(owned_site.is_deleted).must_equal true
|
||||
_(@site.is_deleted).must_equal false
|
||||
|
||||
_(page.current_path).must_equal "/settings"
|
||||
end
|
||||
|
||||
it 'fails to delete parent site if children exist' do
|
||||
owned_site = Fabricate :site, parent_site_id: @site.id
|
||||
visit "/settings/#{@site.username}#delete"
|
||||
_(page.body).must_match /You cannot delete the parent site without deleting the children sites first/i
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,6 +2,7 @@ require_relative './environment.rb'
|
|||
|
||||
describe 'signin' do
|
||||
include Capybara::DSL
|
||||
include Capybara::Minitest::Assertions
|
||||
|
||||
def fill_in_valid
|
||||
@site = Fabricate.attributes_for :site
|
||||
|
|
|
@ -2,6 +2,7 @@ require_relative './environment.rb'
|
|||
|
||||
describe 'signup' do
|
||||
include Capybara::DSL
|
||||
include Capybara::Minitest::Assertions
|
||||
|
||||
def fill_in_valid
|
||||
@site = Fabricate.attributes_for(:site)
|
||||
|
@ -24,7 +25,7 @@ describe 'signup' do
|
|||
end
|
||||
|
||||
before do
|
||||
Capybara.default_driver = :apparition
|
||||
Capybara.default_driver = :selenium_chrome_headless_largewindow
|
||||
Capybara.reset_sessions!
|
||||
visit_signup
|
||||
end
|
||||
|
@ -39,7 +40,6 @@ describe 'signup' do
|
|||
fill_in_valid
|
||||
click_signup_button
|
||||
site_created?
|
||||
|
||||
click_link 'Continue'
|
||||
_(page).must_have_content /almost ready!/
|
||||
fill_in 'token', with: Site[username: @site[:username]].email_confirmation_token
|
||||
|
@ -109,10 +109,10 @@ describe 'signup' do
|
|||
_(page).must_have_content 'Usernames can only contain'
|
||||
fill_in 'username', with: 'nope-'
|
||||
click_signup_button
|
||||
_(page).must_have_content 'A valid user/site name is required'
|
||||
_(page).must_have_content 'Usernames can only contain'
|
||||
fill_in 'username', with: '-nope'
|
||||
click_signup_button
|
||||
_(page).must_have_content 'A valid user/site name is required'
|
||||
_(page).must_have_content 'Usernames can only contain'
|
||||
end
|
||||
|
||||
it 'fails with username greater than 32 characters' do
|
||||
|
|
|
@ -2,6 +2,7 @@ require_relative './environment.rb'
|
|||
|
||||
describe 'site page' do
|
||||
include Capybara::DSL
|
||||
include Capybara::Minitest::Assertions
|
||||
|
||||
after do
|
||||
Capybara.default_driver = :rack_test
|
||||
|
@ -103,6 +104,35 @@ describe 'site page' do
|
|||
visit "/browse?tag=#{@tag}"
|
||||
_(page.find('.website-Gallery .username a')['href']).must_match /\/site\/#{@blocked_site.username}/
|
||||
end
|
||||
|
||||
it 'removes follows/followings when blocking' do
|
||||
site = Fabricate :site
|
||||
not_blocked_site = Fabricate :site
|
||||
blocked_site = Fabricate :site
|
||||
|
||||
site.add_follow actioning_site: not_blocked_site
|
||||
site.add_following site: not_blocked_site
|
||||
|
||||
site.add_follow actioning_site: blocked_site
|
||||
site.add_following site: blocked_site
|
||||
|
||||
_(site.follows.count).must_equal 2
|
||||
_(site.followings.count).must_equal 2
|
||||
|
||||
page.set_rack_session id: site.id
|
||||
|
||||
visit "/site/#{blocked_site.username}"
|
||||
|
||||
click_link 'Block'
|
||||
click_button 'Block Site'
|
||||
|
||||
_(site.follows.count).must_equal 1
|
||||
_(site.followings.count).must_equal 1
|
||||
|
||||
_(site.follows.count {|s| s.actioning_site == blocked_site}).must_equal 0
|
||||
_(site.followings.count {|s| s.site == blocked_site}).must_equal 0
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
it '404s if site is banned' do
|
||||
|
|
|
@ -2,9 +2,10 @@ require_relative './environment.rb'
|
|||
|
||||
describe '/supporter' do
|
||||
include Capybara::DSL
|
||||
include Capybara::Minitest::Assertions
|
||||
|
||||
before do
|
||||
Capybara.default_driver = :apparition
|
||||
Capybara.default_driver = :selenium_chrome_headless
|
||||
Capybara.reset_sessions!
|
||||
|
||||
@site = Fabricate :site
|
||||
|
|
|
@ -95,7 +95,6 @@ describe 'api' do
|
|||
it 'succeeds for valid sitename' do
|
||||
create_site
|
||||
@site.update hits: 31337, domain: 'derp.com', new_tags_string: 'derpie, man'
|
||||
@site.add_archive ipfs_hash: 'QmXGTaGWTT1uUtfSb2sBAvArMEVLK4rQEcQg5bv7wwdzwU'
|
||||
get '/api/info', sitename: @user
|
||||
_(res[:result]).must_equal 'success'
|
||||
_(res[:info][:sitename]).must_equal @site.username
|
||||
|
@ -104,16 +103,9 @@ describe 'api' do
|
|||
_(res[:info][:last_updated]).must_be_nil
|
||||
_(res[:info][:domain]).must_equal 'derp.com'
|
||||
_(res[:info][:tags]).must_equal ['derpie', 'man']
|
||||
_(res[:info][:latest_ipfs_hash]).must_equal 'QmXGTaGWTT1uUtfSb2sBAvArMEVLK4rQEcQg5bv7wwdzwU'
|
||||
_(@site.reload.api_calls).must_equal 0
|
||||
end
|
||||
|
||||
it 'shows latest ipfs hash as nil when not present' do
|
||||
create_site
|
||||
get '/api/info', sitename: @user
|
||||
_(res[:info][:latest_ipfs_hash]).must_be_nil
|
||||
end
|
||||
|
||||
it 'fails for bad auth' do
|
||||
basic_authorize 'derp', 'fake'
|
||||
get '/api/info'
|
||||
|
|
|
@ -48,7 +48,7 @@ end
|
|||
|
||||
Site.bcrypt_cost = BCrypt::Engine::MIN_COST
|
||||
|
||||
MiniTest::Reporters.use! MiniTest::Reporters::SpecReporter.new
|
||||
Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
|
||||
|
||||
# Bootstrap the database
|
||||
Sequel.extension :migration
|
||||
|
@ -80,4 +80,4 @@ StripeMock.start
|
|||
product = $stripe_helper.create_product name: 'supporter'
|
||||
$stripe_helper.create_plan id: 'supporter', amount: 500, product: product.id
|
||||
$stripe_helper.create_plan id: 'free', amount: 0, product: product.id
|
||||
$stripe_helper.create_plan id: 'special', amount: 0, product: product.id
|
||||
$stripe_helper.create_plan id: 'special', amount: 0, product: product.id
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
require_relative '../environment.rb'
|
||||
|
||||
describe ArchiveWorker do
|
||||
it 'stores an IPFS archive' do
|
||||
return if ENV['CI']
|
||||
site = Fabricate :site
|
||||
ipfs_hash = site.add_to_ipfs
|
||||
ArchiveWorker.new.perform site.id
|
||||
_(site.archives.length).must_equal 1
|
||||
archive_one = site.archives.first
|
||||
_(archive_one.ipfs_hash).wont_be_nil
|
||||
_(archive_one.ipfs_hash).must_equal ipfs_hash
|
||||
_(archive_one.updated_at).wont_be_nil
|
||||
|
||||
new_updated_at = Time.now - 500
|
||||
archive_one.update updated_at: new_updated_at
|
||||
|
||||
ArchiveWorker.new.perform site.id
|
||||
_(archive_one.reload.updated_at).wont_equal new_updated_at
|
||||
|
||||
site.store_files [{filename: 'test.jpg', tempfile: Rack::Test::UploadedFile.new('./tests/files/test.jpg', 'image/jpeg')}]
|
||||
ArchiveWorker.new.perform site.id
|
||||
|
||||
site.reload
|
||||
_(site.archives.length).must_equal 2
|
||||
archive_two = site.archives_dataset.exclude(ipfs_hash: archive_one.ipfs_hash).first
|
||||
_(archive_two.ipfs_hash).wont_be_nil
|
||||
end
|
||||
end
|
|
@ -21,3 +21,9 @@ sed -i 's|UsePAM yes|UsePAM no|g' /etc/ssh/sshd_config
|
|||
#sed -i 's|[#]*PermitRootLogin yes|PermitRootLogin no|g' /etc/ssh/sshd_config
|
||||
|
||||
service ssh restart
|
||||
|
||||
|
||||
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
|
||||
sudo dpkg -i google-chrome-stable_current_amd64.deb
|
||||
sudo apt-get install -f -y
|
||||
rm google-chrome-stable_current_amd64.deb
|
|
@ -12,10 +12,10 @@ sudo su postgres -c "createuser -d vagrant"
|
|||
sudo su vagrant -c "createdb neocities"
|
||||
sudo su vagrant -c "createdb neocities_test"
|
||||
|
||||
sudo sh -c 'echo "local all postgres trust" > /etc/postgresql/10/main/pg_hba.conf'
|
||||
sudo sh -c 'echo "local all all trust" >> /etc/postgresql/10/main/pg_hba.conf'
|
||||
sudo sh -c 'echo "host all all 127.0.0.1/32 trust" >> /etc/postgresql/10/main/pg_hba.conf'
|
||||
sudo sh -c 'echo "host all all ::1/128 trust" >> /etc/postgresql/10/main/pg_hba.conf'
|
||||
sudo sh -c 'echo "local all postgres trust" > /etc/postgresql/14/main/pg_hba.conf'
|
||||
sudo sh -c 'echo "local all all trust" >> /etc/postgresql/14/main/pg_hba.conf'
|
||||
sudo sh -c 'echo "host all all 127.0.0.1/32 trust" >> /etc/postgresql/14/main/pg_hba.conf'
|
||||
sudo sh -c 'echo "host all all ::1/128 trust" >> /etc/postgresql/14/main/pg_hba.conf'
|
||||
sudo systemctl restart postgresql
|
||||
|
||||
# Create empty file for disposable email accounts
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
#!/bin/bash
|
||||
|
||||
apt-get -y install python-software-properties
|
||||
apt-add-repository -y ppa:brightbox/ruby-ng
|
||||
apt-get -y update
|
||||
apt-get -y install ruby2.6 ruby2.6-dev
|
||||
gem install bundler --no-document
|
||||
sudo apt-get -y install autoconf patch build-essential rustc libssl-dev libyaml-dev libreadline6-dev zlib1g-dev libgmp-dev libncurses5-dev libffi-dev libgdbm6 libgdbm-dev libdb-dev uuid-dev
|
||||
|
||||
wget https://cache.ruby-lang.org/pub/ruby/3.3/ruby-3.3.0.tar.gz
|
||||
gzip -dc ruby-3.3.0.tar.gz | tar xf -
|
||||
cd ruby-3.3.0
|
||||
./autogen.sh
|
||||
./configure --enable-yjit --disable-install-doc
|
||||
make -j && sudo make install
|
||||
cd ..
|
||||
|
|
|
@ -29,10 +29,15 @@ apt-get install -y \
|
|||
|
||||
sed -i 's|[#]*DetectPUA false|DetectPUA true|g' /etc/clamav/clamd.conf
|
||||
|
||||
freshclam
|
||||
service clamav-freshclam start
|
||||
service clamav-daemon start
|
||||
#sudo freshclam
|
||||
#sudo systemctl start clamav-freshclam
|
||||
|
||||
# clamav download mirrors have insanely stupid limits so we just put in a github mirror for now
|
||||
rm -f main.cvd daily.cld daily.cvd bytecode.cvd main.cvd.sha256 daily.cvd.sha256 bytecode.cvd.sha256 main.cvd.* daily.cvd.* && curl -LSOs https://github.com/ladar/clamav-data/raw/main/main.cvd.[01-10] -LSOs https://github.com/ladar/clamav-data/raw/main/main.cvd.sha256 -LSOs https://github.com/ladar/clamav-data/raw/main/daily.cvd.[01-10] -LSOs https://github.com/ladar/clamav-data/raw/main/daily.cvd.sha256 -LSOs https://github.com/ladar/clamav-data/raw/main/bytecode.cvd -LSOs https://github.com/ladar/clamav-data/raw/main/bytecode.cvd.sha256 && cat main.cvd.01 main.cvd.02 main.cvd.03 main.cvd.04 main.cvd.05 main.cvd.06 main.cvd.07 main.cvd.08 main.cvd.09 main.cvd.10 > main.cvd && cat daily.cvd.01 daily.cvd.02 daily.cvd.03 daily.cvd.04 daily.cvd.05 daily.cvd.06 daily.cvd.07 daily.cvd.08 daily.cvd.09 daily.cvd.10 > daily.cvd && sha256sum -c main.cvd.sha256 daily.cvd.sha256 bytecode.cvd.sha256 || { printf "ClamAV database download failed.\n" ; rm -f main.cvd daily.cvd bytecode.cvd ; } ; rm -f main.cvd.sha256 daily.cvd.sha256 bytecode.cvd.sha256 main.cvd.* daily.cvd.* && sudo mv *.cvd /var/lib/clamav/
|
||||
|
||||
|
||||
sudo systemctl enable clamav-daemon
|
||||
sudo systemctl start clamav-daemon
|
||||
usermod -G vagrant clamav
|
||||
|
||||
cd /vagrant
|
||||
|
|
|
@ -41,8 +41,9 @@
|
|||
</li>
|
||||
<li class="divider"></li>
|
||||
<% end %>
|
||||
<li><a href="/dashboard">Edit Site</a></li>
|
||||
<li><a href="<%= current_site.uri %>" target="_blank">View Site</a></li>
|
||||
<li><a href="/dashboard">Edit</a></li>
|
||||
<li><a href="<%= current_site.uri %>" target="_blank">View</a></li>
|
||||
<li><a href="/site/<%= current_site.username %>/stats">Stats</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="/settings">Settings</a></li>
|
||||
|
||||
|
|
13
views/_meta.erb
Normal file
13
views/_meta.erb
Normal file
|
@ -0,0 +1,13 @@
|
|||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="description" content="Create and surf awesome websites for free.">
|
||||
|
||||
<link rel="canonical" href="https://neocities.org<%= request.fullpath %>">
|
||||
|
||||
<meta property="og:title" content="Neocities">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="https://neocities.org/img/neocities-front-screenshot.jpg">
|
||||
<meta property="og:description" content="Create and surf awesome websites for free.">
|
||||
|
||||
<link rel="icon" type="image/x-icon" href="/img/favicon.png">
|
||||
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1">
|
|
@ -3,7 +3,7 @@
|
|||
page_uri = site.uri
|
||||
end
|
||||
%>
|
||||
<a href="/site/<%= site.username %>.rss" target="_blank"><span>RSS/Atom Feed</span></a>
|
||||
<a href="/site/<%= site.username %>.rss" target="_blank"><span>RSS Feed</span></a>
|
||||
<br>
|
||||
<a href="https://facebook.com/sharer.php?<%= Rack::Utils.build_query(u: "#{page_uri}") %>" target="_blank">Facebook</a>
|
||||
<br>
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
<hr />
|
||||
<section>
|
||||
<h1 class="beta txt-Center">The Neocities Team</h1>
|
||||
|
||||
<div class="row txt-Center">
|
||||
<div class="col col-50">
|
||||
<a href="https://kyledrake.neocities.org" title="Visit Kyle's Website">
|
||||
<img src="https://0.gravatar.com/avatar/62a43048a3c2c688654274abdc0ecb9c?d=https%3A%2F%2Fidenticons.github.com%2Ffde07ba82b25f95afa9d080819f95717.png&r=x&s=440" alt="kyle drake" class="pic-Rounded" />
|
||||
</a>
|
||||
<br />
|
||||
<a href="https://kyledrake.neocities.org" title="Kyle Drake" class="eps">Kyle Drake</a>
|
||||
</div>
|
||||
<div class="col col-50">
|
||||
<a href="https://victoria.neocities.org" title="Visit Victoria's Website">
|
||||
<img src="https://1.gravatar.com/avatar/2b577f8b3e5ab79bc927ed5185c0eae0?d=https%3A%2F%2Fidenticons.github.com%2Fe03006819f4a835afa237716f6701c95.png&r=x&s=440" alt="Victoria Wang" class="pic-Rounded" />
|
||||
</a>
|
||||
<br />
|
||||
<a href="https://victoria.neocities.org" title="Visit Victoria's Website" class="eps">Victoria Wang</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<hr />
|
||||
<section>
|
||||
<h2 class="txt-Center">Follow us on <a href="https://twitter.com/neocities">Twitter</a> or <a href="https://www.facebook.com/neocities">Facebook</a></h2>
|
||||
</section>
|
|
@ -13,15 +13,51 @@
|
|||
</div>
|
||||
<% end %>
|
||||
|
||||
<h2>Contact</h2>
|
||||
|
||||
<p>
|
||||
Please note that we can only respond to english inquiries.
|
||||
Please note that we can only respond to messages in english and klingon. Thank you!
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Frequently asked questions:
|
||||
</p>
|
||||
<h2>Frequently Asked Questions</h2>
|
||||
|
||||
<h4>Sites / Editing</h3>
|
||||
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#cemailresetnotfound">
|
||||
I didn't receive my password reset email.
|
||||
</a>
|
||||
</div>
|
||||
<div id="cemailresetnotfound" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<p>
|
||||
Did you use a different email address to create the site? Also, check the spam folder of your email.
|
||||
</p>
|
||||
<p>
|
||||
This is also a great opportunity to talk about password managers. A password manager keeps a database of randomized strong passwords for all of your sites for you, so you only need to remember
|
||||
one password which then controls all of the other ones. This reduces the chances of forgetting your password and having to reset it. Popular options include <a href="https://bitwarden.com/">BitWarden</a> and <a href="https://1password.com/">1Password</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#ctwo">
|
||||
I lost my email address, can you reset the password?
|
||||
</a>
|
||||
</div>
|
||||
<div id="ctwo" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<p>
|
||||
For security reasons, we cannot reset your password if you don't have control of your email address, because there is no way to know if you're the legitimate owner or an attacker. You will have to make a new site (don’t worry, it’s free!). If you didn’t get an email from the password reset form, check your spam folder.
|
||||
</p>
|
||||
<p>
|
||||
This is also a great opportunity to talk about password managers. A password manager keeps a database of randomized strong passwords for all of your sites for you, so you only need to remember
|
||||
one password which then controls all of the other ones. This reduces the chances of forgetting your password and having to reset it. Popular options include <a href="https://bitwarden.com/">BitWarden</a> and <a href="https://1password.com/">1Password</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion">
|
||||
<div class="accordion-group">
|
||||
|
@ -39,6 +75,23 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion">
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#editingissues">
|
||||
I'm having issues using the editor, it is behaving strangly or redirecting instead of saving.
|
||||
</a>
|
||||
</div>
|
||||
<div id="editingissues" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<p>
|
||||
This can sometimes be caused by ad blockers or other browser extensions. Try disabling them for Neocities and try again (you won't need them here anyways since we don't do any advertising).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#cads">
|
||||
|
@ -75,15 +128,17 @@
|
|||
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#ctwo">
|
||||
I didn't enter an email for my site, can you reset the password?
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#hitsviews">
|
||||
What is the difference between hits and views?
|
||||
</a>
|
||||
</div>
|
||||
<div id="ctwo" class="accordion-body collapse">
|
||||
<div id="hitsviews" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<p>
|
||||
For security reasons, we cannot reset your password if you did not enter an email for your site. You will have to make a new site (don’t worry, it’s free!). If you didn’t get an email from the password reset form, you didn’t enter an email (or it’s in your spam folder). Again you will have to make a new site; we cannot help you for security reasons.
|
||||
<strong>Hits</strong> are number of requests for any file on a site, <strong>views</strong> count for one unique IP address per hour. Views provide better insight into how many individuals (or search engines, etc) are looking at your site. There is no such thing as perfectly accurate stats, so this doesn't necessarily reflect how many actual people are viewing your site, but it does provide a rough way to see how popular your site is becoming. If you put a hit counter on your site, it may give a different number than what Neocities records and that is expected.
|
||||
</p>
|
||||
|
||||
<p>We don't provide a way to look at individual visitor records on Neocities, the main reason being that the information isn't very useful anymore, since most sources block HTTP Referer headers, making it difficult to determine origin.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -97,7 +152,7 @@
|
|||
<div id="cthree" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<p>
|
||||
We don't support FTP, sorry! Aside from security problems with FTP, it isn't used by many people anymore. We need to stay focused on what we work on, so we only support ways to upload content that most people use these days.
|
||||
We don't support FTP, apologies! Aside from security problems with FTP, it isn't used by many people anymore. We need to stay focused on what we work on, so we only support ways to upload content that most people use these days.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
@ -126,6 +181,155 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#cten">
|
||||
Can you delete my site for me?
|
||||
</a>
|
||||
</div>
|
||||
<div id="cten" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<p>
|
||||
For security reasons, we can't. But you can delete your own site by logging in and going to the settings page!
|
||||
</p>
|
||||
|
||||
<p>
|
||||
For safety reasons, if you have more than one site, you'll need to delete all sites before deleting the main account. Click on "Manage Site Settings" to delete each site individually.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#csitebackup">
|
||||
Site backup downloading is not working
|
||||
</a>
|
||||
</div>
|
||||
<div id="csitebackup" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<p>
|
||||
Site downloading issues are almost always related to the internet connectivity issues. If your download is continuing to get cancelled or interrupted, check your WiFi router, or try plugging in directly to the router to see if that resolves it. You could also try downloading from a local library or coffee shop and see if you have the same issue.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#csitename">
|
||||
A site name is taken but is not being actively used or is not found, can I take it over?
|
||||
</a>
|
||||
</div>
|
||||
<div id="csitename" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<p>
|
||||
Neocities does not have a policy of transferring inactive sites to different owners and can't make manual exceptions at the moment. This is a tricky subject because we don't have a way to be sure if a user has actually abandoned their site, even if it's just the default "new site" template. We may have a different policy in the future regarding this.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#ctags">
|
||||
Why can't I add more tags to my site profile?
|
||||
</a>
|
||||
</div>
|
||||
<div id="ctags" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<p>
|
||||
There is a limit of <%= Site::MAXIMUM_TAGS.to_s %> per site, so try removing a tag and then you will be able to add another.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#cfiles">
|
||||
Do you support WASM / MP3 / MP4/ ZIP for free sites?
|
||||
</a>
|
||||
</div>
|
||||
<div id="cfiles" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<p>
|
||||
We don't currently support certain types of files, particularly multimedia files and WebASM with free sites because of persistent issues with abuse (piracy, bitcoin mining, excessive bandwidth usage). In general, these files are not conducive to Neocities' mission of bringing back personal home pages vs being a piracy and webapp hosting platform. You can embed audio and videos through other providers like YouTube, which is preferred because they will automatically provide the streaming media in formats that work better for your specific users' devices (such as mobile phones).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#cfilesizes">
|
||||
Can I upload very large files to Neocities?
|
||||
</a>
|
||||
</div>
|
||||
<div id="cfilesizes" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<p>
|
||||
We currently have a file size limit for very large files on Neocities, including supporter sites. This is mainly because our CDN (content delivery network) is optimized for lots of small files (the kind that web sites use), and also because large files tend to be easily abused (think piracy). Files used to make web pages will not hit these limits, but things like very large zipballs are more likely to. These also tend to be the file types that lead to abuse and would make Neocities unsustainable to operate in the long run (piracy, malware, etc), another reason we're hesitant to add support for them.
|
||||
</p>
|
||||
<p>
|
||||
Even if we supported this, it wouldn't work very well because HTTP servers are not good at this anyways and there are almost always better alternatives for very large file distribution. For example if you need to upload a video, YouTube or Vimeo are better because they automatically format in the different file types that different browsers support, or if you are releasing a large game, it's probably better to release it through a game platform like Steam. Using BitTorrent is also a really effective way to have your site visitors help distribute your content quickly (provided it's legal!)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#cinvoices">
|
||||
Where can I find invoices for supporter membership?
|
||||
</a>
|
||||
</div>
|
||||
<div id="cinvoices" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<p>
|
||||
Invoices are available for download as PDF files <a href="/settings/invoices">here</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#celeven">
|
||||
How can I load Neocities sites on old web browsers?
|
||||
</a>
|
||||
</div>
|
||||
<div id="celeven" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<p>
|
||||
We recommend using an "old web browser" proxy server like <a href="https://github.com/atauenis/webone">WebOne</a> or <a href="https://github.com/tenox7/wrp">WRP</a>. These translate between modern browser
|
||||
standards and old browser standards, allowing for us to provide the speed and security of the new web without compromising the ability to use old browsers.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#cnine">
|
||||
Do you support .htaccess files?
|
||||
</a>
|
||||
</div>
|
||||
<div id="cnine" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<p>
|
||||
Neocities does not support .htaccess files. This is an Apache HTTP server specific file type, and we don't use Apache for our backend. In addition, we generally don't add features to Neocities that make the functionality of sites depend on custom backend functionality (the sole exception being that we make sure "page not found" goes to "not_found.html" so you can create a custom 404.)
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Finally, we sometimes see people trying to use .htaccess for authentication (with usernames and passwords). Neocities is only for sites and information you want to make public, and it is dangerous to use it for private sites! We make backups of your site and attackers can easily find any information you're trying to hide using our archivers. Our goal is to make sure your sites stay up for a long time and persist, not to enforce secrecy zones on your site. If you want to make a controlled access site, Neocities is probably not what you're looking for.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Legal / Abuse</h3>
|
||||
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#cfive">
|
||||
|
@ -135,7 +339,7 @@
|
|||
<div id="cfive" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<p>
|
||||
We will not remove a site unless it is violating our <a href="/terms">Terms of Service</a> or breaking the law. Chances are, it isn't. And the First Amendment of the United States Constitution defends our right to do this. If you'd like a more in-depth explanation of our policies, see our <a href="/legal">legal guide for Neocities</a>.
|
||||
We will not remove a site unless it is violating our <a href="/terms">Terms of Service</a> or breaking the law. Chances are, it isn't. And the laws in place defend our right to do this. If you'd like a more in-depth explanation of our policies, see our <a href="/legal">legal guide for Neocities</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -144,16 +348,13 @@
|
|||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#csix">
|
||||
I want to sue you.
|
||||
I don't like something you're hosting and I'm going to sue you!
|
||||
</a>
|
||||
</div>
|
||||
<div id="csix" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<p>
|
||||
Cool.
|
||||
</p>
|
||||
<p>
|
||||
Please consult our <a href="/legal">legal guide for Neocities</a> for more information.
|
||||
Cool! Please consult our <a href="/legal">legal guide for Neocities</a> for more information on why this is a really bad idea.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -188,67 +389,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#cnine">
|
||||
Do you support .htaccess files?
|
||||
</a>
|
||||
</div>
|
||||
<div id="cnine" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<p>
|
||||
No. And we don't intend to.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The .htaccess file is an Apache specific thing, and we don't use Apache for our backend. In addition, we generally don't add features to Neocities that make the functionality of sites depend on custom backend functionality (the sole exception being that we make sure "page not found" goes to "not_found.html" so you can create a custom 404.)
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Finally, we sometimes see people trying to use .htaccess for authentication (with usernames and passwords). Neocities is only for sites and information you want to make public, and it is dangerous to use it for private sites! We make backups of your site and attackers can easily find any information you're trying to hide using our archivers. Our goal is to make sure your sites stay up for a long time and persist, not to enforce secrecy zones on your site. If you want to make a controlled access site, Neocities is not for you.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#cten">
|
||||
Can you delete my site for me?
|
||||
</a>
|
||||
</div>
|
||||
<div id="cten" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<p>
|
||||
For security reasons, we can't. But you can delete your own site by logging in and going to the settings page!
|
||||
</p>
|
||||
|
||||
<p>
|
||||
For safety reasons, if you have more than one site, you'll need to delete all sites before deleting the main account. Click on "Manage Site Settings" to delete each site individually.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-group">
|
||||
<div class="accordion-heading">
|
||||
<a class="accordion-toggle" data-toggle="collapse" href="#celeven">
|
||||
How can I load Neocities sites on old web browsers?
|
||||
</a>
|
||||
</div>
|
||||
<div id="celeven" class="accordion-body collapse">
|
||||
<div class="accordion-inner">
|
||||
<p>
|
||||
We recommend using an "old web browser" proxy server like <a href="https://github.com/atauenis/webone">WebOne</a> or <a href="https://github.com/tenox7/wrp">WRP</a>. These translate between modern browser
|
||||
standards and old browser standards, allowing for us to provide the speed and security of the new web without compromising the ability to use old browsers.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<h2>Contact Form</h2>
|
||||
|
@ -265,6 +405,14 @@
|
|||
<label for="your_comments">Comments</label>
|
||||
<textarea name="body" id="your_comments" class="col-75" rows="10"><%= params[:body] %></textarea>
|
||||
|
||||
<label class="text-Label" for="faq_check">We may not be able to respond to questions that are already answered.<br>Did you check the Frequently Asked Questions to see if it answered your question?</label>
|
||||
<div class="select-Container" style="width: 100px; float: none">
|
||||
<select name="faq_check" id="faq_check" class="input-Select">
|
||||
<option value="no">No</option>
|
||||
<option value="yes">Yes</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<label>Fill out the captcha so we know you’re not a robot:</label>
|
||||
<%== hcaptcha_input %>
|
||||
|
||||
|
|
|
@ -218,7 +218,7 @@
|
|||
<% if !current_site.plan_feature(:no_file_restrictions) %>
|
||||
<a href="/site_files/allowed_types">Allowed file types</a> |
|
||||
<% end %>
|
||||
<a href="/site_files/<%= current_site.username %>.zip">Download entire site</a> |
|
||||
<a href="/site_files/download">Download entire site</a> |
|
||||
<% unless is_education? %>
|
||||
<a href="/site_files/mount_info">Mount your site as a drive on your computer</a>
|
||||
<% end %>
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
<div class="header-Outro">
|
||||
<div class="row content single-Col">
|
||||
<h1>Neocities and the Distributed Web</h1>
|
||||
<h2 class="subtitle">Working to build a faster, better, more permanent web.</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content single-Col misc-page">
|
||||
<img src="/img/neocities-ipfs.jpg" style="margin-bottom: 20px">
|
||||
<article role="article">
|
||||
<p>
|
||||
Neocities has launched an implementation of <a href="https://ipfs.io">IPFS</a>, a protocol for the distributed web. The idea is simple: Instead of serving web sites from central servers, a distributed web allows any computer to help serve a site.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This is still very early stage technology and subject to change. To learn more, see our <a href="https://blog.neocities.org/blog/2015/09/08/its-time-for-the-distributed-web.html">blog post</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
IPFS archiving is now enabled on all sites. You'll see an IPFS CID link on the site profile, and an archive link that allows you to see past versions of your site (note: this is still a preview, so past site archives may still disappear, but we're working on making it better).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you want to play around with this new technology, you can get IPFS for your computer and use it to retrieve content from our IPFS node servers. All you need to do is <a href="https://ipfs.io/docs/install/">download the IPFS daemon</a> (OSX/Linux only for now), and run the following command in your terminal:
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<code>$ ipfs pin add -r THE_IPFS_CID_FOR_YOUR_SITE</code>
|
||||
</p>
|
||||
|
||||
<% if signed_in? %>
|
||||
<p>
|
||||
IPFS archiving is not enabled by default for all sites. If you would like to enable IPFS for your site, please visit your site's <a href="/settings/<%= current_site.username %>">settings</a> and enable it.
|
||||
</p>
|
||||
<% end %>
|
||||
</article>
|
||||
</div>
|
|
@ -96,54 +96,56 @@
|
|||
<fieldset class="content">
|
||||
<h2 class="gamma">Sign up for free</h2>
|
||||
<hr />
|
||||
<div class="siteCreateInputs">
|
||||
<label for="create-Input">Username</label>
|
||||
<input type="text" name="prevent_autofill_username" id="prevent_autofill_username" value="" style="position:absolute; top:-2000px; left:-2000px;" />
|
||||
<input type="password" name="prevent_autofill_password" id="prevent_autofill_password" value="" style="position:absolute; top:-2000px; left:-2000px;" />
|
||||
<input type="text" class="input-Area" id="create-Input" name="username" placeholder="my-site-name" data-placement="left" data-trigger="manual" autocapitalize="off" autocorrect="off" autocomplete="off" />
|
||||
<label for="create-Input" id="domain-name">.neocities.org</label>
|
||||
<% if @create_disabled %>
|
||||
<p>Sign up is not currently available, please try again later.</p>
|
||||
<% else %>
|
||||
<div class="siteCreateInputs">
|
||||
<label for="create-Input">Username</label>
|
||||
<input type="text" name="prevent_autofill_username" id="prevent_autofill_username" value="" style="position:absolute; top:-2000px; left:-2000px;" />
|
||||
<input type="password" name="prevent_autofill_password" id="prevent_autofill_password" value="" style="position:absolute; top:-2000px; left:-2000px;" />
|
||||
<input type="text" class="input-Area" id="create-Input" name="username" placeholder="my-site-name" data-placement="left" data-trigger="manual" autocapitalize="off" autocorrect="off" autocomplete="off" />
|
||||
<label for="create-Input" id="domain-name">.neocities.org</label>
|
||||
|
||||
<label for="tags-input">Tags (your interests, site topics)</label>
|
||||
<input type="text" class="input-Area" id="tags-input" name="new_tags_string" placeholder="art, videogames, food, music, programming, gardening, cats" data-placement="left" data-trigger="manual" autocapitalize="off" autocorrect="off" autocomplete="off" />
|
||||
<label for="tags-input">Tags (your interests, site topics)</label>
|
||||
<input type="text" class="input-Area" id="tags-input" name="new_tags_string" placeholder="art, videogames, food, music, programming, gardening, cats" data-placement="left" data-trigger="manual" autocapitalize="off" autocorrect="off" autocomplete="off" />
|
||||
|
||||
<div class="col col-50" style="padding-left:0;">
|
||||
<label for="password-input">
|
||||
Password
|
||||
</label>
|
||||
<input type="password" class="input-Area" id="password-input"
|
||||
name="password" placeholder="password"
|
||||
data-placement="left" data-trigger="manual"
|
||||
autocapitalize="off" autocorrect="off" autocomplete="off" />
|
||||
</div>
|
||||
<div class="col col-50" style="padding-left:0;">
|
||||
<label for="password-input">
|
||||
Password
|
||||
</label>
|
||||
<input type="password" class="input-Area" id="password-input"
|
||||
name="password" placeholder="password"
|
||||
data-placement="left" data-trigger="manual"
|
||||
autocapitalize="off" autocorrect="off" autocomplete="off" />
|
||||
</div>
|
||||
|
||||
<div class="col col-50">
|
||||
<label for="email-input">
|
||||
Email
|
||||
</label>
|
||||
<input type="email" class="input-Area"
|
||||
id="email-input" name="email"
|
||||
placeholder="me@example.com" data-placement="left"
|
||||
data-trigger="manual" autocapitalize="off"
|
||||
autocorrect="off" autocomplete="off" />
|
||||
</div>
|
||||
<div class="col col-50">
|
||||
<label for="email-input">
|
||||
Email
|
||||
</label>
|
||||
<input type="email" class="input-Area"
|
||||
id="email-input" name="email"
|
||||
placeholder="me@example.com" data-placement="left"
|
||||
data-trigger="manual" autocapitalize="off"
|
||||
autocorrect="off" autocomplete="off" />
|
||||
</div>
|
||||
|
||||
<div class="col col-50" style="padding-left:0">
|
||||
<label>
|
||||
Confirm you are human
|
||||
</label>
|
||||
<%== hcaptcha_input %>
|
||||
</div>
|
||||
<div class="col col-50" style="padding-left:0">
|
||||
<label>
|
||||
Confirm you are human
|
||||
</label>
|
||||
<%== hcaptcha_input %>
|
||||
</div>
|
||||
|
||||
<div class="col col-50">
|
||||
<div style="margin-top: 15px">
|
||||
<input type="submit" value="Create My Site" class="btn-Action float-Right" />
|
||||
<div class="col col-50">
|
||||
<div style="margin-top: 15px">
|
||||
<input type="submit" value="Create My Site" class="btn-Action float-Right" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% end %>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
<% end %>
|
||||
|
||||
</div> <!-- end .col-50 -->
|
||||
|
@ -218,7 +220,7 @@
|
|||
<div class="row">
|
||||
<div class="col col-50">
|
||||
<h3>
|
||||
<i class="fa fa-eye-slash"></i>Zero advertising
|
||||
<i class="fa fa-eye-slash"></i>Zero Advertising
|
||||
</h3>
|
||||
<p>
|
||||
<strong>Neocities will never sell your personal data or put advertising on your site.</strong> Instead, we are funded directly by people just like you with <a href="/supporter">supporter accounts</a> and <a href="/donate">donations</a>.
|
||||
|
@ -226,14 +228,14 @@
|
|||
</div>
|
||||
|
||||
<div class="col col-50">
|
||||
<h3><i class="fa fa-tachometer"></i>Blazing Fast Performance</h3>
|
||||
<p>Unlike many other web hosts, we don't skimp on infrastructure. Neocities operates our own caching CDN in 11 datacenters all over the world to quickly serve your site. We also force 100% strong SSL on all sites, and have full support for HTTP/2. Because of our commitment to quality, we routinely out-perform the pricey cloud services on reliability, speed and uptime. Whether it’s your personal home page or a busy professional site, <strong>your site loads fast</strong>.</p>
|
||||
<h3><i class="fa fa-tachometer"></i>Fast Site Loading</h3>
|
||||
<p>Neocities operates our own caching anycast CDN in over a dozen datacenters all over the world to quickly serve your site to visitors with strong SSL and support for HTTP/2. Our strict focus on static web hosting allows us to routinely out-perform the pricey cloud services on reliability, speed and uptime.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-50">
|
||||
<h3><i class="fa fa-wrench"></i>Developer tools</h3>
|
||||
<h3><i class="fa fa-wrench"></i>Developer Tools</h3>
|
||||
<p>Our fast static hosting comes with a great in-browser HTML editor, easy file uploading, a <a href="/cli">command line tool</a>, RSS feeds for every site, <a href="/api">APIs</a> for building developer applications, and much more!
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,36 +1,11 @@
|
|||
<!doctype html>
|
||||
<!--[if IE 8 ]><html lang="en" class="ieAll ie8"><![endif]-->
|
||||
<!--[if IE 9 ]><html lang="en" class="ieAll ie9"><![endif]-->
|
||||
<!--[if (gt IE 9)|!(IE)]><!--><html lang="en"><!--<![endif]-->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<!-- <link rel="manifest" href="/manifest.json"> -->
|
||||
|
||||
<title>Neocities: Create your own free website!</title>
|
||||
<meta itemprop="name" content="Neocities" />
|
||||
<meta itemprop="description" content="Free web hosting and tools that allow anyone to create a website. Join our community today!" />
|
||||
<meta name="description" content="Free web hosting and tools that allow anyone to create a website. Join our community today!" />
|
||||
<meta name="keywords" content="free website, free web hosting, html, css, learn to code, free hosting, build a website, create a web page, static hosting, how to make a website" />
|
||||
|
||||
<link rel="canonical" href="//neocities.org" />
|
||||
<%== erb :'_meta' %>
|
||||
|
||||
<meta property="og:title" content="Neocities"/>
|
||||
<meta property="og:site_name" content="Neocities | neocities.org"/>
|
||||
<meta property="og:type" content="website"/>
|
||||
<meta property="og:image" content=""/>
|
||||
<meta property="og:url" content="//www.neocities.org"/>
|
||||
<meta property="og:description" content="Free web hosting and tools that allow anyone to create a website. Join our community today!"/>
|
||||
|
||||
<link rel="shortcut icon" type="image/ico" href="/favicon.ico?v=4" />
|
||||
<link rel="apple-touch-icon-precomposed" href="#apple-icon-144.png" />
|
||||
<link rel="apple-touch-startup-image" href="#startup.png" />
|
||||
|
||||
<!-- Mobile Meta -->
|
||||
<meta name="HandheldFriendly" content="True" />
|
||||
<meta name="MobileOptimized" content="320" />
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1" />
|
||||
|
||||
<link href="/css/neo.css" rel="stylesheet" type="text/css" media="all"/>
|
||||
<link href="/css/neo.css" rel="stylesheet" type="text/css" media="all">
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script type="text/javascript" src="/js/html5.min.js"></script>
|
||||
|
@ -41,4 +16,4 @@
|
|||
|
||||
<%== yield %>
|
||||
|
||||
</html>
|
||||
</html>
|
|
@ -1,22 +1,9 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title><%= title %></title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<!-- <link rel="manifest" href="/manifest.json"> -->
|
||||
<meta itemprop="name" content="Neocities.org">
|
||||
<meta itemprop="description" content="Free web hosting and tools that allow anyone to create a website. Join our community today!">
|
||||
<meta name="description" content="Free web hosting and tools that allow anyone to create a website. Join our community today!">
|
||||
<meta name="keywords" content="free website, html, css, learn to code, free hosting, build a website, create a web page">
|
||||
<meta property="og:title" content="Neocities">
|
||||
<meta property="og:site_name" content="Neocities | neocities.org">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:image" content="/img/cat-larger.png">
|
||||
<meta property="og:url" content="https://www.neocities.org">
|
||||
<meta property="og:description" content="Neocities is the new Geocities. Create your own free home page, and do whatever you want with it.">
|
||||
<meta name="referrer" content="no-referrer-when-downgrade">
|
||||
|
||||
<link rel="canonical" href="https://neocities.org<%= request.fullpath %>" />
|
||||
|
||||
<%== erb :'_meta' %>
|
||||
|
||||
<% if meta_robots %>
|
||||
<meta name="robots" content="<%= meta_robots %>">
|
||||
|
@ -24,13 +11,6 @@
|
|||
|
||||
<link href="/css/neo.css" rel="stylesheet" type="text/css" media="all">
|
||||
|
||||
<link href="/favicon.ico?v=4" rel="shortcut icon" type="image/ico">
|
||||
|
||||
<!-- Mobile Meta -->
|
||||
<meta name="HandheldFriendly" content="True">
|
||||
<meta name="MobileOptimized" content="320">
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1">
|
||||
|
||||
<% if @dont_browser_cache %>
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
|
@ -61,7 +41,7 @@
|
|||
<script src="/js/nav.min.js"></script>
|
||||
<script src="/js/bootstrap.min.js"></script>
|
||||
<script src="/js/typeahead.bundle.js"></script>
|
||||
|
||||
|
||||
<script>
|
||||
$("a#like").tooltip({html: true})
|
||||
$("a.comment_like").tooltip({html: true})
|
||||
|
|
|
@ -98,7 +98,7 @@
|
|||
<h2>Legal Threats</h2>
|
||||
|
||||
<p>
|
||||
<strong>Baseless legal threats are not welcome at Neocities, and will be considered an attack on our protections under the law that all online service providers have a strong interest in defending. As per our <a href="http://www.opencompany.org/">Open Company</a> principles, we reserve the right to publish and expose any and all legal threats made against Neocities. Baseless legal threats will be considered an attempt to harm Neocities by abusing the legal system, and we will respond accordingly. We will additionally invite other major social networks and service providers to join us in defending against baseless legal claims if we conclude it is in their best interests. <u>Think very carefully before sending Neocities legal threats</u>.</strong>
|
||||
<strong>Baseless legal threats are not welcome at Neocities, and will be considered an attack on our protections under the law that all online service providers have a strong interest in defending. We reserve the right to publish and expose any and all legal threats made against Neocities. Baseless legal threats will be considered an attempt to harm Neocities by abusing the legal system, and we will respond accordingly. We will additionally invite other major social networks and service providers to join us in defending against baseless legal claims if we conclude it is in their best interests. <u>Think very carefully before sending Neocities legal threats</u>.</strong>
|
||||
</p>
|
||||
<p>
|
||||
It is strongly recommended that you consider <a href="http://en.wikipedia.org/wiki/Streisand_effect">the consequences</a> of sending us legal threats or filing a lawsuit against Neocities before doing so. We are intimately familiar with our rights under the law, we have access to legal representation, and we can afford it. You will very likely lose your (embarassing and public) court case, and you will very likely be required to compensate us for legal fees.
|
||||
|
|
|
@ -13,6 +13,16 @@
|
|||
<div class="col-left">
|
||||
<div class="col col-66" style="min-height: 43em;">
|
||||
<div class="press-news">
|
||||
<h3><a href="https://www.polygon.com/23024357/neocities-social-media-alternative"><strong>Polygon</strong>: Neocities celebrates ‘the old internet,’ offering relief from 24/7 social feeds</a></h3>
|
||||
<blockquote>
|
||||
Rather than a constantly rushing river of information, Neocities sites are like homes where users fix them up, spend time on them, and invite others to visit.
|
||||
</blockquote>
|
||||
|
||||
<h3><a href="https://www.techspot.com/news/99085-neocities-bringing-eye-bleeding-spirit-geocities-back-modern.html"><strong>TechSpot</strong>: Neocities is bringing the eye-bleeding "spirit" of GeoCities back to the modern web</a></h3>
|
||||
<blockquote>
|
||||
Neocities is yet another alternative to social networking and pure nostalgia trips down memory lane. It offers a hosting space for hundreds of thousands of websites that don't need to comply with static rules or well-defined design policies to be online. Neocities introduces itself as a "social network" that brings back the "lost individual creativity of the web."
|
||||
</blockquote>
|
||||
|
||||
<h3><a href="http://www.hostingadvice.com/blog/neocities-empowers-site-owners-to-showcase-their-creativity-online/"><strong>Hosting Advice</strong>: How a Blank-Canvas, Static Hosting Approach Empowers Site Owners to Showcase Their Creativity Online</a></h3>
|
||||
<blockquote>
|
||||
Neocities has acquired upward of 100,000 users in its relatively short lifespan, which is a testament to its focus on creative web design and its offer of cost-free hosting without host-branded ads — a service that’s the exception rather than the rule in the industry. By embracing ingenuity and a templateless-approach, the organization has effectively picked up where early hosts have left off — with a crucial difference. Neocities provides the modern tools, such as an in-browser HTML editor and a command line prompt, among other features, that make web development a bit more accessible to today’s crop of web visionaries.
|
||||
|
@ -25,15 +35,6 @@
|
|||
It's easy to assume that those attending the Web 1.0 Conference in Portland, Oregon are caught up on an obsolete era of the internet. The conference's organizers, however, think the lowly HTML website may very well be the future of the web.
|
||||
</blockquote>
|
||||
|
||||
<h3><a href="http://motherboard.vice.com/read/the-interplanetary-file-system-wants-to-create-a-permanent-web"><strong>Vice</strong>: The InterPlanetary File System Wants to Create a Permanent Web</a></h3>
|
||||
<blockquote>
|
||||
In addition to satisfying the cravings of some for Geocities clip-art nostalgia, Drake has more serious plans up his sleeve. He wants to give people the ability to "build web sites that persist forever."
|
||||
</blockquote>
|
||||
<br>
|
||||
<blockquote>
|
||||
"Building an information network that will stay up forever is as modern as it gets," he wrote. "[IPFS] will pull the internet out of the Dark Ages of fast information destruction, and move us from a short-term tech culture into a tech civilization, maintaining distributed libraries of information that could continue to persist for hundreds or even thousands of years."
|
||||
</blockquote>
|
||||
|
||||
<h3><a href="http://recode.net/2015/07/17/why-we-all-need-to-make-the-internet-fun-again/"><strong>Re/code</strong>:
|
||||
Why We All Need to Make the Internet Fun Again</a></h3>
|
||||
<blockquote>
|
||||
|
@ -57,17 +58,20 @@ Why We All Need to Make the Internet Fun Again</a></h3>
|
|||
<h3><a href="http://www.fastcodesign.com/3037028/why-the-internet-is-time-traveling-back-to-1994"><strong>Fast Company</strong>: Oh, Snap! '90s Web Design Is Hot Again</a></h3>
|
||||
<blockquote>“What I'm trying to do with Neocities is re-enable that creativity, and show people that it isn’t just a nostalgia thing any more,” [Kyle Drake] says, adding that the site now hosts about 26,000 sites and has proven financially self-sustaining.</blockquote>
|
||||
|
||||
<h3><a href="http://www.wired.com/2013/07/neocities/"><strong>Wired</strong>: NeoCities Wants to Save Us From the Crushing Boredom of Social Networking</a></h3>
|
||||
<h3><a href="http://www.wired.com/2013/07/neocities/"><strong>Wired</strong>: Neocities Wants to Save Us From the Crushing Boredom of Social Networking</a></h3>
|
||||
<blockquote>There needs to be an alternative to the current pre-formatted, template-driven, standardizing platforms, which make it easy to have a web presence, but hard to make that presence your own.</blockquote>
|
||||
|
||||
<h3><a href="https://www.vice.com/en/article/d77mpw/neocities-is-recreating-the-garish-web-10-creativity-of-geocities"><strong>Vice</strong>: NeoCities Is Recreating the Garish, Web 1.0 Creativity of Geocities</a></h3>
|
||||
<h3><a href="https://www.vice.com/en/article/d77mpw/neocities-is-recreating-the-garish-web-10-creativity-of-geocities"><strong>Vice</strong>: Neocities Is Recreating the Garish, Web 1.0 Creativity of Geocities</a></h3>
|
||||
<blockquote>The project is a way to recreate not only the aesthetic of the early personal websites, but also the original mission of Geocities: to give anyone with internet access a free place on the web.</blockquote>
|
||||
<h3><a href="http://arstechnica.com/tech-policy/2014/05/web-host-gives-fcc-a-28-8kbps-slow-lane-in-net-neutrality-protest/"><strong>Ars Technica</strong>: Web host gives FCC a 28.8Kbps slow lane in net neutrality protest</a></h3>
|
||||
<blockquote>Lots of people are angry about FCC Chairman Tom Wheeler's Internet "fast lane" proposal that would let Internet service providers charge Web services for priority access to consumers. But one Web hosting service called NeoCities isn't just writing letters to the FCC. Instead, the company found the FCC's internal IP address range and throttled all connections to 28.8Kbps speeds.</blockquote>
|
||||
<blockquote>Lots of people are angry about FCC Chairman Tom Wheeler's Internet "fast lane" proposal that would let Internet service providers charge Web services for priority access to consumers. But one Web hosting service called Neocities isn't just writing letters to the FCC. Instead, the company found the FCC's internal IP address range and throttled all connections to 28.8Kbps speeds.</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col col-33">
|
||||
|
||||
<p>When writing about us, we prefer "Neocities" over "NeoCities". Thank you!</p>
|
||||
|
||||
<h2 style="margin-bottom: 9px;">Contact Us</h2>
|
||||
<p><a href="https://neocities.org/contact"><i class="fa fa-envelope-o"></i> Contact Form</a></p>
|
||||
|
||||
|
|
|
@ -24,10 +24,10 @@
|
|||
<div class="tabbable" style="margin-top: 20px"> <!-- Only required for left/right tabs -->
|
||||
<ul class="nav nav-tabs">
|
||||
|
||||
<li class="active"><a href="#sites" data-toggle="tab">Manage Sites</a></li>
|
||||
<li><a href="#supporter" data-toggle="tab">Supporter Info</a></li>
|
||||
<li><a href="#password" data-toggle="tab">Change Password</a></li>
|
||||
<li><a href="#email" data-toggle="tab">Change Email</a></li>
|
||||
<li class="active"><a href="#sites" data-toggle="tab">Sites</a></li>
|
||||
<li><a href="#supporter" data-toggle="tab">Supporter</a></li>
|
||||
<li><a href="#password" data-toggle="tab">Password</a></li>
|
||||
<li><a href="#email" data-toggle="tab">Email</a></li>
|
||||
<% if current_site.stripe_paying_supporter? %>
|
||||
<li><a href="#billing" data-toggle="tab">Change Card</a></li>
|
||||
<% end %>
|
||||
|
|
|
@ -4,16 +4,26 @@
|
|||
</p>
|
||||
|
||||
<% if parent_site.paying_supporter? %>
|
||||
<a class="btn-Action" href="/supporter" style="margin-bottom: 20px">Supporter Info</a>
|
||||
<a href="/supporter" style="margin-bottom: 20px">Supporter Info</a>
|
||||
<small><a href="#" onclick="$('#endSupporterConfirm').modal()" style="font-size: 8pt">End Supporter Membership</a></small>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<p class="tiny">
|
||||
You currently have the <strong>Free Plan (<%= current_site.maximum_space.to_space_pretty %>)</strong>.<br>Want to get more space and help Neocities? Become a supporter!
|
||||
</p>
|
||||
<a class="btn-Action" href="/supporter">Supporter Info</a>
|
||||
<a href="/supporter">Supporter Info</a>
|
||||
<% if parent_site.stripe_customer_id || parent_site.paypal_profile_id %>
|
||||
<br>
|
||||
<a href="/settings/invoices">Generated Invoices</a>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% if parent_site.stripe_customer_id || parent_site.paypal_profile_id %>
|
||||
<br>
|
||||
<a href="/settings/invoices">Generated Invoices</a>
|
||||
<% end %>
|
||||
|
||||
|
||||
<div class="modal hide fade" id="endSupporterConfirm" tabindex="-1" role="dialog" aria-labelledby="endSupporterConfirmLabel" aria-hidden="true">
|
||||
<form method="POST" action="/supporter/end">
|
||||
<%== csrf_token_input_html %>
|
||||
|
|
65
views/settings/invoices.erb
Normal file
65
views/settings/invoices.erb
Normal file
|
@ -0,0 +1,65 @@
|
|||
<div class="header-Outro">
|
||||
<div class="row content single-Col">
|
||||
<h1>Account Settings</h1>
|
||||
<h3 class="subtitle">Manage the account for your sites</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content single-Col misc-page txt-Center">
|
||||
<article>
|
||||
<section>
|
||||
<div class="txt-Center">
|
||||
<% if flash[:success] %>
|
||||
<div class="alert alert-block alert-success" style="margin-top: 20px">
|
||||
<%== flash[:success] %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if flash[:error] %>
|
||||
<div class="alert alert-block alert-error" style="margin-top: 20px">
|
||||
<%== flash[:error] %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<h2>Generated Invoices</h2>
|
||||
|
||||
<% if @invoices.empty? && !current_site.paypal_profile_id %>
|
||||
<p>No generated invoices.</p>
|
||||
<% else %>
|
||||
<p>
|
||||
<% @invoices.each do |invoice| %>
|
||||
<%= Time.at(invoice.date).strftime('%m/%d/%y') %> - <a href="<%= invoice.invoice_pdf %>">PDF</a><br>
|
||||
<% end %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<% if current_site.paypal_profile_id %>
|
||||
<p>PayPal invoices are available on their <a href="https://paypal.com">web site</a>.</p>
|
||||
<% end %>
|
||||
|
||||
<p><a href="/settings">Back</a></p>
|
||||
</section>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="modal hide fade" id="deleteSite" tabindex="-1" role="dialog" aria-labelledby="deleteSiteLabel" aria-hidden="true">
|
||||
<form method="POST" action="/site/delete">
|
||||
<%== csrf_token_input_html %>
|
||||
<div class="modal-header">
|
||||
<button class="close" type="button" data-dismiss="modal" aria-hidden="true"><i class="fa fa-times"></i></button>
|
||||
<h3 id="deleteSiteLabel">Permanently Delete Site</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<strong style="color: red">WARNING: This will permanently delete your web site and Neocities account. There is no undo!</strong>
|
||||
|
||||
<p>Delete Site Name: <strong><%= current_site.username %></strong></p>
|
||||
<p>Confirm your site name by typing it here:</p>
|
||||
<input class="input-Area" name="username" type="text">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn cancel" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
||||
<button type="submit" class="btn-Action">Permanently Delete Site</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
<p><code>198.51.233.1</code></p>
|
||||
|
||||
<h3>Step 2 (optional)</h3>
|
||||
<h3>Step 2 (optional but recommended)</h3>
|
||||
|
||||
<p>
|
||||
Next, you can add an "AAAA record" that also points to your root domain. This isn't strictly required, but provides IPv6 support which helps future proof your site. Use this address for the AAAA record:
|
||||
|
@ -24,10 +24,10 @@
|
|||
|
||||
<p><code>2620:2:6000::bad:dab:cafe</code></p>
|
||||
|
||||
<h3>Step 3 (optional)</h3>
|
||||
<h3>Step 3 (optional but recommended)</h3>
|
||||
|
||||
<p>
|
||||
If you would like to have a <strong>www</strong> for your domain, create a CNAME record pointing <strong>www</strong> to <strong>yourdomain.com</strong>:
|
||||
Sometimes users will type in www before a domain despite it no longer being necessary to load web sites. If you would like to have a <strong>www</strong> for your domain, create a CNAME record pointing <strong>www</strong> to <strong>yourdomain.com</strong>:
|
||||
</p>
|
||||
|
||||
<p><code>www CNAME yourdomain.com</code></p>
|
||||
|
@ -36,10 +36,16 @@
|
|||
|
||||
<h3>Step 4</h3>
|
||||
|
||||
<p>Wait about 5 minutes for the nameserver changes to update. Sometimes it can take a short while for your domain provider to update their records.</p>
|
||||
<p>
|
||||
Remove any "URL redirects" if they are present, they are not needed and cause issues with connecting the domain. For example, Namecheap has a <strong>URL Redirect Record</strong> on new domains that needs to be deleted.
|
||||
</p>
|
||||
|
||||
<h3>Step 5</h3>
|
||||
|
||||
<p>Wait about 5 minutes for the nameserver changes to update. Sometimes it can take a short while for your domain provider to update their records.</p>
|
||||
|
||||
<h3>Step 6</h3>
|
||||
|
||||
<p>
|
||||
Finally, add your domain name to the box below (just the <strong>yourdomain.com</strong>, don't add any subdomains), and your domain should come online within 5 minutes! We will automatically create SSL certs for your domain.
|
||||
</p>
|
||||
|
|
|
@ -20,15 +20,6 @@
|
|||
> Disable Site Profile
|
||||
<br>
|
||||
</div>
|
||||
|
||||
<h3>IPFS Archiving</h3>
|
||||
|
||||
<div style="display: inline-block; text-align: left; margin-bottom: 10px">
|
||||
<input name="site[ipfs_archiving_enabled]" type="hidden" value="false">
|
||||
<input name="site[ipfs_archiving_enabled]" type="checkbox" value="true"
|
||||
<% if @site.ipfs_archiving_enabled == true %>checked<% end %>
|
||||
> Enable IPFS Archiving <small>(<a href="/distributed-web">what is this?</a>)</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input class="btn-Action" type="submit" value="Update Settings">
|
||||
|
|
|
@ -19,11 +19,6 @@
|
|||
<div class="col col-50 profile-info">
|
||||
<h2 class="eps title-with-badge"><span><%= site.title %></span> <% if site.supporter? %><a href="/supporter" class="supporter-badge" title="Neocities Supporter"></a> <% end %></h2>
|
||||
<p class="site-url"><a href="<%= site.uri %>"><%= site.host %></a></p>
|
||||
<!--
|
||||
<% if false #site.latest_archive %>
|
||||
<p><a href="<%= site.latest_archive.url %>" style="margin-right: 5px"><%= site.latest_archive.ipfs_hash %></a><small style="font-size: 7pt"><a href="/permanent-web">(what is this?)</a></small></p>
|
||||
<% end %>
|
||||
-->
|
||||
<% follow_count = site.follows_dataset.count %>
|
||||
<div class="stats">
|
||||
<div class="stat"><strong><%= site.views.format_large_number %></strong> <span>view<%= site.views == 1 ? '' : 's' %></span></div>
|
||||
|
@ -36,10 +31,6 @@
|
|||
<a href="/dashboard" class="btn-Action edit"><i class="fa fa-edit" title="Edit"></i> Edit Site</a>
|
||||
<% end %>
|
||||
|
||||
<% if site.latest_archive && site.ipfs_archiving_enabled %>
|
||||
<a href="/site/<%= site.username %>/archives" class="btn-Action edit"><i class="fa fa-history" title="Archives"></i> Archives</a>
|
||||
<% end %>
|
||||
|
||||
<% if current_site && current_site != site %>
|
||||
<% is_following = current_site.is_following?(site) %>
|
||||
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
<div class="header-Outro">
|
||||
<div class="row content single-Col">
|
||||
<h1>IPFS Archives</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content single-Col misc-page">
|
||||
<article role="article">
|
||||
<% if @archives.length == 0 %>
|
||||
No archives yet.
|
||||
<% else %>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>IPFS CID <small style="display: inline"><a href="/permanent-web">(what is this?)</a></small></th>
|
||||
<th>Archived Time</th>
|
||||
</tr>
|
||||
<% @archives.each do |archive| %>
|
||||
<tr>
|
||||
<td><a href="<%= archive.url %>"><%= archive.ipfs_hash %></a></td>
|
||||
<td><%= archive.updated_at.ago.downcase %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
|
||||
<p>
|
||||
This is a preview release of a new technology. We're still figuring things out, and may stop hosting archives without notice. <a href="/permanent-web">Learn how you can host your own copies of these archives</a>.
|
||||
</p>
|
||||
<p>
|
||||
Archives are captured once every <%= Archive::ARCHIVE_WAIT_TIME / 60 %> minutes, so if you don't see your latest changes, check back later.
|
||||
</p>
|
||||
<% end %>
|
||||
</article>
|
||||
</div>
|
|
@ -6,7 +6,9 @@
|
|||
You're almost ready!<br>
|
||||
<% end %>
|
||||
|
||||
We sent an email to <strong><%= current_site.email %></strong> to make sure it's correct.<br>Please check your email, enter the confirmation code here, and you're all set.
|
||||
We sent an email to <strong><%= current_site.email %></strong> to make sure it's correct.<br>
|
||||
Please check your email, and enter the confirmation code here.<br>
|
||||
If you don't see the email in your inbox, try looking in the spam folder.
|
||||
</h3>
|
||||
|
||||
<div class="row">
|
||||
|
|
90
views/site/confirm_phone.erb
Normal file
90
views/site/confirm_phone.erb
Normal file
|
@ -0,0 +1,90 @@
|
|||
<section class="section plans welcome">
|
||||
<h2>Verify your phone number</h2>
|
||||
<div class="txt-Center"><img src="/img/catbus.png" width="90px"></div>
|
||||
<h3 class="subtitle">
|
||||
Last thing!<br>
|
||||
To prevent spam and keep the searchability of your site high, we have one last step:
|
||||
<br>please verify your mobile phone number.
|
||||
</h3>
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-100 txt-Center" style="margin-top: 10px;">
|
||||
<% if flash[:success] %>
|
||||
<div class="alert alert-block alert-success" style="margin-top: 20px">
|
||||
<%== flash[:success] %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if flash[:error] %>
|
||||
<div class="alert alert-block alert-error" style="margin-top: 20px">
|
||||
<%== flash[:error] %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<form method="POST" action="/site/<%= current_site.username %>/confirm_phone" class="content">
|
||||
<%== csrf_token_input_html %>
|
||||
|
||||
<% if current_site.phone_verification_sid %>
|
||||
<fieldset>
|
||||
<label for="token">Enter the 6 digit code:<br></label>
|
||||
<input id="code" name="code" type="text" class="input-Area" autofill="off" autocapitalize="off" autocorrect="off" value="<%= flash[:code] %>" style="width: 100px" maxlength=6>
|
||||
</fieldset>
|
||||
<input id="submitButton" class="btn-Action" type="submit" value="Verify Code" style="display: none" autocomplete="off">
|
||||
|
||||
<script>
|
||||
document.getElementById('code').addEventListener('input', function(e) {
|
||||
var inputVal = e.target.value;
|
||||
var submitButton = document.getElementById('submitButton');
|
||||
|
||||
// Check if there are exactly 6 digits in the input
|
||||
var isValid = /^\d{6}$/.test(inputVal);
|
||||
|
||||
if(isValid) {
|
||||
submitButton.style = 'display: inline-block';
|
||||
} else {
|
||||
submitButton.style = 'display: none';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<% else %>
|
||||
|
||||
<fieldset>
|
||||
<label for="phone">Enter your phone number<br><small>(including country code)</small></label>
|
||||
<input id="phone" name="phone" type="text" class="input-Area" autofill="off" autocapitalize="off" autocorrect="off" autocomplete="off" style="width: 290px">
|
||||
<input id="phone_intl" name="phone_intl" type="hidden">
|
||||
</fieldset>
|
||||
<input id="submitButton" class="btn-Action" type="submit" value="Send Verification Code" style="display: none">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/intl-tel-input@18.2.1/build/css/intlTelInput.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/intl-tel-input@18.2.1/build/js/intlTelInput.min.js"></script>
|
||||
<script>
|
||||
const input = document.querySelector("#phone");
|
||||
const iti = window.intlTelInput(input, {
|
||||
nationalMode: true,
|
||||
utilsScript: "https://cdn.jsdelivr.net/npm/intl-tel-input@18.2.1/build/js/utils.js",
|
||||
});
|
||||
|
||||
const handleChange = () => {
|
||||
let text;
|
||||
if(iti.isValidNumber()) {
|
||||
document.getElementById('submitButton').style = "display: inline-block"
|
||||
document.getElementById('phone_intl').value = iti.getNumber()
|
||||
} else {
|
||||
document.getElementById('submitButton').style = "display: none"
|
||||
}
|
||||
};
|
||||
|
||||
// listen to "keyup", but also "change" to update when the user selects a country
|
||||
input.addEventListener('change', handleChange);
|
||||
input.addEventListener('keyup', handleChange);
|
||||
</script>
|
||||
|
||||
<% end %>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
|
@ -139,7 +139,7 @@
|
|||
<h2>
|
||||
Total Visitors
|
||||
<small>
|
||||
<% if params[:days].blank? %>
|
||||
<% if params[:days].to_s.blank? %>
|
||||
last <%= @default_stat_points %> days
|
||||
<% elsif params[:days] == 'sincethebigbang' %>
|
||||
all time
|
||||
|
@ -174,6 +174,22 @@
|
|||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="row">
|
||||
<div class="col col-100">
|
||||
<h3>What are these numbers?</h3>
|
||||
<p>
|
||||
<strong>Hits</strong> occur each time our servers send a file. For example, if a webpage consists of an HTML file, three images, and two JavaScript files, accessing this page would result in six hits.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Visits</strong> are a count of unique IP addresses requesting pages from a web site per hour, regardless of how many requests that IP address makes. Visits generally give a more accurate representation of website traffic in terms of real users.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Due to bots, search engine crawlers, and proxy servers these numbers should not be considered completely accurate.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<div class="row">
|
||||
<div class="col col-50">
|
||||
|
@ -276,7 +292,7 @@
|
|||
</div>
|
||||
|
||||
<!-- <script src="//www.webglearth.com/v2/api.js"></script> -->
|
||||
<script src="/js/Chart.min.js"></script>
|
||||
<script src="/js/chart.js"></script>
|
||||
<script>
|
||||
//OpenGL globe
|
||||
$(document).ready(function() {
|
||||
|
@ -324,39 +340,75 @@
|
|||
});
|
||||
*/
|
||||
|
||||
//chart.js
|
||||
var data = {
|
||||
const data = {
|
||||
labels: <%== @stats[:stat_days].collect {|s| s.created_at.strftime("%b %-d, %Y")}.to_json %>,
|
||||
datasets: [
|
||||
{
|
||||
label: "Hits",
|
||||
fillColor: "rgba(220,220,220,0.2)",
|
||||
strokeColor: "rgba(220,220,220,1)",
|
||||
pointColor: "rgba(220,220,220,1)",
|
||||
pointStrokeColor: "#fff",
|
||||
pointHighlightFill: "#fff",
|
||||
pointHighlightStroke: "rgba(220,220,220,1)",
|
||||
label: 'Hits',
|
||||
backgroundColor: 'rgba(220,220,220,0.2)',
|
||||
fill: true,
|
||||
borderColor: 'rgba(220,220,220,1)',
|
||||
pointBackgroundColor: 'rgba(220,220,220,1)',
|
||||
pointBorderColor: '#fff',
|
||||
pointHoverBackgroundColor: '#fff',
|
||||
pointHoverBorderColor: 'rgba(220,220,220,1)',
|
||||
data: <%== @stats[:stat_days].collect {|s| s.hits}.to_json %>
|
||||
},
|
||||
{
|
||||
label: "Unique Visits",
|
||||
fillColor: "rgba(151,187,205,0.2)",
|
||||
strokeColor: "rgba(151,187,205,1)",
|
||||
pointColor: "rgba(151,187,205,1)",
|
||||
pointStrokeColor: "#fff",
|
||||
pointHighlightFill: "#fff",
|
||||
pointHighlightStroke: "rgba(151,187,205,1)",
|
||||
label: 'Visits',
|
||||
backgroundColor: 'rgba(151,187,205,0.2)',
|
||||
fill: true,
|
||||
borderColor: 'rgba(151,187,205,1)',
|
||||
pointBackgroundColor: 'rgba(151,187,205,1)',
|
||||
pointBorderColor: '#fff',
|
||||
pointHoverBackgroundColor: '#fff',
|
||||
pointHoverBorderColor: 'rgba(151,187,205,1)',
|
||||
data: <%== @stats[:stat_days].collect {|s| s.views}.to_json %>
|
||||
}
|
||||
]
|
||||
}
|
||||
// Get context with jQuery - using jQuery's .get() method.
|
||||
var ctx = $("#myChart").get(0).getContext("2d")
|
||||
// This will get the first returned node in the jQuery collection.
|
||||
//var myNewChart = new Chart(ctx);
|
||||
var myLineChart = new Chart(ctx).Line(data, {
|
||||
bezierCurve: false,
|
||||
multiTooltipTemplate: "<%== @multi_tooltip_template %>"
|
||||
})
|
||||
})
|
||||
</script>
|
||||
};
|
||||
|
||||
const ctx = $("#myChart").get(0).getContext("2d");
|
||||
|
||||
const config = {
|
||||
type: 'line',
|
||||
data: data,
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
x: {
|
||||
beginAtZero: true
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
mode: "index",
|
||||
intersect: false,
|
||||
bodyFont: {
|
||||
size: 14,
|
||||
},
|
||||
bodyAlign: 'right',
|
||||
titleFont: {
|
||||
size: 14,
|
||||
},
|
||||
callbacks: {
|
||||
afterTitle: function(context) {
|
||||
let tooltipData = [];
|
||||
if (context.length > 0) {
|
||||
const index = context[0].dataIndex;
|
||||
}
|
||||
return tooltipData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const myLineChart = new Chart(ctx, config);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
|
@ -27,5 +27,4 @@
|
|||
<a href="/">Get Started</a>
|
||||
</p>
|
||||
</article>
|
||||
<%== erb :'_team', layout: false %>
|
||||
</div>
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
require 'sidekiq/api'
|
||||
|
||||
class ArchiveWorker
|
||||
include Sidekiq::Worker
|
||||
sidekiq_options queue: :archive, retry: 2, backtrace: true
|
||||
|
||||
def perform(site_id)
|
||||
site = Site[site_id]
|
||||
return if site.nil? || site.is_banned? || site.is_deleted
|
||||
|
||||
if site.site_files_dataset.count > 1000
|
||||
logger.info "skipping #{site_id} (#{site.username}) due to > 1000 files"
|
||||
return
|
||||
end
|
||||
|
||||
queue = Sidekiq::Queue.new self.class.sidekiq_options_hash['queue']
|
||||
logger.info "JOB ID: #{jid} #{site_id.inspect}"
|
||||
queue.each do |job|
|
||||
if job.args == [site_id] && job.jid != jid
|
||||
logger.info "DELETING #{job.jid} for site_id #{site_id}"
|
||||
job.delete
|
||||
end
|
||||
end
|
||||
|
||||
scheduled_jobs = Sidekiq::ScheduledSet.new.select do |scheduled_job|
|
||||
scheduled_job.klass == 'ArchiveWorker' &&
|
||||
scheduled_job.args[0] == site_id
|
||||
end
|
||||
|
||||
scheduled_jobs.each do |scheduled_job|
|
||||
logger.info "DELETING scheduled job #{scheduled_job.jid} for site_id #{site_id}"
|
||||
scheduled_job.delete
|
||||
end
|
||||
|
||||
logger.info "ARCHIVING: #{site.username}"
|
||||
|
||||
site.archive!
|
||||
end
|
||||
end
|
|
@ -1,3 +1,4 @@
|
|||
require 'sidekiq/api'
|
||||
require 'securerandom'
|
||||
require 'open3'
|
||||
|
||||
|
@ -6,7 +7,7 @@ class ScreenshotWorker
|
|||
HARD_TIMEOUT = 30.freeze
|
||||
PAGE_WAIT_TIME = 5.freeze # 3D/VR sites take a bit to render after loading usually.
|
||||
include Sidekiq::Worker
|
||||
sidekiq_options queue: :screenshots, retry: 10, backtrace: true
|
||||
sidekiq_options queue: :screenshots, backtrace: true
|
||||
|
||||
def perform(username, path)
|
||||
site = Site[username: username]
|
||||
|
@ -34,8 +35,6 @@ class ScreenshotWorker
|
|||
|
||||
path = "/#{path}" unless path[0] == '/'
|
||||
|
||||
path_for_screenshot = path
|
||||
|
||||
uri = Addressable::URI.parse $config['screenshot_urls'].sample
|
||||
api_user, api_password = uri.user, uri.password
|
||||
uri = "#{uri.scheme}://#{uri.host}:#{uri.port}" + '?' + Rack::Utils.build_query(
|
||||
|
@ -51,16 +50,24 @@ class ScreenshotWorker
|
|||
File.write base_image_tmpfile_path, http_resp.to_s
|
||||
|
||||
user_screenshots_path = File.join SCREENSHOTS_PATH, Site.sharding_dir(username), username
|
||||
screenshot_path = File.join user_screenshots_path, File.dirname(path_for_screenshot)
|
||||
screenshot_path = File.join user_screenshots_path, File.dirname(path)
|
||||
FileUtils.mkdir_p screenshot_path unless Dir.exist?(screenshot_path)
|
||||
|
||||
FileUtils.cp base_image_tmpfile_path, File.join(user_screenshots_path, "#{path_for_screenshot}.png")
|
||||
# We only need the full PNG for the main index right now
|
||||
if path.match /^\/index.html?$/
|
||||
ImageOptimizer.new(base_image_tmpfile_path, level: 1).optimize
|
||||
FileUtils.cp base_image_tmpfile_path, File.join(user_screenshots_path, "#{path}.png")
|
||||
end
|
||||
|
||||
# Optimized image for open graph link expanders
|
||||
image = Rszr::Image.load base_image_tmpfile_path
|
||||
image.resize! 1200, 630, crop: :n
|
||||
image.save File.join(user_screenshots_path, "#{path}.jpg"), quality: 85
|
||||
ImageOptimizer.new(File.join(user_screenshots_path, "#{path}.jpg")).optimize
|
||||
|
||||
Site::SCREENSHOT_RESOLUTIONS.each do |res|
|
||||
width, height = res.split('x').collect {|r| r.to_i}
|
||||
|
||||
full_screenshot_path = File.join(user_screenshots_path, "#{path_for_screenshot}.#{res}.webp")
|
||||
|
||||
full_screenshot_path = File.join(user_screenshots_path, "#{path}.#{res}.webp")
|
||||
opts = {resize_w: width, resize_h: height, near_lossless: 0}
|
||||
|
||||
if width == height
|
||||
|
|
|
@ -3,8 +3,25 @@ class StopForumSpamWorker
|
|||
sidekiq_options queue: :stop_forum_spam, retry: 1, backtrace: true
|
||||
|
||||
def perform(opts)
|
||||
opts.merge! api_key: $config['stop_forum_spam_api_key']
|
||||
res = HTTP.post 'https://stopforumspam.com/add', form: opts
|
||||
puts res.inspect
|
||||
txn = Minfraud::Components::Report::Transaction.new(
|
||||
ip_address: opts['ip'],
|
||||
tag: :spam_or_abuse,
|
||||
# The following key/values are not mandatory but are encouraged
|
||||
#maxmind_id: 'noideawhatthisis',
|
||||
#minfraud_id: '01c25cb0-f067-4e02-8ed0-a094c580f5e4',
|
||||
#transaction_id: 'txn123'
|
||||
#chargeback_code: 'BL'
|
||||
notes: opts['classifier']
|
||||
)
|
||||
|
||||
reporter = Minfraud::Report.new transaction: txn
|
||||
reporter.report_transaction
|
||||
|
||||
HTTP.post 'https://stopforumspam.com/add', form: {
|
||||
api_key: $config['stop_forum_spam_api_key'],
|
||||
username: opts['username'],
|
||||
email: opts['email'],
|
||||
ip: opts['ip']
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue