timet 1.6.3 → 1.6.5

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: 84fa974afe30946a8b292569e74917c3992097e0ae652339320981f2951f298d
4
- data.tar.gz: dc6cf2d3f5bd30feebb93db4aa07c436203e53bf1d9d0dbba5166695b622b021
3
+ metadata.gz: b43ddbb0c2a4744c09fede5bbe507ea736a9690daebf6cbccba07d427fcd929c
4
+ data.tar.gz: 9b90a62f2e4cc6c7ee089510ca9c1a023c8f35a22164fa1837140109c1cdda77
5
5
  SHA512:
6
- metadata.gz: 49cf7cc38f9e44687d27bee93a8de5678963fd92a9589241e38635afcdf4b86f8229d1c401483d2d6e955ee83e5423ca10f832f4beb0101873e60f5d0ff34821
7
- data.tar.gz: 2098f1a3c297277bd07425961b3e84425a1189ae5984e21c35649a3a2c517a5a175dfd5b477abb59ba49f78925a9c4ec872006d3be025ddc4ee363bf86ff7004
6
+ metadata.gz: dbbcb0cf64a60c26147145f010d7213782b25fe627136e4ed1827e41e4c90748b413d8de5877540cab93cde39f1eb4b093df33bc28da8e8d20d67e894a96c5ab
7
+ data.tar.gz: 927452644bc0201d87343845193b46481f20b836d8d197fd7b42ebbaed1a42b835deba4352af8ec5e25c81df62e800e772ab66f53dc9d31cc5e250dba61120bf
data/.rubocop.yml CHANGED
@@ -6,9 +6,14 @@ AllCops:
6
6
  - timet.gemspec
7
7
  - "vendor/**/*" # Exclude vendor/bundle gems
8
8
 
9
+ Naming/PredicateMethod:
10
+ Exclude:
11
+ - 'lib/timet/database_syncer.rb'
12
+
9
13
  Metrics/ModuleLength:
10
14
  Exclude:
11
15
  - 'lib/timet/tag_distribution.rb'
16
+ - 'lib/timet/database_syncer.rb'
12
17
 
13
18
  Metrics/MethodLength:
14
19
  Max: 15
