Skip to content

Commit

Permalink
Merge pull request #48 from twpol/twpol/issue47
Browse files Browse the repository at this point in the history
Migrate Toggl API from v8 to v9
  • Loading branch information
twpol authored Sep 22, 2024
2 parents bf3a0a0 + 738f106 commit beba89a
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 48 deletions.
10 changes: 5 additions & 5 deletions Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,11 @@ static async Task<Project> GetMatchingProject(Query query, string projectNameOrI

static async Task<string> FormatTimer(Query query, TimeEntry timer)
{
var project = await query.GetProject(timer.pid);
var duration = TimeSpan.FromSeconds(DateTimeOffset.Now.ToUnixTimeSeconds() + timer.duration);
var timeRange = timer.stop.Year == 1 ?
$"{timer.start.ToString("yyyy-MM-dd HH:mm")}-now ({duration.ToString("hh\\:mm")})" :
$"{timer.start.ToString("yyyy-MM-dd HH:mm")}-{timer.stop.TimeOfDay.ToString("hh\\:mm")} ({(timer.stop - timer.start).ToString("hh\\:mm")})";
var project = timer.project_id.HasValue ? await query.GetProject(timer.workspace_id, timer.project_id.Value) : null;
var duration = DateTimeOffset.Now - timer.start;
var timeRange = !timer.stop.HasValue ?
$"{timer.start.LocalDateTime:yyyy-MM-dd HH:mm}-now ({duration:hh\\:mm})" :
$"{timer.start.LocalDateTime:yyyy-MM-dd HH:mm}-{timer.stop.Value.LocalDateTime:HH\\:mm} ({timer.stop.Value - timer.start:hh\\:mm})";
return $"{timeRange} - {project?.name ?? "(none)"} - {timer.description} [{(timer.tags == null ? "" : string.Join(", ", timer.tags))}]";
}

Expand Down
2 changes: 1 addition & 1 deletion Toggl/Project.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ namespace Toggl_CLI.Toggl
public class Project
{
public uint id;
public uint wid;
public uint workspace_id;
public string name;
}
}
72 changes: 33 additions & 39 deletions Toggl/Query.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Net;
using System.Net.Http;
Expand All @@ -21,7 +22,7 @@ public TogglException(string message, Exception inner) : base(message, inner)
}
}

const string Endpoint = "https://api.track.toggl.com/api/v8/";
const string Endpoint = "https://api.track.toggl.com/api/v9/";
const string UserAgent = "Toggl-CLI/1.0";

readonly string Token;
Expand Down Expand Up @@ -80,6 +81,11 @@ internal async Task<JToken> Put(string type, JObject content)
return await Send(HttpMethod.Put, type, new StringContent(JsonConvert.SerializeObject(content), Encoding.UTF8, "application/json"));
}

internal async Task<JToken> Patch(string type, JObject content)
{
return await Send(HttpMethod.Patch, type, new StringContent(JsonConvert.SerializeObject(content), Encoding.UTF8, "application/json"));
}

