rails_app_generator 0.2.43 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d9ac313ea8fc5383a7d3d37ed9495fff8509f9b7dd1ff373ed97c80ce69ae23
4
- data.tar.gz: bb65bdcc19d95bfb69abf92458dbe0eec353a8b3085ba8e679d72955ab55f425
3
+ metadata.gz: 93b684c48a45efa3c3ed644b2908e1299bd0a26e58ae14e3483dd007c2128360
4
+ data.tar.gz: 7b263e75514434f35912bfb867674506839c0a4883bdb94d1427dbe899a7c396
5
5
  SHA512:
6
- metadata.gz: 6f0ccd26ef5090325cbb6a76408fd201bbce81d8bbd739618e14393eebe0cc6814bb9e24eecdfec7dd7fd07664fe9e52ed1e47d92343461b45ab3378d37e22f7
7
- data.tar.gz: 70dee6a466c8976ba4f385d532b34325d188a83599df9d4fcd4330b6805f9f0dcc580141fc4ad5e9d1b9a393c4b2084698f7a2771254b7839a18a4f98471765c
6
+ metadata.gz: 2cc5173515c7d24338097773dcfd3a632e09cd71b9cfbbf2b991cad560d631417f1dde2c1a9cb35dc6accf9c7d1fe5e779a76da5b1bb40cce9f208036b4f6353
7
+ data.tar.gz: 2eadb9067e14ab8812186a03b49b5a04287b69efd6ecedbc5859ad6c5458cbfe4373fa9e74e7445316a77009570a1e1b8bc73eddb1f272245228a8cdd9b08d6c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## [0.2.43](https://github.com/klueless-io/rails_app_generator/compare/v0.2.42...v0.2.43) (2022-08-31)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * add scenic profile ([dc309e6](https://github.com/klueless-io/rails_app_generator/commit/dc309e6f399cec7085de3133aa32989bc7ff7ed4))
7
+ * add scenic profile ([44614f7](https://github.com/klueless-io/rails_app_generator/commit/44614f70d73fe41cee5fd9cb9af5d20f0aa4f915))
8
+
1
9
  ## [0.2.42](https://github.com/klueless-io/rails_app_generator/compare/v0.2.41...v0.2.42) (2022-08-30)
2
10
 
3
11
 
@@ -44,6 +44,10 @@ def scaffolds
44
44
  # add_scaffold('db_schema_foreign_key', 'left', 'right', 'name', 'on_update', 'on_delete', 'column', 'db_schema_table:references')
45
45
  # add_scaffold('db_schema_index', 'name', 'fields', 'using', 'order:jsonb', 'where', 'unique', 'db_schema_table:references')
46
46
  # add_scaffold('db_schema_view', 'name', 'materialized:boolean', 'sql_definition', 'db_schema_table:references')
47
+
48
+ generate('scenic:model rubocop_log --materialized')
49
+
50
+ directory "db/views"
47
51
  end
48
52
 
49
53
  def setup_customizations
@@ -51,13 +55,14 @@ def setup_customizations
51
55
 
52
56
  force_copy
53
57
 
54
- add_controller('home', 'index', 'quick_signin', 'reseed')
58
+ add_controller('home', 'index', 'quick_signin', 'reseed', 'refresh_material_view')
55
59
 
56
60
  directory "app/controllers"
57
61
  directory "app/models"
58
62
  directory "app/views"
59
63
  template 'app/views/layouts/application.html.erb', 'app/views/layouts/application.html.erb'
60
64
  directory "app/queries"
65
+ directory "app/services"
61
66
  end
62
67
 
63
68
  def setup_avo
@@ -0,0 +1,14 @@
1
+ class AmountRaised < Avo::Dashboards::MetricCard
2
+ self.id = "amount_raised"
3
+ self.label = "Amount raised"
4
+ # self.description = "Some description"
5
+ # self.cols = 1
6
+ # self.initial_range = 30
7
+ # self.ranges = [7, 30, 60, 365, "TODAY", "MTD", "QTD", "YTD", "ALL"]
8
+ self.prefix = "$"
9
+ # self.suffix = ""
10
+
11
+ query do
12
+ result 9001
13
+ end
14
+ end
@@ -0,0 +1,7 @@
1
+ class ExampleCustomPartial < Avo::Dashboards::PartialCard
2
+ self.id = "users_custom_card"
3
+ self.cols = 3
4
+ self.rows = 4
5
+ self.partial = "avo/cards/custom_card"
6
+ self.description = "This card has been loaded from a custom partial."
7
+ end
@@ -0,0 +1,38 @@
1
+ class ExampleMetric < Avo::Dashboards::MetricCard
2
+ self.id = "users_metric"
3
+ self.label = "Users count"
4
+ self.description = "Users description"
5
+ self.cols = 1
6
+ self.initial_range = 30
7
+ self.ranges = [7, 30, 60, 365, "TODAY", "MTD", "QTD", "YTD", "ALL"]
8
+ # self.prefix = "$"
9
+ # self.suffix = "%"
10
+ self.refresh_every = 10.minutes
11
+
12
+ # You have access to context, params, range, current dashboard, and current card
13
+ query do
14
+ from = Date.today.midnight - 1.week
15
+ to = DateTime.current
16
+
17
+ if range.present?
18
+ if range.to_s == range.to_i.to_s
19
+ from = DateTime.current - range.to_i.days
20
+ else
21
+ case range
22
+ when "TODAY"
23
+ from = DateTime.current.beginning_of_day
24
+ when "MTD"
25
+ from = DateTime.current.beginning_of_month
26
+ when "QTD"
27
+ from = DateTime.current.beginning_of_quarter
28
+ when "YTD"
29
+ from = DateTime.current.beginning_of_year
30
+ when "ALL"
31
+ from = Time.at(0)
32
+ end
33
+ end
34
+ end
35
+
36
+ result User.where(created_at: from..to).count
37
+ end
38
+ end
@@ -0,0 +1,10 @@
1
+ class PercentDone < Avo::Dashboards::MetricCard
2
+ self.id = "percent_done"
3
+ self.label = "Percent done"
4
+ self.description = "This is the progress we made so far..."
5
+ self.suffix = "%"
6
+
7
+ query do
8
+ result 42
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ class RubocopCard < Avo::Dashboards::PartialCard
2
+ self.id = "percent_done"
3
+ self.label = "Percent done"
4
+ self.description = "This is the progress we made so far..."
5
+
6
+ self.cols = 3
7
+ self.rows = 20
8
+ self.partial = "avo/cards/rubocop_card"
9
+
10
+ end
@@ -9,4 +9,12 @@ class RubocopResource < Avo::BaseResource
9
9
  field :id, as: :id
10
10
  # field :data, as: :textarea, only_on: [:edit]
11
11
  # field :user_name, as: :text
12
+
13
+ tabs do
14
+ tab "List of Cops" do
15
+ panel do
16
+ field :id, as: :id
17
+ end
18
+ end
19
+ end
12
20
  end
@@ -7,6 +7,14 @@ class HomeController < ApplicationController
7
7
  end
8
8
 
9
9
  def reseed
10
- SeedService.seed
10
+ SeedService.seed(variant: :refresh)
11
+
12
+ redirect_back_or_to root_path
13
+ end
14
+
15
+ def refresh_material_view
16
+ RubocopLog.refresh
17
+
18
+ redirect_back_or_to root_path
11
19
  end
12
20
  end
@@ -1,7 +1,53 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class SeedService
4
+ PRINTSPEAK_DATA_PATH = "/Users/davidcruwys/dev/printspeak/printspeak-generator/.builders/.data/"
5
+
2
6
  class << self
3
- def seed
4
- puts 'sssssssssssssssssssssss'
7
+ def seed(variant: :reset)
8
+ service = SeedService.new
9
+ service.call(variant: variant)
5
10
  end
6
11
  end
7
- end
12
+
13
+ def call(variant: :reset)
14
+ reset if variant == :reset
15
+ refresh if variant == :refresh
16
+ create
17
+ end
18
+
19
+ private
20
+
21
+ def get_data(filename)
22
+ json = File.read(File.join(PRINTSPEAK_DATA_PATH, filename))
23
+ JSON.parse(json)
24
+ end
25
+
26
+ def reset
27
+ User.delete_all
28
+ RailsApp.delete_all
29
+ Rubocop.delete_all
30
+ TableCount.delete_all
31
+ end
32
+
33
+ def refresh
34
+ end
35
+
36
+ def create
37
+ refresh_printspeak
38
+
39
+ RubocopLog.refresh
40
+ end
41
+
42
+ def refresh_printspeak
43
+ david = User.create_with(name: 'david', password: 'password').find_or_create_by(email: 'david@site.com')
44
+
45
+ rails_app = RailsApp.create_with(user: david).find_or_create_by(name: 'Printspeak')
46
+
47
+ puts 'Create Rubocop Data'
48
+ Rubocop.create(rails_app: rails_app, data: get_data('rubocop.json'))
49
+
50
+ puts 'Create Table Count Data'
51
+ TableCount.create(rails_app: rails_app, data: get_data('sql_count.json'))
52
+ end
53
+ end
@@ -1,4 +1,10 @@
1
- <%= link_to 'Home', root_path %>
2
- <%= link_to 'Quick Sign In', home_quick_signin_path %> |
3
- <%= link_to 'Admin', avo_path %>
1
+ <%= link_to 'Home', root_path %>
2
+ | <%= link_to 'Re-Seed', home_reseed_path, style: 'color: blue; font-weight: 600;' %>
3
+ | <%= link_to 'Refresh Material View', home_refresh_material_view_path, style: 'color: blue; font-weight: 600;' %>
4
+ | <%= link_to 'Quick Sign In', home_quick_signin_path %>
5
+ | <%= link_to 'Admin', avo_path %>
6
+ <hr />
7
+
8
+ <%= link_to 'Visits (View)', home_visitors_by_monument_path %>
9
+ | <%= link_to 'Visits (Materialized)', home_individual_visitors_by_monument_path %>
4
10
  <hr />
@@ -1,18 +1 @@
1
- @data_path = "/Users/davidcruwys/dev/printspeak/printspeak-generator/.builders/.data/"
2
-
3
- david = User.create_with(name: 'david', password: 'password').find_or_create_by(email: 'david@site.com')
4
-
5
- def get_data(filename)
6
- json = File.read(File.join(@data_path, filename))
7
- JSON.parse(json)
8
- end
9
-
10
- rails_app = RailsApp.create(name: 'Printspeak', user: david)
11
-
12
- puts 'Create Rubocop Data'
13
- Rubocop.create(rails_app: rails_app, data: get_data('rubocop.json'))
14
-
15
- puts 'Create Table Count Data'
16
- TableCount.create(rails_app: rails_app, data: get_data('sql_count.json'))
17
-
18
-
1
+ SeedService.seed
@@ -0,0 +1,23 @@
1
+ WITH
2
+
3
+ files AS (
4
+ SELECT jsonb_array_elements(data->'files') FROM rubocops
5
+ ),
6
+ lines AS (
7
+ SELECT
8
+ jsonb_array_elements(files)->>'file' as file,
9
+ jsonb_array_elements(files)->'lines' as lines
10
+ FROM files
11
+ ),
12
+ rubocop_log AS (
13
+ SELECT
14
+ jsonb_array_elements(lines)->>'position' as position,
15
+ jsonb_array_elements(lines)->>'status' as status,
16
+ jsonb_array_elements(lines)->>'status_message' as status_message,
17
+ jsonb_array_elements(lines)->>'cop' as cop,
18
+ jsonb_array_elements(lines)->>'message' as message,
19
+ jsonb_array_elements(lines)->>'full_line' as full_line,
20
+ jsonb_array_elements(lines)->>'file_name' as file_name
21
+ FROM lines
22
+ )
23
+ select * from rubocop_log
@@ -4,7 +4,7 @@
4
4
  "skip_collision_check": false,
5
5
  "ruby": "/Users/davidcruwys/.asdf/installs/ruby/3.1.1/bin/ruby",
6
6
  "database": "postgresql",
7
- "skip_git": true,
7
+ "skip_git": false,
8
8
  "skip_keeps": false,
9
9
  "skip_action_mailer": false,
10
10
  "skip_action_mailbox": false,
@@ -32,18 +32,18 @@
32
32
  "test": "rspec",
33
33
  "add_acts_as_list": false,
34
34
  "add_administrate": false,
35
- "add_annotate": false,
36
- "add_avo": false,
35
+ "add_annotate": true,
36
+ "add_avo": true,
37
37
  "add_bcrypt": false,
38
- "add_brakeman": false,
38
+ "add_brakeman": true,
39
39
  "add_browser": false,
40
- "add_bundler_audit": false,
40
+ "add_bundler_audit": true,
41
41
  "add_chartkick": false,
42
- "add_devise": false,
42
+ "add_devise": true,
43
43
  "add_devise_masquerade": false,
44
- "add_dotenv": false,
45
- "add_factory_bot_rails": false,
46
- "add_faker": false,
44
+ "add_dotenv": true,
45
+ "add_factory_bot_rails": true,
46
+ "add_faker": true,
47
47
  "add_friendly_id": false,
48
48
  "add_groupdate": false,
49
49
  "add_hexapdf": false,
@@ -52,7 +52,7 @@
52
52
  "add_image_processing": false,
53
53
  "add_kaminari": false,
54
54
  "add_lograge": false,
55
- "add_minimal_css": true,
55
+ "add_minimal_css": false,
56
56
  "minimal_css_library": "water.css",
57
57
  "add_mini_magick": false,
58
58
  "add_motor_admin": false,
@@ -64,8 +64,9 @@
64
64
  "add_ransack": false,
65
65
  "add_redcarpet": false,
66
66
  "add_rolify": false,
67
- "add_rubocop": false,
67
+ "add_rubocop": true,
68
68
  "add_twilio_ruby": false,
69
- "template": "/Users/davidcruwys/dev/kgems/rails_app_generator/after_templates/addons/scenic/_.rb"
69
+ "template": "/Users/davidcruwys/dev/kgems/rails_app_generator/after_templates/application/klueless/_.rb",
70
+ "css": "tailwind"
70
71
  }
71
72
  }