data/CHANGELOG.md CHANGED
@@ -1,3 +1,35 @@
1
+ ## [1.6.5] - 2026-08-01
2
+
3
+ **Improvements:**
4
+
5
+ - **Documentation:** Expanded `README.md` with detailed setup instructions for Supabase S3-compatible storage, environment variable configuration, and usage of the `timet sync` command.
6
+ - **Dependencies:** Updated locked gem dependencies in `Gemfile.lock` including `aws-sdk`, `rubocop`, `sqlite3`, `json`, and `rake`.
7
+
8
+ **Bug Fixes & Security:**
9
+
10
+ - **Security Patches:** Resolved multiple security vulnerabilities by updating locked gem versions:
11
+ - `addressable` (`>= 2.9.0`): Addressed ReDoS vulnerability in Addressable templates ([CVE-2026-35611](https://github.com/sporkmonger/addressable/security/advisories/GHSA-h27x-rffw-24p4)).
12
+ - `json` (`>= 2.19.9`): Fixed heap buffer overflow when streaming to IO ([CVE-2026-54696](https://nvd.nist.gov/vuln/detail/CVE-2026-54696)).
13
+ - `mcp` (`>= 0.23.0`): Resolved SSE stream hijacking, session poisoning, memory exhaustion, and DNS-rebinding protection issues ([CVE-2026-33946](https://github.com/modelcontextprotocol/ruby-sdk/security/advisories/GHSA-qvqr-5cv7-wh35)).
14
+ - `sqlite3` (`>= 2.9.5`): Patched Use-After-Free vulnerabilities when redefining functions and in aggregate callbacks ([CVE-2026-54619](https://www.cve.org/CVERecord/SearchResults?query=CVE-2026-54619), [CVE-2026-54620](https://www.cve.org/CVERecord/SearchResults?query=CVE-2026-54620)).
15
+
16
+ ## [1.6.4] - 2026-03-26
17
+
18
+ **Improvements:**
19
+
20
+ - **Database Synchronization:** Refactored sync logic to use bidirectional comparison with timestamp-based conflict resolution, improving reliability during database merges.
21
+ - **Dependencies:** Updated `icalendar` to `~> 2.12.2` and `json` to `~> 2.19`, removing unused gems.
22
+ - **Code Quality:** Added RuboCop exclusions for predicate methods in `DatabaseSyncer` and module length for `database_syncer.rb`.
23
+ - **Test Coverage:** Expanded test suite for bidirectional sync scenarios, including conflict resolution and remote item handling.
24
+
25
+ **Bug Fixes:**
26
+ - Fix validation editor spec to properly stub database update
27
+
28
+ - **Sync Logic:** Fixed item synchronization to properly handle local-only and remote-only items with timestamp-based decision making.
29
+ - **Database Updates:** Corrected `update_item_from_hash` method to properly bind ID parameter in SQL queries.
30
+ - **Conflict Resolution:** Simplified remote wins logic to compare timestamps directly, removing redundant deleted flag checks.
31
+ - **Sync Flagging:** Fixed return values in sync methods to accurately track whether local changes occurred during synchronization.
32
+
1
33
  ## [1.6.3] - 2026-02-28
2
34
 
3
35
  **Improvements:**
data/README.md CHANGED
@@ -251,7 +251,7 @@ Once configured, Timet will automatically send a notification to your Discord ch
251
251
  | `timet su [start_date]..[end_date]` | Display a report of tracked time for a date range. | `timet su 2024-01-02..2024-01-03` |
252
252
  | `timet resume (r) [id]` | Resume tracking a task by ID or the last completed task. | `timet resume [id]` |
253
253
  | `timet resume (r) [id] --pomodoro=[minutes]` | Resume tracking with a Pomodoro timer. | `timet resume 1 --pomodoro=25` |
254
- | `timet sync` | Sync local db with remote (S3) external db | `timet sync` |
254
+ | `timet sync` | Sync local database with S3 storage (bidirectional sync). | `timet sync` |
255
255
  | `timet version` | Display the current version. | `timet version` |
256
256
 
257
257
  ### Date Range in Summary
@@ -260,18 +260,47 @@ The `timet summary` command now supports specifying a date range for generating
260
260
 
261
261
  #### Examples:
262
262
 
263
- - **Single Date:** Display a report for a specific date.
263
+ - **Single Date**: Display a report for a specific date.
264
264
 
265
265
  ```sh
266
266
  timet su 2024-01-03
267
267
  ```
268
268
 
269
- - **Date Range:** Display a report for a date range.
269
+ - **Date Range**: Display a report for a date range.
270
270
 
271
271
  ```sh
272
272
  timet su 2024-01-02..2024-01-03
273
273
  ```
274
274
 
275
+ ### Sync Operations
276
+
277
+ ```bash
278
+ # Sync local database with S3
279
+ timet sync
280
+ ```
281
+
282
+ This command performs a bidirectional sync:
283
+ 1. Creates the S3 bucket if it doesn't exist
284
+ 2. If a remote database exists, downloads it and compares with the local database using MD5 hashing
285
+ 3. If databases differ, syncs the changes
286
+ 4. If no remote database exists, uploads the local database as `timet.db`
287
+
288
+ ### Example Sync Workflow:
289
+
290
+ ```bash
291
+ # First time setup: configure credentials
292
+ mkdir -p ~/.timet && cd ~/.timet
293
+ cat > .env <<-'EOF'
294
+ S3_ENDPOINT=https://abcdefghijklmnop.supabase.co/storage/v1/object
295
+ S3_ACCESS_KEY=your-access-key-here
296
+ S3_SECRET_KEY=your-secret-key-here
297
+ S3_REGION=us-east-1
298
+ EOF
299
+
300
+ # Sync your time tracking data to cloud
301
+ timet sync
302
+ ```
303
+
275
304
  <a name="data"></a>
276
305
 
277
306
  ## 🗃️ Data
@@ -284,23 +313,101 @@ Timet's data is stored in `~/.timet`.
284
313
 
285
314
  Timet supports backing up and syncing your time tracking data with S3-compatible storage services (such as Supabase S3). To configure S3 backup, follow these steps:
286
315
 
287
- ### Environment Variables
316
+ ### Setting Up Supabase S3 Storage
317
+
318
+ #### 1. Create a Database Project in Supabase
288
319
 
289
- Create a `.env` file in your project root (`~/.timet`) with the following variables:
320
+ If you don't have a Supabase project yet:
321
+ 1. Go to [Supabase](https://supabase.com/) and create a new project
322
+ 2. Choose a region close to your location
323
+ 3. Complete the project setup wizard
324
+
325
+ #### 2. Enable S3 Storage in Supabase
326
+
327
+ 1. In your Supabase project dashboard, navigate to **Settings** → **Storage**
328
+ 2. Click **Create storage bucket** or select an existing bucket
329
+ 3. Configure the bucket:
330
+ - **Bucket name**: Use a descriptive name like `timet-data` or `time-tracking`
331
+ - **File size limit**: Choose appropriate limits (e.g., 10MB per file)
332
+ - **Public access**: Set to **Private** for security
333
+
334
+ #### 3. Generate S3 Access Credentials
335
+
336
+ Supabase uses AWS-compatible S3 APIs, so it requires access keys:
337
+
338
+ 1. Go to your Supabase project dashboard → **Settings** → **API**
339
+ 2. Scroll down to **Service Role Secrets** or **S3 Configuration**
340
+ 3. Click **Generate New Key** or view existing credentials
341
+ 4. Copy the following values (you won't be able to see them again after leaving this page):
342
+ - `SERVICE_ROLE_KEY` → This is your S3_ACCESS_KEY
343
+ - The corresponding secret → This is your S3_SECRET_KEY
344
+
345
+ > ⚠️ **Important**: If you don't see these options in Settings, check:
346
+ > - **Security** tab in project settings
347
+ > - **Database** → **Connection Pooling** section (sometimes under **Settings**)
348
+ > - Contact Supabase support if credentials aren't visible
349
+ >
350
+ > Alternatively, create a custom S3-compatible endpoint using Supabase's API:
351
+ > ```bash
352
+ > # Format: https://<project-ref>.supabase.co/storage/v1/object
353
+ > ```
354
+
355
+ #### 4. Configure Environment Variables
356
+
357
+ Create a `.env` file in `~/.timet/` with the following variables:
290
358
 
291
359
  ```bash
292
- S3_ENDPOINT=your_s3_endpoint_url
293
- S3_ACCESS_KEY=your_access_key
294
- S3_SECRET_KEY=your_secret_key
295
- S3_REGION=your_s3_region
360
+ S3_ENDPOINT=https://<your-project-id>.supabase.co/storage/v1/object
361
+ S3_ACCESS_KEY=<your-service-role-key>
362
+ S3_SECRET_KEY=<your-service-role-secret>
363
+ S3_REGION=us-west-1 # Or your Supabase region (e.g., europe-west3, ap-southeast-1)
296
364
  ```
297
365
 
366
+ Replace the placeholders with your actual values from Step 3. Example:
367
+
368
+ ```bash
369
+ S3_ENDPOINT=https://abcdefghijklmnop.supabase.co/storage/v1/object
370
+ S3_ACCESS_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
371
+ S3_SECRET_KEY=xyz987-abc123-def456-ghi789-jkl012
372
+ S3_REGION=us-east-1
373
+ ```
374
+
375
+ #### 5. Create the S3 Bucket (Optional)
376
+
377
+ If you haven't created a bucket yet in Supabase, Timet can create one:
378
+
379
+ ```bash
380
+ timet s3 create <bucket-name>
381
+ ```
382
+
383
+ Or use the shortcut:
384
+
385
+ ```bash
386
+ timet s3 create timet-data
387
+ ```
388
+
389
+
298
390
  ### Security Considerations
299
391
 
300
- - Keep your `.env` file private and never commit it to version control
392
+ - Keep your `.env` file private and never commit it to version control (`.env` should be in your `.gitignore`)
301
393
  - Use strong, unique access keys
302
394
  - Regularly rotate your S3 access credentials
303
395
  - Implement appropriate IAM policies to restrict bucket access
396
+ - For production use, consider using a dedicated service role with minimum required permissions
397
+
398
+ ### Verification
399
+
400
+ After configuration, verify the connection:
401
+
402
+ ```bash
403
+ # List objects in your bucket (if you have list permissions)
404
+ timet s3 list-buckets
405
+
406
+ # Or simply try a sync operation to upload your data
407
+ timet sync
408
+ ```
409
+
410
+ All S3 operations are logged to `~/.timet/s3_supabase.log`.
304
411
 
305
412
  ## Development
306
413
 
@@ -55,35 +55,65 @@ module Timet
55
55
 
56
56
  def sync_databases(*args)
57
57
  local_db, remote_db, remote_storage, bucket, local_db_path = args
58
- process_database_items(local_db, remote_db)
59
- remote_storage.upload_file(bucket, local_db_path, 'timet.db')
58
+ local_items = local_db.execute_sql('SELECT * FROM items ORDER BY updated_at DESC')
59
+ remote_items = remote_db.execute('SELECT * FROM items ORDER BY updated_at DESC')
60
+
61
+ local_by_id = items_to_hash(local_items)
62
+ remote_by_id = items_to_hash(remote_items)
63
+
64
+ local_changes = process_bidirectional_sync(local_db, local_by_id, remote_by_id)
65
+
66
+ if local_changes
67
+ remote_storage.upload_file(bucket, local_db_path, 'timet.db')
68
+ puts 'Changes uploaded to remote'
69
+ else
70
+ puts 'No local changes to upload'
71
+ end
72
+
60
73
  puts 'Database sync completed'
61
74
  end
62
75
 
63
- def process_database_items(local_db, remote_db)
64
- remote_items = remote_db.execute('SELECT * FROM items ORDER BY updated_at DESC')
65
- local_items = local_db.execute_sql('SELECT * FROM items ORDER BY updated_at DESC')
76
+ def process_bidirectional_sync(local_db, local_items_by_id, remote_items_by_id)
77
+ all_ids = (remote_items_by_id.keys + local_items_by_id.keys).uniq
78
+ local_has_changes = false
79
+
80
+ all_ids.each do |id|
81
+ remote_item = remote_items_by_id[id]
82
+ local_item = local_items_by_id[id]
66
83
 
67
- sync_items_by_id(local_db, items_to_hash(local_items), items_to_hash(remote_items))
84
+ changed = sync_single_item_and_flag(local_db, id, local_item, remote_item)
85
+ local_has_changes = true if changed
86
+ end
87
+
88
+ local_has_changes
68
89
  end
69
90
 
70
- def sync_items_by_id(local_db, local_items_by_id, remote_items_by_id)
71
- all_item_ids = (remote_items_by_id.keys + local_items_by_id.keys).uniq
72
- all_item_ids.each { |id| sync_single_item(local_db, id, local_items_by_id, remote_items_by_id) }
91
+ def sync_single_item_and_flag(local_db, id, local_item, remote_item)
92
+ if !remote_item && local_item
93
+ puts "Local item #{id} will be uploaded"
94
+ true
95
+ elsif !local_item && remote_item
96
+ puts "Adding remote item #{id} to local"
97
+ insert_item_from_hash(local_db, remote_item)
98
+ false
99
+ elsif local_item && remote_item
100
+ merge_and_track_changes?(local_db, id, local_item, remote_item)
101
+ else
102
+ false
103
+ end
73
104
  end
74
105
 
75
- def sync_single_item(*args)
76
- local_db, id, local_items_by_id, remote_items_by_id = args
77
- remote_item = remote_items_by_id[id]
78
- local_item = local_items_by_id[id]
106
+ def merge_and_track_changes?(local_db, id, local_item, remote_item)
107
+ local_time = extract_timestamp(local_item)
108
+ remote_time = extract_timestamp(remote_item)
79
109
 
80
- if !remote_item
81
- log_local_only(id)
82
- elsif !local_item
83
- add_remote_item(local_db, id, remote_item)
84
- else
85
- merge_item(local_db, id, local_item, remote_item)
110
+ if remote_time > local_time
111
+ puts "Remote item #{id} is newer - updating local"
112
+ update_item_from_hash(local_db, remote_item)
113
+ elsif local_time > remote_time
114
+ puts "Local item #{id} is newer - will be uploaded"
86
115
  end
116
+ true
87
117
  end
88
118
 
89
119
  def log_local_only(id)
@@ -132,9 +162,11 @@ module Timet
132
162
 
133
163
  def update_item_from_hash(db, item)
134
164
  fields = "#{ITEM_FIELDS.join(' = ?, ')} = ?"
165
+ values = get_update_values(item)
166
+ values << item['id']
135
167
  db.execute_sql(
136
168
  "UPDATE items SET #{fields} WHERE id = ?",
137
- get_update_values(item)
169
+ values
138
170
  )
139
171
  end
140
172
 
@@ -147,9 +179,8 @@ module Timet
147
179
  items.to_h { |item| [item['id'], item] }
148
180
  end
149
181
 
150
- def remote_wins?(remote_item, remote_time, local_time)
151
- time_diff = remote_time > local_time
152
- time_diff && (remote_item['deleted'].to_i == 1 || time_diff)
182
+ def remote_wins?(_remote_item, remote_time, local_time)
183
+ remote_time > local_time
153
184
  end
154
185
 
155
186
  def format_status_message(id, item, source)
data/lib/timet/version.rb CHANGED
@@ -6,6 +6,6 @@ module Timet
6
6
  # @return [String] The version number in the format 'major.minor.patch'.
7
7
  #
8
8
  # @example Get the version of the Timet application
9
- # Timet::VERSION # => '1.6.3'
10
- VERSION = '1.6.3'
9
+ # Timet::VERSION # => '1.6.5'
10
+ VERSION = '1.6.5'
11
11
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timet
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.3
4
+ version: 1.6.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Frank Vielma
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-02-28 00:00:00.000000000 Z
11
+ date: 2026-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor