2023-01-23 14:13:43 +01:00
#!/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'
2023-01-26 11:55:56 +01:00
# allow dumping crashes in browser
2023-01-23 14:13:43 +01:00
DEBUG = true
2023-01-26 11:55:56 +01:00
# maximum number of parts returned
2023-01-23 14:13:43 +01:00
PARTS_LIMIT = 100
2023-01-26 11:55:56 +01:00
# credentials for database
2023-01-23 14:13:43 +01:00
CREDENTIALS = " credentials.json "
2023-01-26 11:55:56 +01:00
# folder name for served pages
PUBLIC = " public "
# folder name for part attachments (in PUBLIC)
ATTACHMENTS = " attachments "
2023-01-23 14:13:43 +01:00
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 )
2023-01-26 07:17:20 +01:00
return nil unless id
2023-01-25 05:15:15 +01:00
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 = ? " )
2023-01-25 07:48:36 +01:00
part = statement . execute ( id ) . to_a [ 0 ]
2023-01-23 14:13:43 +01:00
return nil unless part
2023-01-26 07:17:20 +01:00
parent = get_part_by_id ( part [ " parent " ] )
# merge parent info
if parent then
part . each do | k , v |
part [ k ] || = parent [ k ]
2023-01-23 14:13:43 +01:00
end
end
2023-01-25 07:47:48 +01:00
# 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
2023-01-25 08:46:21 +01:00
# 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
2023-01-25 11:22:26 +01:00
# 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 = ? " )
2023-01-26 07:17:20 +01:00
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
2023-01-25 11:22:26 +01:00
end
end
2023-01-26 11:55:56 +01:00
# 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
2023-01-23 14:13:43 +01:00
# clean up
2023-01-25 05:15:15 +01:00
delete = [ " parent " ]
2023-01-23 14:13:43 +01:00
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 }
2023-01-26 00:42:54 +01:00
halt 400 if terms . empty?
2023-01-25 11:58:17 +01:00
# 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 " ]
2023-01-23 14:13:43 +01:00
end
end
2023-01-25 11:58:17 +01:00
# AND terms
ids = term_ids . shift
term_ids . each do | term_id |
ids & = term_id
end
2023-01-23 14:13:43 +01:00
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
2023-01-26 00:42:54 +01:00
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