# User model class User < EntityModel attr_reader :email, :name, :bio_text, :balance, :avatar_url, :pw_hash, :reputation def initialize(data) super data @email = data["email"] @name = data["name"] @bio_text = data["bio_text"] @balance = data["balance"].to_f @avatar_url = data["avatar_url"] @reputation = data["reputation"].to_i @pw_hash = data["pw_hash"] end def avatar return @avatar_url end def auctions Auction.get_all "user_id = ?", @id end def role return Role.find_by_id( ROLES[:admin][:id] ).name if self.admin? user_roles = self.roles if user_roles.length > 0 then role = user_roles.max_by { |role| role.flags } return role.name end return "" end def role_ids User_Role_relation.get_user_roles_ids @id end def roles User_Role_relation.get_user_roles @id end def rep_score return BAD_REP if @reputation < 0 return GOOD_REP if @reputation > 0 return NEUTRAL_REP end def bio_html md_parser = Redcarpet::Markdown.new(Redcarpet::Render::HTML) md_parser.render @bio_text end def reputation_text sign = @reputation > 0 ? "+" : "" return "#{sign}#{@reputation}" end def reputation=(val) val = val.clamp MIN_REP, MAX_REP @reputation = val User.update({reputation: val}, "id = ?", @id) end def balance=(val) val = val >= 0 ? val : 0 @balance = val User.update({balance: val}, "id = ?", @id) end def update_creds(data) # Validate input return false, SETTINGS_ERRORS[:name_len] unless data[:name].length.between?(MIN_NAME_LEN, MAX_NAME_LEN) return false, SETTINGS_ERRORS[:bio_len] unless data[:bio_text].length.between?(MIN_BIO_LEN, MAX_BIO_LEN) # Filter unchanged data data.keys.each do |k| data.delete(k) if @data[k.to_s] == data[k] end User.update(data, "id = ?", @id) unless data.length < 1 return true, nil end # Find user by email, same as above but for emails. def self.find_by_email(email) data = self.get("*", "email = ?", email).first data && User.new(data) end def self.validate_register_creds(email, name, password, password_confirm) # Field check check_all_fields = email != "" && name != "" && password != "" && password_confirm != "" # Check email check_email_dupe = self.find_by_email(email) == nil check_email_valid = email.match(EMAIL_REGEX) != nil # Name check_name_len = name.length.between?(MIN_NAME_LEN, MAX_NAME_LEN) # Password check_pass_equals = password == password_confirm check_pass_len = password.length >= MIN_PASSWORD_LEN # This code is really ugly return false, REGISTER_ERRORS[:fields] unless check_all_fields return false, REGISTER_ERRORS[:email_dupe] unless check_email_dupe return false, REGISTER_ERRORS[:email_fake] unless check_email_valid return false, REGISTER_ERRORS[:name_len] unless check_name_len return false, REGISTER_ERRORS[:pass_notequals] unless check_pass_equals return false, REGISTER_ERRORS[:pass_len] unless check_pass_len return true, "" end def self.validate_password(pw_hash, password) BCrypt::Password.new(pw_hash) == password end # Register a new user # Returns: success?, data def self.register(email, name, password, password_confirm) check, errorstr = self.validate_register_creds(email, name, password, password_confirm) if check then pw_hash = BCrypt::Password.create password data = { # payload name: name, email: email, pw_hash: pw_hash } resp = self.insert data # insert into the db return check, resp else return check, errorstr end end # Log in user # Returns: success?, user id def self.login(email, password) user = self.find_by_email email # get the user info return false, LOGIN_ERRORS[:fail] unless user # Verify that the user exists pw_check = self.validate_password(user.pw_hash, password) return false, LOGIN_ERRORS[:fail] unless pw_check # Verify password return true, user.id end # Get a users flags # Returns: bitmap int thingie def flags flags = 0 self.roles.each do |role| if role.is_a? Role then flags |= role.flags end end return flags end def admin? return self.flags[1] == 1 end # Check if user has flags # Returns: true or false depending whether the user has those flags def permitted?(flag, *other_flags) return true if self.admin? flag_mask = PERM_LEVELS[flag] if other_flags then other_flags.each {|f| flag_mask |= PERM_LEVELS[f]} end return self.flags & flag_mask == flag_mask end def banned? return self.flags[ PERM_LEVELS.keys.index(:banned) ] == 1 end def banned=(b) if b then # Add the "banned" role resp = User_Role_relation.give_role(@id, ROLES[:banned][:id]) else # Remove the "banned" role resp = User_Role_relation.revoke_role(@id, ROLES[:banned][:id]) end end end # Role model class Role < EntityModel attr_reader :name, :color, :flags def initialize(data) super data @name = data["name"] @color = data["color"] @flags = data["flags"] end def has_flag?(flag, *other_flags) flag_mask = PERM_LEVELS[flag] # Add other flags if other_flags then other_flags.each do |f| flag_mask += PERM_LEVELS[f] end end return @flags & flag_mask == flag_mask # f AND m = m => flags exists end def self.find_by_name(name) data = self.get("*", "name = ?", name).first data && Role.new(data) end def self.create(name, color="#ffffff", flags=0) return false, REGISTER_ERRORS[:name_len] unless name.length.between?(MIN_NAME_LEN, MAX_NAME_LEN) data = { name: name, color: color, flags: flags } self.insert data end end class User_Role_relation < EntityModel def self.init_table super # Add the "first user" to the admin role search = self.get("role_id", "user_id=1") or [] if search.length <= 0 then q = "INSERT INTO #{self.name} (user_id, role_id) VALUES (?, ?)" self.query(q, 1, 1) end end def self.give_role(user_id, role_id) user = User.find_by_id user_id if not user.role_ids.include?(role_id) then data = { role_id: role_id, user_id: user_id } self.insert data end end def self.revoke_role(user_id, role_id) user = User.find_by_id user_id if user.role_ids.include?(role_id) then self.delete "role_id = ? AND user_id = ?", role_id, user_id end end def self.get_user_roles_ids(user_id) ids = self.get "role_id", "user_id = ?", user_id ids.map! do |ent| ent["role_id"].to_i end end def self.get_user_roles(user_id) roleids = self.get_user_roles_ids user_id roles = roleids.map do |id| Role.find_by_id(id) end end end # Auction model class Auction < EntityModel attr_reader :user_id, :title, :description, :init_price, :start_time, :end_time def initialize(data) super data @user_id = data["user_id"].to_i @title = data["title"] @description = data["description"] @init_price = data["price"].to_f @start_time = data["start_time"].to_i @end_time = data["end_time"].to_i end def self.validate_ah(title, description, init_price, delta_time) return false, AUCTION_ERRORS[:titlelen] unless title.length.between?(MIN_TITLE_LEN, MAX_TITLE_LEN) return false, AUCTION_ERRORS[:initprice] unless init_price >= MIN_INIT_PRICE return false, AUCTION_ERRORS[:deltatime] unless delta_time >= MIN_DELTA_TIME return false, AUCTION_ERRORS[:desclen] unless description.length.between?(MIN_DESC_LEN, MAX_DESC_LEN) return true, "" end def self.create(user_id, title, description, init_price, delta_time) # Validate the input check, errorstr = self.validate_ah(title, description, init_price, delta_time) return check, errorstr unless check # Get current UNIX time start_time = Time.now.to_i end_time = start_time + delta_time # Prep the payload data = { user_id: user_id, title: title, description: description, price: init_price, start_time: start_time, end_time: end_time } self.insert data end def self.compose_query_filters(title=nil, categories=nil, min_price=nil, max_price=nil, expired=nil) querystr = "SELECT * FROM Auction WHERE " filters = [] # Title filter filters << "title LIKE '%#{title}%'" if title and title.length != 0 # Price filters if min_price and max_price then filters << "price BETWEEN #{min_price} AND #{max_price}" elsif min_price then filters << "price >= #{min_price}" elsif max_price then filters << "price <= #{max_price}" end # Time filter filters << "end_time #{ expired == true ? "<" : ">" } #{Time.now.to_i}" # Categories filter if categories then ah_ids = [] categories.each do |catid| if ah_ids == [] then ah_ids = Auction_Category_relation.category_auction_ids(catid) # first time then include all else ah_ids |= Auction_Category_relation.category_auction_ids(catid) # do union for all ids (prevent dupes) end end filters << "id IN (#{ah_ids.join(", ")})" # check if the auction id is any of the ids calculated above end querystr += filters.join " AND " return querystr end def self.search(title=nil, categories=nil, min_price=nil, max_price=nil, expired=nil) q = self.compose_query_filters title, categories, min_price, max_price, expired data = self.query(q) data.map! {|dat| self.new(dat)} end def self.expired?(id) ah = self.find_by_id id ah && ah.expired? end def edit(title, description) return false, AUCTION_ERRORS[:titlelen] unless title.length.between?(MIN_TITLE_LEN, MAX_TITLE_LEN) return false, AUCTION_ERRORS[:desclen] unless description.length.between?(MIN_DESC_LEN, MAX_DESC_LEN) data = { title: title, description: description } Auction.update data, "id = ?", @id end def delete FileUtils.rm_rf("./public/auctions/#{@id}") # delete all images Auction.delete "id = ?", @id # delete the actual post entry Auction_Category_relation.delete "auction_id = ?", @id Image.delete "auction_id = ?", @id Bid.delete "auction_id = ?", @id end def poster User.find_by_id @user_id end def images Image.get_relation @id end def category_ids data = Auction_Category_relation.get "category_id", "auction_id = ?", @id data && data.map! {|category| category["category_id"].to_i} end def categories data = self.category_ids data && data.map! { |catid| Category.find_by_id catid} end def expired? Time.now.to_i > @end_time end def time_left @end_time - Time.now.to_i end def time_left_s left = self.time_left result = [] TIME_FORMATS.each do |sym, count| amount = left.to_i / count if amount > 0 then result << "#{amount}#{sym.to_s}" left -= count*amount end end result = result[0...2] return result.join ", " end def bids Bid.get_bids(@id) end def place_bid(uid, amount, message) Bid.place(@id, uid, amount, message) end def max_bid max_bid = self.bids.max_by {|bid| bid.amount} end def current_bid mbid = self.max_bid if mbid != nil then return mbid.amount.to_f else return @init_price.to_f end end def min_new_bid max_bid = self.max_bid amount = max_bid.nil? ? @init_price : max_bid.amount return amount * AH_BIDS_FACTOR end end # Auction bids class Bid < EntityModel attr_reader :amount, :auction_id, :user_id, :message def initialize(data) super data @amount = data["amount"].to_f @auction_id = data["auction_id"].to_i @user_id = data["user_id"].to_i @message = data["message"] end def self.get_bids(ahid) data = self.get "*", "auction_id = ?", ahid data && data.map! {|dat| self.new(dat)} end def self.get_user_bids(uid) data = self.get "*", "user_id = ?", uid data && data.map! {|dat| self.new(dat)} end def self.get_user_active_amount(uid) bids = self.get_user_bids uid return bids.sum {|bid| bid.amount} end def self.get_delta_amount(ahid, uid, amount) data = self.get "*", "auction_id = ? AND user_id = ?", ahid, uid if data and data.length > 0 then data.map! {|dat| self.new(dat)} max_bid = data.max_by {|bid| bid.amount} p "sgiodfhgiodfhioghoi" p data p "sgiodfhgiodfhioghoi" return amount - max_bid.amount else return amount end end def self.validate_bid(ahid, uid, amount, message) ah = Auction.find_by_id ahid return false, "Invalid auction" unless ah.is_a? Auction return false, AUCTION_ERRORS[:expired] unless not ah.expired? return false, AUCTION_ERRORS[:ownerbid] unless uid != ah.user_id return false, AUCTION_ERRORS[:cantafford] unless User.find_by_id(uid).balance - amount >= 0 return false, AUCTION_ERRORS[:bidamount] unless amount >= ah.min_new_bid return true, "" end def self.place(ahid, uid, amount, message) check, resp = self.validate_bid(ahid, uid, amount, message) if check then # Deduct delta amount from balance delta_amount = self.get_delta_amount(ahid, uid, amount) user = User.find_by_id uid user.balance -= delta_amount payload = { auction_id: ahid, user_id: uid, amount: amount, message: message } resp = self.insert(payload) end return check, resp end end class Category < EntityModel attr_reader :name, :color def initialize(data) super data @name = data["name"] @color = data["color"] end def self.create(name, color) data = { name: name, color: color } self.insert(data) end end class Auction_Category_relation < EntityModel attr_reader :auction_id, :category_id def initialize(data) super data @auction_id = data["auction_id"] @category_id = data["category_id"] end def self.category_auction_ids(catid) ids = self.get "auction_id", "category_id = ?", catid ids && ids.map! {|id| id["auction_id"].to_i} end end class Image < EntityModel attr_reader :auction_id, :image_order, :url def initialize(data) super data @auction_id = data["auction_id"] @image_order = data["image_order"].to_i @url = data["url"] end def self.save(imgdata, ah_id, order) FileUtils.mkdir_p "./public/auctions/#{ah_id}" data = { auction_id: ah_id, image_order: order, url: "/auctions/#{ah_id}/#{order}.png" } newid, resp = self.insert data if newid then image = Magick::Image.from_blob(imgdata).first image.format = "PNG" path = "./public/auctions/#{ah_id}/#{order}.png" File.open(path, 'wb') do |f| image.write(f) { self.quality = 50 } end end end def self.get_relation(ah_id) imgs = self.get "*", "auction_id = ?", ah_id imgs.map! do |img| self.new(img) end return imgs end end