@@ -7,9 +7,9 @@
7
7
  "quiet": false,
8
8
  "skip": false,
9
9
  "ruby": "/Users/davidcruwys/.asdf/installs/ruby/3.1.1/bin/ruby",
10
- "template": "/Users/davidcruwys/dev/kgems/rails_app_generator/after_templates/addons/scenic/_.rb",
10
+ "template": "/Users/davidcruwys/dev/kgems/rails_app_generator/after_templates/application/klueless/_.rb",
11
11
  "database": "postgresql",
12
- "skip_git": true,
12
+ "skip_git": false,
13
13
  "skip_keeps": false,
14
14
  "skip_action_mailer": false,
15
15
  "skip_action_mailbox": false,
@@ -36,24 +36,24 @@
36
36
  "api": false,
37
37
  "minimal": false,
38
38
  "javascript": "importmap",
39
- "css": "",
39
+ "css": "tailwind",
40
40
  "skip_bundle": false,
41
41
  "note": "",
42
42
  "test": "rspec",
43
43
  "add_acts_as_list": false,
44
44
  "add_administrate": false,
45
- "add_annotate": false,
46
- "add_avo": false,
45
+ "add_annotate": true,
46
+ "add_avo": true,
47
47
  "add_bcrypt": false,
48
- "add_brakeman": false,
48
+ "add_brakeman": true,
49
49
  "add_browser": false,
