Merge branch 'master' into feature/bootstrap
This commit is contained in:
commit
7767eeae9f
|
@ -0,0 +1,69 @@
|
|||
name: Retrospring
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:10.12
|
||||
env:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: justask_test
|
||||
ports:
|
||||
- 5432/tcp
|
||||
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
||||
redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
options: --entrypoint redis-server
|
||||
|
||||
env:
|
||||
RAILS_ENV: test
|
||||
POSTGRES_HOST: localhost
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: justask_test
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: vendor/bundle
|
||||
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gems-
|
||||
- name: Set up Ruby 2.7
|
||||
uses: actions/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 2.7.x
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get install -y libpq-dev libxml2-dev libxslt1-dev libmagickwand-dev imagemagick
|
||||
- name: Copy default configuration
|
||||
run: |
|
||||
cp config/database.yml.postgres config/database.yml
|
||||
cp config/justask.yml.example config/justask.yml
|
||||
- name: Install gems
|
||||
run: |
|
||||
gem install bundler
|
||||
bundle config path vendor/bundle
|
||||
bundle config set without 'production'
|
||||
bundle install --jobs 4 --retry 3
|
||||
- name: Set up database
|
||||
run: bundle exec rake db:setup
|
||||
env:
|
||||
POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }}
|
||||
- name: Run tests
|
||||
run: bundle exec rake spec
|
||||
env:
|
||||
POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }}
|
||||
REDIS_URL: "redis://localhost:${{ job.services.redis.ports[6379] }}"
|
|
@ -35,3 +35,5 @@ desktop.ini
|
|||
|
||||
# ignore exports
|
||||
/public/export
|
||||
|
||||
/spec/examples.txt
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
AllCops:
|
||||
RunRailsCops: true
|
||||
Rails:
|
||||
Enabled: true
|
||||
Exclude:
|
||||
- 'vendor/**/*'
|
||||
- 'db/schema.rb'
|
||||
|
@ -7,7 +8,7 @@ AllCops:
|
|||
Metrics/ClassLength:
|
||||
Enabled: false
|
||||
|
||||
Metrics/LineLength:
|
||||
Layout/LineLength:
|
||||
Enabled: false
|
||||
|
||||
Metrics/MethodLength:
|
||||
|
@ -16,9 +17,6 @@ Metrics/MethodLength:
|
|||
Metrics/ModuleLength:
|
||||
Enabled: false
|
||||
|
||||
Style/BracesAroundHashParameters:
|
||||
Enabled: false
|
||||
|
||||
Style/ClassAndModuleChildren:
|
||||
Enabled: false
|
||||
|
||||
|
|
12
.travis.yml
12
.travis.yml
|
@ -1,12 +0,0 @@
|
|||
language: ruby
|
||||
cache: bundler
|
||||
rvm: 2.1
|
||||
services:
|
||||
- redis-server
|
||||
before_script:
|
||||
- cp config/database.yml.postgres config/database.yml
|
||||
- cp config/justask.yml.example config/justask.yml
|
||||
- bundle exec rake db:drop db:create db:migrate db:test:prepare
|
||||
notifications:
|
||||
slack:
|
||||
secure: "p+LieMEgRpQQimqT6+Up4cHpbFSyFWoEAzt3gl1yg+cECT2btJR0xkL10Z74gRhNcmcyrhP3IU10u+/RVclqvKAx39bG6r/+8kbyhCA1yXAJRScHwJMKRUqFwT8FV7UWZNyHEbpJmuC2M+VwGAf6PYZcNbfcjZzR8xPdM6+Mjq6mxNTMiJuGCTaL/+FPUOmiTTjl0ZNQqsbyScAtTWh8EMCUIrJ6fif2tESVY1g2DuknOhRAqs6SnhGXmOE+tmmi2bwx8dGULMbFKO9seeqGqHgRO/SJ7+o+qaPXC5pZmdl7gIjLubEQI8pZnLyYnm4mT7LE+LQzuhreHUiEOIvJ43Gxnpq9Dt48spQNIh3oViyw2JVbYE+RprFjfw6Dk1EtgfZ7qQEbUZwkguXZ98kz9ps6w/m3/fKWTOEHheHcCeuN35MnLkZfdUiodTsQuOnwSpMGsIWnOQP0XwiZ7bMshRET/Mg2amZhp6xcIt52ZZbC/ojDRoq+bakmjKWnq5mkZK5EqzTaHm9xo//nMNA0UIPYJyotgmauGc5xkSUHbyy/uf5xMRmcwuiss+VBCMI8z5a1uHkLGd805JU6Gr3OXp8fuiu8zWr80k4+w7vojsS84vvd2ntVktJswXRVrj/e1TdxrYCNY/32bKu23E24ygxtKt//fx0Jif0sCdSHXf4="
|
|
@ -0,0 +1,128 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
team@retrospring.net.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
|
@ -1,12 +1,18 @@
|
|||
# Contributing
|
||||
|
||||
Thank you for reading this! This document has some notes on contributing to the development of Retrospring.
|
||||
Thank you for reading this! This document has some notes on contributing to the
|
||||
development of Retrospring.
|
||||
|
||||
## Reporting bugs
|
||||
|
||||
Before submitting an issue please check if there is already an existing issue. If there is, please add any additional information or give it a "+1" in the comments.
|
||||
Before submitting an issue please check if there is already an existing issue.
|
||||
If there is, please add any additional information or give it a "+1" in the
|
||||
comments.
|
||||
|
||||
When submitting an issue please describe the issue clearly, including how to reproduce the bug, which situations it appears in, what you expect to happen, what actually happens, and what platform (browser and operating system) you are using. We find screenshots (for front-end issues) very helpful.
|
||||
When submitting an issue please describe the issue clearly, including how to
|
||||
reproduce the bug, which situations it appears in, what you expect to happen,
|
||||
what actually happens, and what platform (browser and operating system) you are
|
||||
using. We find screenshots (for front-end issues) very helpful.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
|
@ -16,13 +22,18 @@ When submitting an issue please describe the issue clearly, including how to rep
|
|||
4. Push to the branch (`git push origin feature-new`)
|
||||
5. Create a new Pull Request
|
||||
|
||||
We love pull requests! We are very happy to work with you to get your changes merged in, however please keep the following in mind.
|
||||
We love pull requests! We are very happy to work with you to get your changes
|
||||
merged in, however please keep the following in mind.
|
||||
|
||||
* Please use the core team standard of `feature-*` branch naming.
|
||||
* Adhere to the coding conventions you see in the surrounding code.
|
||||
* If you include a new feature also include tests, and make sure they'll pass.
|
||||
* Before submitting a pull-request, clean up the history by going over your commits and squashing together minor changes and fixes into the corresponding commits. You can do this using the interactive rebase command.
|
||||
* Before submitting a pull-request, clean up the history by going over your
|
||||
commits and squashing together minor changes and fixes into the corresponding
|
||||
commits. You can do this using the interactive rebase command.
|
||||
|
||||
## What to work on
|
||||
|
||||
If you want to know what you can help us with, like new features, check the Github issues or the [TODO](https://github.com/Retrospring/retrospring/blob/master/TODO).
|
||||
If you want to know what you can help us with, like new features, check the
|
||||
GitHub issues or the
|
||||
[TODO](https://github.com/Retrospring/retrospring/blob/master/TODO).
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
List of contributors, format is "Name @github <email>"
|
||||
|
||||
Andreas N. @pixeldesu
|
||||
Georg G. @nilsding
|
||||
Yuki @yukimono
|
||||
Adel D.-K.
|
||||
Howl
|
||||
Iain D.
|
||||
Jona H. @schisma @ix
|
||||
Robin B.
|
18
Capfile
18
Capfile
|
@ -1,18 +0,0 @@
|
|||
# Load DSL and set up stages
|
||||
require "capistrano/setup"
|
||||
|
||||
# Include default deployment tasks
|
||||
require "capistrano/deploy"
|
||||
|
||||
# Load the SCM plugin
|
||||
require "capistrano/scm/git"
|
||||
install_plugin Capistrano::SCM::Git
|
||||
|
||||
# Include tasks from other gems included in your Gemfile
|
||||
#require "rvm1/capistrano3"
|
||||
require "capistrano/bundler"
|
||||
require "capistrano/rails/assets"
|
||||
require "capistrano/rails/migrations"
|
||||
|
||||
# Load custom tasks from `lib/capistrano/tasks` if you have any defined
|
||||
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
|
18
Gemfile
18
Gemfile
|
@ -21,8 +21,6 @@ gem 'bcrypt', '~> 3.1.7'
|
|||
gem 'haml', '~> 5.0'
|
||||
gem 'bootstrap', '~> 4.4', '>= 4.4.1'
|
||||
gem 'sweetalert-rails'
|
||||
gem 'will_paginate'
|
||||
gem 'will_paginate-bootstrap'
|
||||
gem 'devise', '~> 4.0'
|
||||
gem 'devise-i18n'
|
||||
gem 'devise-async'
|
||||
|
@ -41,6 +39,8 @@ gem 'tiny-color-rails'
|
|||
gem 'jquery-minicolors-rails'
|
||||
gem 'colorize'
|
||||
|
||||
gem "rolify", "~> 5.2"
|
||||
|
||||
source "https://rails-assets.org" do
|
||||
gem 'rails-assets-growl'
|
||||
gem 'rails-assets-jquery', '~> 2.2.0'
|
||||
|
@ -81,17 +81,6 @@ group :development do
|
|||
gem 'web-console', '< 4.0.0'
|
||||
end
|
||||
|
||||
# Deployment
|
||||
group :development do
|
||||
gem 'capistrano', '~> 3.8', require: false
|
||||
gem 'capistrano-rails', require: false
|
||||
|
||||
gem 'rbnacl', '>= 3.2', '< 5.0', require: false
|
||||
gem 'rbnacl-libsodium', require: false
|
||||
gem 'bcrypt_pbkdf', '>= 1.0', '< 2.0', require: false
|
||||
gem 'ed25519', require: false
|
||||
end
|
||||
|
||||
group :production do
|
||||
gem 'unicorn', group: :production
|
||||
end
|
||||
|
@ -100,6 +89,7 @@ group :development, :test do
|
|||
gem 'rake'
|
||||
gem 'puma'
|
||||
gem 'rspec-rails', '~> 3.9'
|
||||
gem 'rspec-its', '~> 1.3'
|
||||
gem 'factory_bot_rails', require: false
|
||||
gem 'faker'
|
||||
gem 'capybara'
|
||||
|
@ -112,4 +102,6 @@ group :development, :test do
|
|||
gem 'letter_opener' # Use this just in local test environments
|
||||
gem 'brakeman'
|
||||
gem 'guard-brakeman'
|
||||
gem 'timecop'
|
||||
gem 'rails-controller-testing'
|
||||
end
|
||||
|
|
48
Gemfile.lock
48
Gemfile.lock
|
@ -67,13 +67,10 @@ GEM
|
|||
tzinfo (~> 1.1)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
airbrussh (1.4.0)
|
||||
sshkit (>= 1.6.1, != 1.7.0)
|
||||
arel (9.0.0)
|
||||
autoprefixer-rails (9.7.6)
|
||||
execjs
|
||||
bcrypt (3.1.13)
|
||||
bcrypt_pbkdf (1.0.1)
|
||||
better_errors (2.6.0)
|
||||
coderay (>= 1.0.0)
|
||||
erubi (>= 1.0.0)
|
||||
|
@ -92,16 +89,6 @@ GEM
|
|||
buftok (0.2.0)
|
||||
builder (3.2.4)
|
||||
byebug (11.1.2)
|
||||
capistrano (3.13.0)
|
||||
airbrussh (>= 1.0.0)
|
||||
i18n
|
||||
rake (>= 10.0.0)
|
||||
sshkit (>= 1.9.0)
|
||||
capistrano-bundler (1.6.0)
|
||||
capistrano (~> 3.1)
|
||||
capistrano-rails (1.4.0)
|
||||
capistrano (~> 3.1)
|
||||
capistrano-bundler (~> 1.1)
|
||||
capybara (3.32.1)
|
||||
addressable
|
||||
mini_mime (>= 0.1.3)
|
||||
|
@ -143,7 +130,6 @@ GEM
|
|||
docile (1.3.2)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
ed25519 (1.2.4)
|
||||
equalizer (0.0.11)
|
||||
erubi (1.9.0)
|
||||
excon (0.73.0)
|
||||
|
@ -290,9 +276,6 @@ GEM
|
|||
naught (1.1.0)
|
||||
nenv (0.3.0)
|
||||
nested_form (0.3.2)
|
||||
net-scp (2.0.0)
|
||||
net-ssh (>= 2.6.5, < 6.0.0)
|
||||
net-ssh (5.2.0)
|
||||
newrelic_rpm (6.10.0.364)
|
||||
nio4r (2.5.2)
|
||||
nokogiri (1.10.9)
|
||||
|
@ -361,6 +344,10 @@ GEM
|
|||
rails-assets-growl (1.3.5)
|
||||
rails-assets-jquery
|
||||
rails-assets-jquery (2.2.4)
|
||||
rails-controller-testing (1.0.4)
|
||||
actionpack (>= 5.0.1.x)
|
||||
actionview (>= 5.0.1.x)
|
||||
activesupport (>= 5.0.1.x)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
|
@ -392,10 +379,6 @@ GEM
|
|||
rb-fsevent (0.10.3)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rbnacl (4.0.2)
|
||||
ffi
|
||||
rbnacl-libsodium (1.0.16)
|
||||
rbnacl (>= 3.0.1)
|
||||
redcarpet (3.5.0)
|
||||
redis (4.1.3)
|
||||
regexp_parser (1.7.0)
|
||||
|
@ -403,11 +386,15 @@ GEM
|
|||
responders (3.0.0)
|
||||
actionpack (>= 5.0)
|
||||
railties (>= 5.0)
|
||||
rolify (5.2.0)
|
||||
rspec-core (3.9.1)
|
||||
rspec-support (~> 3.9.1)
|
||||
rspec-expectations (3.9.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-its (1.3.0)
|
||||
rspec-core (>= 3.0.0)
|
||||
rspec-expectations (>= 3.0.0)
|
||||
rspec-mocks (3.9.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
|
@ -468,9 +455,6 @@ GEM
|
|||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
sshkit (1.21.0)
|
||||
net-scp (>= 1.1.2)
|
||||
net-ssh (>= 2.8.0)
|
||||
sweetalert-rails (1.1.3)
|
||||
railties (>= 3.1.0)
|
||||
temple (0.8.2)
|
||||
|
@ -479,6 +463,7 @@ GEM
|
|||
thor (1.0.1)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.10)
|
||||
timecop (0.9.1)
|
||||
tiny-color-rails (0.0.2)
|
||||
railties (>= 3.0)
|
||||
turbolinks (2.5.4)
|
||||
|
@ -514,9 +499,6 @@ GEM
|
|||
websocket-driver (0.7.1)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.4)
|
||||
will_paginate (3.3.0)
|
||||
will_paginate-bootstrap (1.0.2)
|
||||
will_paginate (>= 3.0.3)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
|
||||
|
@ -525,15 +507,12 @@ PLATFORMS
|
|||
|
||||
DEPENDENCIES
|
||||
bcrypt (~> 3.1.7)
|
||||
bcrypt_pbkdf (>= 1.0, < 2.0)
|
||||
better_errors
|
||||
bootstrap (~> 4.4, >= 4.4.1)
|
||||
bootstrap3-datetimepicker-rails (~> 4.7.14)
|
||||
bootstrap_form
|
||||
brakeman
|
||||
byebug
|
||||
capistrano (~> 3.8)
|
||||
capistrano-rails
|
||||
capybara
|
||||
coffee-rails (~> 4.1)
|
||||
colorize
|
||||
|
@ -542,7 +521,6 @@ DEPENDENCIES
|
|||
devise (~> 4.0)
|
||||
devise-async
|
||||
devise-i18n
|
||||
ed25519
|
||||
factory_bot_rails
|
||||
fake_email_validator
|
||||
faker
|
||||
|
@ -576,13 +554,14 @@ DEPENDENCIES
|
|||
rails (~> 5.2)
|
||||
rails-assets-growl!
|
||||
rails-assets-jquery (~> 2.2.0)!
|
||||
rails-controller-testing
|
||||
rails-i18n (~> 5.0)
|
||||
rails_admin
|
||||
rake
|
||||
rbnacl (>= 3.2, < 5.0)
|
||||
rbnacl-libsodium
|
||||
redcarpet
|
||||
redis
|
||||
rolify (~> 5.2)
|
||||
rspec-its (~> 1.3)
|
||||
rspec-rails (~> 3.9)
|
||||
ruby-progressbar
|
||||
sanitize
|
||||
|
@ -593,6 +572,7 @@ DEPENDENCIES
|
|||
simplecov-rcov
|
||||
spring (~> 2.0)
|
||||
sweetalert-rails
|
||||
timecop
|
||||
tiny-color-rails
|
||||
tumblr_client!
|
||||
turbolinks (~> 2.5.3)
|
||||
|
@ -600,8 +580,6 @@ DEPENDENCIES
|
|||
uglifier (>= 1.3.0)
|
||||
unicorn
|
||||
web-console (< 4.0.0)
|
||||
will_paginate
|
||||
will_paginate-bootstrap
|
||||
|
||||
BUNDLED WITH
|
||||
2.1.4
|
||||
|
|
20
README.md
20
README.md
|
@ -1,17 +1,25 @@
|
|||
# Retrospring [![Build Status](https://travis-ci.org/Retrospring/retrospring.svg)](https://travis-ci.org/Retrospring/retrospring)
|
||||
# Retrospring
|
||||
![Retrospring](https://github.com/Retrospring/retrospring/workflows/Retrospring/badge.svg)
|
||||
|
||||
This is the source code that powers Retrospring. This is a detached fork of [nilsding/justask](https://github.com/nilsding/justask), where we continue development.
|
||||
This is the source code that powers Retrospring. This is a detached fork of
|
||||
[nilsding/justask](https://github.com/nilsding/justask), where we continue
|
||||
development.
|
||||
|
||||
Retrospring shut down on June 12, 2016. The source code will continue to be hosted here, as though no further updates will be made to it (there weren't any to begin with, though).
|
||||
Retrospring shut down on June 12, 2016, but it's somehow still alive. You're
|
||||
welcome.
|
||||
|
||||
## Installation
|
||||
|
||||
You can find all the installation instructions needed for a local/production setup of Retrospring in the [Wiki](https://github.com/Retrospring/retrospring/wiki/Setup)!
|
||||
You can find all the installation instructions needed for a local/production
|
||||
setup of Retrospring in the
|
||||
[Wiki](https://github.com/Retrospring/retrospring/wiki/Setup).
|
||||
|
||||
## Contributing
|
||||
|
||||
Guidelines for Pull Requests and general information about how you can help us improving Retrospring can be found [here](https://github.com/Retrospring/retrospring/blob/master/CONTRIBUTING.md)!
|
||||
Guidelines for Pull Requests and general information about how you can help us
|
||||
improving Retrospring can be found
|
||||
[here](https://github.com/Retrospring/retrospring/blob/master/CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
[AGPLv3](https://github.com/Retrospring/retrospring/blob/master/LICENSE).
|
||||
[AGPLv3](https://github.com/Retrospring/retrospring/blob/master/LICENSE).
|
||||
|
|
52
Rakefile
52
Rakefile
|
@ -105,44 +105,48 @@ namespace :justask do
|
|||
end
|
||||
end
|
||||
|
||||
desc "Gives admin status to an user."
|
||||
desc "Gives admin status to a user."
|
||||
task :admin, [:screen_name] => :environment do |t, args|
|
||||
fail "screen name required" if args[:screen_name].nil?
|
||||
abort "screen name required" if args[:screen_name].nil?
|
||||
|
||||
user = User.find_by_screen_name(args[:screen_name])
|
||||
fail "user #{args[:screen_name]} not found" if user.nil?
|
||||
user.admin = true
|
||||
user.save!
|
||||
puts "#{user.screen_name} is now an admin."
|
||||
abort "user #{args[:screen_name]} not found" if user.nil?
|
||||
|
||||
user.add_role :administrator
|
||||
puts "#{user.screen_name} is now an administrator."
|
||||
end
|
||||
|
||||
desc "Removes admin status from an user."
|
||||
desc "Removes admin status from a user."
|
||||
task :deadmin, [:screen_name] => :environment do |t, args|
|
||||
fail "screen name required" if args[:screen_name].nil?
|
||||
abort "screen name required" if args[:screen_name].nil?
|
||||
|
||||
user = User.find_by_screen_name(args[:screen_name])
|
||||
fail "user #{args[:screen_name]} not found" if user.nil?
|
||||
user.admin = false
|
||||
user.save!
|
||||
puts "#{user.screen_name} is no longer an admin."
|
||||
abort "user #{args[:screen_name]} not found" if user.nil?
|
||||
|
||||
user.remove_role :administrator
|
||||
puts "#{user.screen_name} is no longer an administrator."
|
||||
end
|
||||
|
||||
desc "Gives moderator status to an user."
|
||||
desc "Gives moderator status to a user."
|
||||
task :mod, [:screen_name] => :environment do |t, args|
|
||||
fail "screen name required" if args[:screen_name].nil?
|
||||
abort "screen name required" if args[:screen_name].nil?
|
||||
|
||||
user = User.find_by_screen_name(args[:screen_name])
|
||||
fail "user #{args[:screen_name]} not found" if user.nil?
|
||||
user.moderator = true
|
||||
user.save!
|
||||
puts "#{user.screen_name} is now an moderator."
|
||||
abort "user #{args[:screen_name]} not found" if user.nil?
|
||||
|
||||
user.add_role :moderator
|
||||
puts "#{user.screen_name} is now a moderator."
|
||||
end
|
||||
|
||||
desc "Removes moderator status from an user."
|
||||
desc "Removes moderator status from a user."
|
||||
task :demod, [:screen_name] => :environment do |t, args|
|
||||
fail "screen name required" if args[:screen_name].nil?
|
||||
abort "screen name required" if args[:screen_name].nil?
|
||||
|
||||
user = User.find_by_screen_name(args[:screen_name])
|
||||
fail "user #{args[:screen_name]} not found" if user.nil?
|
||||
user.moderator = false
|
||||
user.save!
|
||||
puts "#{user.screen_name} is no longer an moderator."
|
||||
abort "user #{args[:screen_name]} not found" if user.nil?
|
||||
|
||||
user.remove_role :moderator
|
||||
puts "#{user.screen_name} is no longer a moderator."
|
||||
end
|
||||
|
||||
desc "Hits an user with the banhammer."
|
||||
|
|
|
@ -75,6 +75,16 @@ _ready = ->
|
|||
lineColor: bodyColor
|
||||
density: 23000
|
||||
|
||||
$(".alert-announcement").each ->
|
||||
aId = $(this)[0].dataset.announcementId
|
||||
unless (window.localStorage.getItem("announcement#{aId}"))
|
||||
$(this).toggleClass("hidden")
|
||||
|
||||
$(document).on "click", ".alert-announcement button.close", (evt) ->
|
||||
announcement = event.target.closest(".alert-announcement")
|
||||
aId = announcement.dataset.announcementId
|
||||
window.localStorage.setItem("announcement#{aId}", true)
|
||||
|
||||
$('.arctic_scroll').arctic_scroll speed: 500
|
||||
|
||||
|
||||
|
|
|
@ -125,9 +125,9 @@ class Ajax::ModerationController < ApplicationController
|
|||
unban = params[:ban] == "0"
|
||||
perma = params[:permaban] == "1"
|
||||
|
||||
buntil = DateTime.strptime params[:until], "%m/%d/%Y %I:%M %p" unless unban or perma
|
||||
buntil = DateTime.strptime params[:until], "%m/%d/%Y %I:%M %p" unless unban || perma
|
||||
|
||||
if not unban and target.admin?
|
||||
if !unban && target.has_role?(:administrator)
|
||||
@status = :nopriv
|
||||
@message = I18n.t('messages.moderation.ban.nopriv')
|
||||
@success = false
|
||||
|
@ -166,7 +166,7 @@ class Ajax::ModerationController < ApplicationController
|
|||
@message = I18n.t('messages.moderation.privilege.nope')
|
||||
return unless %w(blogger supporter moderator admin contributor translator).include? params[:type].downcase
|
||||
|
||||
if %w(supporter moderator admin).include?(params[:type].downcase) and !current_user.admin?
|
||||
if %w(supporter moderator admin).include?(params[:type].downcase) && !current_user.has_role?(:administrator)
|
||||
@status = :nopriv
|
||||
@message = I18n.t('messages.moderation.privilege.nopriv')
|
||||
@success = false
|
||||
|
@ -174,7 +174,9 @@ class Ajax::ModerationController < ApplicationController
|
|||
end
|
||||
|
||||
@checked = status
|
||||
case params[:type].downcase
|
||||
type = params[:type].downcase
|
||||
target_role = {"admin" => "administrator"}.fetch(type, type).to_sym
|
||||
case type
|
||||
when 'blogger'
|
||||
target_user.blogger = status
|
||||
when 'contributor'
|
||||
|
@ -183,10 +185,12 @@ class Ajax::ModerationController < ApplicationController
|
|||
target_user.translator = status
|
||||
when 'supporter'
|
||||
target_user.supporter = status
|
||||
when 'moderator'
|
||||
target_user.moderator = status
|
||||
when 'admin'
|
||||
target_user.admin = status
|
||||
when 'moderator', 'admin'
|
||||
if status
|
||||
target_user.add_role target_role
|
||||
else
|
||||
target_user.remove_role target_role
|
||||
end
|
||||
end
|
||||
target_user.save!
|
||||
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
class AnnouncementController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
|
||||
def index
|
||||
@announcements = Announcement.all
|
||||
end
|
||||
|
||||
def new
|
||||
@announcement = Announcement.new
|
||||
end
|
||||
|
||||
def create
|
||||
@announcement = Announcement.new(announcement_params)
|
||||
@announcement.user = current_user
|
||||
if @announcement.save
|
||||
flash[:success] = "Announcement created successfully."
|
||||
redirect_to action: :index
|
||||
else
|
||||
render 'announcement/new'
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@announcement = Announcement.find(params[:id])
|
||||
end
|
||||
|
||||
def update
|
||||
@announcement = Announcement.find(params[:id])
|
||||
@announcement.update(announcement_params)
|
||||
if @announcement.save
|
||||
flash[:success] = "Announcement updated successfully."
|
||||
redirect_to announcement_index_path
|
||||
else
|
||||
render 'announcement/edit'
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
if Announcement.destroy(params[:id])
|
||||
flash[:success] = "Announcement deleted successfully."
|
||||
else
|
||||
flash[:error] = "Failed to delete announcement."
|
||||
end
|
||||
redirect_to announcement_index_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def announcement_params
|
||||
params.require(:announcement).permit(:content, :link_text, :link_href, :starts_at, :ends_at)
|
||||
end
|
||||
end
|
|
@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base
|
|||
before_action :configure_permitted_parameters, if: :devise_controller?
|
||||
before_action :check_locale
|
||||
before_action :banned?
|
||||
before_action :find_active_announcements
|
||||
|
||||
# check if user wants to read
|
||||
def check_locale
|
||||
|
@ -50,6 +51,10 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
end
|
||||
|
||||
def find_active_announcements
|
||||
@active_announcements ||= Announcement.find_active
|
||||
end
|
||||
|
||||
include ApplicationHelper
|
||||
|
||||
protected
|
||||
|
|
|
@ -3,6 +3,13 @@ class GroupController < ApplicationController
|
|||
|
||||
def index
|
||||
@group = current_user.groups.find_by_name!(params[:group_name])
|
||||
@timeline = @group.timeline.paginate(page: params[:page])
|
||||
@timeline = @group.cursored_timeline(last_id: params[:last_id])
|
||||
@timeline_last_id = @timeline.map(&:id).min
|
||||
@more_data_available = !@group.cursored_timeline(last_id: @timeline_last_id, size: 1).count.zero?
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.js
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,28 +2,37 @@ class InboxController < ApplicationController
|
|||
before_action :authenticate_user!
|
||||
|
||||
def show
|
||||
@inbox = Inbox.where(user: current_user)
|
||||
.order(:created_at).reverse_order
|
||||
.paginate(page: params[:page])
|
||||
@inbox_count = Inbox.where(user: current_user).count
|
||||
@inbox = current_user.cursored_inbox(last_id: params[:last_id])
|
||||
@inbox_last_id = @inbox.map(&:id).min
|
||||
@more_data_available = !current_user.cursored_inbox(last_id: @inbox_last_id, size: 1).count.zero?
|
||||
@inbox_count = current_user.inboxes.count
|
||||
|
||||
if params[:author].present?
|
||||
begin
|
||||
@author = true
|
||||
@target_user = User.where('LOWER(screen_name) = ?', params[:author].downcase).first!
|
||||
@inbox_author = current_user.inboxes.joins(:question)
|
||||
.where(questions: { user_id: @target_user.id, author_is_anonymous: false })
|
||||
.paginate(page: params[:page])
|
||||
@inbox_author_count = current_user.inboxes.joins(:question)
|
||||
.where(questions: { user_id: @target_user.id, author_is_anonymous: false })
|
||||
.count
|
||||
@inbox_author = @inbox.joins(:question)
|
||||
.where(questions: { user_id: @target_user.id, author_is_anonymous: false })
|
||||
@inbox_author_count = current_user.inboxes
|
||||
.joins(:question)
|
||||
.where(questions: { user_id: @target_user.id, author_is_anonymous: false })
|
||||
.count
|
||||
|
||||
if @inbox_author.empty?
|
||||
@empty = true
|
||||
flash.now[:info] = "No questions from @#{params[:author]} found, showing default entries instead!"
|
||||
else
|
||||
@inbox = @inbox_author
|
||||
@inbox_count = @inbox_author_count
|
||||
@inbox_last_id = @inbox.map(&:id).min
|
||||
@more_data_available = !current_user.cursored_inbox(last_id: @inbox_last_id, size: 1)
|
||||
.joins(:question)
|
||||
.where(questions: { user_id: @target_user.id, author_is_anonymous: false })
|
||||
.count
|
||||
.zero?
|
||||
end
|
||||
rescue
|
||||
rescue => e
|
||||
NewRelic::Agent.notice_error(e)
|
||||
flash.now[:error] = "No user with the name @#{params[:author]} found, showing default entries instead!"
|
||||
@not_found = true
|
||||
end
|
||||
|
|
|
@ -57,7 +57,7 @@ class ModerationController < ApplicationController
|
|||
@host = User.find(@user_id)
|
||||
@users = []
|
||||
return if @host.nil?
|
||||
@users = User.where('(current_sign_in_ip = ? OR last_sign_in_ip = ?) AND id != ?', @host.current_sign_in_ip, @host.last_sign_in_ip, @user_id)
|
||||
@users = User.where('(current_sign_in_ip = ? OR last_sign_in_ip = ?) AND id != ?', @host.current_sign_in_ip, @host.last_sign_in_ip, @user_id).to_a
|
||||
@users.unshift @host
|
||||
|
||||
render template: 'moderation/priority'
|
||||
|
|
|
@ -3,16 +3,28 @@ class NotificationsController < ApplicationController
|
|||
|
||||
def index
|
||||
@type = params[:type]
|
||||
@notifications = if @type == 'all'
|
||||
Notification.for(current_user)
|
||||
elsif @type == 'new'
|
||||
Notification.for(current_user).where(new: true)
|
||||
else
|
||||
Notification.for(current_user).where('LOWER(target_type) = ?', @type)
|
||||
end.paginate(page: params[:page])
|
||||
@notifications = cursored_notifications_for(type: @type, last_id: params[:last_id])
|
||||
@notifications_last_id = @notifications.map(&:id).min
|
||||
@more_data_available = !cursored_notifications_for(type: @type, last_id: @notifications_last_id, size: 1).count.zero?
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.js
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cursored_notifications_for(type:, last_id:, size: nil)
|
||||
cursor_params = { last_id: last_id, size: size }.compact
|
||||
|
||||
case type
|
||||
when 'all'
|
||||
Notification.cursored_for(current_user, **cursor_params)
|
||||
when 'new'
|
||||
Notification.cursored_for(current_user, new: true, **cursor_params)
|
||||
else
|
||||
Notification.cursored_for_type(current_user, type, **cursor_params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,10 @@ class PublicController < ApplicationController
|
|||
before_action :authenticate_user!
|
||||
|
||||
def index
|
||||
@timeline = Answer.joins(:user).where(users: { privacy_allow_public_timeline: true }).all.reverse_order.paginate(page: params[:page])
|
||||
@timeline = Answer.cursored_public_timeline(last_id: params[:last_id])
|
||||
@timeline_last_id = @timeline.map(&:id).min
|
||||
@more_data_available = !Answer.cursored_public_timeline(last_id: @timeline_last_id, size: 1).count.zero?
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.js
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
class QuestionController < ApplicationController
|
||||
def show
|
||||
@question = Question.find(params[:id])
|
||||
@answers = @question.answers.reverse_order.paginate(page: params[:page])
|
||||
@answers = @question.cursored_answers(last_id: params[:last_id])
|
||||
@answers_last_id = @answers.map(&:id).min
|
||||
@more_data_available = !@question.cursored_answers(last_id: @answers_last_id, size: 1).count.zero?
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.js
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class StaticController < ApplicationController
|
||||
def index
|
||||
if user_signed_in?
|
||||
@timeline = current_user.timeline.paginate(page: params[:page])
|
||||
@timeline = current_user.cursored_timeline(last_id: params[:last_id])
|
||||
@timeline_last_id = @timeline.map(&:id).min
|
||||
@more_data_available = !current_user.cursored_timeline(last_id: @timeline_last_id, size: 1).count.zero?
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.js
|
||||
|
|
|
@ -5,7 +5,9 @@ class UserController < ApplicationController
|
|||
|
||||
def show
|
||||
@user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first!
|
||||
@answers = @user.answers.reverse_order.paginate(page: params[:page])
|
||||
@answers = @user.cursored_answers(last_id: params[:last_id])
|
||||
@answers_last_id = @answers.map(&:id).min
|
||||
@more_data_available = !@user.cursored_answers(last_id: @answers_last_id, size: 1).count.zero?
|
||||
|
||||
if user_signed_in?
|
||||
notif = Notification.where(target_type: "Relationship", target_id: @user.active_relationships.where(target_id: current_user.id).pluck(:id), recipient_id: current_user.id, new: true).first
|
||||
|
@ -72,7 +74,9 @@ class UserController < ApplicationController
|
|||
def followers
|
||||
@title = 'Followers'
|
||||
@user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first!
|
||||
@users = @user.followers.reverse_order.paginate(page: params[:page])
|
||||
@users = @user.cursored_followers(last_id: params[:last_id])
|
||||
@users_last_id = @users.map(&:id).min
|
||||
@more_data_available = !@user.cursored_followers(last_id: @users_last_id, size: 1).count.zero?
|
||||
@type = :friend
|
||||
render 'show_follow'
|
||||
end
|
||||
|
@ -80,7 +84,9 @@ class UserController < ApplicationController
|
|||
def friends
|
||||
@title = 'Following'
|
||||
@user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first!
|
||||
@users = @user.friends.reverse_order.paginate(page: params[:page])
|
||||
@users = @user.cursored_friends(last_id: params[:last_id])
|
||||
@users_last_id = @users.map(&:id).min
|
||||
@more_data_available = !@user.cursored_friends(last_id: @users_last_id, size: 1).count.zero?
|
||||
@type = :friend
|
||||
render 'show_follow'
|
||||
end
|
||||
|
@ -88,7 +94,9 @@ class UserController < ApplicationController
|
|||
def questions
|
||||
@title = 'Questions'
|
||||
@user = User.where('LOWER(screen_name) = ?', params[:username].downcase).first!
|
||||
@questions = @user.questions.where(author_is_anonymous: false).reverse_order.paginate(page: params[:page])
|
||||
@questions = @user.cursored_questions(author_is_anonymous: false, last_id: params[:last_id])
|
||||
@questions_last_id = @questions.map(&:id).min
|
||||
@more_data_available = !@user.cursored_questions(author_is_anonymous: false, last_id: @questions_last_id, size: 1).count.zero?
|
||||
end
|
||||
|
||||
def data
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
module AnnouncementHelper
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
class Announcement < ApplicationRecord
|
||||
belongs_to :user
|
||||
|
||||
validates :content, presence: true
|
||||
validates :starts_at, presence: true
|
||||
validates :link_href, presence: true, if: -> { link_text.present? }
|
||||
validate :starts_at, :validate_date_range
|
||||
|
||||
def self.find_active
|
||||
Rails.cache.fetch "announcement_active", expires_in: 1.minute do
|
||||
where "starts_at <= :now AND ends_at > :now", now: Time.current
|
||||
end
|
||||
end
|
||||
|
||||
def active?
|
||||
Time.now.utc >= starts_at && Time.now.utc < ends_at
|
||||
end
|
||||
|
||||
def link_present?
|
||||
link_text.present?
|
||||
end
|
||||
|
||||
def validate_date_range
|
||||
if starts_at > ends_at
|
||||
errors.add(:starts_at, "Start date must be before end date")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,4 +1,6 @@
|
|||
class Answer < ApplicationRecord
|
||||
extend Answer::TimelineMethods
|
||||
|
||||
belongs_to :user
|
||||
belongs_to :question
|
||||
has_many :comments, dependent: :destroy
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Answer::TimelineMethods
|
||||
include CursorPaginatable
|
||||
|
||||
define_cursor_paginator :cursored_public_timeline, :public_timeline
|
||||
|
||||
def public_timeline
|
||||
joins(:user)
|
||||
.where(users: { privacy_allow_public_timeline: true })
|
||||
.order(:created_at)
|
||||
.reverse_order
|
||||
end
|
||||
end
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module CursorPaginatable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
module ClassMethods
|
||||
# Defines a cursor paginator.
|
||||
#
|
||||
# This method will define a new method +name+, which accepts the keyword
|
||||
# arguments +last_id+ for defining the last id the cursor will operate on,
|
||||
# and +size+ for the amount of records it should return.
|
||||
#
|
||||
# @param name [Symbol] The name of the method for the cursor paginator
|
||||
# @param scope [Symbol] The name of the method which returns an
|
||||
# ActiveRecord scope.
|
||||
#
|
||||
# @example
|
||||
# class User
|
||||
# has_many :answers
|
||||
#
|
||||
# include CursorPaginatable
|
||||
# define_cursor_paginator :cursored_answers, :recent_answers
|
||||
#
|
||||
# def recent_answers
|
||||
# self.answers.order(:created_at).reverse_order
|
||||
# end
|
||||
# end
|
||||
def define_cursor_paginator(name, scope, default_size: APP_CONFIG.fetch('items_per_page'))
|
||||
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||
def #{name}(*args, last_id: nil, size: #{default_size}, **kwargs)
|
||||
s = self.#{scope}(*args, **kwargs).limit(size)
|
||||
s = s.where(s.arel_table[:id].lt(last_id)) if last_id.present?
|
||||
s
|
||||
end
|
||||
RUBY
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Group < ApplicationRecord
|
||||
include Group::TimelineMethods
|
||||
|
||||
belongs_to :user
|
||||
has_many :group_members, dependent: :destroy
|
||||
|
||||
validates :name, length: { minimum: 1 }
|
||||
validates :display_name, length: { maximum: 30 }
|
||||
|
||||
before_validation do
|
||||
|
@ -19,9 +24,4 @@ class Group < ApplicationRecord
|
|||
def remove_member(user)
|
||||
GroupMember.where(group: self, user: user).first!.destroy
|
||||
end
|
||||
|
||||
# @return [Array] the groups' timeline
|
||||
def timeline
|
||||
Answer.where("user_id in (?)", members.pluck(:user_id)).order(:created_at).reverse_order
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Group::TimelineMethods
|
||||
include CursorPaginatable
|
||||
|
||||
define_cursor_paginator :cursored_timeline, :timeline
|
||||
|
||||
# @return [Array] the groups' timeline
|
||||
def timeline
|
||||
Answer.where('user_id in (?)', members.pluck(:user_id)).order(:created_at).reverse_order
|
||||
end
|
||||
end
|
|
@ -3,10 +3,19 @@ class Notification < ApplicationRecord
|
|||
belongs_to :target, polymorphic: true
|
||||
|
||||
class << self
|
||||
include CursorPaginatable
|
||||
|
||||
define_cursor_paginator :cursored_for, :for
|
||||
define_cursor_paginator :cursored_for_type, :for_type
|
||||
|
||||
def for(recipient, options={})
|
||||
self.where(options.merge!(recipient: recipient)).order(:created_at).reverse_order
|
||||
end
|
||||
|
||||
def for_type(recipient, type, options={})
|
||||
self.where(options.merge!(recipient: recipient)).where('LOWER(target_type) = ?', type).order(:created_at).reverse_order
|
||||
end
|
||||
|
||||
def notify(recipient, target)
|
||||
return nil unless target.respond_to? :notification_type
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class Question < ApplicationRecord
|
||||
include Question::AnswerMethods
|
||||
|
||||
belongs_to :user
|
||||
has_many :answers, dependent: :destroy
|
||||
has_many :inboxes, dependent: :destroy
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Question::AnswerMethods
|
||||
include CursorPaginatable
|
||||
|
||||
define_cursor_paginator :cursored_answers, :ordered_answers
|
||||
|
||||
def ordered_answers
|
||||
answers
|
||||
.order(:created_at)
|
||||
.reverse_order
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Role < ApplicationRecord
|
||||
has_and_belongs_to_many :users, join_table: :users_roles
|
||||
|
||||
belongs_to :resource,
|
||||
polymorphic: true,
|
||||
optional: true
|
||||
|
||||
validates :resource_type,
|
||||
inclusion: { in: Rolify.resource_types },
|
||||
allow_nil: true
|
||||
|
||||
scopify
|
||||
end
|
|
@ -1,10 +1,18 @@
|
|||
class User < ApplicationRecord
|
||||
include User::AnswerMethods
|
||||
include User::InboxMethods
|
||||
include User::QuestionMethods
|
||||
include User::RelationshipMethods
|
||||
include User::TimelineMethods
|
||||
|
||||
# Include default devise modules. Others available are:
|
||||
# :confirmable, :lockable, :timeoutable and :omniauthable
|
||||
devise :database_authenticatable, :async, :registerable,
|
||||
:recoverable, :rememberable, :trackable,
|
||||
:validatable, :confirmable, :authentication_keys => [:login]
|
||||
|
||||
rolify
|
||||
|
||||
# attr_accessor :login
|
||||
|
||||
has_many :questions, dependent: :destroy
|
||||
|
@ -98,11 +106,6 @@ class User < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
# @return [Array] the users' timeline
|
||||
def timeline
|
||||
Answer.where("user_id in (?) OR user_id = ?", friend_ids, id).order(:created_at).reverse_order
|
||||
end
|
||||
|
||||
# follows an user.
|
||||
def follow(target_user)
|
||||
active_relationships.create(target: target_user)
|
||||
|
@ -183,7 +186,7 @@ class User < ApplicationRecord
|
|||
|
||||
# @return [Boolean] is the user a moderator?
|
||||
def mod?
|
||||
self.moderator? || self.admin?
|
||||
has_role?(:moderator) || has_role?(:administrator)
|
||||
end
|
||||
|
||||
# region stuff used for reporting/moderation
|
||||
|
@ -258,4 +261,10 @@ class User < ApplicationRecord
|
|||
end
|
||||
!self.export_processing
|
||||
end
|
||||
|
||||
# %w[admin moderator].each do |m|
|
||||
# define_method(m) { raise "not allowed: #{m}" }
|
||||
# define_method(m+??) { raise "not allowed: #{m}?"}
|
||||
# define_method(m+?=) { |*a| raise "not allowed: #{m}="}
|
||||
# end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module User::AnswerMethods
|
||||
include CursorPaginatable
|
||||
|
||||
define_cursor_paginator :cursored_answers, :ordered_answers
|
||||
|
||||
def ordered_answers
|
||||
answers
|
||||
.order(:created_at)
|
||||
.reverse_order
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module User::InboxMethods
|
||||
include CursorPaginatable
|
||||
|
||||
define_cursor_paginator :cursored_inbox, :ordered_inbox
|
||||
|
||||
def ordered_inbox
|
||||
inboxes
|
||||
.includes(:question, :user)
|
||||
.order(:created_at)
|
||||
.reverse_order
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module User::QuestionMethods
|
||||
include CursorPaginatable
|
||||
|
||||
define_cursor_paginator :cursored_questions, :ordered_questions
|
||||
|
||||
def ordered_questions(author_is_anonymous: nil)
|
||||
questions
|
||||
.where({ author_is_anonymous: author_is_anonymous }.compact)
|
||||
.order(:created_at)
|
||||
.reverse_order
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module User::RelationshipMethods
|
||||
include CursorPaginatable
|
||||
|
||||
define_cursor_paginator :cursored_friends, :ordered_friends
|
||||
define_cursor_paginator :cursored_followers, :ordered_followers
|
||||
|
||||
def ordered_friends
|
||||
friends.reverse_order
|
||||
end
|
||||
|
||||
def ordered_followers
|
||||
followers.reverse_order
|
||||
end
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module User::TimelineMethods
|
||||
include CursorPaginatable
|
||||
|
||||
define_cursor_paginator :cursored_timeline, :timeline
|
||||
|
||||
# @return [Array] the users' timeline
|
||||
def timeline
|
||||
Answer.where('user_id in (?) OR user_id = ?', friend_ids, id).order(:created_at).reverse_order
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
- provide(:title, generate_title("Edit announcement"))
|
||||
.container.j2-page
|
||||
= bootstrap_form_for(@announcement, url: {action: "update"}, method: "PATCH") do |f|
|
||||
- if @announcement.errors.any?
|
||||
.row
|
||||
.col-md-12
|
||||
.alert.alert-danger
|
||||
%strong
|
||||
= pluralize(@announcement.errors.count, "error")
|
||||
prohibited this announcement from being saved:
|
||||
%ul
|
||||
- @announcement.errors.full_messages.each do |err|
|
||||
%li= err
|
||||
.row
|
||||
.col-md-12
|
||||
= f.text_area :content, label: "Content"
|
||||
.row
|
||||
.col-md-6
|
||||
= f.url_field :link_href, label: "Link URL"
|
||||
.col-md-6
|
||||
= f.datetime_field :link_text, label: "Link text"
|
||||
.row
|
||||
.col-md-6
|
||||
= f.datetime_field :starts_at, label: "Start time"
|
||||
.col-md-6
|
||||
= f.datetime_field :ends_at, label: "End time"
|
||||
.row
|
||||
.col-md-12.text-right
|
||||
= f.submit class: "btn btn-primary"
|
|
@ -0,0 +1,14 @@
|
|||
- provide(:title, generate_title("Announcements"))
|
||||
.container.j2-page
|
||||
.row
|
||||
.col-md-12
|
||||
= link_to "Add new", :announcement_new, class: "btn btn-default"
|
||||
- @announcements.each do |announcement|
|
||||
.panel.panel-default
|
||||
.panel-heading
|
||||
= announcement.starts_at
|
||||
.panel-body
|
||||
= announcement.content
|
||||
.panel-footer
|
||||
= button_to "Edit", announcement_edit_path(id: announcement.id), method: :get, class: 'btn btn-link'
|
||||
= button_to "Delete", announcement_destroy_path(id: announcement.id), method: :delete, class: 'btn btn-link', confirm: 'Are you sure you want to delete this announcement?'
|
|
@ -0,0 +1,29 @@
|
|||
- provide(:title, generate_title("Add new announcement"))
|
||||
.container.j2-page
|
||||
= bootstrap_form_for(@announcement, url: {action: "create"}) do |f|
|
||||
- if @announcement.errors.any?
|
||||
.row
|
||||
.col-md-12
|
||||
.alert.alert-danger
|
||||
%strong
|
||||
= pluralize(@announcement.errors.count, "error")
|
||||
prohibited this announcement from being saved:
|
||||
%ul
|
||||
- @announcement.errors.full_messages.each do |err|
|
||||
%li= err
|
||||
.row
|
||||
.col-md-12
|
||||
= f.text_area :content, label: "Content"
|
||||
.row
|
||||
.col-md-6
|
||||
= f.url_field :link_href, label: "Link URL"
|
||||
.col-md-6
|
||||
= f.datetime_field :link_text, label: "Link text"
|
||||
.row
|
||||
.col-md-6
|
||||
= f.datetime_field :starts_at, label: "Start time"
|
||||
.col-md-6
|
||||
= f.datetime_field :ends_at, label: "End time"
|
||||
.row
|
||||
.col-md-12.text-right
|
||||
= f.submit class: "btn btn-primary"
|
|
@ -4,7 +4,7 @@
|
|||
= bootstrap_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f|
|
||||
= devise_error_messages!
|
||||
|
||||
= f.text_field :login, autofocus: true, label: "User name"
|
||||
= f.email_field :email, autofocus: true, label: "Email address"
|
||||
= f.submit "Send me password reset instructions"
|
||||
|
||||
= render "devise/shared/links"
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
- @timeline.each do |answer|
|
||||
= render 'shared/answerbox', a: answer
|
||||
|
||||
#pagination= will_paginate @timeline, renderer: BootstrapPagination::Rails, page_links: false
|
||||
= render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @timeline_last_id
|
||||
|
||||
- if @timeline.next_page
|
||||
%button#load-more-btn.btn.btn-default{type: :button, data: { current_page: @timeline.current_page }}
|
||||
= t 'views.actions.load'
|
||||
.d-block.d-sm-none= render 'shared/links'
|
||||
- if @more_data_available
|
||||
%button#load-more-btn.btn.btn-default{type: :button, data: { last_id: @timeline_last_id }}
|
||||
= t 'views.actions.load'
|
||||
.visible-xs= render 'shared/links'
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
$('#timeline').append('<% @timeline.each do |answer|
|
||||
%><%= j render 'shared/answerbox', a: answer
|
||||
%><% end %>');
|
||||
<% if @timeline.next_page %>
|
||||
$('#pagination').html('<%= j will_paginate @timeline, renderer: BootstrapPagination::Rails, page_links: false %>');
|
||||
<% if @more_data_available %>
|
||||
$('#pagination').html('<%= j render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @timeline_last_id %>');
|
||||
<% else %>
|
||||
$('#pagination, #load-more-btn').remove();
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -12,10 +12,10 @@
|
|||
- if @inbox.empty?
|
||||
= t 'views.inbox.empty'
|
||||
|
||||
#pagination= will_paginate @inbox, renderer: BootstrapPagination::Rails, page_links: false
|
||||
= render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @inbox_last_id
|
||||
|
||||
- if @inbox.next_page
|
||||
%button#load-more-btn.btn.btn-default{type: :button, data: { current_page: @inbox.current_page }}
|
||||
- if @more_data_available
|
||||
%button#load-more-btn.btn.btn-default{type: :button, data: { last_id: @inbox_last_id }}
|
||||
= t 'views.actions.load'
|
||||
|
||||
.col-md-9.col-xs-12.col-sm-8.d-block.d-sm-none
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
$('#entries').append('<% @inbox.each do |i|
|
||||
%><%= j render 'inbox/entry', i: i
|
||||
%><% end %>');
|
||||
<% if @inbox.next_page %>
|
||||
$('#pagination').html('<%= j will_paginate @inbox, renderer: BootstrapPagination::Rails, page_links: false %>');
|
||||
<% if @more_data_available %>
|
||||
$('#pagination').html('<%= j render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @inbox_last_id %>');
|
||||
<% else %>
|
||||
$('#pagination, #load-more-btn').remove();
|
||||
<% end %>
|
||||
<% Inbox.where(id: @inbox.pluck(:id)).update_all(new: false) %>
|
||||
<% Inbox.where(id: @inbox.pluck(:id)).update_all(new: false) %>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
%i.fa.fa-fw.fa-cog
|
||||
= t('views.navigation.settings')
|
||||
.dropdown-divider
|
||||
- if current_user.admin?
|
||||
- if current_user.has_role?(:administrator)
|
||||
%a.dropdown-item{href: rails_admin_path}
|
||||
%i.fa.fa-fw.fa-cogs
|
||||
= t('views.navigation.admin')
|
||||
|
@ -23,6 +23,9 @@
|
|||
%a.dropdown-item{href: pghero_path}
|
||||
%i.fa.fa-fw.fa-database
|
||||
Database Monitor
|
||||
%a.dropdown-item{href: announcement_index_path}
|
||||
%i.fa.fa-fw.fa-info
|
||||
Announcements
|
||||
.dropdown-divider
|
||||
- if current_user.mod?
|
||||
%a.dropdown-item{href: moderation_path}
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
= csrf_meta_tags
|
||||
%body#version1
|
||||
= render 'layouts/header'
|
||||
= render 'shared/announcements'
|
||||
= yield
|
||||
= render 'shared/locales'
|
||||
- if Rails.env.development?
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
- @notifications.each do |notification|
|
||||
= render 'notifications/notification', notification: notification
|
||||
|
||||
#pagination= will_paginate @notifications, renderer: BootstrapPagination::Rails, page_links: false
|
||||
= render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @notifications_last_id, permitted_params: %i[type]
|
||||
|
||||
- if @notifications.next_page
|
||||
%button#load-more-btn.btn.btn-default{type: :button, data: { current_page: @notifications.current_page }}
|
||||
Load more
|
||||
- if @more_data_available
|
||||
%button#load-more-btn.btn.btn-default{type: :button, data: { last_id: @notifications_last_id }}
|
||||
Load more
|
||||
- Notification.for(current_user).update_all(new: false)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
$('#notifications').append('<% @notifications.each do |notification|
|
||||
%><%= j render 'notifications/notification', notification: notification
|
||||
%><% end %>');
|
||||
<% if @notifications.next_page %>
|
||||
$('#pagination').html('<%= j will_paginate @notifications, renderer: BootstrapPagination::Rails, page_links: false %>');
|
||||
<% if @more_data_available %>
|
||||
$('#pagination').html('<%= j render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @notifications_last_id, permitted_params: %i[type] %>');
|
||||
<% else %>
|
||||
$('#pagination, #load-more-btn').remove();
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
- @timeline.each do |answer|
|
||||
= render 'shared/answerbox', a: answer
|
||||
|
||||
#pagination= will_paginate @timeline, renderer: BootstrapPagination::Rails, page_links: false
|
||||
= render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @timeline_last_id
|
||||
|
||||
- if @timeline.next_page
|
||||
%button#load-more-btn.btn.btn-default{type: :button, data: { current_page: @timeline.current_page }}
|
||||
Load more
|
||||
- if @more_data_available
|
||||
%button#load-more-btn.btn.btn-default{type: :button, data: { last_id: @timeline_last_id }}
|
||||
Load more
|
||||
.visible-xs= render 'shared/links'
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
$('#timeline').append('<% @timeline.each do |answer|
|
||||
%><%= j render 'shared/answerbox', a: answer
|
||||
%><% end %>');
|
||||
<% if @timeline.next_page %>
|
||||
$('#pagination').html('<%= j will_paginate @timeline, renderer: BootstrapPagination::Rails, page_links: false %>');
|
||||
<% if @more_data_available %>
|
||||
$('#pagination').html('<%= j render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @timeline_last_id %>');
|
||||
<% else %>
|
||||
$('#pagination, #load-more-btn').remove();
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -8,10 +8,10 @@
|
|||
- @answers.each do |a|
|
||||
= render 'shared/answerbox', a: a, show_question: false
|
||||
|
||||
#pagination= will_paginate @answers, renderer: BootstrapPagination::Rails, page_links: false
|
||||
= render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @answers_last_id
|
||||
|
||||
- if @answers.next_page
|
||||
%button#load-more-btn.btn.btn-default{type: :button, data: { current_page: @answers.current_page }}
|
||||
- if @more_data_available
|
||||
%button#load-more-btn.btn.btn-default{type: :button, data: { last_id: @answers_last_id }}
|
||||
Load more
|
||||
|
||||
- if user_signed_in? and !current_user.answered? @question and current_user != @question.user and @question.user.privacy_allow_stranger_answers
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
$('#answers').append('<% @answers.each do |answer|
|
||||
%><%= j render 'shared/answerbox', a: answer, show_question: false
|
||||
%><% end %>');
|
||||
<% if @answers.next_page %>
|
||||
$('#pagination').html('<%= j will_paginate @answers, renderer: BootstrapPagination::Rails, page_links: false %>');
|
||||
<% if @more_data_available %>
|
||||
$('#pagination').html('<%= j render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @answers_last_id %>');
|
||||
<% else %>
|
||||
$('#pagination, #load-more-btn').remove();
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
.container.announcements
|
||||
- @active_announcements.each do |announcement|
|
||||
.alert.alert-announcement.alert-info.alert-dismissable.hidden{ data: { 'announcement-id': announcement.id } }
|
||||
%button.close{ type: "button", "data-dismiss" => "alert" }
|
||||
%span{ "aria-hidden" => "true" } ×
|
||||
%p= announcement.content
|
||||
- if announcement.link_present?
|
||||
%a.alert-link{ href: announcement.link_href }= announcement.link_text
|
|
@ -0,0 +1,11 @@
|
|||
-# this renders a pagination html to keep compatibility with the current pagination js
|
||||
-# it _should_ be replaced with something else entirely later on.
|
||||
- permitted_params ||= []
|
||||
#pagination
|
||||
%ul.pagination
|
||||
%li.next{class: more_data_available ? nil : "disabled"}
|
||||
- if more_data_available
|
||||
%a{rel: :next, href: url_for(params.permit(*permitted_params).merge(last_id: last_id))}
|
||||
Next page
|
||||
- else
|
||||
Next page
|
|
@ -5,7 +5,7 @@
|
|||
·
|
||||
= link_to t('views.general.about'), about_path
|
||||
·
|
||||
= link_to "Github", 'https://github.com/retrospring/retrospring'
|
||||
= link_to "GitHub", 'https://github.com/retrospring/retrospring'
|
||||
·
|
||||
= link_to t('views.general.terms'), terms_path
|
||||
·
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
- @timeline.each do |answer|
|
||||
= render 'shared/answerbox', a: answer
|
||||
|
||||
#pagination= will_paginate @timeline, renderer: BootstrapPagination::Rails, page_links: false
|
||||
= render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @timeline_last_id
|
||||
|
||||
- if @timeline.next_page
|
||||
%button#load-more-btn.btn.btn-default{type: :button, data: { current_page: @timeline.current_page }}
|
||||
Load more
|
||||
.visible-xs= render 'shared/links'
|
||||
- if @more_data_available
|
||||
%button#load-more-btn.btn.btn-default{type: :button, data: { last_id: @timeline_last_id }}
|
||||
Load more
|
||||
.visible-xs= render 'shared/links'
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
$('#timeline').append('<% @timeline.each do |answer|
|
||||
%><%= j render 'shared/answerbox', a: answer
|
||||
%><% end %>');
|
||||
<% if @timeline.next_page %>
|
||||
$('#pagination').html('<%= j will_paginate @timeline, renderer: BootstrapPagination::Rails, page_links: false %>');
|
||||
<% if @more_data_available %>
|
||||
$('#pagination').html('<%= j render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @timeline_last_id %>');
|
||||
<% else %>
|
||||
$('#pagination, #load-more-btn').remove();
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
%a{href: '#', data: { target: "#modal-privileges", toggle: :modal }}
|
||||
%i.fa.fa-wrench
|
||||
= raw t('views.actions.privilege', user: user.screen_name)
|
||||
- unless user.admin?
|
||||
- unless user.has_role?(:administrator)
|
||||
%li
|
||||
%a{href: '#', data: { target: "#modal-ban", toggle: :modal }}
|
||||
%i.fa.fa-ban
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
= render 'user/modal_privileges_item', privilege: 'blogger', description: t('views.modal.privilege.blogger'), user: @user
|
||||
= render 'user/modal_privileges_item', privilege: 'contributor', description: t('views.modal.privilege.contributor'), user: @user
|
||||
= render 'user/modal_privileges_item', privilege: 'translator', description: t('views.modal.privilege.translator'), user: @user
|
||||
- if current_user.admin?
|
||||
- if current_user.has_role?(:administrator)
|
||||
= render 'user/modal_privileges_item', privilege: 'supporter', description: t('views.modal.privilege.supporter'), user: @user
|
||||
= render 'user/modal_privileges_item', privilege: 'moderator', description: t('views.modal.privilege.moderator'),user: @user
|
||||
= render 'user/modal_privileges_item', privilege: 'admin', description: t('views.modal.privilege.admin'), user: @user
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
- description ||= ''
|
||||
- role_mapping = {"admin" => "administrator"}
|
||||
- requires_role = %w[admin moderator].include?(privilege)
|
||||
- checked = requires_role ? user.has_role?(role_mapping.fetch(privilege, privilege).to_sym) : user.public_send("#{privilege}?")
|
||||
%li.list-group-item{id: "privilege-#{privilege}"}
|
||||
.media
|
||||
.pull-left.j2-table
|
||||
%input.input--center{type: :checkbox, name: 'check-your-privileges', data: { type: privilege, user: user.screen_name }, checked: user.send("#{privilege}?"), autocomplete: 'off'}
|
||||
%input.input--center{type: :checkbox, name: 'check-your-privileges', data: { type: privilege, user: user.screen_name }, checked: checked, autocomplete: 'off'}
|
||||
.media-body
|
||||
.list-group-item-heading= privilege.capitalize
|
||||
- unless description.blank?
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
.card#profile
|
||||
%img.profile--avatar{src: @user.profile_picture.url(:large)}
|
||||
- if user_signed_in? && current_user.admin?
|
||||
- if @user.admin?
|
||||
- if user_signed_in? && current_user.has_role?(:administrator)
|
||||
- if @user.has_role?(:administrator)
|
||||
.profile--panel-badge.panel-badge-danger
|
||||
%i.fa.fa-flask
|
||||
= t 'views.user.title.admin'
|
||||
- if @user.moderator?
|
||||
- if @user.has_role?(:moderator)
|
||||
.profile--panel-badge.panel-badge-success
|
||||
%i.fa.fa-users
|
||||
= t 'views.user.title.moderator'
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
- @questions.each do |q|
|
||||
= render 'shared/question', q: q, type: nil
|
||||
|
||||
#pagination= will_paginate @questions, renderer: BootstrapPagination::Rails, page_links: false
|
||||
= render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @questions_last_id
|
||||
|
||||
- if @questions.next_page
|
||||
%button#load-more-btn.btn.btn-default{type: :button, data: { current_page: @questions.current_page }}
|
||||
= t 'views.actions.load'
|
||||
- if @more_data_available
|
||||
%button#load-more-btn.btn.btn-default{type: :button, data: { last_id: @questions_last_id }}
|
||||
= t 'views.actions.load'
|
||||
.visible-xs= render 'shared/links'
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
$('#questions').append('<% @questions.each do |q|
|
||||
%><%= j render 'shared/question', q: q, type: nil
|
||||
%><% end %>');
|
||||
<% if @questions.next_page %>
|
||||
$('#pagination').html('<%= j will_paginate @questions, renderer: BootstrapPagination::Rails, page_links: false %>');
|
||||
<% if @more_data_available %>
|
||||
$('#pagination').html('<%= j render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @questions_last_id %>');
|
||||
<% else %>
|
||||
$('#pagination, #load-more-btn').remove();
|
||||
<% end %>
|
||||
|
|
|
@ -15,11 +15,11 @@
|
|||
- @answers.each do |a|
|
||||
= render 'shared/answerbox', a: a
|
||||
|
||||
#pagination= will_paginate @answers, renderer: BootstrapPagination::Rails, page_links: false
|
||||
= render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @answers_last_id
|
||||
|
||||
- if @answers.next_page
|
||||
%button#load-more-btn.btn.btn-default{type: :button, data: { current_page: @answers.current_page }}
|
||||
= t 'views.actions.load'
|
||||
- if @more_data_available
|
||||
%button#load-more-btn.btn.btn-default{type: :button, data: { last_id: @answers_last_id }}
|
||||
= t 'views.actions.load'
|
||||
.visible-xs= render 'shared/links'
|
||||
- if user_signed_in?
|
||||
= render 'user/modal_group_memberships'
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
$('#answers').append('<% @answers.each do |a|
|
||||
%><%= j render 'shared/answerbox', a: a
|
||||
%><% end %>');
|
||||
<% if @answers.next_page %>
|
||||
$('#pagination').html('<%= j will_paginate @answers, renderer: BootstrapPagination::Rails, page_links: false %>');
|
||||
<% if @more_data_available %>
|
||||
$('#pagination').html('<%= j render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @answers_last_id %>');
|
||||
<% else %>
|
||||
$('#pagination, #load-more-btn').remove();
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
@ -15,11 +15,11 @@
|
|||
.col-md-4.col-sm-6.col-xs-12
|
||||
= render 'shared/userbox', user: user
|
||||
|
||||
#pagination= will_paginate @users, renderer: BootstrapPagination::Rails, page_links: false
|
||||
= render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @users_last_id
|
||||
|
||||
- if @users.next_page
|
||||
%button#load-more-btn.btn.btn-default{type: :button, data: { current_page: @users.current_page }}
|
||||
= t 'views.actions.load'
|
||||
- if @more_data_available
|
||||
%button#load-more-btn.btn.btn-default{type: :button, data: { last_id: @users_last_id }}
|
||||
= t 'views.actions.load'
|
||||
.visible-xs= render 'shared/links'
|
||||
- if user_signed_in?
|
||||
= render 'user/modal_group_memberships'
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
$('#users').append('<% @users.each do |user|
|
||||
%><div class="col-md-4 col-sm-6 col-xs-12"><%= j render 'shared/userbox', user: user
|
||||
%></div><% end %>');
|
||||
<% if @users.next_page %>
|
||||
$('#pagination').html('<%= j will_paginate @users, renderer: BootstrapPagination::Rails, page_links: false %>');
|
||||
<% if @more_data_available %>
|
||||
$('#pagination').html('<%= j render 'shared/cursored_pagination_dummy', more_data_available: @more_data_available, last_id: @users_last_id %>');
|
||||
<% else %>
|
||||
$('#pagination, #load-more-btn').remove();
|
||||
<% end %>
|
||||
|
|
|
@ -28,7 +28,9 @@ development:
|
|||
test: &test
|
||||
adapter: postgresql
|
||||
encoding: unicode
|
||||
database: justask_test
|
||||
database: <%= ENV['POSTGRES_DB'] %>
|
||||
host: <%= ENV.fetch('POSTGRES_HOST', 'localhost') %>
|
||||
pool: 5
|
||||
username: postgres
|
||||
password:
|
||||
username: <%= ENV['POSTGRES_USER'] %>
|
||||
password: <%= ENV['POSTGRES_PASSWORD'] %>
|
||||
port: <%= ENV.fetch('POSTGRES_PORT', 5432) %>
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
# config valid only for current version of Capistrano
|
||||
lock "3.13.0"
|
||||
|
||||
set :application, "retrospring"
|
||||
set :repo_url, "git@git.rrerr.net:nilsding/retrospring.git"
|
||||
|
||||
# Default branch is :master
|
||||
ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp
|
||||
|
||||
# Default deploy_to directory is /var/www/my_app_name
|
||||
set :deploy_to, "/home/justask/apps/retrospring"
|
||||
|
||||
# Default value for :format is :airbrussh.
|
||||
# set :format, :airbrussh
|
||||
|
||||
# You can configure the Airbrussh format using :format_options.
|
||||
# These are the defaults.
|
||||
# set :format_options, command_output: true, log_file: "log/capistrano.log", color: :auto, truncate: :auto
|
||||
|
||||
# Default value for :pty is false
|
||||
# set :pty, true
|
||||
|
||||
# Default value for :linked_files is []
|
||||
append :linked_files, "config/database.yml", "config/justask.yml", "config/secrets.yml", "config/unicorn.rb",
|
||||
"config/newrelic.yml", "config/initializers/devise.rb"
|
||||
|
||||
# Default value for linked_dirs is []
|
||||
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/uploads", "public/system", "public/exports"
|
||||
|
||||
# Default value for default_env is {}
|
||||
# set :default_env, { path: "/opt/ruby/bin:$PATH" }
|
||||
|
||||
# Default value for keep_releases is 5
|
||||
# set :keep_releases, 5
|
||||
|
||||
# Ruby version / RVM
|
||||
set :rvm1_ruby_version, '2.3.3@retrospring'
|
||||
|
||||
# Create JS i18n files before precompiling assets
|
||||
before 'deploy:assets:precompile', 'deploy:i18n_js'
|
||||
|
||||
# Restart the app server after successful deploy
|
||||
after 'deploy:cleanup', 'deploy:restart'
|
|
@ -1,39 +0,0 @@
|
|||
# Defines a single server with a list of roles and multiple properties.
|
||||
server "retrospring-001.aws.infra.retrospring.net", user: "justask", roles: %w{app db web}
|
||||
|
||||
# Configuration
|
||||
# =============
|
||||
# You can set any configuration variable like in config/deploy.rb
|
||||
# These variables are then only loaded and set in this stage.
|
||||
# For available Capistrano configuration variables see the documentation page.
|
||||
# http://capistranorb.com/documentation/getting-started/configuration/
|
||||
# Feel free to add new variables to customise your setup.
|
||||
|
||||
set :rails_env, :production
|
||||
|
||||
# Custom SSH Options
|
||||
# ==================
|
||||
# You may pass any option but keep in mind that net/ssh understands a
|
||||
# limited set of options, consult the Net::SSH documentation.
|
||||
# http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start
|
||||
#
|
||||
# Global options
|
||||
# --------------
|
||||
# set :ssh_options, {
|
||||
# keys: %w(/home/rlisowski/.ssh/id_rsa),
|
||||
# forward_agent: false,
|
||||
# auth_methods: %w(password)
|
||||
# }
|
||||
#
|
||||
# The server-based syntax can be used to override options:
|
||||
# ------------------------------------
|
||||
# server "example.com",
|
||||
# user: "user_name",
|
||||
# roles: %w{web app},
|
||||
# ssh_options: {
|
||||
# user: "user_name", # overrides user setting above
|
||||
# keys: %w(/home/user_name/.ssh/id_rsa),
|
||||
# forward_agent: false,
|
||||
# auth_methods: %w(publickey password)
|
||||
# # password: "please use keys"
|
||||
# }
|
|
@ -1,7 +1,9 @@
|
|||
redis_url = ENV.fetch("REDIS_URL") { APP_CONFIG["redis_url"] }
|
||||
|
||||
Sidekiq.configure_server do |config|
|
||||
config.redis = { url: APP_CONFIG['redis_url'] }
|
||||
config.redis = { url: redis_url }
|
||||
end
|
||||
|
||||
Sidekiq.configure_client do |config|
|
||||
config.redis = { url: APP_CONFIG['redis_url'] }
|
||||
config.redis = { url: redis_url }
|
||||
end
|
|
@ -1 +0,0 @@
|
|||
WillPaginate.per_page = APP_CONFIG['items_per_page']
|
|
@ -181,7 +181,7 @@ Devise.setup do |config|
|
|||
# ==> Configuration for :recoverable
|
||||
#
|
||||
# Defines which key will be used when recovering the password for an account
|
||||
config.reset_password_keys = [ :login ]
|
||||
config.reset_password_keys = [ :email ]
|
||||
|
||||
# Time interval you can reset your password with a reset password key.
|
||||
# Don't put a too small interval or your users won't have the time to
|
||||
|
|
|
@ -1,17 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# workaround to get pagination right
|
||||
if defined? WillPaginate
|
||||
Kaminari.configure do |config|
|
||||
config.page_method_name = :per_page_kaminari
|
||||
end
|
||||
end
|
||||
|
||||
RailsAdmin.config do |config|
|
||||
|
||||
config.main_app_name = ['justask', 'Kontrollzentrum']
|
||||
|
||||
## == Authentication ==
|
||||
config.authenticate_with do
|
||||
redirect_to main_app.root_path unless current_user.try :admin?
|
||||
redirect_to main_app.root_path unless current_user&.has_role?(:administrator)
|
||||
end
|
||||
config.current_user_method(&:current_user)
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Rolify.configure do |config|
|
||||
# By default ORM adapter is ActiveRecord. uncomment to use mongoid
|
||||
# config.use_mongoid
|
||||
|
||||
# Dynamic shortcuts for User class (user.is_admin? like methods). Default is: false
|
||||
# config.use_dynamic_shortcuts
|
||||
|
||||
# Configuration to remove roles from database once the last resource is removed. Default is: true
|
||||
config.remove_role_if_empty = false
|
||||
end
|
|
@ -2,19 +2,26 @@ require 'sidekiq/web'
|
|||
Rails.application.routes.draw do
|
||||
start = Time.now
|
||||
|
||||
# Admin panel
|
||||
mount RailsAdmin::Engine => '/justask_admin', as: 'rails_admin'
|
||||
|
||||
# Sidekiq
|
||||
constraints ->(req) { req.env["warden"].authenticate?(scope: :user) &&
|
||||
req.env['warden'].user.admin? } do
|
||||
req.env["warden"].user.has_role?(:administrator) } do
|
||||
# Admin panel
|
||||
mount RailsAdmin::Engine => "/justask_admin", as: "rails_admin"
|
||||
|
||||
mount Sidekiq::Web, at: "/sidekiq"
|
||||
mount PgHero::Engine, at: "/pghero", as: 'pghero'
|
||||
mount PgHero::Engine, at: "/pghero", as: "pghero"
|
||||
|
||||
match "/admin/announcements", to: "announcement#index", via: :get, as: :announcement_index
|
||||
match "/admin/announcements", to: "announcement#create", via: :post, as: :announcement_create
|
||||
match "/admin/announcements/new", to: "announcement#new", via: :get, as: :announcement_new
|
||||
match "/admin/announcements/:id/edit", to: "announcement#edit", via: :get, as: :announcement_edit
|
||||
match "/admin/announcements/:id", to: "announcement#update", via: :patch, as: :announcement_update
|
||||
match "/admin/announcements/:id", to: "announcement#destroy", via: :delete, as: :announcement_destroy
|
||||
end
|
||||
|
||||
# Moderation panel
|
||||
constraints ->(req) { req.env['warden'].authenticate?(scope: :user) &&
|
||||
(req.env['warden'].user.mod?) } do
|
||||
constraints ->(req) { req.env["warden"].authenticate?(scope: :user) &&
|
||||
req.env["warden"].user.mod? } do
|
||||
match '/moderation/priority(/:user_id)', to: 'moderation#priority', via: :get, as: :moderation_priority
|
||||
match '/moderation/ip/:user_id', to: 'moderation#ip', via: :get, as: :moderation_ip
|
||||
match '/moderation(/:type)', to: 'moderation#index', via: :get, as: :moderation, defaults: {type: 'all'}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
class CreateAnnouncements < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table :announcements do |t|
|
||||
t.text :content, null: false
|
||||
t.string :link_text
|
||||
t.string :link_href
|
||||
t.datetime :starts_at, null: false
|
||||
t.datetime :ends_at, null: false
|
||||
t.belongs_to :user, null: false
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RolifyCreateRoles < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
create_table(:roles) do |t|
|
||||
t.string :name
|
||||
t.references :resource, polymorphic: true
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table(:users_roles, id: false) do |t|
|
||||
t.references :user
|
||||
t.references :role
|
||||
end
|
||||
|
||||
add_index(:roles, %i[name resource_type resource_id])
|
||||
add_index(:users_roles, %i[user_id role_id])
|
||||
end
|
||||
end
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateInitialRoles < ActiveRecord::Migration[5.2]
|
||||
def up
|
||||
%w[Administrator Moderator].each do |role|
|
||||
Role.where(name: role.parameterize).first_or_create
|
||||
end
|
||||
|
||||
{
|
||||
admin: :administrator,
|
||||
moderator: :moderator
|
||||
}.each do |legacy_role, new_role|
|
||||
User.where(legacy_role => true).each do |u|
|
||||
puts "-- migrating #{u.screen_name} (#{u.id}) from field:#{legacy_role} to role:#{new_role}"
|
||||
u.add_role new_role
|
||||
u.public_send("#{legacy_role}=", false)
|
||||
u.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
{
|
||||
administrator: :admin,
|
||||
moderator: :moderator
|
||||
}.each do |new_role, legacy_role|
|
||||
User.with_role(new_role).each do |u|
|
||||
puts "-- migrating #{u.screen_name} (#{u.id}) from role:#{new_role} to field:#{legacy_role}"
|
||||
u.public_send("#{legacy_role}=", true)
|
||||
u.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
32
db/schema.rb
32
db/schema.rb
|
@ -10,11 +10,23 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2016_01_05_165913) do
|
||||
ActiveRecord::Schema.define(version: 2020_04_19_185535) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
||||
create_table "announcements", force: :cascade do |t|
|
||||
t.text "content", null: false
|
||||
t.string "link_text"
|
||||
t.string "link_href"
|
||||
t.datetime "starts_at", null: false
|
||||
t.datetime "ends_at", null: false
|
||||
t.bigint "user_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["user_id"], name: "index_announcements_on_user_id"
|
||||
end
|
||||
|
||||
create_table "answers", id: :serial, force: :cascade do |t|
|
||||
t.text "content"
|
||||
t.integer "question_id"
|
||||
|
@ -137,6 +149,16 @@ ActiveRecord::Schema.define(version: 2016_01_05_165913) do
|
|||
t.string "reason"
|
||||
end
|
||||
|
||||
create_table "roles", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.string "resource_type"
|
||||
t.bigint "resource_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id"
|
||||
t.index ["resource_type", "resource_id"], name: "index_roles_on_resource_type_and_resource_id"
|
||||
end
|
||||
|
||||
create_table "services", id: :serial, force: :cascade do |t|
|
||||
t.string "type", null: false
|
||||
t.integer "user_id", null: false
|
||||
|
@ -270,4 +292,12 @@ ActiveRecord::Schema.define(version: 2016_01_05_165913) do
|
|||
t.index ["screen_name"], name: "index_users_on_screen_name", unique: true
|
||||
end
|
||||
|
||||
create_table "users_roles", id: false, force: :cascade do |t|
|
||||
t.bigint "user_id"
|
||||
t.bigint "role_id"
|
||||
t.index ["role_id"], name: "index_users_roles_on_role_id"
|
||||
t.index ["user_id", "role_id"], name: "index_users_roles_on_user_id_and_role_id"
|
||||
t.index ["user_id"], name: "index_users_roles_on_user_id"
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -5,3 +5,7 @@
|
|||
#
|
||||
# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
|
||||
# Mayor.create(name: 'Emanuel', city: cities.first)
|
||||
|
||||
%w[Administrator Moderator].each do |role|
|
||||
Role.where(name: role.parameterize).first_or_create
|
||||
end
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
namespace :deploy do
|
||||
task :i18n_js do
|
||||
on roles(:all) do
|
||||
within release_path do
|
||||
with rails_env: fetch(:rails_env), rails_groups: fetch(:rails_assets_groups) do
|
||||
execute :rake, "i18n:js:export"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,38 +0,0 @@
|
|||
namespace :deploy do
|
||||
task :start do
|
||||
on roles(:all) do
|
||||
puts "------- skip start"
|
||||
next
|
||||
rvm_prefix = "#{fetch(:rvm1_auto_script_path)}/rvm-auto.sh #{fetch(:rvm1_ruby_version)}"
|
||||
execute :tmux, 'new-session',
|
||||
'-d',
|
||||
'-s', 'retrospring',
|
||||
'-n', 'retrospring',
|
||||
'-c', '/usr/home/justask/apps/retrospring/current',
|
||||
"'#{rvm_prefix} bundle exec foreman start'"
|
||||
end
|
||||
end
|
||||
|
||||
task :stop do
|
||||
on roles(:all) do
|
||||
puts "------- skip stop"
|
||||
next
|
||||
execute :sh, '-c', '\'tmux list-panes -t retrospring -F "#{pane_pid}" | xargs kill\''
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Restart the server'
|
||||
task :restart do
|
||||
on roles(:all) do
|
||||
puts "------- skip restart"
|
||||
next
|
||||
info 'Restarting application server'
|
||||
invoke('deploy:stop')
|
||||
|
||||
info 'Waiting 5 seconds'
|
||||
sleep 5
|
||||
|
||||
invoke('deploy:start')
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,9 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'json'
|
||||
require 'yaml'
|
||||
require 'httparty'
|
||||
require 'securerandom'
|
||||
|
||||
class Exporter
|
||||
EXPORT_ROLES = [:administrator, :moderator].freeze
|
||||
|
||||
def initialize(user)
|
||||
@user = user
|
||||
@obj = {}
|
||||
|
@ -30,10 +34,10 @@ class Exporter
|
|||
private
|
||||
|
||||
def collect_user_info
|
||||
%i(admin answered_count asked_count ban_reason banned_until bio blogger comment_smiled_count commented_count
|
||||
%i(answered_count asked_count ban_reason banned_until bio blogger comment_smiled_count commented_count
|
||||
confirmation_sent_at confirmed_at contributor created_at crop_h crop_h_h crop_h_w crop_h_x crop_h_y
|
||||
crop_w crop_x crop_y current_sign_in_at current_sign_in_ip display_name email follower_count friend_count
|
||||
id last_sign_in_at last_sign_in_ip locale location moderator motivation_header permanently_banned
|
||||
id last_sign_in_at last_sign_in_ip locale location motivation_header permanently_banned
|
||||
privacy_allow_anonymous_questions privacy_allow_public_timeline privacy_allow_stranger_answers
|
||||
privacy_show_in_search profile_header_content_type profile_header_file_name profile_header_file_size
|
||||
profile_header_updated_at profile_picture_content_type profile_picture_file_name profile_picture_file_size
|
||||
|
@ -41,6 +45,10 @@ class Exporter
|
|||
updated_at website).each do |f|
|
||||
@obj[f] = @user.send f
|
||||
end
|
||||
|
||||
EXPORT_ROLES.each do |role|
|
||||
@obj[role] = @user.has_role?(role)
|
||||
end
|
||||
end
|
||||
|
||||
def collect_questions
|
||||
|
@ -221,11 +229,16 @@ class Exporter
|
|||
|
||||
def user_stub(user)
|
||||
uobj = {}
|
||||
%i(admin answered_count asked_count bio blogger comment_smiled_count commented_count contributor created_at
|
||||
display_name follower_count friend_count id location moderator motivation_header permanently_banned screen_name
|
||||
%i(answered_count asked_count bio blogger comment_smiled_count commented_count contributor created_at
|
||||
display_name follower_count friend_count id location motivation_header permanently_banned screen_name
|
||||
smiled_count supporter translator website).each do |f|
|
||||
uobj[f] = user.send f
|
||||
end
|
||||
|
||||
EXPORT_ROLES.each do |role|
|
||||
uobj[role] = user.has_role?(role)
|
||||
end
|
||||
|
||||
uobj
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
describe AnnouncementController, type: :controller do
|
||||
let(:user) { FactoryBot.create(:user, roles: [:administrator]) }
|
||||
|
||||
describe "#index" do
|
||||
subject { get :index }
|
||||
|
||||
context "user signed in" do
|
||||
before(:each) { sign_in(user) }
|
||||
|
||||
it "renders the index template" do
|
||||
subject
|
||||
expect(response).to render_template(:index)
|
||||
end
|
||||
|
||||
context "no announcements" do
|
||||
it "@announcements is empty" do
|
||||
subject
|
||||
expect(assigns(:announcements)).to be_blank
|
||||
end
|
||||
end
|
||||
|
||||
context "one announcement" do
|
||||
let!(:announcement) { Announcement.create(content: "I am announcement", user: user, starts_at: Time.current, ends_at: Time.current + 2.days) }
|
||||
|
||||
it "includes the announcement in the @announcements assign" do
|
||||
subject
|
||||
expect(assigns(:announcements)).to include(announcement)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#new" do
|
||||
subject { get :new }
|
||||
|
||||
context "user signed in" do
|
||||
before(:each) { sign_in(user) }
|
||||
|
||||
it "renders the new template" do
|
||||
subject
|
||||
expect(response).to render_template(:new)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#create" do
|
||||
let :announcement_params do
|
||||
{
|
||||
announcement: {
|
||||
content: "I like dogs!",
|
||||
starts_at: Time.current,
|
||||
ends_at: Time.current + 2.days
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
subject { post :create, params: announcement_params }
|
||||
|
||||
context "user signed in" do
|
||||
before(:each) { sign_in(user) }
|
||||
|
||||
it "creates an announcement" do
|
||||
expect { subject }.to change { Announcement.count }.by(1)
|
||||
end
|
||||
|
||||
it "redirects to announcement#index" do
|
||||
subject
|
||||
expect(response).to redirect_to(:announcement_index)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#edit" do
|
||||
let! :announcement do
|
||||
Announcement.create(content: "Dogs are pretty cool, I guess",
|
||||
starts_at: Time.current + 3.days,
|
||||
ends_at: Time.current + 10.days,
|
||||
user: user)
|
||||
end
|
||||
|
||||
subject { get :edit, params: { id: announcement.id } }
|
||||
|
||||
context "user signed in" do
|
||||
before(:each) { sign_in(user) }
|
||||
|
||||
it "renders the edit template" do
|
||||
subject
|
||||
expect(response).to render_template(:edit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#update" do
|
||||
let :announcement_params do
|
||||
{
|
||||
content: "The trebuchet is the superior siege weapon"
|
||||
}
|
||||
end
|
||||
|
||||
let! :announcement do
|
||||
Announcement.create(content: "Dogs are pretty cool, I guess",
|
||||
starts_at: Time.current + 3.days,
|
||||
ends_at: Time.current + 10.days,
|
||||
user: user)
|
||||
end
|
||||
|
||||
subject do
|
||||
patch :update, params: {
|
||||
id: announcement.id,
|
||||
announcement: announcement_params
|
||||
}
|
||||
end
|
||||
|
||||
context "user signed in" do
|
||||
before(:each) { sign_in(user) }
|
||||
|
||||
it "updates the announcement" do
|
||||
subject
|
||||
updated = Announcement.find announcement.id
|
||||
expect(updated.content).to eq(announcement_params[:content])
|
||||
end
|
||||
|
||||
it "redirects to announcement#index" do
|
||||
subject
|
||||
expect(response).to redirect_to(:announcement_index)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#destroy" do
|
||||
let! :announcement do
|
||||
Announcement.create(content: "Dogs are pretty cool, I guess",
|
||||
starts_at: Time.current + 3.days,
|
||||
ends_at: Time.current + 10.days,
|
||||
user: user)
|
||||
end
|
||||
|
||||
subject { delete :destroy, params: { id: announcement.id } }
|
||||
|
||||
context "user signed in" do
|
||||
before(:each) { sign_in(user) }
|
||||
|
||||
it "deletes the announcement" do
|
||||
expect { subject }.to change { Announcement.count }.by(-1)
|
||||
end
|
||||
|
||||
it "redirects to announcement#index" do
|
||||
subject
|
||||
expect(response).to redirect_to(:announcement_index)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe UserController, :type => :controller do
|
||||
before do
|
||||
@user = create(:user)
|
||||
end
|
||||
|
||||
it 'responds successfully with a HTTP 200 status code' do
|
||||
get :show, username: @user.screen_name, page: 1
|
||||
expect(response).to be_success
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
|
@ -1,9 +0,0 @@
|
|||
FactoryBot.define do
|
||||
factory :user do |u|
|
||||
u.sequence(:screen_name) { |n| "#{Faker::Internet.user_name 0..12, %w(_)}#{n}" }
|
||||
u.sequence(:email) { |n| "#{n}#{Faker::Internet.email}" }
|
||||
u.password { "P4s5w0rD" }
|
||||
u.sequence(:confirmed_at) { Time.zone.now }
|
||||
u.display_name { Faker::Name.name }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :answer do
|
||||
transient do
|
||||
question_content { Faker::Lorem.sentence }
|
||||
end
|
||||
|
||||
content { Faker::Lorem.sentence }
|
||||
question { FactoryBot.build(:question, content: question_content) }
|
||||
end
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
FactoryBot.define do
|
||||
factory :answer do |u|
|
||||
u.sequence(:content) { |n| "This is an answer. I'm number #{n}!" }
|
||||
u.user { FactoryBot.create(:user) }
|
||||
end
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
FactoryBot.define do
|
||||
factory :notification do
|
||||
target_type { "MyString" }
|
||||
target_id { 1 }
|
||||
recipient_id { 1 }
|
||||
new { false }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :question do
|
||||
user { nil }
|
||||
content { Faker::Lorem.sentence }
|
||||
author_is_anonymous { true }
|
||||
end
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
FactoryBot.define do
|
||||
factory :question do |u|
|
||||
u.sequence(:content) { |n| "#{QuestionGenerator.generate}#{n}" }
|
||||
u.author_is_anonymous { true }
|
||||
end
|
||||
end
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :user do
|
||||
sequence(:screen_name) { |i| "#{Faker::Internet.username(specifier: 0..12, separators: %w[_])}#{i}" }
|
||||
sequence(:email) { |i| "#{i}#{Faker::Internet.email}" }
|
||||
password { 'P4s5w0rD' }
|
||||
confirmed_at { Time.now.utc }
|
||||
display_name { Faker::Name.name }
|
||||
|
||||
transient do
|
||||
roles { [] }
|
||||
end
|
||||
|
||||
after(:create) do |user, evaluator|
|
||||
evaluator.roles.each do |role|
|
||||
user.add_role role
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,45 +0,0 @@
|
|||
include Warden::Test::Helpers
|
||||
Warden.test_mode!
|
||||
|
||||
# Feature: Ban users
|
||||
# As a user
|
||||
# I want to get banned
|
||||
# So I can't sign in anymore
|
||||
feature "Ban users", :devise do
|
||||
|
||||
after :each do
|
||||
Warden.test_reset!
|
||||
end
|
||||
|
||||
# Scenario: User gets banned
|
||||
# Given I am signed in
|
||||
# When I visit another page
|
||||
# And I am banned
|
||||
# Then I see the sign in page
|
||||
scenario "user gets banned", js: true do
|
||||
me = FactoryBot.create :user
|
||||
|
||||
login_as me, scope: :user
|
||||
visit root_path
|
||||
expect(page).to have_text("Timeline")
|
||||
page.driver.render Rails.root.join("tmp/ban_#{Time.now.to_i}_1.png"), full: true
|
||||
|
||||
me.permanently_banned = true
|
||||
me.save
|
||||
|
||||
visit "/inbox"
|
||||
|
||||
expect(current_path).to eq(new_user_session_path)
|
||||
page.driver.render Rails.root.join("tmp/ban_#{Time.now.to_i}_2.png"), full: true
|
||||
end
|
||||
|
||||
scenario 'user visits banned user profiles', js: true do
|
||||
evil_user = FactoryBot.create :user
|
||||
evil_user.permanently_banned = true
|
||||
evil_user.save
|
||||
|
||||
visit show_user_profile_path(evil_user.screen_name)
|
||||
expect(page).to have_text('BANNED')
|
||||
page.driver.render Rails.root.join("tmp/ban_#{Time.now.to_i}_3.png"), full: true
|
||||
end
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue