Compare commits
6 commits
6d343989de
...
470c805880
Author | SHA1 | Date | |
---|---|---|---|
470c805880 | |||
b84942d77c | |||
e8db9cf729 | |||
0be9dee6fc | |||
27f21ab775 | |||
cc2d2906e4 |
18 changed files with 557 additions and 248 deletions
|
@ -27,6 +27,8 @@
|
|||
// we partially override `config/database.yml` to connect to `db`!
|
||||
"DATABASE_URL_PRIMARY_DEV": "mysql2://db",
|
||||
"DATABASE_URL_OPENNEO_ID_DEV": "mysql2://db",
|
||||
"DATABASE_URL_PRIMARY_TEST": "mysql2://db",
|
||||
"DATABASE_URL_OPENNEO_ID_TEST": "mysql2://db",
|
||||
|
||||
// HACK: Out of the box, this dev container doesn't allow installation to
|
||||
// the default GEM_HOME, because of a weird thing going on with RVM.
|
||||
|
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"editor.rulers": [80]
|
||||
}
|
5
Gemfile
5
Gemfile
|
@ -79,3 +79,8 @@ gem "stackprof", "~> 0.2.25"
|
|||
# For monitoring errors in production.
|
||||
gem "sentry-ruby", "~> 5.12"
|
||||
gem "sentry-rails", "~> 5.12"
|
||||
|
||||
# For automated testing.
|
||||
group :test do
|
||||
gem 'sqlite3', '~> 1.7'
|
||||
end
|
||||
|
|
|
@ -297,6 +297,8 @@ GEM
|
|||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
sprockets (>= 3.0.0)
|
||||
sqlite3 (1.7.0)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
stackprof (0.2.25)
|
||||
stringio (3.0.8)
|
||||
temple (0.8.2)
|
||||
|
@ -359,6 +361,7 @@ DEPENDENCIES
|
|||
sentry-rails (~> 5.12)
|
||||
sentry-ruby (~> 5.12)
|
||||
sprockets (~> 4.2)
|
||||
sqlite3 (~> 1.7)
|
||||
stackprof (~> 0.2.25)
|
||||
terser (~> 1.1, >= 1.1.17)
|
||||
thread-local (~> 1.1)
|
||||
|
|
|
@ -1,131 +1,148 @@
|
|||
@import "../partials/clean/constants"
|
||||
@import "../partials/clean/mixins"
|
||||
|
||||
=grayscale
|
||||
// http://www.karlhorky.com/2012/06/cross-browser-image-grayscale-with-css.html
|
||||
filter: url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\'><filter id=\'grayscale\'><feColorMatrix type=\'matrix\' values=\'0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0.3333 0.3333 0.3333 0 0 0 0 0 1 0\'/></filter></svg>#grayscale")
|
||||
filter: gray
|
||||
|
||||
// Cheap hack: https://github.com/chriseppstein/compass/issues/811
|
||||
$percent: 100%
|
||||
-webkit-filter: unquote('grayscale(#{$percent})')
|
||||
|
||||
body.items-show
|
||||
#item-header
|
||||
border-bottom: 1px solid $module-border-color
|
||||
display: block
|
||||
margin-bottom: 1em
|
||||
padding: 1em 0
|
||||
|
||||
div, img
|
||||
+inline-block
|
||||
display: grid
|
||||
grid-template-areas: "img gap1" "img name" "img links" "img gap2"
|
||||
align-items: center
|
||||
justify-content: center
|
||||
column-gap: 1em
|
||||
row-gap: .5em
|
||||
|
||||
#item-thumbnail
|
||||
grid-area: img
|
||||
|
||||
border: 1px solid $module-border-color
|
||||
height: 80px
|
||||
width: 80px
|
||||
|
||||
#item-name
|
||||
grid-area: name
|
||||
|
||||
div
|
||||
text-align: left
|
||||
line-height: 100%
|
||||
|
||||
a
|
||||
font-size: 75%
|
||||
margin-left: 1em
|
||||
#item-links
|
||||
grid-area: links
|
||||
|
||||
#item-thumbnail
|
||||
border: 1px solid $module-border-color
|
||||
height: 80px
|
||||
margin-right: .5em
|
||||
width: 80px
|
||||
text-align: left
|
||||
a
|
||||
font-size: 75%
|
||||
margin-left: 1em
|
||||
|
||||
#item-name
|
||||
margin-bottom: 0
|
||||
|
||||
#item-preview
|
||||
+clearfix
|
||||
#item-info-section
|
||||
display: grid
|
||||
|
||||
> div, > ul
|
||||
float: left
|
||||
grid-template-areas: "info form"
|
||||
grid-template-columns: 1fr auto
|
||||
|
||||
#item-preview-species
|
||||
display: block
|
||||
width: 400px
|
||||
#item-info
|
||||
grid-area: info
|
||||
|
||||
.pet-type, img
|
||||
height: 50px
|
||||
width: 50px
|
||||
#item-zones
|
||||
font:
|
||||
family: $text-font
|
||||
size: 85%
|
||||
margin-bottom: 1em
|
||||
|
||||
.pet-type
|
||||
+inline-block
|
||||
&.current
|
||||
background: $module-bg-color
|
||||
outline: 1px solid $module-border-color
|
||||
&.deactivated
|
||||
img
|
||||
+grayscale
|
||||
+opacity(0.5)
|
||||
&.current
|
||||
background: transparent
|
||||
outline-color: $error_border_color
|
||||
p
|
||||
display: inline
|
||||
|
||||
ul, li
|
||||
display: inline
|
||||
&:first-child
|
||||
margin-right: 1em
|
||||
|
||||
#item-preview-error
|
||||
display: none
|
||||
padding: 20px 10px 0
|
||||
width: 380px
|
||||
#your-items-form
|
||||
grid-area: form
|
||||
|
||||
#item-preview-swf
|
||||
height: 300px
|
||||
overflow: hidden
|
||||
width: 300px
|
||||
border: 1px solid $module-border-color
|
||||
font-size: 85%
|
||||
margin-bottom: 3em
|
||||
margin-left: 1em
|
||||
padding: 1em
|
||||
width: 30em
|
||||
|
||||
#item-zones
|
||||
font:
|
||||
family: $text-font
|
||||
size: 85%
|
||||
margin-bottom: 1em
|
||||
|
||||
p
|
||||
display: inline
|
||||
|
||||
&:first-child
|
||||
margin-right: 1em
|
||||
|
||||
#trade-hangers
|
||||
font-size: 85%
|
||||
margin-bottom: 3em
|
||||
text-align: left
|
||||
|
||||
p
|
||||
// compete with #trade-hangers
|
||||
position: relative
|
||||
z-index: 2
|
||||
|
||||
&:first-child
|
||||
h3
|
||||
font-size: 150%
|
||||
font-weight: bold
|
||||
margin-bottom: .25em
|
||||
|
||||
#closet-hangers-ownership-groups
|
||||
+clearfix
|
||||
margin-bottom: .5em
|
||||
|
||||
&.overflows
|
||||
.toggle
|
||||
display: block
|
||||
div
|
||||
float: left
|
||||
margin: 0 5%
|
||||
text-align: left
|
||||
width: 40%
|
||||
|
||||
&.showing-more
|
||||
.toggle
|
||||
.less
|
||||
li
|
||||
list-style: none
|
||||
word-wrap: break-word
|
||||
|
||||
label.unlisted
|
||||
font-style: italic
|
||||
|
||||
form
|
||||
padding: .5em 0
|
||||
|
||||
select
|
||||
width: 9em
|
||||
|
||||
input[type=number]
|
||||
margin-right: .5em
|
||||
width: 3em
|
||||
|
||||
#trade-hangers
|
||||
font-size: 85%
|
||||
margin-bottom: 3em
|
||||
text-align: left
|
||||
|
||||
p
|
||||
position: relative
|
||||
|
||||
&:first-child
|
||||
margin-bottom: .5em
|
||||
|
||||
&.overflows
|
||||
.toggle
|
||||
display: block
|
||||
|
||||
.more
|
||||
display: none
|
||||
&.showing-more
|
||||
.toggle
|
||||
.less
|
||||
display: block
|
||||
|
||||
.toggle
|
||||
background: white
|
||||
bottom: 0
|
||||
cursor: pointer
|
||||
display: none
|
||||
font-family: $main-font
|
||||
padding: 0 1em
|
||||
position: absolute
|
||||
right: 0
|
||||
.more
|
||||
display: none
|
||||
|
||||
&:hover
|
||||
text-decoration: underline
|
||||
|
||||
.less
|
||||
.toggle
|
||||
background: white
|
||||
bottom: 0
|
||||
cursor: pointer
|
||||
display: none
|
||||
font-family: $main-font
|
||||
padding: 0 1em
|
||||
position: absolute
|
||||
right: 0
|
||||
|
||||
&:hover
|
||||
text-decoration: underline
|
||||
|
||||
.less
|
||||
display: none
|
||||
|
||||
#item-contributors
|
||||
+subtle-banner
|
||||
|
@ -149,64 +166,10 @@ body.items-show
|
|||
li:last-child::after
|
||||
content: "."
|
||||
|
||||
#item-preview-header
|
||||
clear: both
|
||||
|
||||
h3, a
|
||||
+inline-block
|
||||
a
|
||||
font-size: 85%
|
||||
margin: -1.5em 0 0 1em
|
||||
|
||||
.nc-icon
|
||||
height: 16px
|
||||
width: 16px
|
||||
|
||||
#closet-hangers
|
||||
border: 1px solid $module-border-color
|
||||
float: right
|
||||
font-size: 85%
|
||||
margin-bottom: 3em
|
||||
margin-left: 1em
|
||||
padding: 1em
|
||||
width: 30em
|
||||
|
||||
// compete with #trade-hangers
|
||||
position: relative
|
||||
z-index: 2
|
||||
|
||||
h3
|
||||
font-size: 150%
|
||||
font-weight: bold
|
||||
margin-bottom: .25em
|
||||
|
||||
#closet-hangers-ownership-groups
|
||||
+clearfix
|
||||
margin-bottom: .5em
|
||||
|
||||
div
|
||||
float: left
|
||||
margin: 0 5%
|
||||
text-align: left
|
||||
width: 40%
|
||||
|
||||
li
|
||||
list-style: none
|
||||
word-wrap: break-word
|
||||
|
||||
label.unlisted
|
||||
font-style: italic
|
||||
|
||||
form
|
||||
padding: .5em 0
|
||||
|
||||
select
|
||||
width: 9em
|
||||
|
||||
input[type=number]
|
||||
margin-right: .5em
|
||||
width: 3em
|
||||
|
||||
&.js
|
||||
#trade-hangers
|
||||
p
|
||||
|
|
|
@ -4,6 +4,7 @@ class ClosetHanger < ApplicationRecord
|
|||
belongs_to :user
|
||||
|
||||
delegate :name, to: :item, prefix: true
|
||||
delegate :log_trade_activity, to: :user
|
||||
|
||||
validates :item_id, :uniqueness => {:scope => [:user_id, :owned, :list_id]}
|
||||
validates :quantity, :numericality => {:greater_than => 0}
|
||||
|
@ -16,6 +17,32 @@ class ClosetHanger < ApplicationRecord
|
|||
joins(:item => :translations).where(it[:locale].eq(I18n.locale)).
|
||||
order(it[:name].asc)
|
||||
}
|
||||
scope :trading, -> {
|
||||
ch = arel_table
|
||||
cl = ClosetList.arel_table
|
||||
u = User.arel_table
|
||||
joins(:user, :list).where(
|
||||
# sigh… our default-lists continue to be a pain
|
||||
(
|
||||
ch[:list_id].not_eq(nil).and(cl[:visibility].gteq(
|
||||
ClosetVisibility[:trading].id))
|
||||
).or(
|
||||
(
|
||||
ch[:list_id].eq(nil).and(ch[:owned].eq(true))
|
||||
).and(
|
||||
u[:owned_closet_hangers_visibility].gteq(
|
||||
ClosetVisibility[:trading].id)
|
||||
)
|
||||
).or(
|
||||
(
|
||||
ch[:list_id].eq(nil).and(ch[:owned].eq(false))
|
||||
).and(
|
||||
u[:wanted_closet_hangers_visibility].gteq(
|
||||
ClosetVisibility[:trading].id)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
scope :newest, -> { order(arel_table[:created_at].desc) }
|
||||
scope :owned_before_wanted, -> { order(arel_table[:owned].desc) }
|
||||
scope :unlisted, -> { where(:list_id => nil) }
|
||||
|
@ -36,6 +63,9 @@ class ClosetHanger < ApplicationRecord
|
|||
|
||||
before_validation :merge_quantities, :set_owned_by_list
|
||||
|
||||
after_save :log_trade_activity, if: :trading?
|
||||
after_destroy :log_trade_activity, if: :trading?
|
||||
|
||||
def possibly_null_closet_list
|
||||
list || user.null_closet_list(owned)
|
||||
end
|
||||
|
|
|
@ -6,10 +6,15 @@ class ClosetList < ApplicationRecord
|
|||
validates :user, :presence => true
|
||||
validates :hangers_owned, :inclusion => {:in => [true, false], :message => "can't be blank"}
|
||||
|
||||
delegate :log_trade_activity, to: :user
|
||||
|
||||
scope :alphabetical, -> { order(:name) }
|
||||
scope :publicly_visible, -> {
|
||||
where(arel_table[:visibility].gteq(ClosetVisibility[:public].id))
|
||||
}
|
||||
scope :trading, -> {
|
||||
where(arel_table[:visibility].gteq(ClosetVisibility[:trading].id))
|
||||
}
|
||||
scope :visible_to, ->(user) {
|
||||
condition = arel_table[:visibility].gteq(ClosetVisibility[:public].id)
|
||||
condition = condition.or(arel_table[:user_id].eq(user.id)) if user
|
||||
|
@ -17,6 +22,12 @@ class ClosetList < ApplicationRecord
|
|||
}
|
||||
|
||||
after_save :sync_hangers_owned!
|
||||
after_save :log_trade_activity, if: :trading?
|
||||
after_destroy :log_trade_activity, if: :trading?
|
||||
|
||||
def trading?
|
||||
visibility >= ClosetVisibility[:trading].id
|
||||
end
|
||||
|
||||
def sync_hangers_owned!
|
||||
if hangers_owned_changed?
|
||||
|
|
|
@ -25,6 +25,12 @@ class User < ApplicationRecord
|
|||
scope :top_contributors, -> { order('points DESC').where('points > 0') }
|
||||
|
||||
after_update :sync_name_with_auth_user!, if: :saved_change_to_name?
|
||||
after_update :log_trade_activity, if: -> user {
|
||||
(user.saved_change_to_owned_closet_hangers_visibility? &&
|
||||
user.owned_closet_hangers_visibility >= ClosetVisibility[:trading].id) ||
|
||||
(user.saved_change_to_wanted_closet_hangers_visibility? &&
|
||||
user.wanted_closet_hangers_visibility >= ClosetVisibility[:trading].id)
|
||||
}
|
||||
|
||||
def sync_name_with_auth_user!
|
||||
auth_user.name = name
|
||||
|
@ -169,6 +175,10 @@ class User < ApplicationRecord
|
|||
contact_neopets_connection.try(:neopets_username)
|
||||
end
|
||||
|
||||
def log_trade_activity
|
||||
touch(:last_trade_activity_at)
|
||||
end
|
||||
|
||||
def self.points_required_to_pass_top_contributor(offset)
|
||||
user = User.top_contributors.select(:points).limit(1).offset(offset).first
|
||||
user ? user.points : 0
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
%header#item-header
|
||||
= image_tag @item.thumbnail_url, :id => 'item-thumbnail'
|
||||
%div
|
||||
%h2#item-name= @item.name
|
||||
%h2#item-name= @item.name
|
||||
%nav#item-links
|
||||
= nc_icon_for(@item)
|
||||
- unless @item.rarity.blank?
|
||||
== #{t '.rarity'}: #{@item.rarity_index} (#{@item.rarity})
|
||||
|
@ -19,59 +19,61 @@
|
|||
= link_to t('.resources.trading_post'), trading_post_url_for(@item)
|
||||
= link_to t('.resources.auction_genie'), auction_genie_url_for(@item)
|
||||
|
||||
- if user_signed_in?
|
||||
#closet-hangers
|
||||
%h3
|
||||
= t '.closet_hangers.header_html',
|
||||
:user_items_link => link_to(t('your_items'),
|
||||
user_closet_hangers_path(current_user))
|
||||
= form_tag update_quantities_user_item_closet_hangers_path(:user_id => current_user, :item_id => @item), :method => :put do
|
||||
#closet-hangers-ownership-groups
|
||||
- @current_user_lists.each do |owned, lists|
|
||||
%div
|
||||
%h4= closet_lists_group_name(:you, owned)
|
||||
%ul
|
||||
- lists.each do |list|
|
||||
%section#item-info-section
|
||||
#item-info
|
||||
%p= @item.description
|
||||
|
||||
#item-zones
|
||||
%p
|
||||
%strong #{t '.zones.occupied_header'}:
|
||||
= list_zones @occupied_zones, :uncertain_label
|
||||
%p
|
||||
%strong #{t '.zones.restricted_header'}:
|
||||
- if @restricted_zones.empty?
|
||||
= t '.zones.none'
|
||||
- else
|
||||
= list_zones @restricted_zones
|
||||
|
||||
#trade-hangers
|
||||
- [true, false].each do |owned|
|
||||
%p
|
||||
%strong
|
||||
= trading_closet_hangers_header(owned, @trading_closet_hangers_by_owned[owned].size)
|
||||
= render_trading_closet_hangers(owned)
|
||||
%span.toggle
|
||||
%span.more= t '.trading_closet_hangers.show_more'
|
||||
%span.less= t '.trading_closet_hangers.show_less'
|
||||
|
||||
- if user_signed_in?
|
||||
#your-items-form
|
||||
%h3
|
||||
= t '.closet_hangers.header_html',
|
||||
:user_items_link => link_to(t('your_items'),
|
||||
user_closet_hangers_path(current_user))
|
||||
= form_tag update_quantities_user_item_closet_hangers_path(:user_id => current_user, :item_id => @item), :method => :put do
|
||||
#closet-hangers-ownership-groups
|
||||
- @current_user_lists.each do |owned, lists|
|
||||
%div
|
||||
%h4= closet_lists_group_name(:you, owned)
|
||||
%ul
|
||||
- lists.each do |list|
|
||||
%li
|
||||
= number_field_tag "quantity[#{list.id}]",
|
||||
@current_user_quantities[list.id], :min => 0
|
||||
= label_tag "quantity[#{list.id}]", list.name
|
||||
|
||||
%li
|
||||
= number_field_tag "quantity[#{list.id}]",
|
||||
@current_user_quantities[list.id], :min => 0
|
||||
= label_tag "quantity[#{list.id}]", list.name
|
||||
= number_field_tag "quantity[#{owned}]",
|
||||
@current_user_quantities[owned], :min => 0
|
||||
|
||||
%li
|
||||
= number_field_tag "quantity[#{owned}]",
|
||||
@current_user_quantities[owned], :min => 0
|
||||
|
||||
- unless lists.empty?
|
||||
= label_tag "quantity[#{owned}]",
|
||||
t('closet_lists.unlisted_name'),
|
||||
:class => 'unlisted'
|
||||
- else
|
||||
= label_tag "quantity[#{owned}]",
|
||||
t('.closet_hangers.quantity_label')
|
||||
= submit_tag t('.closet_hangers.submit')
|
||||
|
||||
%p= @item.description
|
||||
|
||||
#item-zones
|
||||
%p
|
||||
%strong #{t '.zones.occupied_header'}:
|
||||
= list_zones @occupied_zones, :uncertain_label
|
||||
%p
|
||||
%strong #{t '.zones.restricted_header'}:
|
||||
- if @restricted_zones.empty?
|
||||
= t '.zones.none'
|
||||
- else
|
||||
= list_zones @restricted_zones
|
||||
|
||||
#trade-hangers
|
||||
- [true, false].each do |owned|
|
||||
%p
|
||||
%strong
|
||||
= trading_closet_hangers_header(owned, @trading_closet_hangers_by_owned[owned].size)
|
||||
= render_trading_closet_hangers(owned)
|
||||
%span.toggle
|
||||
%span.more= t '.trading_closet_hangers.show_more'
|
||||
%span.less= t '.trading_closet_hangers.show_less'
|
||||
- unless lists.empty?
|
||||
= label_tag "quantity[#{owned}]",
|
||||
t('closet_lists.unlisted_name'),
|
||||
:class => 'unlisted'
|
||||
- else
|
||||
= label_tag "quantity[#{owned}]",
|
||||
t('.closet_hangers.quantity_label')
|
||||
= submit_tag t('.closet_hangers.submit')
|
||||
|
||||
#outfit-preview-root{'data-item-id': @item.id}
|
||||
|
||||
|
|
|
@ -24,6 +24,32 @@ development:
|
|||
sql_mode: TRADITIONAL
|
||||
migrations_paths: db/openneo_id_migrate
|
||||
|
||||
test:
|
||||
primary:
|
||||
# You can override these default settings with this environment variable,
|
||||
# fully or partially. We do this in the .devcontainer setup!
|
||||
url: <%= ENV['DATABASE_URL_PRIMARY_TEST'] %>
|
||||
adapter: mysql2
|
||||
database: openneo_impress_test
|
||||
username: impress_dev
|
||||
password: impress_dev
|
||||
pool: 5
|
||||
variables:
|
||||
sql_mode: TRADITIONAL
|
||||
|
||||
openneo_id:
|
||||
# You can override these default settings with this environment variable,
|
||||
# fully or partially. We do this in the .devcontainer setup!
|
||||
url: <%= ENV['DATABASE_URL_OPENNEO_ID_TEST'] %>
|
||||
adapter: mysql2
|
||||
database: openneo_id_test
|
||||
username: impress_dev
|
||||
password: impress_dev
|
||||
pool: 2
|
||||
variables:
|
||||
sql_mode: TRADITIONAL
|
||||
migrations_paths: db/openneo_id_migrate
|
||||
|
||||
production:
|
||||
primary:
|
||||
url: <%= ENV['DATABASE_URL_PRIMARY'] %>
|
||||
|
|
|
@ -35,7 +35,7 @@ Rails.application.configure do
|
|||
config.action_controller.allow_forgery_protection = false
|
||||
|
||||
# Store uploaded files on the local file system in a temporary directory.
|
||||
config.active_storage.service = :test
|
||||
# config.active_storage.service = :test
|
||||
|
||||
config.action_mailer.perform_caching = false
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
# Make sure your secret_key_base is kept private
|
||||
# if you're sharing your code publicly.
|
||||
if Rails.env.development?
|
||||
if Rails.env.development? || Rails.env.test?
|
||||
# In development, we use a hardcoded secret key, because it doesn't actually
|
||||
# need to be secret!
|
||||
OpenneoImpressItems::Application.config.secret_key_base = "7584841652f89044a8b5a428efa6dfac2461449eb24741a33668cd642130d79f93b0347766ebf4a4d7d5033a263c36431594ad56b5735a7325c8cdda991219c2"
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
class AddLastTradeActivityAtToUsers < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
add_column :users, :last_trade_activity_at, :timestamp
|
||||
|
||||
reversible do |direction|
|
||||
direction.up do
|
||||
User.find_in_batches do |users|
|
||||
# Find the last ClosetList/ClosetHanger updated_at timestamp for each
|
||||
# user, for trading lists/hangers only.
|
||||
max_closet_list_updated_at_by_user_id = ClosetList.
|
||||
trading.
|
||||
group(:user_id).
|
||||
where(user_id: users.map(&:id)).
|
||||
maximum(:updated_at)
|
||||
max_closet_hanger_updated_at_by_user_id = ClosetHanger.
|
||||
trading.
|
||||
group(:user_id).
|
||||
where(user_id: users.map(&:id)).
|
||||
maximum(:updated_at)
|
||||
|
||||
# Set `last_trade_activity_at` to the largest such `updated_at` for
|
||||
# that user, or nil if there's none.
|
||||
User.transaction do
|
||||
users.each do |user|
|
||||
user.last_trade_activity_at = [
|
||||
max_closet_list_updated_at_by_user_id[user.id],
|
||||
max_closet_hanger_updated_at_by_user_id[user.id],
|
||||
].filter(&:present?).max
|
||||
user.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -11,7 +11,7 @@
|
|||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.1].define(version: 2023_08_07_005748) do
|
||||
create_table "users", id: { type: :integer, unsigned: true }, charset: "utf8mb3", force: :cascade do |t|
|
||||
create_table "users", id: { type: :integer, unsigned: true }, charset: "utf8mb3", collation: "utf8mb3_general_ci", force: :cascade do |t|
|
||||
t.string "name", limit: 20, null: false
|
||||
t.string "encrypted_password", limit: 64, null: false
|
||||
t.string "email", limit: 50, null: false
|
||||
|
|
45
db/schema.rb
45
db/schema.rb
|
@ -10,8 +10,8 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
||||
create_table "auth_servers", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
ActiveRecord::Schema[7.1].define(version: 2024_01_19_061745) do
|
||||
create_table "auth_servers", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
t.string "short_name", limit: 10, null: false
|
||||
t.string "name", limit: 40, null: false
|
||||
t.text "icon", size: :medium, null: false
|
||||
|
@ -19,7 +19,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
|||
t.string "secret", limit: 64, null: false
|
||||
end
|
||||
|
||||
create_table "campaigns", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
create_table "campaigns", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
t.integer "progress", default: 0, null: false
|
||||
t.integer "goal", null: false
|
||||
t.boolean "active", null: false
|
||||
|
@ -33,7 +33,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
|||
t.string "name"
|
||||
end
|
||||
|
||||
create_table "closet_hangers", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
create_table "closet_hangers", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
t.integer "item_id"
|
||||
t.integer "user_id"
|
||||
t.integer "quantity"
|
||||
|
@ -49,7 +49,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
|||
t.index ["user_id"], name: "index_closet_hangers_on_user_id"
|
||||
end
|
||||
|
||||
create_table "closet_lists", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
create_table "closet_lists", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.text "description"
|
||||
t.integer "user_id"
|
||||
|
@ -60,7 +60,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
|||
t.index ["user_id"], name: "index_closet_lists_on_user_id"
|
||||
end
|
||||
|
||||
create_table "color_translations", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
create_table "color_translations", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
t.integer "color_id"
|
||||
t.string "locale"
|
||||
t.string "name"
|
||||
|
@ -70,13 +70,13 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
|||
t.index ["locale"], name: "index_color_translations_on_locale"
|
||||
end
|
||||
|
||||
create_table "colors", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
create_table "colors", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
t.boolean "basic"
|
||||
t.boolean "standard"
|
||||
t.boolean "prank", default: false, null: false
|
||||
end
|
||||
|
||||
create_table "contributions", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
create_table "contributions", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
t.string "contributed_type", limit: 8, null: false
|
||||
t.integer "contributed_id", null: false
|
||||
t.integer "user_id", null: false
|
||||
|
@ -85,14 +85,14 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
|||
t.index ["user_id"], name: "index_contributions_on_user_id"
|
||||
end
|
||||
|
||||
create_table "donation_features", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
create_table "donation_features", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
t.integer "donation_id", null: false
|
||||
t.integer "outfit_id"
|
||||
t.datetime "created_at", precision: nil, null: false
|
||||
t.datetime "updated_at", precision: nil, null: false
|
||||
end
|
||||
|
||||
create_table "donations", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
create_table "donations", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
t.integer "amount", null: false
|
||||
t.string "charge_id", null: false
|
||||
t.integer "user_id"
|
||||
|
@ -104,7 +104,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
|||
t.integer "campaign_id", null: false
|
||||
end
|
||||
|
||||
create_table "item_outfit_relationships", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
create_table "item_outfit_relationships", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
t.integer "item_id"
|
||||
t.integer "outfit_id"
|
||||
t.boolean "is_worn"
|
||||
|
@ -149,7 +149,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
|||
t.index ["modeling_status_hint"], name: "items_modeling_status_hint"
|
||||
end
|
||||
|
||||
create_table "login_cookies", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
create_table "login_cookies", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
t.integer "user_id", null: false
|
||||
t.integer "series", null: false
|
||||
t.integer "token", null: false
|
||||
|
@ -157,20 +157,20 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
|||
t.index ["user_id"], name: "login_cookies_user_id"
|
||||
end
|
||||
|
||||
create_table "modeling_logs", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
create_table "modeling_logs", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
t.datetime "created_at", precision: nil, null: false
|
||||
t.text "log_json", null: false
|
||||
t.string "pet_name", limit: 128, null: false
|
||||
end
|
||||
|
||||
create_table "neopets_connections", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
create_table "neopets_connections", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
t.integer "user_id"
|
||||
t.string "neopets_username"
|
||||
t.datetime "created_at", precision: nil, null: false
|
||||
t.datetime "updated_at", precision: nil, null: false
|
||||
end
|
||||
|
||||
create_table "outfits", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
create_table "outfits", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
t.integer "pet_state_id"
|
||||
t.integer "user_id"
|
||||
t.datetime "created_at", precision: nil
|
||||
|
@ -193,7 +193,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
|||
t.index ["swf_asset_id"], name: "parents_swf_assets_swf_asset_id"
|
||||
end
|
||||
|
||||
create_table "pet_loads", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
create_table "pet_loads", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
t.string "pet_name", limit: 20, null: false
|
||||
t.text "amf", size: :medium, null: false
|
||||
t.datetime "created_at", precision: nil, null: false
|
||||
|
@ -225,17 +225,17 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
|||
t.index ["species_id", "color_id"], name: "pet_types_species_color", unique: true
|
||||
end
|
||||
|
||||
create_table "pets", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
create_table "pets", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
t.string "name", limit: 20, null: false
|
||||
t.integer "pet_type_id", limit: 3, null: false
|
||||
t.index ["name"], name: "pets_name", unique: true
|
||||
t.index ["pet_type_id"], name: "pets_pet_type_id"
|
||||
end
|
||||
|
||||
create_table "species", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
create_table "species", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
end
|
||||
|
||||
create_table "species_translations", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
create_table "species_translations", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
t.integer "species_id"
|
||||
t.string "locale"
|
||||
t.string "name"
|
||||
|
@ -267,7 +267,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
|||
t.index ["zone_id"], name: "idx_swf_assets_zone_id"
|
||||
end
|
||||
|
||||
create_table "users", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
create_table "users", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
t.string "name", limit: 20, null: false
|
||||
t.integer "auth_server_id", limit: 1, null: false
|
||||
t.integer "remote_id", null: false
|
||||
|
@ -278,9 +278,10 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
|||
t.integer "owned_closet_hangers_visibility", default: 1, null: false
|
||||
t.integer "wanted_closet_hangers_visibility", default: 1, null: false
|
||||
t.integer "contact_neopets_connection_id"
|
||||
t.timestamp "last_trade_activity_at"
|
||||
end
|
||||
|
||||
create_table "zone_translations", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
create_table "zone_translations", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
t.integer "zone_id"
|
||||
t.string "locale"
|
||||
t.string "label"
|
||||
|
@ -291,7 +292,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_11_234255) do
|
|||
t.index ["zone_id"], name: "index_zone_translations_on_zone_id"
|
||||
end
|
||||
|
||||
create_table "zones", id: :integer, charset: "latin1", force: :cascade do |t|
|
||||
create_table "zones", id: :integer, charset: "latin1", collation: "latin1_swedish_ci", force: :cascade do |t|
|
||||
t.integer "depth"
|
||||
t.integer "type_id"
|
||||
end
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
require 'test_helper'
|
||||
require 'rails/performance_test_help'
|
||||
|
||||
# Profiling results for each test method are written to tmp/performance.
|
||||
class BrowsingTest < ActionDispatch::PerformanceTest
|
||||
def test_homepage
|
||||
get '/'
|
||||
end
|
||||
end
|
232
test/trade_activity_test.rb
Normal file
232
test/trade_activity_test.rb
Normal file
|
@ -0,0 +1,232 @@
|
|||
require 'test_helper'
|
||||
|
||||
class TradeActivityTest < ActiveSupport::TestCase
|
||||
test "New user's last trade activity is nil" do
|
||||
user = create_user
|
||||
assert_nil user.last_trade_activity_at
|
||||
end
|
||||
|
||||
test "Adding or removing items in a Trading list updates last trade activity" do
|
||||
user = create_user
|
||||
|
||||
list = create_closet_list(
|
||||
user: user, visibility: ClosetVisibility[:trading].id)
|
||||
hanger = create_closet_hanger(user: user, list: list)
|
||||
created_at = Time.now
|
||||
|
||||
assert_equal created_at, user.last_trade_activity_at
|
||||
|
||||
travel 1.day
|
||||
hanger.destroy!
|
||||
|
||||
assert_equal created_at + 1.day, user.last_trade_activity_at
|
||||
end
|
||||
|
||||
test "Adding or removing items in a Public list does not update last trade activity" do
|
||||
user = create_user
|
||||
|
||||
list = create_closet_list(
|
||||
user: user, visibility: ClosetVisibility[:public].id)
|
||||
hanger = create_closet_hanger(user: user, list: list)
|
||||
|
||||
assert_nil user.last_trade_activity_at
|
||||
|
||||
travel 1.day
|
||||
hanger.destroy!
|
||||
|
||||
assert_nil user.last_trade_activity_at
|
||||
end
|
||||
|
||||
test "Adding or removing items in a Private list does not update last trade activity" do
|
||||
user = create_user
|
||||
|
||||
list = create_closet_list(
|
||||
user: user, visibility: ClosetVisibility[:private].id)
|
||||
hanger = create_closet_hanger(user: user, list: list)
|
||||
|
||||
assert_nil user.last_trade_activity_at
|
||||
|
||||
travel 1.day
|
||||
hanger.destroy!
|
||||
|
||||
assert_nil user.last_trade_activity_at
|
||||
end
|
||||
|
||||
test "Adding or removing items in a Trading default-list updates last trade activity" do
|
||||
user = create_user(
|
||||
owned_closet_hangers_visibility: ClosetVisibility[:trading].id,
|
||||
wanted_closet_hangers_visibility: ClosetVisibility[:private].id,
|
||||
)
|
||||
|
||||
hanger = create_closet_hanger(user: user, owned: true)
|
||||
created_at = Time.now
|
||||
|
||||
assert_equal created_at, user.last_trade_activity_at
|
||||
|
||||
travel 1.day
|
||||
hanger.destroy!
|
||||
|
||||
assert_equal created_at + 1.day, user.last_trade_activity_at
|
||||
end
|
||||
|
||||
test "Adding or removing items in a Public default-list does not update last trade activity" do
|
||||
user = create_user(
|
||||
owned_closet_hangers_visibility: ClosetVisibility[:public].id,
|
||||
wanted_closet_hangers_visibility: ClosetVisibility[:private].id,
|
||||
)
|
||||
|
||||
hanger = create_closet_hanger(user: user, owned: true)
|
||||
|
||||
assert_nil user.last_trade_activity_at
|
||||
|
||||
travel 1.day
|
||||
hanger.destroy!
|
||||
|
||||
assert_nil user.last_trade_activity_at
|
||||
end
|
||||
|
||||
test "Adding or removing items in a Private default-list does not update last trade activity" do
|
||||
user = create_user(
|
||||
owned_closet_hangers_visibility: ClosetVisibility[:private].id,
|
||||
wanted_closet_hangers_visibility: ClosetVisibility[:private].id,
|
||||
)
|
||||
|
||||
hanger = create_closet_hanger(user: user, owned: true)
|
||||
|
||||
assert_nil user.last_trade_activity_at
|
||||
|
||||
travel 1.day
|
||||
hanger.destroy!
|
||||
|
||||
assert_nil user.last_trade_activity_at
|
||||
end
|
||||
|
||||
test "Creating, editing, or deleting a Trading list updates last trade activity" do
|
||||
user = create_user
|
||||
list = create_closet_list(
|
||||
user: user, visibility: ClosetVisibility[:trading].id
|
||||
)
|
||||
created_at = Time.now
|
||||
|
||||
assert_equal created_at, user.last_trade_activity_at
|
||||
|
||||
travel 1.day
|
||||
list.update!(description: "Hello, world!")
|
||||
|
||||
assert_equal created_at + 1.day, user.last_trade_activity_at
|
||||
|
||||
travel 1.day
|
||||
list.destroy!
|
||||
|
||||
assert_equal created_at + 2.day, user.last_trade_activity_at
|
||||
end
|
||||
|
||||
test "Creating, editing, or deleting a Public list does not update last trade activity" do
|
||||
user = create_user
|
||||
list = create_closet_list(
|
||||
user: user, visibility: ClosetVisibility[:public].id
|
||||
)
|
||||
|
||||
assert_nil user.last_trade_activity_at
|
||||
|
||||
travel 1.day
|
||||
list.update!(description: "Hello, world!")
|
||||
|
||||
assert_nil user.last_trade_activity_at
|
||||
|
||||
travel 1.day
|
||||
list.destroy!
|
||||
|
||||
assert_nil user.last_trade_activity_at
|
||||
end
|
||||
|
||||
test "Creating, editing, or deleting a Private list does not update last trade activity" do
|
||||
user = create_user
|
||||
list = create_closet_list(
|
||||
user: user, visibility: ClosetVisibility[:private].id
|
||||
)
|
||||
|
||||
assert_nil user.last_trade_activity_at
|
||||
|
||||
travel 1.day
|
||||
list.update!(description: "Hello, world!")
|
||||
|
||||
assert_nil user.last_trade_activity_at
|
||||
|
||||
travel 1.day
|
||||
list.destroy!
|
||||
|
||||
assert_nil user.last_trade_activity_at
|
||||
end
|
||||
|
||||
test "Updating default-list visibility to Trading updates last trade activity" do
|
||||
user = create_user(
|
||||
owned_closet_hangers_visibility: ClosetVisibility[:private].id,
|
||||
)
|
||||
|
||||
assert_nil user.last_trade_activity_at
|
||||
|
||||
user.update!(
|
||||
owned_closet_hangers_visibility: ClosetVisibility[:trading].id,
|
||||
)
|
||||
|
||||
assert_equal Time.now, user.last_trade_activity_at
|
||||
end
|
||||
|
||||
test "Updating default-list visibility to Public does not update last trade activity" do
|
||||
user = create_user(
|
||||
owned_closet_hangers_visibility: ClosetVisibility[:private].id,
|
||||
)
|
||||
|
||||
assert_nil user.last_trade_activity_at
|
||||
|
||||
user.update!(
|
||||
owned_closet_hangers_visibility: ClosetVisibility[:public].id,
|
||||
)
|
||||
|
||||
assert_nil user.last_trade_activity_at
|
||||
end
|
||||
|
||||
test "Updating default-list visibility to Private does not update last trade activity" do
|
||||
user = create_user(
|
||||
owned_closet_hangers_visibility: ClosetVisibility[:public].id,
|
||||
)
|
||||
|
||||
assert_nil user.last_trade_activity_at
|
||||
|
||||
user.update!(
|
||||
owned_closet_hangers_visibility: ClosetVisibility[:private].id,
|
||||
)
|
||||
|
||||
assert_nil user.last_trade_activity_at
|
||||
end
|
||||
|
||||
setup do
|
||||
freeze_time # to compare timestamps accurately
|
||||
|
||||
Item.create!(
|
||||
thumbnail_url: "https://images.neopets.com/foo.png",
|
||||
zones_restrict: "",
|
||||
price: 123,
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_user(**args)
|
||||
auth_user = AuthUser.create!(
|
||||
name: 'test', email: 'test@example.com', password: 'test123!'
|
||||
)
|
||||
auth_user.user.update!(**args) unless args.empty?
|
||||
auth_user.user
|
||||
end
|
||||
|
||||
def create_closet_list(**args)
|
||||
num = ClosetList.count + 1
|
||||
ClosetList.create!(name: "Test List #{num}", hangers_owned: true, **args)
|
||||
end
|
||||
|
||||
def create_closet_hanger(**args)
|
||||
ClosetHanger.create!(item: Item.first, quantity: 1, **args)
|
||||
end
|
||||
end
|
BIN
vendor/cache/sqlite3-1.7.0-x86_64-linux.gem
vendored
Normal file
BIN
vendor/cache/sqlite3-1.7.0-x86_64-linux.gem
vendored
Normal file
Binary file not shown.
Loading…
Reference in a new issue