piccle 0.1.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/NOTES.md +69 -0
- data/README.md +175 -0
- data/Rakefile +8 -0
- data/agpl-3.0.md +660 -0
- data/assets/css/default.css +397 -0
- data/assets/css/normalize.css +427 -0
- data/assets/icons/android-chrome-192x192.png +0 -0
- data/assets/icons/android-chrome-512x512.png +0 -0
- data/assets/icons/apple-touch-icon.png +0 -0
- data/assets/icons/favicon-16x16.png +0 -0
- data/assets/icons/favicon-32x32.png +0 -0
- data/assets/icons/favicon.ico +0 -0
- data/bin/console +14 -0
- data/bin/piccle +355 -0
- data/bin/setup +8 -0
- data/db/migrations/001_create_photos.rb +15 -0
- data/db/migrations/002_update_photos.rb +14 -0
- data/db/migrations/003_create_keywords_and_join_table.rb +14 -0
- data/db/migrations/004_add_focal_length.rb +7 -0
- data/db/migrations/005_create_locations.rb +20 -0
- data/js-renderer/handlebars.min-v4.7.6.js +29 -0
- data/js-renderer/renderer.js +93 -0
- data/lib/piccle.rb +52 -0
- data/lib/piccle/config.rb +136 -0
- data/lib/piccle/database.rb +33 -0
- data/lib/piccle/dstk_service.rb +64 -0
- data/lib/piccle/extractor.rb +128 -0
- data/lib/piccle/js_renderer.rb +37 -0
- data/lib/piccle/models/keyword.rb +6 -0
- data/lib/piccle/models/location.rb +11 -0
- data/lib/piccle/models/photo.rb +211 -0
- data/lib/piccle/parser.rb +230 -0
- data/lib/piccle/quilt_generator.rb +30 -0
- data/lib/piccle/renderer.rb +175 -0
- data/lib/piccle/streams.rb +2 -0
- data/lib/piccle/streams/base_stream.rb +56 -0
- data/lib/piccle/streams/camera_stream.rb +35 -0
- data/lib/piccle/streams/date_stream.rb +95 -0
- data/lib/piccle/streams/event_stream.rb +73 -0
- data/lib/piccle/streams/keyword_stream.rb +24 -0
- data/lib/piccle/streams/location_stream.rb +57 -0
- data/lib/piccle/template_helpers.rb +79 -0
- data/lib/piccle/version.rb +3 -0
- data/lib/tasks/development.rake +38 -0
- data/piccle.gemspec +43 -0
- data/templates/_breadcrumbs.handlebars.slim +16 -0
- data/templates/_footer.handlebars.slim +2 -0
- data/templates/_header.handlebars.slim +36 -0
- data/templates/_navigation.handlebars.slim +16 -0
- data/templates/_substream.handlebars.slim +17 -0
- data/templates/feed.atom.slim +29 -0
- data/templates/index.html.handlebars.slim +36 -0
- data/templates/show.html.handlebars.slim +64 -0
- metadata +340 -0
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "piccle"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/piccle
ADDED
@@ -0,0 +1,355 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'handlebars'
|
5
|
+
require 'json'
|
6
|
+
require 'slim'
|
7
|
+
require 'thor'
|
8
|
+
require 'piccle'
|
9
|
+
|
10
|
+
class CLI < Thor
|
11
|
+
default_task :generate
|
12
|
+
class_option :"image-dir", desc: "Input image directory. Defaults to $CWD/images.", aliases: "-i"
|
13
|
+
class_option :database, desc: "The location of the database to use. Defaults to $CWD/piccle_data.db.", aliases: "-d"
|
14
|
+
class_option :config, desc: "Config file to use. Defaults to $CWD/piccle.config.yaml, then ~/.piccle.config.yaml.", aliases: "-c"
|
15
|
+
class_option :debug, desc: "Enable debug mode.", type: :boolean, default: false
|
16
|
+
|
17
|
+
# Prints a banner describing Piccle. qv. https://github.com/erikhuda/thor/issues/612
|
18
|
+
def help(*)
|
19
|
+
print_wrapped <<~INTRO
|
20
|
+
Piccle #{Piccle::VERSION}: a static website generator for photographers.
|
21
|
+
|
22
|
+
Piccle reads the metadata from your photos and uses it to generate a gallery that lets people explore
|
23
|
+
your photos by date, keyword, location, etc. Run "piccle help generate" for more details, or see
|
24
|
+
usage instructions at https://piccle.alexpounds.com/.
|
25
|
+
INTRO
|
26
|
+
puts
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "generate", "Generates a web photo gallery based on image metadata."
|
31
|
+
option :"output-dir", desc: "Output directory. Defaults to $CWD/generated.", aliases: "-o"
|
32
|
+
option :events, desc: "The location of the events file to use, if any. Defaults to $CWD/events.yaml", aliases: "-e"
|
33
|
+
option :"author-name", desc: "Author name.", aliases: "-n"
|
34
|
+
option :url, desc: "The URL where you'll deploy your gallery, if any. Used to generate Atom feeds and OpenGraph tags.", aliases: "-u"
|
35
|
+
option :"ruby-renderer", desc: "Render templates with a Ruby codepath, rather than a JavaScript helper app. You don't need node in your path, but it is 10x slower.", type: :boolean, default: false
|
36
|
+
def generate
|
37
|
+
if options.key?("config") && !File.exist?(options["config"])
|
38
|
+
puts "Specified config file #{options["config"]} not found."
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
|
42
|
+
Piccle.config = piccle_config(options)
|
43
|
+
report_options
|
44
|
+
check_image_dir_exists
|
45
|
+
|
46
|
+
update_db
|
47
|
+
generate_everything
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "geocode", "Retrieve locations from photos, and convert lat/longs to named locations."
|
51
|
+
def geocode
|
52
|
+
Piccle.config = piccle_config(options)
|
53
|
+
report_image_and_config_options
|
54
|
+
check_image_dir_exists
|
55
|
+
|
56
|
+
Piccle::Photo.where(path: Piccle.config.images_dir).each do |photo|
|
57
|
+
if File.exist?(File.join(Piccle.config.images_dir, photo.file_name))
|
58
|
+
print "Updating location data for #{photo.file_name}... "
|
59
|
+
|
60
|
+
# Is this photo fully geocoded? That is, does it have lat/long/city/state/country?
|
61
|
+
if photo.geocoded?
|
62
|
+
puts " Already geocoded, no changes made."
|
63
|
+
save_location_data(photo)
|
64
|
+
|
65
|
+
# Does it have just a lat/long? Either retrieve a cached location record or look it up.
|
66
|
+
elsif photo.latitude && photo.longitude
|
67
|
+
puts "\n Photo has lat/long, looking for place data... "
|
68
|
+
dstk_service = Piccle::DstkService.new
|
69
|
+
if location = dstk_service.location_for(photo)
|
70
|
+
if photo.update(city: location.city, state: location.state, country: location.country)
|
71
|
+
puts " Done."
|
72
|
+
else
|
73
|
+
puts " Couldn't save data: #{photo.errors.inspect}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Maybe it's got city/state/country in the DB already.
|
78
|
+
else
|
79
|
+
places = [photo.city, photo.state, photo.country].compact
|
80
|
+
if places.any?
|
81
|
+
puts " Photo has metadata labels for #{places.join(", ")}, no changes made."
|
82
|
+
else
|
83
|
+
puts " No geo information in this photo's metadata, no changes made."
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
# Merge our supplied options with a couple of required details, and return a config object.
|
93
|
+
def piccle_config(options)
|
94
|
+
Piccle::Config.new(options.merge("working_directory" => Dir.pwd, "home_directory" => Dir.home))
|
95
|
+
end
|
96
|
+
|
97
|
+
# How are we going to generate this gallery - based on which options?
|
98
|
+
def report_options
|
99
|
+
report_image_and_config_options
|
100
|
+
option_message("Writing gallery to #{Piccle.config.output_dir}", "output-dir")
|
101
|
+
option_message("Photos will be credited to #{Piccle.config.author_name}", "author-name")
|
102
|
+
|
103
|
+
if Piccle.config.using_default?("events") && !File.exist?(Piccle.config.events_file)
|
104
|
+
puts "No events file found."
|
105
|
+
elsif File.exist?(Piccle.config.events_file)
|
106
|
+
option_message("Events read from #{Piccle.config.events_file}", "events")
|
107
|
+
else
|
108
|
+
option_message("⚠️ Events file #{Piccle.config.events_file}", "events")
|
109
|
+
end
|
110
|
+
|
111
|
+
puts "⚠️ Not generating an Atom feed, because URL is unset." unless Piccle.config.atom?
|
112
|
+
puts "⚠️ Not generating OpenGraph tags, because URL is unset." unless Piccle.config.open_graph?
|
113
|
+
puts ""
|
114
|
+
end
|
115
|
+
|
116
|
+
# Geocoding has fewer relevant options, so we can output this little summary instead.
|
117
|
+
def report_image_and_config_options
|
118
|
+
option_message("Reading images from #{Piccle.config.images_dir}", "image-dir")
|
119
|
+
|
120
|
+
if Piccle.config.database_exists?
|
121
|
+
option_message("Using existing database #{Piccle.config.database_file}", "database")
|
122
|
+
else
|
123
|
+
option_message("Creating new database #{Piccle.config.database_file}", "database")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Given a message and a parameter name, generate one of our standard report strings.
|
128
|
+
def option_message(message, config_param)
|
129
|
+
puts "#{message} (#{Piccle.config.source_for(config_param)})"
|
130
|
+
end
|
131
|
+
|
132
|
+
# Read all the images in the images directory, and load their data into the DB.
|
133
|
+
def update_db
|
134
|
+
Dir.glob(File.join(Piccle.config.images_dir, "**")).each do |filename|
|
135
|
+
print "Examining #{filename}..."
|
136
|
+
photo = Piccle::Photo.from_file(filename)
|
137
|
+
if photo.changed_hash?
|
138
|
+
print " updating..."
|
139
|
+
photo.update_from_file
|
140
|
+
puts " done."
|
141
|
+
elsif photo.freshly_created?
|
142
|
+
puts " created."
|
143
|
+
else
|
144
|
+
puts " done."
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Ensure we have some images to work with; if not, output an error and quit.
|
150
|
+
def check_image_dir_exists
|
151
|
+
unless File.exist?(Piccle.config.images_dir)
|
152
|
+
STDERR.puts "\n⚠️ The images directory, #{Piccle.config.images_dir}, does not exist. You can specify it using the -i option; run 'piccle help generate' for more info."
|
153
|
+
exit 1
|
154
|
+
end
|
155
|
+
if Dir.empty?(Piccle.config.images_dir)
|
156
|
+
STDERR.puts "\n⚠️ There are no images in #{Piccle.config.images_dir}, so we cannot continue."
|
157
|
+
exit 1
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Generates an entire site. Atom feeds, HTML templates, smaller images, JSON data, copied assets, the whole enchilada.
|
162
|
+
def generate_everything
|
163
|
+
start_time = Time.now
|
164
|
+
puts "Generating website..."
|
165
|
+
|
166
|
+
parser = new_parser_with_streams
|
167
|
+
parse_photos(parser)
|
168
|
+
renderer = if Piccle.config.ruby_renderer?
|
169
|
+
Piccle::Renderer.new(parser)
|
170
|
+
else
|
171
|
+
Piccle::JsRenderer.new(parser)
|
172
|
+
end
|
173
|
+
|
174
|
+
FileUtils.mkdir_p(Piccle.config.output_dir)
|
175
|
+
generate_templates(renderer)
|
176
|
+
generate_atom_feeds(parser, renderer)
|
177
|
+
generate_html_indexes(parser, renderer)
|
178
|
+
generate_html_photos(parser, renderer)
|
179
|
+
generate_json(parser, renderer)
|
180
|
+
generate_thumbnails
|
181
|
+
generate_quilts(parser)
|
182
|
+
copy_assets
|
183
|
+
puts "Website generated in #{(Time.now - start_time)} seconds."
|
184
|
+
end
|
185
|
+
|
186
|
+
# Get a parser, with streams (metadata filters and extractors) registered.
|
187
|
+
def new_parser_with_streams
|
188
|
+
Piccle::Parser.new.tap do |p|
|
189
|
+
p.add_stream(Piccle::Streams::DateStream)
|
190
|
+
p.add_stream(Piccle::Streams::LocationStream)
|
191
|
+
p.add_stream(Piccle::Streams::EventStream)
|
192
|
+
p.add_stream(Piccle::Streams::CameraStream)
|
193
|
+
p.add_stream(Piccle::Streams::KeywordStream)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Load all the photos, and parse them all.
|
198
|
+
def parse_photos(parser)
|
199
|
+
Piccle::Photo.where(path: Piccle.config.images_dir).each do |p|
|
200
|
+
parser.parse(p)
|
201
|
+
end
|
202
|
+
parser.load_events
|
203
|
+
parser.order
|
204
|
+
end
|
205
|
+
|
206
|
+
# Given a parser object, generate some HTML index pages from the data it contains.
|
207
|
+
def generate_html_indexes(parser, renderer)
|
208
|
+
puts " ... generating HTML indexes ..."
|
209
|
+
print " ... generating main index ... "
|
210
|
+
File.write(File.join(Piccle.config.output_dir, "index.html"), renderer.render_main_index)
|
211
|
+
puts "Done."
|
212
|
+
|
213
|
+
parser.subsections.each do |subsection|
|
214
|
+
if parser.subsection_photo_hashes(subsection).any?
|
215
|
+
subdir = File.join(Piccle.config.output_dir, *subsection)
|
216
|
+
print " ... generating #{subdir} index ... "
|
217
|
+
FileUtils.mkdir_p(subdir)
|
218
|
+
File.write(File.join(subdir, "index.html"), renderer.render_index(subsection))
|
219
|
+
puts "Done."
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Given a parser object, generate Atom feeds for everything, and all substreams.
|
225
|
+
def generate_atom_feeds(parser, renderer)
|
226
|
+
if Piccle.config.atom?
|
227
|
+
puts " ... generating Atom feeds ..."
|
228
|
+
print " ... generating main Atom feed ... "
|
229
|
+
File.write(File.join(Piccle.config.output_dir, "feed.atom"), renderer.render_feed)
|
230
|
+
puts "Done."
|
231
|
+
|
232
|
+
parser.subsections.each do |subsection|
|
233
|
+
if parser.subsection_photo_hashes(subsection).any?
|
234
|
+
subdir = File.join(Piccle.config.output_dir, *subsection)
|
235
|
+
print " ... generating #{subdir} feed ... "
|
236
|
+
FileUtils.mkdir_p(subdir)
|
237
|
+
File.write(File.join(subdir, "feed.atom"), renderer.render_feed(subsection))
|
238
|
+
puts "Done."
|
239
|
+
end
|
240
|
+
end
|
241
|
+
else
|
242
|
+
puts " Not generating Atom feeds, because no home URL is set."
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Given a parser object, generate photo pages from the data it contains.
|
247
|
+
def generate_html_photos(parser, renderer)
|
248
|
+
puts " ... generating HTML photo pages ..."
|
249
|
+
parser.photo_hashes.each do |hash|
|
250
|
+
print " ... generating canonical page for #{hash}... "
|
251
|
+
File.write(File.join(Piccle.config.output_dir, "#{hash}.html"), renderer.render_photo(hash))
|
252
|
+
puts "Done."
|
253
|
+
|
254
|
+
parser.links_for(hash).each do |selector|
|
255
|
+
destination_page = File.join(Piccle.config.output_dir, *selector, "#{hash}.html")
|
256
|
+
print " ... generating stream page #{destination_page}..."
|
257
|
+
File.write(destination_page, renderer.render_photo(hash, selector))
|
258
|
+
puts "Done."
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def generate_json(parser, _renderer)
|
264
|
+
puts " ... generating JSON files..."
|
265
|
+
FileUtils.mkdir_p(File.join(Piccle.config.output_dir, "json"))
|
266
|
+
File.write(File.join(Piccle.config.output_dir, "json", "all.json"), parser.data.to_json)
|
267
|
+
end
|
268
|
+
|
269
|
+
|
270
|
+
# Stubby, hacky method that demos generating thumbnails.
|
271
|
+
def generate_thumbnails
|
272
|
+
puts " ... generating thumbnails..."
|
273
|
+
FileUtils.mkdir_p(File.join(Piccle.config.output_dir, "images", "thumbnails"))
|
274
|
+
FileUtils.mkdir_p(File.join(Piccle.config.output_dir, "images", "photos"))
|
275
|
+
|
276
|
+
Piccle::Photo.where(path: Piccle.config.images_dir).each do |photo|
|
277
|
+
print " ... generating #{photo.thumbnail_path}... "
|
278
|
+
if photo.thumbnail_exists?
|
279
|
+
puts "Already exists, skipping."
|
280
|
+
else
|
281
|
+
photo.generate_thumbnail!
|
282
|
+
puts "Done."
|
283
|
+
end
|
284
|
+
|
285
|
+
print " ... generating #{photo.full_image_path}... "
|
286
|
+
if photo.full_image_exists?
|
287
|
+
puts "Already exists, skipping."
|
288
|
+
else
|
289
|
+
photo.generate_full_image!
|
290
|
+
puts "Done."
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# Generates "quilts" - stitched together images for each section, which we use in OpenGraph tags.
|
296
|
+
def generate_quilts(parser)
|
297
|
+
puts "Generating gallery quilts (preview images for sharing galleries on social media)..."
|
298
|
+
if Piccle.config.open_graph?
|
299
|
+
thumbnail_path_proc = Proc.new { |k, v| File.join(Piccle.config.output_dir, "images", "thumbnails", "#{v[:hash]}.#{v[:file_name]}") }
|
300
|
+
|
301
|
+
print " ... Creating main index quilt..."
|
302
|
+
main_thumbnails = parser.data[:photos].first(9).map(&thumbnail_path_proc)
|
303
|
+
main_quilt = Piccle::QuiltGenerator.generate_for(main_thumbnails)
|
304
|
+
main_quilt.write(File.join(Piccle.config.output_dir, "quilt.jpg"))
|
305
|
+
puts " Done."
|
306
|
+
|
307
|
+
parser.subsections.each do |subsection|
|
308
|
+
thumbnails = parser.subsection_photos(subsection).map(&thumbnail_path_proc)
|
309
|
+
if thumbnails.any?
|
310
|
+
output_path = File.join(Piccle.config.output_dir, *subsection, "quilt.jpg")
|
311
|
+
print " ... Creating gallery quilt #{output_path}..."
|
312
|
+
quilt = Piccle::QuiltGenerator.generate_for(thumbnails.first(9))
|
313
|
+
quilt.write(output_path)
|
314
|
+
puts " Done."
|
315
|
+
end
|
316
|
+
end
|
317
|
+
else
|
318
|
+
puts " Not generating gallery quilt images, because no home URL is set."
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def generate_templates(_renderer)
|
323
|
+
puts " ... generating templates..."
|
324
|
+
FileUtils.mkdir_p(File.join(Piccle.config.output_dir, "js"))
|
325
|
+
File.write(File.join(Piccle.config.output_dir, "js", "index.handlebars"), Piccle::TemplateHelpers.compile_template("index"))
|
326
|
+
File.write(File.join(Piccle.config.output_dir, "js", "show.handlebars"), Piccle::TemplateHelpers.compile_template("show"))
|
327
|
+
end
|
328
|
+
|
329
|
+
# Copy our static assets into the expected location.
|
330
|
+
def copy_assets
|
331
|
+
puts " ... copying static assets..."
|
332
|
+
puts " ... copying CSS..."
|
333
|
+
copy_asset_type("css")
|
334
|
+
puts " ... copying icons..."
|
335
|
+
copy_asset_type("icons")
|
336
|
+
end
|
337
|
+
|
338
|
+
def copy_asset_type(type)
|
339
|
+
FileUtils.mkdir_p(File.join(Piccle.config.output_dir, type))
|
340
|
+
Dir.glob("#{Piccle.config.gem_root_join("assets", type)}/**").each do |f|
|
341
|
+
FileUtils.cp(f, File.join(Piccle.config.output_dir, type, File.basename(f)))
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
# Take geo information from a photo, and save it to our Piccle database.
|
346
|
+
def save_location_data(photo)
|
347
|
+
unless Piccle::Location.find(latitude: photo.latitude, longitude: photo.longitude)
|
348
|
+
Piccle::Location.create(latitude: photo.latitude, longitude: photo.longitude, city: photo.city,
|
349
|
+
state: photo.state, country: photo.country)
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
end
|
354
|
+
|
355
|
+
CLI.start(ARGV)
|
data/bin/setup
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
change do
|
3
|
+
create_table(:photos) do
|
4
|
+
primary_key :id
|
5
|
+
String :file_name, null: false, text: true
|
6
|
+
String :path, null: false, text: true
|
7
|
+
String :md5, null: false
|
8
|
+
Integer :width, null: false
|
9
|
+
Integer :height, null: false
|
10
|
+
String :camera_name, null: false, text: true
|
11
|
+
DateTime :taken_at
|
12
|
+
DateTime :created_at
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
change do
|
3
|
+
alter_table(:photos) do
|
4
|
+
add_column :title, String, text: true
|
5
|
+
add_column :description, String, text: true
|
6
|
+
add_column :aperture, Float
|
7
|
+
add_column :shutter_speed_numerator, Integer
|
8
|
+
add_column :shutter_speed_denominator, Integer
|
9
|
+
add_column :iso, Integer
|
10
|
+
add_column :latitude, Float
|
11
|
+
add_column :longitude, Float
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
change do
|
3
|
+
create_table(:keywords) do
|
4
|
+
primary_key :id
|
5
|
+
String :name, null: false, unique: true
|
6
|
+
end
|
7
|
+
|
8
|
+
create_table(:keywords_photos) do
|
9
|
+
Integer :photo_id, null: false
|
10
|
+
Integer :keyword_id, null: false
|
11
|
+
primary_key [:photo_id, :keyword_id], name: :keywords_photos_pk
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
Sequel.migration do
|
2
|
+
change do
|
3
|
+
create_table(:locations) do
|
4
|
+
primary_key :id
|
5
|
+
Float :latitude, null: false
|
6
|
+
Float :longitude, null: false
|
7
|
+
String :city, text: true
|
8
|
+
String :state, text: true
|
9
|
+
String :country, text: true
|
10
|
+
DateTime :created_at
|
11
|
+
check { (city !~ nil) | (state !~ nil) | (country !~ nil) }
|
12
|
+
end
|
13
|
+
|
14
|
+
alter_table(:photos) do
|
15
|
+
add_column :city, String, text: true
|
16
|
+
add_column :state, String, text: true
|
17
|
+
add_column :country, String, text: true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|