123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- # flickrlicense -- A thingy to update Flickr photo licenses
- # Copyright (C) 2017 Douglas Thrift
- #
- # This program is free software: you can redistribute it and/or modify
- # it under the terms of the GNU Affero General Public License as published
- # by the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU Affero General Public License for more details.
- #
- # You should have received a copy of the GNU Affero General Public License
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
- require 'flickr/login'
- require 'flickraw'
- require 'pry'
- require 'sequel_enum'
- require 'sinatra'
- require 'sinatra/config_file'
- require 'sinatra/json'
- require 'sinatra/reloader'
- require 'sinatra/sequel'
- config_file 'config.yml'
- enable :sessions
- set :agpl_src_url, 'https://git.douglasthrift.net/douglas/flickrlicense'
- set :database, 'sqlite://db.sqlite3'
- FlickRaw.api_key = settings.flickr_api_key
- FlickRaw.shared_secret = settings.flickr_shared_secret
- migration 'create users, licenses, and photos tables' do
- database.create_table :users do
- column :nsid, String, primary_key: true
- column :username, String
- column :fullname, String
- # column :json, 'text'
- # foreign_key :show_license_id, :licenses, null: true, on_delete: :set_null, on_update: :restrict
- # column :show_privacy, Integer, default: 0, null: false
- # column :show_ignored, 'boolean', default: true, null: false
- end
- database.create_table :licenses do
- column :id, Integer, primary_key: true
- column :name, String
- column :url, String
- end
- database.create_table :photos do
- column :id, Integer, primary_key: true
- # foreign_key :user_id, :users, on_delete: :cascade, on_update: :restrict
- foreign_key :owner, :users
- # foreign_key :license_id, :licenses, on_delete: :cascade, on_update: :restrict
- foreign_key :license, :licenses
- column :json, 'text'
- column :ignore, 'boolean'
- # column :public, 'boolean'
- # column :friend, 'boolean'
- # column :family, 'boolean'
- end
- end
- migration 'rename photo owner and license columns to user_id and license_id' do
- database.alter_table :photos do
- drop_foreign_key [:owner]
- rename_column :owner, :user_id
- add_foreign_key [:user_id], :users
- drop_foreign_key [:license]
- rename_column :license, :license_id
- add_foreign_key [:license_id], :licenses
- end
- end
- migration 'add on delete and update constraints to photo user_id and license_id' do
- database.alter_table :photos do
- drop_foreign_key [:user_id]
- add_foreign_key [:user_id], :users, on_delete: :cascade, on_update: :restrict
- drop_foreign_key [:license_id]
- add_foreign_key [:license_id], :licenses, on_delete: :cascade, on_update: :restrict
- end
- end
- migration 'add not null constraint to photo user_id and license_id' do
- database.alter_table :photos do
- set_column_not_null :user_id
- set_column_not_null :license_id
- end
- end
- migration 'add json field to user' do
- database.alter_table :users do
- add_column :json, 'text'
- end
- database.select(:nsid).from(:users).select_map(:nsid).each do |nsid|
- info = OpenStruct.new(flickr.people.getInfo(user_id: nsid).to_hash)
- info.timezone = info.timezone.to_hash
- info.photos = info.photos.to_hash
- database.from(:users).where(nsid: nsid).update(json: info.to_h.to_json)
- end
- end
- migration 'add show license/privacy/ignored fields to user' do
- database.alter_table :users do
- add_foreign_key :show_license_id, :licenses, null: true, on_delete: :set_null, on_update: :restrict
- add_column :show_privacy, Integer, default: 0, null: false
- add_column :show_ignored, 'boolean', default: true, null: false
- end
- end
- migration 'add public/friend/family fields to photos' do
- database.alter_table :photos do
- add_column :public, 'boolean'
- add_column :friend, 'boolean'
- add_column :family, 'boolean'
- end
- database.select(:id, :json).from(:photos).select_map([:id, :json]).each do |id, json|
- photo = OpenStruct.new(JSON.parse(json))
- database.from(:photos).where(id: id).update(public: photo.ispublic != 0, friend: photo.isfriend != 0, family: photo.isfamily != 0)
- end
- end
- class User < Sequel::Model
- plugin :enum
- one_to_many :photos
- many_to_one :show_license, class: :License
- enum :show_privacy, [:all, :public, :friends_family, :friends, :family, :private]
- unrestrict_primary_key
- def flickraw
- @flickraw ||= OpenStruct.new(JSON.parse(json))
- end
- def buddyicon
- if flickraw.iconserver.to_i > 0
- "https://farm#{flickraw.iconfarm}.staticflickr.com/#{flickraw.iconserver}/buddyicons/#{nsid}.jpg"
- else
- "https://www.flickr.com/images/buddyicon.gif"
- end
- end
- def photosurl
- flickraw.photosurl
- end
- end
- class License < Sequel::Model
- one_to_many :photos
- unrestrict_primary_key
- def icon
- case url
- when %r{/by-nc-sa/}
- '<i class="cc cc-cc"></i><i class="cc cc-by"></i><i class="cc cc-nc"></i><i class="cc cc-sa"></i>'
- when %r{/by-nc/}
- '<i class="cc cc-cc"></i><i class="cc cc-by"></i><i class="cc cc-nc"></i>'
- when %r{/by-nc-nd/}
- '<i class="cc cc-cc"></i><i class="cc cc-by"></i><i class="cc cc-nc"></i><i class="cc cc-nd"></i>'
- when %r{/by/}
- '<i class="cc cc-cc"></i><i class="cc cc-by"></i>'
- when %r{/by-sa/}
- '<i class="cc cc-cc"></i><i class="cc cc-by"></i><i class="cc cc-sa"></i>'
- when %r{/by-nd/}
- '<i class="cc cc-cc"></i><i class="cc cc-by"></i><i class="cc cc-nd"></i>'
- when %r{/commons/}, %r{/mark/}
- '<i class="cc cc-pd-alt"></i>'
- when %r{/zero/}
- '<i class="cc cc-zero"></i>'
- else
- '©'
- end
- end
- def icon_name
- icon + ' ' + name
- end
- def as_json(*)
- {
- id: id,
- name: name,
- url: url,
- icon: icon,
- iconname: icon_name,
- }
- end
- def to_json(*args)
- as_json.to_json(*args)
- end
- end
- class Photo < Sequel::Model
- many_to_one :user
- many_to_one :license
- unrestrict_primary_key
- def flickraw
- @flickraw ||= OpenStruct.new(JSON.parse(json))
- end
- def as_json(*)
- {
- id: id,
- license: license_id,
- ignore: ignore,
- img: FlickRaw.url_q(flickraw),
- url: FlickRaw.url_photopage(flickraw),
- title: flickraw.title,
- public: public,
- friend: friend,
- family: family,
- path: "/photos/#{id}",
- }
- end
- def to_json(*args)
- as_json.to_json(*args)
- end
- end
- helpers Flickr::Login::Helpers
- helpers do
- def flickr
- unless @flickr
- @flickr = FlickRaw::Flickr.new(api_key: settings.flickr_api_key, shared_secret: settings.flickr_shared_secret)
- @flickr.access_token, @flickr.access_secret = flickr_access_token
- end
- @flickr
- end
- end
- before do
- redirect to('/login?perms=write') unless flickr_user
- @user = User.find_or_create(nsid: flickr_user[:user_nsid]) do |user|
- user.username = flickr_user[:username]
- user.fullname = flickr_user[:fullname]
- info = OpenStruct.new(flickr.people.getInfo(user_id: user.nsid).to_hash)
- info.timezone = info.timezone.to_hash
- info.photos = info.photos.to_hash
- user.json = info.to_h.to_json
- user.show_license = nil
- user.show_privacy = :all
- user.show_ignored = true
- end
- end
- get '/' do
- @licenses = License.all
- if @licenses.count == 0
- flickr.photos.licenses.getInfo.each do |flickr_license|
- License.create do |license|
- license.id = flickr_license.id
- license.name = flickr_license.name
- license.url = flickr_license.url
- end
- end
- @licenses = License.all
- end
- @show_privacies = {
- all: 'show public and private photos',
- public: 'show only public photos',
- friends_family: 'show only photos visible to friends and family',
- friends: 'show only photos visible to only friends',
- family: 'show only photos visible to only family',
- private: 'show only completely private photos',
- }
- @show_ignoreds = {
- true => 'show ignored photos',
- false => 'hide ignored photos',
- }
- erb :index
- end
- get '/logout' do
- flickr_clear
- redirect to('/')
- end
- get %r{/photos/([1-8])} do |page|
- @user.photos_dataset.delete if params['reload'] == 'true'
- page, per_page = page.to_i, 500
- photos = @user.photos_dataset.reverse(:id).limit(per_page, (page - 1) * per_page).all
- begin
- photos = flickr.photos.search(user_id: :me, extras: 'license', per_page: per_page, page: page).map do |flickr_photo|
- Photo.create do |photo|
- photo.id = flickr_photo.id
- photo.user = @user
- photo.license_id = flickr_photo.license
- photo.json = flickr_photo.to_hash.to_json
- photo.ignore = false
- photo.public = flickr_photo.ispublic != 0
- photo.friend = flickr_photo.isfriend != 0
- photo.family = flickr_photo.isfamily != 0
- end
- end if photos.count == 0
- rescue FlickRaw::Error => e
- halt 422, json(error: e.message)
- rescue Sequel::UniqueConstraintViolation
- # sometimes the Flickr API will just keep repeating the same results for subsequent pages
- end
- halt json path: "/photos/#{page + 1}", photos: photos if photos.count == per_page && page < 8
- json photos: photos
- end
- post '/user' do
- halt 422, json(error: 'Missing required parameter(s)') unless %w(show_license show_privacy show_ignored).any? {|param| params[param]}
- show_license_id = params['show_license']
- if show_license_id
- if show_license_id.empty?
- show_license = nil
- else
- show_license_id = show_license_id.to_i
- show_license = License[show_license_id]
- halt 422, json(error: "Could not find license with ID: #{show_license_id.inspect}") unless show_license
- end
- @user.show_license = show_license
- @user.save
- end
- show_privacy = params['show_privacy']
- if show_privacy
- @user.show_privacy = show_privacy
- begin
- @user.save
- rescue Sequel::NotNullConstraintViolation
- halt 422, json(error: "Invalid privacy value: #{show_privacy.inspect}")
- end
- end
- show_ignored = params['show_ignored']
- if show_ignored
- @user.show_ignored = show_ignored == 'true'
- @user.save
- end
- status 204
- end
- post '/photos' do
- ignore = params['ignore'] == 'true'
- ids = params['photos'] && params['photos'].is_a?(Array) ? params['photos'] : [params['photo']]
- photos = @user.photos_dataset.where(id: ids)
- halt 404, json(error: "Could not find photo(s) with ID(s): #{ids.map(&:to_i) - photos.map(:id)}") unless photos.count == ids.size
- photos.update(ignore: ignore)
- status 204
- end
- post '/photos/*' do |id|
- photo = @user.photos_dataset.where(id: id).first
- halt 404, json(error: "Could not find photo with ID: #{id}") unless photo
- license_id = params['license'] && params['license'].to_i
- license = License[license_id]
- halt 422, json(error: "Could not find license with ID: #{license_id.inspect}") unless license
- case @user.show_privacy
- when :all
- # no need to check privacy
- when :public
- halt 422, json(error: 'Could not change license of non public photo') unless photo.public
- when :friends_family
- halt 422, json(error: 'Could not change license of non friends and family photo') unless photo.friend && photo.family
- when :friends
- halt 422, json(error: 'Could not change license of non only friends photo') unless photo.friend && !photo.family
- when :family
- halt 422, json(error: 'Could not change license of non only family photo') unless !photo.friend && photo.family
- when :private
- halt 422, json(error: 'Could not change license of non private photo') unless !photo.public && !photo.friend && !photo.family
- end
- halt 422, json(error: 'Could not change license of photo with unselected license') if @user.show_license && @user.show_license != photo.license
- halt 422, json(error: 'Could not change license of ignored photo') if photo.ignore
- begin
- flickr.photos.licenses.setLicense(photo_id: photo.id, license_id: license.id)
- rescue FlickRaw::Error => e
- halt 422, json(error: e.message)
- end
- photo.update(license: license)
- status 204
- end
|