medium_to_webflow 0.1.0 → 0.2.0.beta1
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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 064aff3e131593838b26d0592e549d504afaa66cc7b053b35e5befedf7d60f73
|
4
|
+
data.tar.gz: e0b5c19118e1746b5f68b8b5a3100c656bca29f6023e13948d57a856b11d2662
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0263e5d23b9da36898d14fcd246c8eb85262997d2c623ac72de52a33a59730c602451eaecd7e195b8820ca2081624ee0ee4c4ef59ea7c36db9774687bd3a0ec8
|
7
|
+
data.tar.gz: ca5ab0e0eda799500c9b151a4818c6768898ef92ace248dfe4d00f776b80dd47277d8597208bc3f10514af59c4d1074482a47f7bd9f4873a9f1e9fbb6dbf57b5
|
@@ -8,8 +8,7 @@ module MediumToWebflow
|
|
8
8
|
|
9
9
|
def initialize(medium_username:, webflow_api_token:, webflow_collection_id:, field_mappings:)
|
10
10
|
@medium_username = medium_username
|
11
|
-
@
|
12
|
-
@webflow_collection_id = webflow_collection_id
|
11
|
+
@webflow_adapter = Webflow::Adapter.new(webflow_api_token, webflow_collection_id)
|
13
12
|
@field_mappings = field_mappings
|
14
13
|
@logger = MediumToWebflow.configuration.logger
|
15
14
|
end
|
@@ -21,7 +20,7 @@ module MediumToWebflow
|
|
21
20
|
medium_posts = fetch_medium_posts
|
22
21
|
@logger.info "Found #{medium_posts.count} posts to sync"
|
23
22
|
|
24
|
-
|
23
|
+
sync_medium_posts_to_webflow(medium_posts)
|
25
24
|
|
26
25
|
@logger.info "Sync completed successfully!"
|
27
26
|
rescue StandardError => e
|
@@ -36,18 +35,61 @@ module MediumToWebflow
|
|
36
35
|
Medium::Client.new(username: @medium_username).fetch_posts
|
37
36
|
end
|
38
37
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
38
|
+
def sync_medium_posts_to_webflow(medium_posts)
|
39
|
+
medium_posts.each_with_index do |medium_post, index|
|
40
|
+
@logger.debug "Processing post: #{medium_post.title}"
|
41
|
+
sync_medium_post_to_webflow(medium_post)
|
42
|
+
@logger.info "Successfully synced: #{medium_post.title} (#{index + 1}/#{medium_posts.count})"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def sync_medium_post_to_webflow(medium_post)
|
47
|
+
fields = build_webflow_fields(medium_post)
|
48
|
+
medium_slug_field_name = @field_mappings.key("slug")
|
49
|
+
existing_item = find_webflow_item_by_slug(medium_post.send(medium_slug_field_name))
|
50
|
+
|
51
|
+
if existing_item
|
52
|
+
handle_existing_webflow_item(existing_item, fields, medium_post)
|
53
|
+
else
|
54
|
+
create_webflow_item(fields)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_webflow_item_by_slug(slug)
|
59
|
+
@webflow_adapter.find_by_slug(slug)
|
60
|
+
end
|
61
|
+
|
62
|
+
def handle_existing_webflow_item(existing_item, fields, medium_post)
|
63
|
+
if MediumToWebflow.configuration.force_update
|
64
|
+
@logger.debug "Forcing update of existing item: #{existing_item[:id]}"
|
65
|
+
@webflow_adapter.update_item(existing_item[:id], fields)
|
66
|
+
else
|
67
|
+
@logger.info "Skipping existing item: #{medium_post.title} (use --force-update to override)"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def create_webflow_item(fields)
|
72
|
+
@webflow_adapter.create_item(fields)
|
73
|
+
end
|
74
|
+
|
75
|
+
def build_webflow_fields(medium_post)
|
76
|
+
@field_mappings.each_with_object({}) do |(medium_field_name, webflow_field_name), fields|
|
77
|
+
value = medium_post.public_send(medium_field_name)
|
78
|
+
next if value.nil?
|
45
79
|
|
46
|
-
|
47
|
-
@logger.debug "Processing post: #{post.title}"
|
48
|
-
webflow_client.upsert_post(post)
|
49
|
-
@logger.info "Successfully synced: #{post.title} (#{index + 1}/#{posts.count})"
|
80
|
+
fields[webflow_field_name] = process_field_value(medium_field_name, value)
|
50
81
|
end
|
51
82
|
end
|
83
|
+
|
84
|
+
def process_field_value(medium_field_name, value)
|
85
|
+
# Handle the image field by converting it to Webflow's expected format { url: "image_url" }
|
86
|
+
return { url: value } if medium_field_name == :image_url
|
87
|
+
|
88
|
+
# Convert DateTime/Time objects to ISO8601 format for Webflow's date fields
|
89
|
+
return value.iso8601 if value.respond_to?(:iso8601)
|
90
|
+
|
91
|
+
# Return value as-is for all other field types
|
92
|
+
value
|
93
|
+
end
|
52
94
|
end
|
53
95
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "webflow"
|
4
|
+
|
5
|
+
module MediumToWebflow
|
6
|
+
module Webflow
|
7
|
+
class Adapter
|
8
|
+
def initialize(api_token, collection_id)
|
9
|
+
@client = ::Webflow::Client.new(api_token)
|
10
|
+
@collection_id = collection_id
|
11
|
+
@logger = MediumToWebflow.configuration.logger
|
12
|
+
end
|
13
|
+
|
14
|
+
def find_by_slug(slug)
|
15
|
+
@client.list_items(@collection_id, query_params: { slug: slug }).first
|
16
|
+
rescue ::Webflow::Error => e
|
17
|
+
handle_error("list_items", e)
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_item(fields)
|
22
|
+
@logger.debug "Creating Webflow item in collection: #{@collection_id}"
|
23
|
+
@logger.debug "Fields: #{fields.inspect}" if MediumToWebflow.configuration.verbose
|
24
|
+
|
25
|
+
@client.create_item(@collection_id, fields, is_draft: true)
|
26
|
+
rescue ::Webflow::Error => e
|
27
|
+
handle_error("create_item", e)
|
28
|
+
end
|
29
|
+
|
30
|
+
def update_item(item_id, fields)
|
31
|
+
@logger.debug "Updating Webflow item: #{item_id} in collection: #{@collection_id}"
|
32
|
+
@logger.debug "Fields: #{fields.inspect}" if MediumToWebflow.configuration.verbose
|
33
|
+
|
34
|
+
@client.update_item(@collection_id, item_id, fields, is_draft: true)
|
35
|
+
rescue ::Webflow::Error => e
|
36
|
+
handle_error("update_item", e)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def handle_error(operation, error)
|
42
|
+
error_message = "Webflow #{operation} operation failed: #{error.message}"
|
43
|
+
@logger.error error_message
|
44
|
+
raise MediumToWebflow::Error, error_message
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/medium_to_webflow.rb
CHANGED
@@ -9,7 +9,7 @@ require_relative "medium_to_webflow/errors"
|
|
9
9
|
|
10
10
|
require_relative "medium_to_webflow/medium/client"
|
11
11
|
require_relative "medium_to_webflow/medium/post"
|
12
|
-
require_relative "medium_to_webflow/webflow/
|
12
|
+
require_relative "medium_to_webflow/webflow/adapter"
|
13
13
|
require_relative "medium_to_webflow/sync_service"
|
14
14
|
require_relative "medium_to_webflow/cli"
|
15
15
|
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: medium_to_webflow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0.beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paulo Santos
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-03-18 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: httparty
|
@@ -66,6 +65,20 @@ dependencies:
|
|
66
65
|
- - "~>"
|
67
66
|
- !ruby/object:Gem::Version
|
68
67
|
version: '1.3'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: webflow-rb
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '1.1'
|
75
|
+
type: :runtime
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '1.1'
|
69
82
|
description: A library and CLI tool to fetch posts from Medium and sync them to Webflow
|
70
83
|
CMS collections
|
71
84
|
email:
|
@@ -88,7 +101,7 @@ files:
|
|
88
101
|
- lib/medium_to_webflow/medium/post.rb
|
89
102
|
- lib/medium_to_webflow/sync_service.rb
|
90
103
|
- lib/medium_to_webflow/version.rb
|
91
|
-
- lib/medium_to_webflow/webflow/
|
104
|
+
- lib/medium_to_webflow/webflow/adapter.rb
|
92
105
|
homepage: https://github.com/deemaze/medium_to_webflow
|
93
106
|
licenses:
|
94
107
|
- MIT
|
@@ -98,7 +111,6 @@ metadata:
|
|
98
111
|
source_code_uri: https://github.com/deemaze/medium_to_webflow
|
99
112
|
changelog_uri: https://github.com/deemaze/medium_to_webflow/blob/main/CHANGELOG.md
|
100
113
|
rubygems_mfa_required: 'true'
|
101
|
-
post_install_message:
|
102
114
|
rdoc_options: []
|
103
115
|
require_paths:
|
104
116
|
- lib
|
@@ -113,8 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
125
|
- !ruby/object:Gem::Version
|
114
126
|
version: '0'
|
115
127
|
requirements: []
|
116
|
-
rubygems_version: 3.
|
117
|
-
signing_key:
|
128
|
+
rubygems_version: 3.6.2
|
118
129
|
specification_version: 4
|
119
130
|
summary: Sync Medium posts to Webflow CMS collections
|
120
131
|
test_files: []
|
@@ -1,99 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module MediumToWebflow
|
4
|
-
module Webflow
|
5
|
-
class Client
|
6
|
-
include HTTParty
|
7
|
-
base_uri "https://api.webflow.com/v2"
|
8
|
-
headers "Accept" => "application/json"
|
9
|
-
headers "Content-Type" => "application/json"
|
10
|
-
|
11
|
-
def initialize(api_token:, collection_id:, field_mappings:)
|
12
|
-
@api_token = api_token
|
13
|
-
@collection_id = collection_id
|
14
|
-
@field_mappings = field_mappings
|
15
|
-
self.class.headers "Authorization" => "Bearer #{api_token}"
|
16
|
-
@logger = MediumToWebflow.configuration.logger
|
17
|
-
end
|
18
|
-
|
19
|
-
def upsert_post(medium_post)
|
20
|
-
fields = build_fields(medium_post)
|
21
|
-
medium_slug_field = @field_mappings.key("slug")
|
22
|
-
existing_item = find_item(slug: medium_post.send(medium_slug_field))
|
23
|
-
|
24
|
-
handle_existing_or_create_item(existing_item, fields, medium_post)
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def handle_existing_or_create_item(existing_item, fields, medium_post)
|
30
|
-
if existing_item
|
31
|
-
handle_existing_item(existing_item, fields, medium_post)
|
32
|
-
else
|
33
|
-
create_item(fields: fields)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def handle_existing_item(existing_item, fields, medium_post)
|
38
|
-
if MediumToWebflow.configuration.force_update
|
39
|
-
@logger.debug "Forcing update of existing item: #{existing_item["id"]}"
|
40
|
-
update_item(item_id: existing_item["id"], fields: fields)
|
41
|
-
else
|
42
|
-
@logger.info "Skipping existing item: #{medium_post.title} (use --force-update to override)"
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def find_item(slug:)
|
47
|
-
response = self.class.get("/collections/#{@collection_id}/items/live", query: { slug: slug })
|
48
|
-
|
49
|
-
handle_response(response)["items"]&.first
|
50
|
-
end
|
51
|
-
|
52
|
-
def create_item(fields:)
|
53
|
-
@logger.debug "Creating Webflow item in collection: #{@collection_id}"
|
54
|
-
@logger.debug "Fields: #{fields.inspect}" if MediumToWebflow.configuration.verbose
|
55
|
-
|
56
|
-
response = self.class.post("/collections/#{@collection_id}/items/live", body: {
|
57
|
-
fieldData: fields
|
58
|
-
}.to_json)
|
59
|
-
handle_response(response)
|
60
|
-
end
|
61
|
-
|
62
|
-
def update_item(item_id:, fields:)
|
63
|
-
@logger.debug "Updating Webflow item: #{item_id} in collection: #{@collection_id}"
|
64
|
-
@logger.debug "Fields: #{fields.inspect}" if MediumToWebflow.configuration.verbose
|
65
|
-
|
66
|
-
response = self.class.patch("/collections/#{@collection_id}/items/#{item_id}/live", body: {
|
67
|
-
fieldData: fields
|
68
|
-
}.to_json)
|
69
|
-
handle_response(response)
|
70
|
-
end
|
71
|
-
|
72
|
-
def build_fields(medium_post)
|
73
|
-
@field_mappings.each_with_object({}) do |(medium_field, webflow_field), fields|
|
74
|
-
value = medium_post.public_send(medium_field)
|
75
|
-
next if value.nil?
|
76
|
-
|
77
|
-
fields[webflow_field] = process_field_value(medium_field, value)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def process_field_value(field, value)
|
82
|
-
# Handle the image field by converting it to Webflow's expected format { url: "image_url" }
|
83
|
-
return { url: value } if field == :image_url
|
84
|
-
|
85
|
-
# Convert DateTime/Time objects to ISO8601 format for Webflow's date fields
|
86
|
-
return value.iso8601 if value.respond_to?(:iso8601)
|
87
|
-
|
88
|
-
# Return value as-is for all other field types
|
89
|
-
value
|
90
|
-
end
|
91
|
-
|
92
|
-
def handle_response(response)
|
93
|
-
return response.parsed_response if response.success?
|
94
|
-
|
95
|
-
raise Error, "Webflow API error: #{response.code} - #{response.body}"
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|