internal async Task<U> GetCached<T, U>(Dictionary<T, U> cache, T key, Func<Task<U>> generator)
{
if (!cache.ContainsKey(key))
Expand All @@ -96,93 +102,81 @@ public async Task<IReadOnlyList<Workspace>> GetWorkspaces()

public async Task<IReadOnlyList<Project>> GetProjects()
{
return (await GetWorkspaces())
.Select(async workspace => await GetProjects(workspace))
.SelectMany(projects => projects.Result)
.ToList();
return (await Get($"me/projects")).ToObject<List<Project>>();
}

public async Task<IReadOnlyList<Project>> GetProjects(Workspace workspace)
{
return (await Get($"workspaces/{workspace.id}/projects")).ToObject<List<Project>>();
}

public async Task<Project> GetProject(uint projectId)
public async Task<Project> GetProject(uint workspaceId, uint projectId)
{
if (projectId == 0)
{
return null;
}
return await GetCached(ProjectCache, projectId, async () => (await Get($"projects/{projectId}"))["data"].ToObject<Project>());
return await GetCached(ProjectCache, projectId, async () => (await Get($"workspaces/{workspaceId}/projects/{projectId}")).ToObject<Project>());
}

public async Task<IReadOnlyList<TimeEntry>> GetRecentTimers()
{
return (await Get("time_entries")).ToObject<IReadOnlyList<TimeEntry>>();
return (await Get("me/time_entries")).ToObject<IList<TimeEntry>>().Reverse().ToImmutableList();
}

public async Task<TimeEntry> GetCurrentTimer()
{
return (await Get("time_entries/current"))["data"].ToObject<TimeEntry>();
return (await Get("me/time_entries/current")).ToObject<TimeEntry>();
}

public async Task SetCurrentTimerProject(Project project)
{
var timer = await GetCurrentTimer();
await Put($"time_entries/{timer.id}", new JObject(
new JProperty("time_entry", new JObject(
new JProperty("pid", project.id)
))
await Put($"workspaces/{timer.workspace_id}/time_entries/{timer.id}", new JObject(
new JProperty("project_id", project.id)
));
}

public async Task SetCurrentTimerDescription(string description)
{
var timer = await GetCurrentTimer();
await Put($"time_entries/{timer.id}", new JObject(
new JProperty("time_entry", new JObject(
new JProperty("description", description)
))
await Put($"workspaces/{timer.workspace_id}/time_entries/{timer.id}", new JObject(
new JProperty("description", description)
));
}

public async Task SetCurrentTimerTags(IReadOnlyList<string> tags)
{
var timer = await GetCurrentTimer();
await Put($"time_entries/{timer.id}", new JObject(
new JProperty("time_entry", new JObject(
new JProperty("tags", JArray.FromObject(tags))
))
await Put($"workspaces/{timer.workspace_id}/time_entries/{timer.id}", new JObject(
new JProperty("tags", JArray.FromObject(tags))
));
}

public async Task<TimeEntry> StartTimer(Project project, string description, IReadOnlyList<string> tags)
{
var response = await Post("time_entries/start", new JObject(
new JProperty("time_entry", project != null ?
new JObject(
new JProperty("pid", project.id),
new JProperty("description", description),
new JProperty("tags", JArray.FromObject(tags)),
new JProperty("created_with", UserAgent)
) :
new JObject(
new JProperty("description", description),
new JProperty("tags", JArray.FromObject(tags)),
new JProperty("created_with", UserAgent)
)
var workspaceId = project?.workspace_id ?? (await GetWorkspaces()).First().id;
var startTime = DateTimeOffset.Now;
var response = await Post($"workspaces/{workspaceId}/time_entries",
new JObject(
new JProperty("workspace_id", workspaceId),
new JProperty("project_id", project?.id),
new JProperty("start", startTime),
new JProperty("duration", -1),
new JProperty("description", description),
new JProperty("tags", JArray.FromObject(tags)),
new JProperty("created_with", UserAgent)
)
));
return response["data"].ToObject<TimeEntry>();
);
return response.ToObject<TimeEntry>();
}

public async Task<bool> StopTimer()
{
var response = await Get("time_entries/current");
var currentTimer = response["data"].ToObject<JObject>();
var currentTimer = await GetCurrentTimer();
if (currentTimer != null)
{
await Put($"time_entries/{currentTimer["id"]}/stop", new JObject());
await Patch($"workspaces/{currentTimer.workspace_id}/time_entries/{currentTimer.id}/stop", new JObject());
return true;
}
return false;
Expand Down
6 changes: 3 additions & 3 deletions Toggl/TimeEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ namespace Toggl_CLI.Toggl
public class TimeEntry
{
public uint id;
public uint wid;
public uint pid;
public uint workspace_id;
public uint? project_id;
public DateTimeOffset start;
public DateTimeOffset stop;
public DateTimeOffset? stop;
public int duration;
public string description;
public IReadOnlyList<string> tags;
Expand Down

0 comments on commit beba89a

Please sign in to comment.