class Site < Sequel::Model # We might need to include fonts in here.. VALID_MIME_TYPES = %w{ text/plain text/html text/css application/javascript image/png image/jpeg image/gif image/svg+xml application/vnd.ms-fontobject application/x-font-ttf application/octet-stream text/csv text/tsv text/cache-manifest image/x-icon application/pdf application/pgp-keys text/xml application/xml audio/midi } VALID_EXTENSIONS = %w{ html htm txt text css js jpg jpeg png gif svg md markdown eot ttf woff json geojson csv tsv mf ico pdf asc key pgp xml mid midi } MAX_SPACE = (5242880*2) # 10MB MINIMUM_PASSWORD_LENGTH = 5 BAD_USERNAME_REGEX = /[^\w-]/i VALID_HOSTNAME = /^[a-z0-9][a-z0-9-]+?[a-z0-9]$/i # http://tools.ietf.org/html/rfc1123 SITE_FILES_ROOT = File.join(DIR_ROOT, 'public', (ENV['RACK_ENV'] == 'test' ? 'sites_test' : 'sites')) many_to_one :server many_to_many :tags class << self def valid_login?(username, plaintext) site = self[username: username] return false if site.nil? site.valid_password? plaintext end def bcrypt_cost @bcrypt_cost end def bcrypt_cost=(cost) @bcrypt_cost = cost end end def username=(val) super val.downcase end def valid_password?(plaintext) BCrypt::Password.new(values[:password]) == plaintext end def password=(plaintext) @password_length = plaintext.nil? ? 0 : plaintext.length @password_plaintext = plaintext values[:password] = BCrypt::Password.create plaintext, cost: (self.class.bcrypt_cost || BCrypt::Engine::DEFAULT_COST) end def new_tags=(tags_string) tags_string.gsub! /[^a-zA-Z0-9, ]/, '' tags = tags_string.split ',' tags.collect! {|c| (c.match(/^\w+\s\w+/) || c.match(/^\w+/)).to_s } @new_tag_strings = tags end def before_validation self.server ||= Server.with_slots_available super end def after_save if @new_tag_strings @new_tag_strings.each do |new_tag_string| add_tag Tag[name: new_tag_string] || Tag.create(name: new_tag_string) end end super end def after_create DB['update servers set slots_available=slots_available-1 where id=?', self.server.id].first super end # def after_destroy # FileUtils.rm_rf file_path # super # end def validate super if server.nil? errors.add :over_capacity, 'We are currently at capacity, and cannot create your home page. We will fix this shortly. Please come back later and try again, our apologies.' end if !values[:username].match(VALID_HOSTNAME) errors.add :username, 'A valid user/site name is required.' end if values[:username].length > 32 errors.add :username, 'User/site name cannot exceed 32 characters.' end # Check for existing user user = self.class.select(:id, :username).filter(username: values[:username]).first if user if user.id != values[:id] errors.add :username, 'This username is already taken. Try using another one.' end end if values[:password].nil? || (@password_length && @password_length < MINIMUM_PASSWORD_LENGTH) errors.add :password, "Password must be at least #{MINIMUM_PASSWORD_LENGTH} characters." end if !values[:domain].nil? && !values[:domain].empty? if !(values[:domain] =~ /^[a-zA-Z0-9.-]+\.[a-zA-Z0-9]+$/i) || values[:domain].length > 90 errors.add :domain, "Domain provided is not valid. Must take the form of domain.com" end site = Site[domain: values[:domain]] if !site.nil? && site.id != self.id errors.add :domain, "Domain provided is already being used by another site, please choose another." end end end def file_path File.join SITE_FILES_ROOT, username end def file_list Dir.glob(File.join(file_path, '*')).collect {|p| File.basename(p)}.sort.collect {|sitename| SiteFile.new sitename} end def total_space space = Dir.glob(File.join(file_path, '*')).collect {|p| File.size(p)}.inject {|sum,x| sum += x} space.nil? ? 0 : space end def total_space_in_megabytes (total_space.to_f / 2**20).round(2) end def available_space remaining = MAX_SPACE - total_space remaining < 0 ? 0 : remaining end def available_space_in_megabytes (available_space.to_f / 2**20).round(2) end end