app.rb 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  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. end
  39. end
  40. migration 'rename photo owner and license columns to user_id and license_id' do
  41. database.alter_table :photos do
  42. drop_foreign_key [:owner]
  43. rename_column :owner, :user_id
  44. add_foreign_key [:user_id], :users
  45. drop_foreign_key [:license]
  46. rename_column :license, :license_id
  47. add_foreign_key [:license_id], :licenses
  48. end
  49. end
  50. migration 'add on delete and update constraints to photo user_id and license_id' do
  51. database.alter_table :photos do
  52. drop_foreign_key [:user_id]
  53. add_foreign_key [:user_id], :users, on_delete: :cascade, on_update: :restrict
  54. drop_foreign_key [:license_id]
  55. add_foreign_key [:license_id], :licenses, on_delete: :cascade, on_update: :restrict
  56. end
  57. end
  58. migration 'add not null constraint to photo user_id and license_id' do
  59. database.alter_table :photos do
  60. set_column_not_null :user_id
  61. set_column_not_null :license_id
  62. end
  63. end
  64. migration 'add json field to user' do
  65. database.alter_table :users do
  66. add_column :json, 'text'
  67. end
  68. database.select(:nsid).from(:users).select_map(:nsid).each do |nsid|
  69. info = OpenStruct.new(flickr.people.getInfo(user_id: nsid).to_hash)
  70. info.timezone = info.timezone.to_hash
  71. info.photos = info.photos.to_hash
  72. database.from(:users).where(nsid: nsid).update(json: info.to_h.to_json)
  73. end
  74. end
  75. migration 'add show license/privacy/ignored fields to user' do
  76. database.alter_table :users do
  77. add_foreign_key :show_license_id, :licenses, null: true, on_delete: :set_null, on_update: :restrict
  78. add_column :show_privacy, Integer, default: 0, null: false
  79. add_column :show_ignored, 'boolean', default: true, null: false
  80. end
  81. end
  82. class User < Sequel::Model
  83. plugin :enum
  84. one_to_many :photos
  85. many_to_one :show_license, class: :License
  86. enum :show_privacy, [:all, :public, :friends_family, :friends, :family, :private]
  87. unrestrict_primary_key
  88. def flickraw
  89. @flickraw ||= OpenStruct.new(JSON.parse(json))
  90. end
  91. def buddyicon
  92. if flickraw.iconserver.to_i > 0
  93. "https://farm#{flickraw.iconfarm}.staticflickr.com/#{flickraw.iconserver}/buddyicons/#{nsid}.jpg"
  94. else
  95. "https://www.flickr.com/images/buddyicon.gif"
  96. end
  97. end
  98. def photosurl
  99. flickraw.photosurl
  100. end
  101. end
  102. class License < Sequel::Model
  103. one_to_many :photos
  104. unrestrict_primary_key
  105. def as_json(*)
  106. {
  107. id: id,
  108. name: name,
  109. url: url,
  110. }
  111. end
  112. def to_json(*args)
  113. as_json.to_json(*args)
  114. end
  115. end
  116. class Photo < Sequel::Model
  117. many_to_one :user
  118. many_to_one :license
  119. unrestrict_primary_key
  120. def flickraw
  121. @flickraw ||= OpenStruct.new(JSON.parse(json))
  122. end
  123. def as_json(*)
  124. {
  125. id: id,
  126. license: license_id,
  127. ignore: ignore,
  128. img: FlickRaw.url_q(flickraw),
  129. url: FlickRaw.url_photopage(flickraw),
  130. title: flickraw.title,
  131. public: flickraw.ispublic != 0,
  132. friend: flickraw.isfriend != 0,
  133. family: flickraw.isfamily != 0,
  134. }
  135. end
  136. def to_json(*args)
  137. as_json.to_json(*args)
  138. end
  139. end
  140. helpers Flickr::Login::Helpers
  141. helpers do
  142. def flickr
  143. unless @flickr
  144. @flickr = FlickRaw::Flickr.new(api_key: settings.flickr_api_key, shared_secret: settings.flickr_shared_secret)
  145. @flickr.access_token, @flickr.access_secret = flickr_access_token
  146. end
  147. @flickr
  148. end
  149. end
  150. before do
  151. redirect to('/login?perms=write') unless flickr_user
  152. @user = User.find_or_create(nsid: flickr_user[:user_nsid]) do |user|
  153. user.username = flickr_user[:username]
  154. user.fullname = flickr_user[:fullname]
  155. info = OpenStruct.new(flickr.people.getInfo(user_id: user.nsid).to_hash)
  156. info.timezone = info.timezone.to_hash
  157. info.photos = info.photos.to_hash
  158. user.json = info.to_h.to_json
  159. user.show_license = nil
  160. user.show_privacy = :all
  161. user.show_ignored = true
  162. end
  163. end
  164. get '/' do
  165. @licenses = License.all
  166. @licenses = flickr.photos.licenses.getInfo.map do |flickr_license|
  167. License.create do |license|
  168. license.id = flickr_license.id
  169. license.name = flickr_license.name
  170. license.url = flickr_license.url
  171. end
  172. end if @licenses.count == 0
  173. @show_licenses = [
  174. OpenStruct.new(id: nil, name: 'show photos with any license'),
  175. ] + @licenses
  176. @show_privacies = {
  177. all: 'show public and private photos',
  178. public: 'show only public photos',
  179. friends_family: 'show photos visible to friends and family',
  180. friends: 'show photos visible to only friends',
  181. family: 'show photos visible to only family',
  182. private: 'show completely private photos',
  183. }
  184. @show_ignoreds = {
  185. true => 'show ignored photos',
  186. false => 'hide ignored photos',
  187. }
  188. erb :index
  189. end
  190. get '/logout' do
  191. flickr_clear
  192. redirect to('/')
  193. end
  194. get %r{/photos/([1-8])} do |page|
  195. @user.photos_dataset.delete if params['reload'] == 'true'
  196. page, per_page = page.to_i, 500
  197. photos = @user.photos_dataset.reverse(:id).limit(per_page, (page - 1) * per_page).all
  198. begin
  199. photos = flickr.photos.search(user_id: :me, extras: 'license', per_page: per_page, page: page).map do |flickr_photo|
  200. Photo.create do |photo|
  201. photo.id = flickr_photo.id
  202. photo.user = @user
  203. photo.license_id = flickr_photo.license
  204. photo.json = flickr_photo.to_hash.to_json
  205. photo.ignore = false
  206. end
  207. end if photos.count == 0
  208. rescue FlickRaw::Error => e
  209. halt 422, json(error: e.message)
  210. rescue Sequel::UniqueConstraintViolation
  211. # sometimes the Flickr API will just keep repeating the same results for subsequent pages
  212. end
  213. halt json path: "/photos/#{page + 1}", photos: photos if photos.count == per_page && page < 8
  214. json photos: photos
  215. end
  216. post '/user' do
  217. halt 422, json(error: 'Missing required parameter(s)') unless %w(show_license show_privacy show_ignored).any? {|param| params[param]}
  218. show_license_id = params['show_license']
  219. if show_license_id
  220. if show_license_id.empty?
  221. show_license = nil
  222. else
  223. show_license_id = show_license_id.to_i
  224. show_license = License[show_license_id]
  225. halt 422, json(error: "Could not find license with ID: #{show_license_id.inspect}") unless show_license
  226. end
  227. @user.show_license = show_license
  228. @user.save
  229. end
  230. show_privacy = params['show_privacy']
  231. if show_privacy
  232. @user.show_privacy = show_privacy
  233. begin
  234. @user.save
  235. rescue Sequel::NotNullConstraintViolation
  236. halt 422, json(error: "Invalid privacy value: #{show_privacy.inspect}")
  237. end
  238. end
  239. show_ignored = params['show_ignored']
  240. if show_ignored
  241. @user.show_ignored = show_ignored == 'true'
  242. @user.save
  243. end
  244. status 204
  245. end
  246. post '/photos' do
  247. ignore = params['ignore'] == 'true'
  248. ids = params['photos'] && params['photos'].is_a?(Array) ? params['photos'] : [params['photo']]
  249. photos = @user.photos_dataset.where(id: ids)
  250. halt 404, json(error: "Could not find photo(s) with ID(s): #{ids.map(&:to_i) - photos.map(:id)}") unless photos.count == ids.size
  251. photos.update(ignore: ignore)
  252. status 204
  253. end
  254. post '/photos/*' do |id|
  255. photo = @user.photos_dataset.where(id: id).first
  256. halt 404, json(error: "Could not find photo with ID: #{id}") unless photo
  257. license_id = params['license'] && params['license'].to_i
  258. license = License[license_id]
  259. halt 422, json(error: "Could not find license with ID: #{license_id.inspect}") unless license
  260. halt 422, json(error: "Could not change license of ignored photo") if photo.ignore
  261. begin
  262. flickr.photos.licenses.setLicense(photo_id: photo.id, license_id: license.id)
  263. rescue FlickRaw::Error => e
  264. halt 422, json(error: e.message)
  265. end
  266. photo.update(license: license)
  267. status 204
  268. end