50
- "add_bundler_audit": false,
50
+ "add_bundler_audit": true,
51
51
  "add_chartkick": false,
52
- "add_devise": false,
52
+ "add_devise": true,
53
53
  "add_devise_masquerade": false,
54
- "add_dotenv": false,
55
- "add_factory_bot_rails": false,
56
- "add_faker": false,
54
+ "add_dotenv": true,
55
+ "add_factory_bot_rails": true,
56
+ "add_faker": true,
57
57
  "add_friendly_id": false,
58
58
  "add_groupdate": false,
59
59
  "add_hexapdf": false,
@@ -62,7 +62,7 @@
62
62
  "add_image_processing": false,
63
63
  "add_kaminari": false,
64
64
  "add_lograge": false,
65
- "add_minimal_css": true,
65
+ "add_minimal_css": false,
66
66
  "minimal_css_library": "water.css",
67
67
  "add_mini_magick": false,
68
68
  "add_motor_admin": false,
@@ -74,7 +74,7 @@
74
74
  "add_ransack": false,
75
75
  "add_redcarpet": false,
76
76
  "add_rolify": false,
77
- "add_rubocop": false,
77
+ "add_rubocop": true,
78
78
  "add_twilio_ruby": false
79
79
  }
80
80
  }
@@ -29,9 +29,7 @@ module RailsAppGenerator
29
29
  RailsAppGenerator::Util.write_last_run('rails_options_data.json', opts.to_h)
