freshjots 1.1.0 → 1.2.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 +4 -4
- data/README.md +14 -2
- data/lib/freshjots/version.rb +1 -1
- data/lib/freshjots.rb +93 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3916dec8cefe696476c18a0a28aba36a1657721082098eef1929129c05355b84
|
|
4
|
+
data.tar.gz: 94b852d213f92017d897fe309efd21f9ecf60c6d398e0cbfe6c37af0f11219b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8c650cb3dfdb4ff2c3d4680652e9fa8ec82cfd2fa5f6605fe9e33a3f67b3b6149d46ac5b3f6aada3cb2b3a7c4c8e77d2e4ebf98c122a5957439ce2205001aa9b
|
|
7
|
+
data.tar.gz: 1ee7ec3cace2bf20ce328f27d9ffb1ed91348eabb5fc3bf1354a9c21f09cd06707d22ca8f9beeb8dfc42b23a39bf8c873d19c0d7bc48af8089f149d659ede7e9
|
data/README.md
CHANGED
|
@@ -44,13 +44,25 @@ end
|
|
|
44
44
|
created = client.create(title: "Research 2026 Q2", body: "Initial outline.")
|
|
45
45
|
puts created[:filename] # server-derived stream name
|
|
46
46
|
|
|
47
|
-
#
|
|
47
|
+
# Update a note's fields (only the keys you pass change). By id or by filename:
|
|
48
|
+
client.update(42, title: "Q2 research", body: "Revised outline.")
|
|
49
|
+
client.set("cron-jobs-prod", folder: "Ops", deadline: 26) # metadata only, no body needed
|
|
50
|
+
|
|
51
|
+
# Create many notes in one atomic batch (up to 50 — all land or none do).
|
|
52
|
+
client.bulk([{ title: "a", plain_body: "1" }, { title: "b", plain_body: "2" }])
|
|
53
|
+
|
|
54
|
+
# Organize: move into a folder (by id or name), delete (by id or filename).
|
|
48
55
|
client.move("cron-jobs-prod", folder: "Ops")
|
|
49
56
|
client.delete("old-note")
|
|
57
|
+
|
|
58
|
+
# Folders: list, read one, create, rename, delete (its notes survive, un-foldered).
|
|
50
59
|
client.folders.each { |f| puts "#{f[:id]}\t#{f[:name]}" }
|
|
60
|
+
ops = client.create_folder("Ops")
|
|
61
|
+
client.rename_folder(ops[:id], "Operations")
|
|
62
|
+
client.delete_folder(ops[:id])
|
|
51
63
|
```
|
|
52
64
|
|
|
53
|
-
Client methods: `notes(sort:, folder_id:, limit:, offset:)`, `note(filename)`, `note_by_id(id)`, `create(title:, body:, client_encrypted:)`, `append(filename, text, client_encrypted:)`, `delete(id_or_filename)`, `move(id_or_filename, folder:)`, and `
|
|
65
|
+
Client methods: `notes(sort:, folder_id:, limit:, offset:)`, `note(filename)`, `note_by_id(id)`, `create(title:, body:, client_encrypted:)`, `append(filename, text, client_encrypted:)`, `update(id, **fields)`, `set(filename, **fields)`, `bulk(notes)`, `delete(id_or_filename)`, `move(id_or_filename, folder:)`, `folders`, `folder(id)`, `create_folder(name)`, `rename_folder(id, name)`, and `delete_folder(id)`. Client-side crypto: `Freshjots.encrypt(text, passphrase)` / `Freshjots.decrypt(token, passphrase)` (see [Encryption](#encryption)). `note`/`note_by_id`/`create`/`update`/`set` and the single-folder methods return the hash directly (no `{ note: … }` / `{ folder: … }` wrapper); `notes` and `folders` return arrays, and `bulk` returns `{ created: [...] }`. For `update`/`set`, the fields are `title:`, `body:`, `folder:` (id or name — `root: true` un-folders), `deadline:`, `alert_email:`, `webhook_url:`, `webhook_secret:`; changing `title:` rewrites the body as a unit, so pass `body:` too. For `notes`, `sort` is `created|updated|appended` and `folder_id` may be a folder id or `"none"` (un-foldered only).
|
|
54
66
|
|
|
55
67
|
## Encryption
|
|
56
68
|
|
data/lib/freshjots/version.rb
CHANGED
data/lib/freshjots.rb
CHANGED
|
@@ -34,6 +34,11 @@ module Freshjots
|
|
|
34
34
|
|
|
35
35
|
class Client
|
|
36
36
|
DEFAULT_BASE_URL = "https://freshjots.com/api/v1"
|
|
37
|
+
BULK_MAX = 50
|
|
38
|
+
# Friendly field names accepted by #update / #set, mapped to the API's
|
|
39
|
+
# note keys in #note_fields. append_only / format are intentionally
|
|
40
|
+
# absent — the API does not update them.
|
|
41
|
+
UPDATABLE_FIELDS = %i[title body folder root deadline alert_email webhook_url webhook_secret].freeze
|
|
37
42
|
|
|
38
43
|
def initialize(token: ENV["FRESHJOTS_TOKEN"], base_url: DEFAULT_BASE_URL)
|
|
39
44
|
raise ArgumentError, "FRESHJOTS_TOKEN missing — pass token: or set the env var" if token.nil? || token.empty?
|
|
@@ -96,9 +101,37 @@ module Freshjots
|
|
|
96
101
|
true
|
|
97
102
|
end
|
|
98
103
|
|
|
104
|
+
# Update a note by id. Pass any of: title:, body:, folder:, root: true,
|
|
105
|
+
# deadline:, alert_email:, webhook_url:, webhook_secret: — only the keys
|
|
106
|
+
# you pass are changed, so an unmentioned field is never clobbered. A
|
|
107
|
+
# content change (title/body) rewrites the body as a unit, so a title
|
|
108
|
+
# change needs body: too (the API requires plain_body). append_only and
|
|
109
|
+
# format are not updatable. Returns the updated note hash (top level).
|
|
110
|
+
def update(id, **fields)
|
|
111
|
+
request(:patch, "/notes/#{escape(id)}", { note: note_fields(fields) })
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Update a note addressed by its exact filename / stream name. Same
|
|
115
|
+
# fields as #update.
|
|
116
|
+
def set(filename, **fields)
|
|
117
|
+
request(:patch, "/notes/by-filename/#{escape(filename)}", { note: note_fields(fields) })
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Create up to 50 notes in one atomic batch (all land or none do).
|
|
121
|
+
# `notes` is an array of note hashes ({ title:, plain_body:,
|
|
122
|
+
# format: "plain" }). Returns the response ({ created: [...] }).
|
|
123
|
+
def bulk(notes)
|
|
124
|
+
items = Array(notes)
|
|
125
|
+
raise ArgumentError, "bulk requires at least one note" if items.empty?
|
|
126
|
+
raise ArgumentError, "bulk accepts at most #{BULK_MAX} notes (got #{items.size})" if items.size > BULK_MAX
|
|
127
|
+
|
|
128
|
+
request(:post, "/notes/bulk", { notes: items })
|
|
129
|
+
end
|
|
130
|
+
|
|
99
131
|
# Delete a note. Accepts a numeric id or a filename (resolved to its
|
|
100
|
-
# id via the by-filename lookup).
|
|
101
|
-
#
|
|
132
|
+
# id via the by-filename lookup). Works on any note, including locked
|
|
133
|
+
# (append-only) ones — the lock freezes content, not deletability.
|
|
134
|
+
# Returns true on success.
|
|
102
135
|
def delete(id_or_filename)
|
|
103
136
|
request(:delete, "/notes/#{resolve_note_id(id_or_filename)}")
|
|
104
137
|
true
|
|
@@ -117,6 +150,28 @@ module Freshjots
|
|
|
117
150
|
request(:get, "/folders")[:folders]
|
|
118
151
|
end
|
|
119
152
|
|
|
153
|
+
# Fetch one folder by id (GET /folders/:id) — top-level serializer.
|
|
154
|
+
def folder(id)
|
|
155
|
+
request(:get, "/folders/#{escape(id)}")
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Create a folder. Returns the created folder hash (top level).
|
|
159
|
+
def create_folder(name)
|
|
160
|
+
request(:post, "/folders", { folder: { name: name } })
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Rename a folder. Returns the updated folder hash.
|
|
164
|
+
def rename_folder(id, name)
|
|
165
|
+
request(:patch, "/folders/#{escape(id)}", { folder: { name: name } })
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Delete a folder by id. Its notes survive — they just become
|
|
169
|
+
# un-foldered. Returns true.
|
|
170
|
+
def delete_folder(id)
|
|
171
|
+
request(:delete, "/folders/#{escape(id)}")
|
|
172
|
+
true
|
|
173
|
+
end
|
|
174
|
+
|
|
120
175
|
private
|
|
121
176
|
|
|
122
177
|
# A note reference is either a numeric id (used as-is) or a
|
|
@@ -140,6 +195,42 @@ module Freshjots
|
|
|
140
195
|
matches.first[:id]
|
|
141
196
|
end
|
|
142
197
|
|
|
198
|
+
# Map the friendly #update / #set keyword fields to the API's note keys,
|
|
199
|
+
# sending only what the caller passed. A title change rewrites the body
|
|
200
|
+
# as a unit (the API requires plain_body), so a title-only change is
|
|
201
|
+
# refused here — mirrors the CLI. Unknown fields and an empty change are
|
|
202
|
+
# errors. `folder:` accepts an id or a name (resolved via /folders);
|
|
203
|
+
# `root: true` un-folders the note.
|
|
204
|
+
def note_fields(fields)
|
|
205
|
+
unknown = fields.keys - UPDATABLE_FIELDS
|
|
206
|
+
unless unknown.empty?
|
|
207
|
+
raise ArgumentError,
|
|
208
|
+
"unknown update field(s): #{unknown.join(', ')}. " \
|
|
209
|
+
"allowed: #{UPDATABLE_FIELDS.join(', ')} (append_only/format are not updatable)"
|
|
210
|
+
end
|
|
211
|
+
if fields.key?(:title) && !fields.key?(:body)
|
|
212
|
+
raise ArgumentError,
|
|
213
|
+
"changing the title also rewrites the body — pass body: too. For metadata " \
|
|
214
|
+
"only, use folder/root/deadline/alert_email/webhook_* without title."
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
note = {}
|
|
218
|
+
note[:title] = fields[:title] if fields.key?(:title)
|
|
219
|
+
note[:plain_body] = fields[:body] if fields.key?(:body)
|
|
220
|
+
if fields[:root]
|
|
221
|
+
note[:folder_id] = nil
|
|
222
|
+
elsif fields.key?(:folder)
|
|
223
|
+
note[:folder_id] = resolve_folder_id(fields[:folder])
|
|
224
|
+
end
|
|
225
|
+
note[:append_deadline_hours] = fields[:deadline] if fields.key?(:deadline)
|
|
226
|
+
note[:alert_email] = fields[:alert_email] if fields.key?(:alert_email)
|
|
227
|
+
note[:webhook_url] = fields[:webhook_url] if fields.key?(:webhook_url)
|
|
228
|
+
note[:webhook_secret] = fields[:webhook_secret] if fields.key?(:webhook_secret)
|
|
229
|
+
raise ArgumentError, "no fields to update" if note.empty?
|
|
230
|
+
|
|
231
|
+
note
|
|
232
|
+
end
|
|
233
|
+
|
|
143
234
|
def request(method, path, body = nil)
|
|
144
235
|
uri = URI("#{@base_url}#{path}")
|
|
145
236
|
req = Net::HTTP.const_get(method.to_s.capitalize).new(uri)
|