#!/usr/bin/env ruby # encoding: utf-8 # ruby: 3.0.2 =begin server to query part database to install sinatra sudo pacman -S ruby-sinatra ruby-webrick pikaur -S ruby-mysql2 =end require 'set' require 'mysql2' require 'json' require 'sinatra' # allow dumping crashes in browser DEBUG = true # maximum number of parts returned PARTS_LIMIT = 100 # credentials for database CREDENTIALS = "credentials.json" # folder name for served pages PUBLIC = "public" # folder name for part attachments (in PUBLIC) ATTACHMENTS = "attachments" raise "database information #{CREDENTIALS} do not exist" unless File.file? CREDENTIALS # open server configure do if DEBUG then set :show_exceptions, true set :logging, true else set :show_exceptions, false set :environment, :production set :logging, false end set :protection, :except => :json_csrf set :bind, 'localhost' set :port, 4244 set :public_folder, "public" set :static, true end before do response.headers["Access-Control-Allow-Origin"] = "*" response.headers["Access-Control-Allow-Headers"] = "Content-Type" if request.request_method == 'OPTIONS' response.headers["Access-Control-Allow-Methods"] = "GET,POST" halt 200 end # all replies are only JSON content_type 'application/json' # open database credentials = {} JSON.parse(IO.read(CREDENTIALS)).each {|key,value| credentials[key.to_sym] = value} Mysql2::Client.default_query_options.merge!(:as => :hash) @db = Mysql2::Client.new(credentials) end after do response.headers["Access-Control-Allow-Origin"] = "*" response.headers["Access-Control-Allow-Headers"] = "Content-Type" end get '/' do redirect to('/index.html') end def get_part_by_id(id) return nil unless id statement = @db.prepare("SELECT part.id, part.name, part.description, part.datasheet, manufacturer.name AS manufacturer, package.name AS package, part.pincount AS pincount, part.page AS page, part.family AS parent FROM part LEFT JOIN package ON package.id = part.package LEFT JOIN manufacturer ON manufacturer.id = part.manufacturer WHERE part.id = ?") part = statement.execute(id).to_a[0] return nil unless part parent = get_part_by_id(part["parent"]) # merge parent info if parent then part.each do |k,v| part[k] ||= parent[k] end end # add all distributors distributors = @db.query("SELECT * FROM distributor").to_a statement = @db.prepare("SELECT * FROM distribution WHERE part = ?") distributions = statement.execute(id).to_a distributors.each do |distributor| distributions.each do |distribution| if distribution["distributor"] == distributor["id"] then distributor["sku"] = distribution["sku"] distributor["url"] = distributor["product_page"].gsub("%s", distribution["sku"]) end end distributor.delete("id") distributor.delete("homepage") distributor.delete("product_page") end part["distributors"] = distributors # add inventory statement = @db.prepare("SELECT location.name AS location, inventory.quantity AS stock FROM inventory LEFT JOIN location ON location.id = inventory.location WHERE inventory.part = ? ORDER BY inventory.quantity DESC LIMIT 1") inventory = statement.execute(id).to_a[0] if inventory then part["location"] = inventory["location"] part["stock"] = inventory["stock"] end # add properties part["properties"] = {} statement = @db.prepare("SELECT property.name AS name, property_value.value AS value FROM property_value JOIN property ON property.id = property_value.property WHERE property_value.part = ?") statement.execute(id).each do |row| part["properties"][row["name"]] ||= [] part["properties"][row["name"]] << row["value"] end if parent then parent["properties"].each do |k,v| part["properties"][k] ||= v end end # add attachments part["attachments"] = [] dir = PUBLIC + "/" + ATTACHMENTS + "/" + part["name"] Dir.entries(dir).each do |file| path = dir + "/" + file next unless File.file? path part["attachments"] << ATTACHMENTS + "/" + part["name"] + "/" + file end if parent then part["attachments"] += parent["attachments"] end # clean up delete = ["parent"] delete.each do |k| part.delete k end return part end def get_part_by_name(name) statement = @db.prepare("SELECT id FROM part WHERE part.name = ?") id = statement.execute(name).to_a[0] if id then return get_part_by_id(id["id"]) else return nil end end get '/part/:name' do part = get_part_by_name(params['name']) halt 404 unless part part.to_json end get '/search/:terms' do terms = params['terms'].split(" ") terms.keep_if {|term| term.length >= 3} halt 400 if terms.empty? # search in names, description, and category statements = [] statements << @db.prepare("SELECT id FROM part WHERE name LIKE ?") statements << @db.prepare("SELECT id FROM part WHERE description LIKE ?") statements << @db.prepare("SELECT property_value.part AS id FROM property_value JOIN property ON property.id = property_value.property WHERE property.name = 'category' AND property_value.value LIKE ?") term_ids = [] terms.each do |term| ids = Set.new # OR term location statements.each do |statement| statement.execute("%#{term}%").each do |row| ids << row["id"] end end term_ids << ids end # get all children statement = @db.prepare("SELECT id FROM part WHERE family IN (?)") term_ids.each do |term_id| statement.execute(term_id.to_a * ",").each do |row| term_id << row["id"] end end # AND terms ids = term_ids.shift term_ids.each do |term_id| ids &= term_id end parts = ids.collect {|id| get_part_by_id(id)} parts.compact! parts = parts[0, PARTS_LIMIT] parts.sort! {|x,y| x["name"] <=> y["name"]} parts.to_json end def delete_part(id) # first delete all children statement = @db.prepare("SELECT id FROM part WHERE family = ?") statement.execute(id).each do |row| puts "child: #{row['id']}" delete_part(row['id']) puts "deleted" end # delete all fields statements = [] statements << @db.prepare("DELETE FROM property_value WHERE part = ?") statements << @db.prepare("DELETE FROM assembly WHERE assembled = ?") statements << @db.prepare("DELETE FROM assembly WHERE component = ?") statements << @db.prepare("DELETE FROM drawing WHERE part = ?") statements << @db.prepare("DELETE FROM attachment WHERE part = ?") statements << @db.prepare("DELETE FROM distribution WHERE part = ?") statements << @db.prepare("DELETE FROM property_value WHERE part = ?") statements << @db.prepare("DELETE FROM inventory WHERE part = ?") statements << @db.prepare("DELETE FROM part WHERE id = ?") statements.each do |statement| statement.execute(id) end end get '/delete/:id' do statement = @db.prepare("SELECT id FROM part WHERE id = ?") result = statement.execute(params['id']) halt 400 if result.to_a.empty? delete_part(params['id']) return 200 end