30
30
 
31
31
  starter = RailsAppGenerator::Starter.new(**args)
32
-
33
- starter.delete_target_folder
34
- starter.start(opts)
32
+ starter.start(opts) if starter.handle_target_folder_existence?
35
33
  end
36
34
  # rubocop:enable Metrics/AbcSize
37
35
  end
@@ -28,17 +28,22 @@ module RailsAppGenerator
28
28
  # points to templates related to rails addons
29
29
  attr_reader :addon_template_path
30
30
 
31
+ attr_reader :target_folder_exist_action # [abort destroy keep_git overwrite]
32
+
31
33
  attr_reader :capture_output
32
34
  attr_reader :console_output
33
35
 
36
+ # rubocop:disable Metrics/CyclomaticComplexity
34
37
  def initialize(**args)
35
- @app_path = args[:app_path] || '.'
36
- @destination_root = args[:destination_root] || Dir.pwd
37
- @rails_template_path = args[:rails_template_path] || AppGenerator.rails_template_path
38
- @override_template_path = args[:override_template_path] || AppGenerator.override_template_path
39
- @addon_template_path = args[:addon_template_path] || AppGenerator.addon_template_path
40
- @capture_output = args[:capture_output].nil? ? false : args[:capture_output]
38
+ @app_path = args[:app_path] || '.'
39
+ @destination_root = args[:destination_root] || Dir.pwd
40
+ @rails_template_path = args[:rails_template_path] || AppGenerator.rails_template_path
41
+ @override_template_path = args[:override_template_path] || AppGenerator.override_template_path
42
+ @addon_template_path = args[:addon_template_path] || AppGenerator.addon_template_path
43
+ @target_folder_exist_action = args[:target_folder_exist_action] || 'abort'
44
+ @capture_output = args[:capture_output].nil? ? false : args[:capture_output]
41
45
  end
