app.rb 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. require 'flickr/login'
  2. require 'flickraw'
  3. require 'pry'
  4. require 'sequel_enum'
  5. require 'sinatra'
  6. require 'sinatra/config_file'
  7. require 'sinatra/json'
  8. require 'sinatra/reloader'
  9. require 'sinatra/sequel'
  10. config_file 'config.yml'
  11. enable :sessions
  12. set :database, 'sqlite://db.sqlite3'
  13. FlickRaw.api_key = settings.flickr_api_key
  14. FlickRaw.shared_secret = settings.flickr_shared_secret
  15. migration 'create users, licenses, and photos tables' do
  16. database.create_table :users do
  17. column :nsid, String, primary_key: true
  18. column :username, String
  19. column :fullname, String
  20. # column :json, 'text'
  21. # foreign_key :show_license_id, :licenses, null: true, on_delete: :set_null, on_update: :restrict
  22. # column :show_privacy, Integer, default: 0, null: false
  23. # column :show_ignored, 'boolean', default: true, null: false
  24. end
  25. database.create_table :licenses do
  26. column :id, Integer, primary_key: true
  27. column :name, String
  28. column :url, String
  29. end
  30. database.create_table :photos do
  31. column :id, Integer, primary_key: true
  32. # foreign_key :user_id, :users, on_delete: :cascade, on_update: :restrict
  33. foreign_key :owner, :users
  34. # foreign_key :license_id, :licenses, on_delete: :cascade, on_update: :restrict
  35. foreign_key :license, :licenses
  36. column :json, 'text'
  37. column :ignore, 'boolean'
  38. # column :public, 'boolean'
  39. # column :friend, 'boolean'
  40. # column :family, 'boolean'
  41. end
  42. end
  43. migration 'rename photo owner and license columns to user_id and license_id' do
  44. database.alter_table :photos do
  45. drop_foreign_key [:owner]
  46. rename_column :owner, :user_id
  47. add_foreign_key [:user_id], :users
  48. drop_foreign_key [:license]
  49. rename_column :license, :license_id
  50. add_foreign_key [:license_id], :licenses
  51. end
  52. end
  53. migration 'add on delete and update constraints to photo user_id and license_id' do
  54. database.alter_table :photos do
  55. drop_foreign_key [:user_id]
  56. add_foreign_key [:user_id], :users, on_delete: :cascade, on_update: :restrict
  57. drop_foreign_key [:license_id]
  58. add_foreign_key [:license_id], :licenses, on_delete: :cascade, on_update: :restrict
  59. end
  60. end
  61. migration 'add not null constraint to photo user_id and license_id' do
  62. database.alter_table :photos do
  63. set_column_not_null :user_id
  64. set_column_not_null :license_id
  65. end
  66. end
  67. migration 'add json field to user' do
  68. database.alter_table :users do
  69. add_column :json, 'text'
  70. end
  71. database.select(:nsid).from(:users).select_map(:nsid).each do |nsid|
  72. info = OpenStruct.new(flickr.people.getInfo(user_id: nsid).to_hash)
  73. info.timezone = info.timezone.to_hash
  74. info.photos = info.photos.to_hash
  75. database.from(:users).where(nsid: nsid).update(json: info.to_h.to_json)
  76. end
  77. end
  78. migration 'add show license/privacy/ignored fields to user' do
  79. database.alter_table :users do
  80. add_foreign_key :show_license_id, :licenses, null: true, on_delete: :set_null, on_update: :restrict
  81. add_column :show_privacy, Integer, default: 0, null: false
  82. add_column :show_ignored, 'boolean', default: true, null: false
  83. end
  84. end
  85. migration 'add public/friend/family fields to photos' do
  86. database.alter_table :photos do
  87. add_column :public, 'boolean'
  88. add_column :friend, 'boolean'
  89. add_column :family, 'boolean'
  90. end
  91. database.select(:id, :json).from(:photos).select_map([:id, :json]).each do |id, json|
  92. photo = OpenStruct.new(JSON.parse(json))
  93. database.from(:photos).where(id: id).update(public: photo.ispublic != 0, friend: photo.isfriend != 0, family: photo.isfamily != 0)
  94. end
  95. end
  96. class User < Sequel::Model
  97. plugin :enum
  98. one_to_many :photos
  99. many_to_one :show_license, class: :License
  100. enum :show_privacy, [:all, :public, :friends_family, :friends, :family, :private]
  101. unrestrict_primary_key
  102. def flickraw
  103. @flickraw ||= OpenStruct.new(JSON.parse(json))
  104. end
  105. def buddyicon
  106. if flickraw.iconserver.to_i > 0
  107. "https://farm#{flickraw.iconfarm}.staticflickr.com/#{flickraw.iconserver}/buddyicons/#{nsid}.jpg"
  108. else
  109. "https://www.flickr.com/images/buddyicon.gif"
  110. end
  111. end
  112. def photosurl
  113. flickraw.photosurl
  114. end
  115. end
  116. class License < Sequel::Model
  117. one_to_many :photos
  118. unrestrict_primary_key
  119. def icon
  120. case url
  121. when %r{/by-nc-sa/}
  122. '<i class="cc cc-cc"></i><i class="cc cc-by"></i><i class="cc cc-nc"></i><i class="cc cc-sa"></i>'
  123. when %r{/by-nc/}
  124. '<i class="cc cc-cc"></i><i class="cc cc-by"></i><i class="cc cc-nc"></i>'
  125. when %r{/by-nc-nd/}
  126. '<i class="cc cc-cc"></i><i class="cc cc-by"></i><i class="cc cc-nc"></i><i class="cc cc-nd"></i>'
  127. when %r{/by/}
  128. '<i class="cc cc-cc"></i><i class="cc cc-by"></i>'
  129. when %r{/by-sa/}
  130. '<i class="cc cc-cc"></i><i class="cc cc-by"></i><i class="cc cc-sa"></i>'
  131. when %r{/by-nd/}
  132. '<i class="cc cc-cc"></i><i class="cc cc-by"></i><i class="cc cc-nd"></i>'
  133. when %r{/commons/}, %r{/mark/}
  134. '<i class="cc cc-pd-alt"></i>'
  135. when %r{/zero/}
  136. '<i class="cc cc-zero"></i>'
  137. else
  138. '©'
  139. end
  140. end
  141. def icon_name
  142. icon + ' ' + name
  143. end
  144. def as_json(*)
  145. {
  146. id: id,
  147. name: name,
  148. url: url,
  149. icon: icon,
  150. iconname: icon_name,
  151. }
  152. end
  153. def to_json(*args)
  154. as_json.to_json(*args)
  155. end
  156. end
  157. class Photo < Sequel::Model
  158. many_to_one :user
  159. many_to_one :license
  160. unrestrict_primary_key
  161. def flickraw
  162. @flickraw ||= OpenStruct.new(JSON.parse(json))
  163. end
  164. def as_json(*)
  165. {
  166. id: id,
  167. license: license_id,
  168. ignore: ignore,
  169. img: FlickRaw.url_q(flickraw),
  170. url: FlickRaw.url_photopage(flickraw),
  171. title: flickraw.title,
  172. public: public,
  173. friend: friend,
  174. family: family,
  175. }
  176. end
  177. def to_json(*args)
  178. as_json.to_json(*args)
  179. end
  180. end
  181. helpers Flickr::Login::Helpers
  182. helpers do
  183. def flickr
  184. unless @flickr
  185. @flickr = FlickRaw::Flickr.new(api_key: settings.flickr_api_key, shared_secret: settings.flickr_shared_secret)
  186. @flickr.access_token, @flickr.access_secret = flickr_access_token
  187. end
  188. @flickr
  189. end
  190. end
  191. before do
  192. redirect to('/login?perms=write') unless flickr_user
  193. @user = User.find_or_create(nsid: flickr_user[:user_nsid]) do |user|
  194. user.username = flickr_user[:username]
  195. user.fullname = flickr_user[:fullname]
  196. info = OpenStruct.new(flickr.people.getInfo(user_id: user.nsid).to_hash)
  197. info.timezone = info.timezone.to_hash
  198. info.photos = info.photos.to_hash
  199. user.json = info.to_h.to_json
  200. user.show_license = nil
  201. user.show_privacy = :all
  202. user.show_ignored = true
  203. end
  204. end
  205. get '/' do
  206. @licenses = License.all
  207. @licenses = flickr.photos.licenses.getInfo.map do |flickr_license|
  208. License.create do |license|
  209. license.id = flickr_license.id
  210. license.name = flickr_license.name
  211. license.url = flickr_license.url
  212. end
  213. end if @licenses.count == 0
  214. @show_privacies = {
  215. all: 'show public and private photos',
  216. public: 'show only public photos',
  217. friends_family: 'show photos visible to friends and family',
  218. friends: 'show photos visible to only friends',
  219. family: 'show photos visible to only family',
  220. private: 'show completely private photos',
  221. }
  222. @show_ignoreds = {
  223. true => 'show ignored photos',
  224. false => 'hide ignored photos',
  225. }
  226. erb :index
  227. end
  228. get '/logout' do
  229. flickr_clear
  230. redirect to('/')
  231. end
  232. get %r{/photos/([1-8])} do |page|
  233. @user.photos_dataset.delete if params['reload'] == 'true'
  234. page, per_page = page.to_i, 500
  235. photos = @user.photos_dataset.reverse(:id).limit(per_page, (page - 1) * per_page).all
  236. begin
  237. photos = flickr.photos.search(user_id: :me, extras: 'license', per_page: per_page, page: page).map do |flickr_photo|
  238. Photo.create do |photo|
  239. photo.id = flickr_photo.id
  240. photo.user = @user
  241. photo.license_id = flickr_photo.license
  242. photo.json = flickr_photo.to_hash.to_json
  243. photo.ignore = false
  244. photo.public = flickr_photo.ispublic != 0
  245. photo.friend = flickr_photo.isfriend != 0
  246. photo.family = flickr_photo.isfamily != 0
  247. end
  248. end if photos.count == 0
  249. rescue FlickRaw::Error => e
  250. halt 422, json(error: e.message)
  251. rescue Sequel::UniqueConstraintViolation
  252. # sometimes the Flickr API will just keep repeating the same results for subsequent pages
  253. end
  254. halt json path: "/photos/#{page + 1}", photos: photos if photos.count == per_page && page < 8
  255. json photos: photos
  256. end
  257. post '/user' do
  258. halt 422, json(error: 'Missing required parameter(s)') unless %w(show_license show_privacy show_ignored).any? {|param| params[param]}
  259. show_license_id = params['show_license']
  260. if show_license_id
  261. if show_license_id.empty?
  262. show_license = nil
  263. else
  264. show_license_id = show_license_id.to_i
  265. show_license = License[show_license_id]
  266. halt 422, json(error: "Could not find license with ID: #{show_license_id.inspect}") unless show_license
  267. end
  268. @user.show_license = show_license
  269. @user.save
  270. end
  271. show_privacy = params['show_privacy']
  272. if show_privacy
  273. @user.show_privacy = show_privacy
  274. begin
  275. @user.save
  276. rescue Sequel::NotNullConstraintViolation
  277. halt 422, json(error: "Invalid privacy value: #{show_privacy.inspect}")
  278. end
  279. end
  280. show_ignored = params['show_ignored']
  281. if show_ignored
  282. @user.show_ignored = show_ignored == 'true'
  283. @user.save
  284. end
  285. status 204
  286. end
  287. post '/photos' do
  288. ignore = params['ignore'] == 'true'
  289. ids = params['photos'] && params['photos'].is_a?(Array) ? params['photos'] : [params['photo']]
  290. photos = @user.photos_dataset.where(id: ids)
  291. halt 404, json(error: "Could not find photo(s) with ID(s): #{ids.map(&:to_i) - photos.map(:id)}") unless photos.count == ids.size
  292. photos.update(ignore: ignore)
  293. status 204
  294. end
  295. post '/photos/*' do |id|
  296. photo = @user.photos_dataset.where(id: id).first
  297. halt 404, json(error: "Could not find photo with ID: #{id}") unless photo
  298. license_id = params['license'] && params['license'].to_i
  299. license = License[license_id]
  300. halt 422, json(error: "Could not find license with ID: #{license_id.inspect}") unless license
  301. case @user.show_privacy
  302. when :all
  303. # no need to check privacy
  304. when :public
  305. halt 422, json(error: 'Could not change license of non public photo') unless photo.public
  306. when :friends_family
  307. halt 422, json(error: 'Could not change license of non friends and family photo') unless photo.friend && photo.family
  308. when :friends
  309. halt 422, json(error: 'Could not change license of non only friends photo') unless photo.friend && !photo.family
  310. when :family
  311. halt 422, json(error: 'Could not change license of non only family photo') unless !photo.friend && photo.family
  312. when :private
  313. halt 422, json(error: 'Could not change license of non private photo') unless !photo.public && !photo.friend && !photo.family
  314. end
  315. halt 422, json(error: 'Could not change license of photo with unselected license') if @user.show_license && @user.show_license != photo.license
  316. halt 422, json(error: 'Could not change license of ignored photo') if photo.ignore
  317. begin
  318. flickr.photos.licenses.setLicense(photo_id: photo.id, license_id: license.id)
  319. rescue FlickRaw::Error => e
  320. halt 422, json(error: e.message)
  321. end
  322. photo.update(license: license)
  323. status 204
  324. end