Compare commits

..

6 commits

Author SHA1 Message Date
470c805880 Save last trade activity time onto User
In impress-2020, we do a big slow query to figure out which users have
been active in trades recently. Now, we cache that timestamp on the
User model.

This won't have any immediate effect; it's to clear the way for Classic
DTI to receive the better trade ratios feature people like from 2020.

I also added some unit testing infra because I finally wanted it! for
all the ways you can trigger this timestamp lol

Note too that this is a bit of an unusually complex migration, but my
hope is that the batching and query structure and such helps it run
surprisingly fast! 🤞
2024-01-19 00:00:46 -08:00
b84942d77c Use CSS grid instead of floats for item page info
I'm planning to mess with this layout a bit, I don't want floats
getting in my way!
2024-01-18 22:16:56 -08:00
e8db9cf729 Use CSS grid for item page header
Just modernizing some stuff, hum dee dum
2024-01-18 22:07:30 -08:00
0be9dee6fc Delete unused #item-preview styles from items#show page
Oops, left these lying around after replacing the UI with the 2020 one!
2024-01-18 21:53:00 -08:00
27f21ab775 Delete unused test directory
Until we're actually using the little helpers and scaffolding code,
let's just not have the clutter!
2024-01-18 21:42:20 -08:00
cc2d2906e4 Remove VS Code settings
I don't want to propagate VS Code too much, and this is a trivial file,
goodbye!
2024-01-18 21:37:19 -08:00
18 changed files with 557 additions and 248 deletions

View file

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

View file

@ -1,3 +0,0 @@
{
"editor.rulers": [80]
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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'] %>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

Binary file not shown.