46
+ # rubocop:enable Metrics/CyclomaticComplexity
42
47
 
43
48
  def target_path
44
49
  File.expand_path(File.join(destination_root, app_path))
@@ -58,12 +63,45 @@ module RailsAppGenerator
58
63
  end
59
64
  end
60
65
 
61
- def delete_target_folder
62
- FileUtils.rm_rf(target_path)
66
+ def handle_target_folder_found?
67
+ return true unless File.directory?(target_path)
68
+
69
+ case target_folder_exist_action
70
+ when 'abort'
71
+ puts "Target folder [#{target_path}] already exists. Aborting"
72
+ false
73
+ when 'destroy'
74
+ puts "Target folder [#{target_path}] already exists. Destroying it"
75
+ FileUtils.rm_rf(target_path)
76
+ true
77
+ when 'keep_git'
78
+ puts "Target folder [#{target_path}] already exists. Wiping it but keeping git history"
79
+ clean_target_folder
80
+ true
81
+ when 'overwrite'
82
+ puts "Target folder [#{target_path}] already exists. Overwriting it"
83
+ true
84
+ else
85
+ raise "Invalid target_folder_exist_action: #{target_folder_exist_action}"
86
+ end
63
87
  end
64
88
 
65
89
  private
66
90
 
91
+ def clean_target_folder
92
+ Dir.entries(target_path).each do |entry|
93
+ entry_path = File.join(target_path, entry)
94
+
95
+ next if ['.', '..', '.git'].include?(entry)
96
+
97
+ if File.file?(entry_path)
98
+ File.delete(entry_path)
99
+ elsif File.directory?(entry_path)
100
+ FileUtils.rm_rf(entry_path)
101
+ end
102
+ end
103
+ end
104
+
67
105
  # Rails options returns a flat array of options
68
106
  #
69
107
  # It can work accept a flat array of options or a
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsAppGenerator
4
- VERSION = '0.2.43'
4
+ VERSION = '0.3.0'
5
5
  end
data/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "rails_app_generator",
3
- "version": "0.2.43",
3
+ "version": "0.3.0",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "rails_app_generator",
9
- "version": "0.2.43",
9
+ "version": "0.3.0",
10
10
  "dependencies": {
11
11
  "daisyui": "^2.20.0"
12
12
  },
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rails_app_generator",
3
- "version": "0.2.43",
3
+ "version": "0.3.0",
4
4
  "description": "Create new Rails Application with custom opinions",
5
5
  "scripts": {
6
6
  "release": "semantic-release"
@@ -2,7 +2,8 @@
2
2
  "args": {
3
3
  "app_path": "klueless",
4
4
  "destination_root": "/Users/davidcruwys/dev/kweb",
5
- "note": "add sidekiq to handle the data import tasks, add impersonate"
5
+ "note": "add sidekiq to handle the data import tasks, add impersonate",
6
+ "target_folder_exists": "keep_git"
6
7
  },
7
8
  "opts": {
8
9
  "skip_test": true,
@@ -18,6 +19,7 @@
18
19
  "add_factory_bot_rails": true,
19
20
  "add_faker": true,
20
21
  "add_ransack": false,
21
- "add_rubocop": true
22
+ "add_rubocop": true,
23
+ "add_scenic": true
22
24
  }
23
25
  }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_app_generator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.43
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Cruwys
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-31 00:00:00.000000000 Z
11
+ date: 2022-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bootsnap
@@ -458,6 +458,11 @@ files:
458
458
  - after_templates/addons/twilio_ruby/app/views/home/index.html.erb
459
459
  - after_templates/addons/twilio_ruby/config/initializers/twilio.rb
460
460
  - after_templates/application/klueless/_.rb
461
+ - after_templates/application/klueless/app/avo/cards/amount_raised.rb
462
+ - after_templates/application/klueless/app/avo/cards/example_custom_partial.rb
463
+ - after_templates/application/klueless/app/avo/cards/example_metric.rb
464
+ - after_templates/application/klueless/app/avo/cards/percent_done.rb
465
+ - after_templates/application/klueless/app/avo/cards/rubocop_card.rb
461
466
  - after_templates/application/klueless/app/avo/dashboards/dashboard.rb
462
467
  - after_templates/application/klueless/app/avo/resources/db_schema_resource.rb
463
468
  - after_templates/application/klueless/app/avo/resources/rails_app_resource.rb
@@ -481,6 +486,7 @@ files:
481
486
  - after_templates/application/klueless/config/initializers/avo.rb
482
487
  - after_templates/application/klueless/config/locales/en.yml
483
488
  - after_templates/application/klueless/db/seeds.rb
489
+ - after_templates/application/klueless/db/views/rubocop_logs_v01.sql
484
490
  - after_templates/application/printspeak/_.rb
485
491
  - after_templates/application/printspeak/app/assets/images/.keep
486
492
  - after_templates/application/printspeak/app/assets/images/about/1.jpg