From 3767aa2b4b0b297cc239fb089551b6e247a88083 Mon Sep 17 00:00:00 2001 From: changiinlee Date: Fri, 2 Feb 2024 21:16:55 +0900 Subject: =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refact:=20Refactor=20auth=20comman?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cz.toml | 46 + .gitignore | 1 + .python-version | 1 + girok/README.md | 623 -------- girok/__init__.py | 1 - girok/api/auth.py | 137 +- girok/api/category.py | 275 +--- girok/api/entity.py | 18 + girok/api/task.py | 291 ---- girok/api_client.py | 10 + girok/auth_handler.py | 57 + girok/calendar_cli/calendar_app.py | 37 - girok/calendar_cli/calendar_container.py | 315 ---- girok/calendar_cli/calendar_main.css | 222 --- girok/calendar_cli/calendar_main.py | 177 --- girok/calendar_cli/sidebar.py | 184 --- .../textual_demo_2023-03-11T22_49_21_681307.svg | 246 ---- girok/commands/auth.py | 138 -- girok/commands/auth/command.py | 247 ++++ girok/commands/calendar.py | 10 - girok/commands/category.py | 79 - girok/commands/category/command.py | 35 + girok/commands/category/util.py | 6 + girok/commands/info.py | 8 - girok/commands/task.py | 613 -------- girok/config.json | 5 - girok/config.py | 22 - girok/constants.py | 112 +- girok/girok.py | 117 +- girok/server/requirements.txt | 27 - girok/server/src/category/config.py | 0 girok/server/src/category/constants.py | 3 - girok/server/src/category/dependencies.py | 0 girok/server/src/category/enums.py | 0 girok/server/src/category/exceptions.py | 41 - girok/server/src/category/models.py | 16 - girok/server/src/category/router.py | 165 --- girok/server/src/category/schemas.py | 37 - girok/server/src/category/service.py | 196 --- girok/server/src/category/utils.py | 0 girok/server/src/database.py | 18 - girok/server/src/task/config.py | 0 girok/server/src/task/constants.py | 0 girok/server/src/task/dependencies.py | 0 girok/server/src/task/enums.py | 5 - girok/server/src/task/exceptions.py | 19 - girok/server/src/task/models.py | 23 - girok/server/src/task/router.py | 199 --- girok/server/src/task/schemas.py | 123 -- girok/server/src/task/service.py | 250 ---- girok/server/src/task/utils.py | 2 - girok/server/src/utils.py | 59 - girok/styles/stopwatch03.css | 53 - girok/utils/__init__.py | 0 girok/utils/auth.py | 90 -- girok/utils/calendar.py | 129 -- girok/utils/display.py | 349 +---- girok/utils/general.py | 57 - girok/utils/json_utils.py | 20 + girok/utils/task.py | 161 -- poetry.lock | 1553 ++++---------------- processreq.py | 9 +- pyproject.toml | 59 +- 63 files changed, 950 insertions(+), 6746 deletions(-) create mode 100644 .cz.toml create mode 100644 .python-version delete mode 100644 girok/README.md create mode 100644 girok/api/entity.py delete mode 100644 girok/api/task.py create mode 100644 girok/api_client.py create mode 100644 girok/auth_handler.py delete mode 100644 girok/calendar_cli/calendar_app.py delete mode 100644 girok/calendar_cli/calendar_container.py delete mode 100644 girok/calendar_cli/calendar_main.css delete mode 100644 girok/calendar_cli/calendar_main.py delete mode 100644 girok/calendar_cli/sidebar.py delete mode 100644 girok/calendar_cli/textual_demo_2023-03-11T22_49_21_681307.svg delete mode 100644 girok/commands/auth.py create mode 100644 girok/commands/auth/command.py delete mode 100644 girok/commands/calendar.py delete mode 100644 girok/commands/category.py create mode 100644 girok/commands/category/command.py create mode 100644 girok/commands/category/util.py delete mode 100644 girok/commands/info.py delete mode 100644 girok/commands/task.py delete mode 100644 girok/config.json delete mode 100644 girok/server/requirements.txt delete mode 100644 girok/server/src/category/config.py delete mode 100644 girok/server/src/category/constants.py delete mode 100644 girok/server/src/category/dependencies.py delete mode 100644 girok/server/src/category/enums.py delete mode 100644 girok/server/src/category/exceptions.py delete mode 100644 girok/server/src/category/models.py delete mode 100644 girok/server/src/category/router.py delete mode 100644 girok/server/src/category/schemas.py delete mode 100644 girok/server/src/category/service.py delete mode 100644 girok/server/src/category/utils.py delete mode 100644 girok/server/src/database.py delete mode 100644 girok/server/src/task/config.py delete mode 100644 girok/server/src/task/constants.py delete mode 100644 girok/server/src/task/dependencies.py delete mode 100644 girok/server/src/task/enums.py delete mode 100644 girok/server/src/task/exceptions.py delete mode 100644 girok/server/src/task/models.py delete mode 100644 girok/server/src/task/router.py delete mode 100644 girok/server/src/task/schemas.py delete mode 100644 girok/server/src/task/service.py delete mode 100644 girok/server/src/task/utils.py delete mode 100644 girok/server/src/utils.py delete mode 100644 girok/styles/stopwatch03.css delete mode 100644 girok/utils/__init__.py delete mode 100644 girok/utils/auth.py delete mode 100644 girok/utils/calendar.py delete mode 100644 girok/utils/general.py create mode 100644 girok/utils/json_utils.py delete mode 100644 girok/utils/task.py diff --git a/.cz.toml b/.cz.toml new file mode 100644 index 0000000..09dbad0 --- /dev/null +++ b/.cz.toml @@ -0,0 +1,46 @@ +# ref: https://github.com/commitizen-tools/commitizen/blob/master/docs/customization.md + +[tool.commitizen] +name = "cz_customize" + +[tool.commitizen.customize] +message_template = "{{change_type}}: {{message}}" +example = "๐Ÿ“ docs: create README.md" +schema = ": " +# schema_pattern = "(feature|bug fix):(\\s.*)" +# schema_pattern = "^(?P):\\s(?P.*)?" +bump_pattern = "^(break|new|fix|hotfix)" +bump_map = {"break" = "MAJOR", "new" = "MINOR", "fix" = "PATCH", "hotfix" = "PATCH"} +change_type_order = ["BREAKING CHANGE", "Feat", "Fix", "Refactor", "Perf"] +# commit_parser = "^(?Pfeature|bug fix):\\s(?P.*)?" +# changelog_pattern = "^(โœจ feat|๐Ÿ”ง fix)?(!)?" +# change_type_map = {"โœจ feat" = "Feat", "๐Ÿ”ง fix" = "Fix"} + +[[tool.commitizen.customize.questions]] +type = "list" +name = "change_type" +choices = [ + {value = "โœจ feat", name = "โœจ feat: Work on feature-related tasks."}, + {value = "๐Ÿ”ง fix", name = "๐Ÿ”ง fix: Fix a bug."}, + {value = "๐Ÿ“ docs", name = "๐Ÿ“ docs: Add or update documentation."}, + {value = "โ™ป๏ธ refact", name = "โ™ป๏ธ refact: Refactor code."}, + {value = "๐ŸŽจ style", name = "๐ŸŽจ style: Improve structure / format of the code or apply linter."}, + {value = "โœ… test", name = "โœ… test: Add, update, or pass tests."}, + {value = "๐Ÿงน chore", name = "๐Ÿงน chore: Do other grunt works."}, + {value = "โš™๏ธ setting", name = "โš™๏ธ setting: Add or update setting related tasks such as modifying dependencies, writing util scripts, etc."}, + {value = "๐Ÿท๏ธ bump", name = "๐Ÿท๏ธ bump: Add a release / version tag."}, + {value = "๐ŸŽ‰ init", name = "๐ŸŽ‰ init: Create a new project."}, +] +message = "Select the type of change you are committing" + +[[tool.commitizen.customize.questions]] +type = "input" +name = "message" +message = "Write a commit message:" + +[[tool.commitizen.customize.questions]] +# type = "confirm" +# message = "Do you want to confirm this commit?" +type = "press_any_key_to_continue" +name = "confirm_commit" +message = "Press any key to confirm this commit, or ctrl+c key to cancel this commit." \ No newline at end of file diff --git a/.gitignore b/.gitignore index f1190bc..8ccd2cb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ **/node_modules/ /.pnp .pnp.js +**debug_** **/__pycache__ **/.env diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..0a59033 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.9.10 diff --git a/girok/README.md b/girok/README.md deleted file mode 100644 index fd54bc6..0000000 --- a/girok/README.md +++ /dev/null @@ -1,623 +0,0 @@ -

โœ๏ธ Girok - The most powerful CLI task manager

- -

Who said you cannot have a beautiful UI on terminal?

- -

- -**Girok**, which means "to record" in Korean, is a **powerful terminal-based task manager** which provides a multitude of scheduling operations that can be done in less than 10 seconds. It also supports **beautiful and responsive calendar GUI** in which you can move around with VIM key bindings. - -Girok is running on AWS server so you can **login from any device in the world**! - -Girok works fluently with `MacOS` and `Linux` users. It also works with `Windows` but some features and UIs might break. - -p.s) Since I launched the project a couple days ago, you might feel a little bit of lag for `showtask` operation, especially when you have many data. I'm working on optimizing the speed to enhance your experience! - -# ๐Ÿค– Version `0.1.11` is released now! - -To see the current version, enter `girok --version`. - -### Upgrade with `pip install girok --upgrade` - -# ๐Ÿ’ก Future Updates (coming soon) - -1. ๐Ÿ’ป ๐Ÿ“ฑ **Web** and **app** which synchronizing all data with CLI. -2. ๐Ÿ”จ Task operations in the calendar view -3. โฐ **Girok Slack Bot** notification feature for tasks - set an alarm in 5 - seconds! -4. ๐Ÿ‘ช **Team workspace** where users and create group and invite other people for collaboration - -# ๐Ÿ“– Table of Contents - -- [๐Ÿš€ Installation](#-Installation) -- [๐Ÿ”ฅ Get Started !](#-get-started) - - [๐Ÿ™ 1. help command](#helpcommand) - - [๐Ÿ”’ 2. Register](#register) - - [๐Ÿ”“ 3. Login / Logout](#loginandlogout) - - [๐Ÿ“š 4. Category commands](#categorycommands) - - [4.1. `showcat`](#showcatcommand) - - [4.2. `addcat`](#addcatcommand) - - [4.3. `mvcat`](#mvcatcommand) - - [4.4. `rmcat`](#rmcatcommand) - - [4.5. `rncat`](#rncatcommand) - - [๐Ÿ“• 5. Task Commands](#taskcommands) - - [5.1. `addtask`](#addtaskcommand) - - [5.2. `showtask`](#showtaskcommand) - - [5.3. `done`](#donecommand) - - [5.4. `chdate`](#chdatecommand) - - [5.5. `chpri`](#chpricommand) - - [5.6. `chtag`](#chtagcommand) - - [5.8. `chname`](#chnamecommand) - - [5.9. `showtag`](#showtagcommand) - - [๐Ÿ“… 6. Calendar Commands](#calendarcommands) -- [๐Ÿš’ Report Bugs](#-report-bugs) -- [๐Ÿ˜ญ Uninstall](#-uninstall) -- [๐Ÿ’Œ Contributions](#-contributions) - -# ๐Ÿš€ Installation - -Girok supports all operating systems including Linux, MacOS, Windows. - -However, it works well on Unix-based shells such as `bash`, `zsh`, `fish`, `wsl`, etc. - -Some shells like `powershell` might break some UIs. - -1. Make sure you have Python `>3.9` version installed. -2. Enter the following in your terminal - -```bash -pip install girok -``` - -Now you have installed `girok` on your machine. To make sure that it works, enter the following. - -```bash -girok --help -``` - -Now, let's dive into Girok! - -# ๐Ÿ”ฅ Get Started - -## ๐Ÿ™ 1. Help command - -In order to see **all the commands** of **Girok**, enter `girok --help` in your terminal. - -![](images/girok-help.png) - -In order to see the details of each command, enter the following in your terminal. - -``` -girok --help -``` - -For example, if you enter - -``` -girok addtask --help -``` - -then you'll see the following help message - -![](images/girok-command-help.png) - -## ๐Ÿ”’ 2. Register - -

- -To register a new account enter the following in your terminal. - -```bash -girok register -``` - -Enter the email address and password and check your mail inbox to get the **verification code**. - -Congratulations! Now let's go ahead and login to our account. - -## ๐Ÿ”“ 3. Login and Logout - -In order to login with your registered account, enter the following command. - -```bash -girok login -``` - -![](images/girok-login.png) - -Now you're ready to use all the features. - -## ๐Ÿ“š 4. Category Commands - -You can pre-define **categories** such as `School`, `Life` and `Career` with automatically assigned category color. - -Girok supports **infinite recursive subcategories**. All the subcategories will be assigned with the color of its topmost parent category. - -Later on, you can link tasks to these categories. - -### 4.1 `showcat` command - -In order to see all the categories you have created, enter the following command. - -By default, `No Category` category is set (later for tasks which have no category). - -```bash -girok showcat -``` - -![](images/girok-addcat3.png) - -### 4.2 `addcat` command - -`addtask` command takes a single argument `category full path`. - -In order to add a new category, enter the following command. - -```bash -girok addcat -``` - -The `` is the **full path including the new category name**. For example, if you want to add a **topmost category** named `Career`, then enter - -```bash -girok addcat Career -``` - -Then, you'll see the category tree with the newly created category being highlighted. - -![](images/girok-addcat1.png) - -In order to nest a sub-category under a previously defined category, pass the **FULL PATH** starting from the topmost category delimited by `/`, ending with the new category name. - -For example, if you want to create a new category named `Resume` under the previously created `Career` category, enter the following command. - -```bash -girok addcat Career/Resume -``` - -Then, you'll see `Resume` is created under `Career`. - -![](images/girok-addcat2.png) - -In this way, you can create as many categories and sub-categories as you want! - -### 4.3 `mvcat` command - -Now you might want to move a category under another category. - -In order to move a `category A` (recursively all its sub-categories) under `category B`, enter the following command. - -```bash -girok mvcat -``` - -For example, if you want to move the whole `Career` category under `Dev/Network` (for some weird reason), enter the following command. - -```bash -girok mvcat Career Dev/Network -``` - -![](images/girok-addcat4.png) - -If you want to move a category to the `root category`, then pass `/` as the second argument. Let's move `Dev/Network/Career` back to the topmost category. - -```bash -girok mvcat Dev/Network/Career / -``` - -![](images/girok-addcat5.png) - -### 4.4 `rmcat` command - -Of course, you want to delete a category. In that case, enter the following command. - -```bash -girok rmcat -``` - -Let's add a dummy category named `Dummy` under `Dev` then remove it. - -As you already know, enter - -```bash -girok addcat Career/Dummy -``` - -![](images/girok-addcat6.png) - -Now, let's delete it with the following command. - -**[WARNING]** If you delete a category, **all its sub-categories and tasks will be DELETED**. I'll consider adding an option for users to preserve all the orphan tasks in the future. Please let me know in the issue if you need this feature! - -```bash -girok rmcat Career/Dummy -``` - -Then, you'll be asked to confirm the deletion. Enter `y`. - -![](images/girok-rmcat1.png) - -### 4.5 `rncat` command - -To rename an existing category, - -```bash -girok rncat -``` - -Great job! Now let's move on to the task commands. - -## ๐Ÿ“• 5. Task commands - -**Girok** provides powerful task scheduling operations. You can perform different operations that would've taken a long time in other schedulers like Notion and Google Calendar in less than 10 seconds (If you get used to the commands). - -### 5.1 `addtask` command - -```bash -girok addtask [One of deadline date options] [-c | --category ] [-p | --priority ] [-t | --time ] [-T | --tag ] -``` - -It looks quite complicated but don't worry! Let's go through some rules. - -#### 5.1.1 `addtask` rules - -1. `` (Argument / **Required**) - If the task name has no space you can omit double quotes `""`. If it does, enclose the task name by double quotes `""` -2. `` (Option / **required**) - You must specify a **deadline "date"** of a task. There're many ways to add a deadline. Note that **ONLY ONE DATE OPTION** is allowed. - - `-d ` - - Specify an exact date delimited by `/`. You can enter the full date in the form of `yyyy/mm/dd`. Or, you can omit the year like `mm/dd` then the deadline year will be set to the current year. - - You don't have to enter the exact form filled with `0`s. If the month is May, then just enter `5/23` or `05/23`. - - `-t1 ~ -t7 | --thismon ~ --thissun` - - Sometimes, you're not aware of the exact date. If the date is some weekday of this week, you can just pass `-t{1-7}` referrting to this monday to this sunday (monday indexed as `1`). - - For example, if the deadline is this friday, enter `girok addtask "dummy" -t5` - - `-n1 ~ -n7 | --nextmon ~ --nextsun` - - Similar to the above but referring to **next week**. - - `-a <# days>` - - Sometimes, you process the deadline in your mind like "it's due 5 days later". - - In this case, pass the number of days a task is due after. - - For example, if the deadline is 5 days later, enter `girok addtask "dummy" -a 5` - - `--tdy` - - Set the deadline to today. - - `--tmr` - - Set the deadline to tomorrow. -3. `-t | --time ` (Option, **Optional**) - You can also set the specific deadline time. - - You must provide the full time format in **24 hour scale** such as `07:23` or `21:59`. -4. `-c | --category ` (Option / **Optional**) - Your tasks might belong to a specific category you have previously defined. - - Provide the **full category path**. - - For example, if your task belongs to `Career/Resume`, then enter `girok addtask "dummy task 1" --tmr -c Career/Resume`. - - If you specify a category, then the task color will be automatically linked to that category's color. - - If no category is provided, the task will belong to `No Category` category. -5. `-p | --priority ` (Option, **Optional**) - You can set the priority of a task so that you can filter out by priority when you query your tasks. - - For example, to set the priority of a task as `5`, enter `girok addtask "dummy task 1" -c Career/Resume -p 5`. -6. `-T | --tag ` (Option, **Optional**) - You can set the **tag**(or type) of a task such as `assignment` and `meeting`. With tags, you can more efficiently query your tasks with different types. - - Unlike category, tag doesn't allow nested tags and you don't have to pre-define them. - - For example, if you want to set the tag of a task as `assignment`, enter `girok addtask "assignment 4" -c HKU/COMP3234 -d 4/24 --tag assignment` - -In summary, keep the following rules in mind. - -1. Always provide **task name** and **one of date options**. -2. Although not required, I think it's better to provide **category** to manage your tasks more effectively. -3. Other options are up to you! - -For example, the following command is a typical command that I use on everyday basis. - -```bash -girok addtask "Implement tag filtering feature" -c Dev/Girok -a 3 -p 5 -``` - -It looks quite complicated, but you'll get used to it quickly after playing out little bit. - -#### 5.1.2 `addtask` demonstration - -Now let's play around with `addtask` command. - -Recall our category list is - -![](images/girok-addtask1.png) - -In the demonstration, I will add several tasks and show how it works. - -Let's add a task named `go over resume again` whose category is `Career/Resume` and I will do it by `next thursday`. This is a quite important task, so I will assign the `priority` of `5`. - -```bash -girok addtask "go over resume again" -c Career/Resume -n4 -p 5 -``` - -![](images/girok-addtask2.png) - -When adding it, you will see the same category tree with tasks attached to the belonged category. (Priority is now shown by default. You can see the priority with `girok showtask` command we'll talk about very soon). - -Now I'll add another task named `Midterm exam` with the category `HKU/COMP3234` and the deadline is `4/18 09:30`. Hmm.. I think I have plenty of time so I will not provide the priority. However, I will assign the tag `exam`. - -```bash -girok addtask "Midterm exam" -c HKU/COMP3234 -d 4/18 -t 09:30 --tag exam -``` - -![](images/girok-addtask3.png) - -In the tree view, priority and tag are not shown to avoid complexity. Don't worry! You can view all the information when we go into `girok showtask` command. - -Lastly, I'll add a task named `Hangout with Jason` and the appointment date is `tomorrow`. This time, I will not provide any option. - -```bash -girok addtask "Hangout with Jason" --tmr -``` - -![](images/girok-addtask4.png) - -Notice that the newly added task is highlighted with green color. - -### 5.2 `showtask` command. - -```bash -girok showtask [--tree] Deadline date options] [-c | --category ] [-p | --priority ] [-T | --tag ] -``` - -Girok provides powerful commands to effectively query your schedule with many different options. You can filter tasks by category, priority, deadline, and tag. - -#### 5.2.1 View options - -You can type `girok showtask` command with no parameter. The default view of the command is **list view**. - -Note that I've added some more tasks to make the visualization rich. - -```bash -girok showtask -``` - -![](images/girok-showtask1.png) - -By default, all tasks will be shown in a nice table format. - -If you want to view your tasks in a categorized manner, then provide `--tree` flag. - -```bash -girok showtask --tree -``` - -![](images/girok-showtask2.png) - -#### 5.2.2 Filter by category - -To query tasks under a specific category, use the following command, - -```bash -girok showtask -c -``` - -For example, to query tasks only for the `HKU` category. Enter the following command. - -```bash -girok showtask -c HKU -``` - -or - -```bash -girok showtask -c HKU --tree # tree view -``` - -![](images/girok-showtask5.png) - -#### 5.2.3 Filter by date options - -You can query your tasks filtering by many different date options. Notice that all the options for `showtask` command are **OPTIONAL**. - -1. `-e | --exact ` - - To view tasks due to a specific day, provide the exact date after the flag -2. `-d | --day <# days>` - - To view tasks due **within `n` days**, provide the number of days `n` after the flag -3. `-w | --week <# days>` - - To view tasks due **within `n` weeks**, provide the number of weeks `n` after the flag -4. `-m | --month <# days>` - - To view tasks due **within `n` months**, provide the number of months `n` after the flag -5. `--tdy` - - To view tasks due today. -6. `--tmr` - - To view tasks due within tomorrow (today && tomorrow) -7. `--tw`, `--nw` - - To view tasks due within this week and next week, respectively -8. `--tm`, `--nm` - - To view tasks due within this month and next month, respectively -9. `-t1 ~ -t7 | --thismon ~ --thissun` - - To view tasks due **exactly** the weekday of this week - - Monday is indexed as `1` -10. `-n1 ~ -n7 | --nextmon ~ --nextsun` - -- To view tasks due **exactly** the weekday of next week -- Monday is indexed as `1` - -11. `-u | --urgent` - -- To view urgent tasks that are within `3 days` by default - -#### 5.2.4 Filter by priority - -```bash -girok showtask -p -``` - -To view tasks with a specific priority, provide `-p` option followed by the priority number between `1` and `5`. - -For example, to view tasks with priority 5, enter the following command - -```bash -girok showtask -p 5 -``` - -![](images/girok-showtask3.png) - -To view tasks with priority 5, but with **tree view**, enter the following command. - -```bash -girok showtask -p 5 --tree -``` - -![](images/girok-showtask4.png) - -#### 5.2.5 Filter by tag - -``` -girok showtask [-T | --tag ] -``` - -### 5.3 `done` command - -To complete(delete) a task, provide the `done` command followed by the task ID. - -``` -girok done -``` - -**[IMPORTANT]** The **TASK ID** is the IDs you can see when you perform `showtask` operations. Note that the **ONLY the Task IDs of the LATEST showtask operation are valid**. In other words, if you consecutively type `girok showtask` and `girok showtask -p 5` but try to delete a task with the task IDs shown in the table of the first `girok showtask` command, you might delete an unexpected task!! - -For example, suppose you enter `girok showtask` command. - -![](images/girok-donetask1.png) - -If you completed the task `Migrate DB to RDS` under `Dev/Girok` category, provide the task ID at the leftmost column. - -```bash -girok done 5 -``` - -![](images/girok-donetask2.png) - -Notice that the task is now striked out. - -### 5.4 `chdate` command - -To change the date of an existing task, enter the following command. - -```bash -girok chdate -``` - -### 5.5 `chpri` command - -To change the priority of an existing task, enter the following command. - -```bash -girok chpri -``` - -### 5.6 `chtag` command - -To change the tag of an existing task, enter the following command. - -```bash -girok chtag -``` - -### 5.7 `chname` command - -To change the name of an existing task, enter the following command. - -```bash -girok chname -``` - -### 5.8 `showtag` command - -To view all the tags you have created so far, enter the following command. - -```bash -girok showtag -``` - -## ๐Ÿ“… 6. Calendar Commands - -The beauty of **Girok** is the **beautiful and responsive full calendar GUI**. - -![](images/girok-cal1.png) - -To fire up the calendar, enter the following command - -``` -girok cal -``` - -Then you'll be prompted to the calendar GUI. - -**girokcal** offers a beautiful but minimalistic GUI in which you can move around with (not exactly same but similar) **VIM key bindings**. - -Notice that all the categories and tags we have created so far are linked to the **sidebar**. - -### 6.1 Moving around calendar - -![](images/girok-cal8.png) - -Upon `girok cal` command, the starting **"focus"** is the **category tree**. - -- Select a category/tag - - `o` - select the current category/tag -- Move inside **category tree** or **tag tree** - - `j` - down - - `k` - up -- Move from **category tree** to **tag tree** - - `ctrl + j` -- Move from **tag tree** to \*\_category tree - - `ctrl + k` -- Moving from **category tree** or **tag tree** to **calendar** - - `e` -- Moving from **calendar** to back **sidebar** (to category tree by default) - - `w` -- Move inside the **calendar** - - `h` - left - - `j` - down - - `k` - up - - `l` - right -- Select a **calendar cell** (day) to view details - - `o` - select the currently focused calendar cell -- **Next month** - - `i` -- **Previous month** - - `u` -- **Direct to the current month** - - `y` -- **Toggle side bar** - - `f` -- **Close calendar** - - `q` - -### 6.2 Calendar Demonstrations - -When you click on a category, then the category title will change accordingly at the left-bottom corner of the calendar. All the tasks belonging to the selected category will be shown. - -Let's select `HKU` category by pressing alphabet `o` key. - -![](images/girok-cal4.png) - -Notice that only the tasks with the yellow dots (HKU category color) are shown. - -Now let's select `All Categories` and change our focus from the **category tree** to the **tag tree** by pressing `ctrl+j` key. Then, select `exam` tag. - -![](images/girok-cal5.png) - -Yay! I don't have any exams this month. - -But.. do I have exams next month? To check it out, let's press `i` to move to the next month. - -![](images/girok-cal6.png) - -Lastly, let's press `f` to close the sidebar to have make the calendar bigger. - -![](images/girok-cal7.png) - -Great job! Now, it's time to explore all the features of **Girok** on your own!! - -# ๐Ÿš’ Report Bugs - -The first version of **Girok** was released just a couple days ago so you might encounter some bugs and errors. - -If so, I'd greatly appreciate if you report them as raising `issues` then I will respond and update as soon as possible!! - -# ๐Ÿ˜ญ Uninstall - -I'm sorry that there's no way to uninstall this package. - -Just kidding. Enter `pip uninstall girok` in your terminal. Bye..๐Ÿ˜ข - -# ๐Ÿ’Œ Contribute to the project - -If you have any new features that would make your life easier, please don't hesitate to raise issues. - -If you wish to contribute to the project as a programmer, drop pull requests and I will review each of them carefully! diff --git a/girok/__init__.py b/girok/__init__.py index 970659c..e69de29 100644 --- a/girok/__init__.py +++ b/girok/__init__.py @@ -1 +0,0 @@ -__version__ = "0.1.16" diff --git a/girok/api/auth.py b/girok/api/auth.py index 9240fd7..221d5a2 100644 --- a/girok/api/auth.py +++ b/girok/api/auth.py @@ -1,54 +1,85 @@ +from urllib.parse import urljoin + import requests -from girok.config import get_config -import girok.utils.general as general_utils -import girok.utils.display as display_utils - -cfg = get_config() - -base_url = cfg.base_url - -def register(email, password): - resp = requests.post(base_url + "/register", json={ - "email": email, - "password": password - }) - return resp - -def verify_verification_code(email, verification_code): - resp = requests.post(base_url + "/register/verification_code", json={ - "email": email, - "verification_code": verification_code - }) - if resp.status_code == 200: - return True - elif resp.status_code == 401: - err_msg = general_utils.bytes2dict(resp.content)['detail'] - # display_utils.center_print(err_msg, type="error") - return False - else: - err_msg = general_utils.bytes2dict(resp.content)['detail'][0]['msg'] - # print(err_msg) - # display_utils.center_print(str(err_msg), type="error") - return False - - -def login(email, password): - files = { - "username": (None, email), - "password": (None, password) - } - - resp = requests.post(base_url + "/login", files=files) - return resp - - -def validate_access_token(access_token): - options = { - "headers": { - "Authorization": "Bearer " + access_token, - } - } - - - resp = requests.get(base_url + "/validate-access-token", headers=options['headers']) - return resp \ No newline at end of file +from requests import HTTPError + +from girok.api.entity import APIResponse +from girok.constants import BASE_URL + + +def verify_access_token(access_token: str) -> bool: + resp = requests.get( + url=urljoin(BASE_URL, "auth/verify/access-token"), + headers={"Authorization": "Bearer " + access_token}, + ) + return resp.status_code == 200 + + +def send_verification_code(email: str) -> APIResponse: + resp = requests.post( + url=urljoin(BASE_URL, "auth/verification-code"), json={"email": email} + ) + + try: + resp.raise_for_status() + return APIResponse(is_success=True) + except HTTPError as e: + error_body = resp.json() + error_message = error_body["message"] + return APIResponse(is_success=False, error_message=error_message) + + +def verify_verification_code(email: str, verification_code: str) -> APIResponse: + resp = requests.post( + url=urljoin(BASE_URL, "auth/verification-code/check"), + json={"email": email, "verificationCode": verification_code}, + ) + + try: + resp.raise_for_status() + return APIResponse(is_success=True) + except HTTPError as e: + error_body = resp.json() + error_message = error_body["message"] + return APIResponse(is_success=False, error_message=error_message) + + +def register(email: str, verification_code: str, password: str) -> APIResponse: + resp = requests.post( + url=urljoin(BASE_URL, "sign-up"), + json={ + "email": email, + "verificationCode": verification_code, + "password": password, + }, + ) + + try: + resp.raise_for_status() + return APIResponse(is_success=True) + except HTTPError as e: + try: + error_body = resp.json() + error_message = error_body["message"] + except: + error_message = "Registration failed" + + return APIResponse(is_success=False, error_message=error_message) + + +def login(email: str, password: str) -> APIResponse: + resp = requests.post( + url=urljoin(BASE_URL, "login"), json={"email": email, "password": password} + ) + + try: + resp.raise_for_status() + return APIResponse(is_success=True, body=resp.json()) + except HTTPError as e: + try: + error_body = resp.json() + error_message = error_body["message"] + except: + error_message = "Login failed" + + return APIResponse(is_success=False, error_message=error_message) diff --git a/girok/api/category.py b/girok/api/category.py index 36dffbf..35c7b2b 100644 --- a/girok/api/category.py +++ b/girok/api/category.py @@ -1,249 +1,28 @@ -import requests -from rich.console import Console - -from girok.config import get_config -import girok.utils.auth as auth_utils -import girok.utils.general as general_utils -import girok.utils.display as display_utils -import girok.constants as constants - -# Guest mode imports -import girok.server.src.category.router as category_router - -console = Console() -cfg = get_config() - -def get_categories(): - mode = auth_utils.get_mode(cfg.config_path) - if mode == "user": - resp = requests.get( - cfg.base_url + "/categories", - headers=auth_utils.build_jwt_header(cfg.config_path) - ) - if resp.status_code == 200: - return general_utils.bytes2dict(resp.content) - elif mode == "guest": - resp = category_router.get_all_categories() - return resp - - -def add_category(cat_str: str, color=None): - mode = auth_utils.get_mode(cfg.config_path) - cats = cat_str.split('/') - if mode == "user": - resp = requests.post( - cfg.base_url + "/categories", - json={ - "names": cats, - "color": color - }, - headers=auth_utils.build_jwt_header(cfg.config_path) - ) - if resp.status_code == 201: - display_utils.center_print("Category added successfully!", type="success") - cats_dict = get_categories() - display_utils.display_categories(cats_dict, highlight_cat=cat_str) - elif resp.status_code == 400: - err_msg = general_utils.bytes2dict(resp.content)['detail'] - display_utils.center_print(err_msg, type="error") - else: - print(resp) - return resp - elif mode == "guest": - resp = category_router.create_category({"names": cats, "color": color}) - if resp['success']: - display_utils.center_print("Category added successfully!", type="success") - cats_dict = get_categories() - display_utils.display_categories(cats_dict, highlight_cat=cat_str) - else: - display_utils.center_print(resp['detail'], type="error") - - -def remove_category(cat_str: str): - mode = auth_utils.get_mode(cfg.config_path) - cats = cat_str.split('/') - if mode == "user": - resp = requests.delete( - cfg.base_url + "/categories", - json={ - "cats": cats - }, - headers=auth_utils.build_jwt_header(cfg.config_path) - ) - if resp.status_code == 204: - display_utils.center_print(f"Deleted {cat_str} successfully.", type="success") - cats_dict = get_categories() - display_utils.display_categories(cats_dict) - elif resp.status_code == 400: - err_msg = general_utils.bytes2dict(resp.content)['detail'] - display_utils.center_print(err_msg, type="error") - else: - display_utils.center_print(resp.content, type="error") - elif mode == "guest": - resp = category_router.delete_category({"cats": cats}) - if resp['success']: - display_utils.center_print(f"Deleted {cat_str} successfully.", type="success") - cats_dict = get_categories() - display_utils.display_categories(cats_dict) - else: - display_utils.center_print(resp['detail'], type="error") - - -def rename_category(cat_str: str, new_name: str): - mode = auth_utils.get_mode(cfg.config_path) - cats = cat_str.split('/') - - if mode == "user": - resp = requests.patch( - cfg.base_url + "/categories/name", - json={ - "cats": cats, - "new_name": new_name - }, - headers=auth_utils.build_jwt_header(cfg.config_path) - ) - if resp.status_code == 204: - new_cat = '/'.join(cat_str.split('/')[:-1] + [new_name]) - display_utils.center_print(f"Successfully renamed {cat_str} to {new_cat}.", type="success") - cats_dict = get_categories() - display_utils.display_categories(cats_dict, highlight_cat=new_cat) - elif resp.status_code == 400: - err_msg = general_utils.bytes2dict(resp.content)['detail'] - display_utils.center_print(err_msg, type="error") - else: - display_utils.center_print(resp.content, type="error") - elif mode == "guest": - resp = category_router.rename_category({ - "cats": cats, - "new_name": new_name - }) - if resp['success']: - new_cat = '/'.join(cat_str.split('/')[:-1] + [new_name]) - display_utils.center_print(f"Successfully renamed {cat_str} to {new_cat}.", type="success") - cats_dict = get_categories() - display_utils.display_categories(cats_dict, highlight_cat=new_cat) - else: - display_utils.center_print(resp['detail'], type="error") +from urllib.parse import urljoin - -def move_category(cat_str: str, new_parent_cat_str: str): - mode = auth_utils.get_mode(cfg.config_path) - if cat_str.endswith('/'): - cat_str = cat_str[:-1] - if new_parent_cat_str.endswith('/'): - new_parent_cat_str = new_parent_cat_str[:-1] - - cats = cat_str.split('/') if cat_str else [] - new_parent_cats = new_parent_cat_str.split('/') if new_parent_cat_str else [] - - if mode == "user": - resp = requests.patch( - cfg.base_url + "/categories/parent", - json={ - "cats": cats, - "new_parent_cats": new_parent_cats - }, - headers=auth_utils.build_jwt_header(cfg.config_path) - ) - if resp.status_code == 200: - new_cat = '/'.join(new_parent_cat_str.split('/') + [cat_str.split('/')[-1]]) - display_utils.center_print(f"Successfully moved {cat_str} to {new_parent_cat_str}/.", type="success") - cats_dict = get_categories() - display_utils.display_categories(cats_dict, highlight_cat=new_cat) - elif resp.status_code == 400: - err_msg = general_utils.bytes2dict(resp.content)['detail'] - display_utils.center_print(err_msg, type="error") - else: - display_utils.center_print(resp.content, type="error") - return resp - elif mode == "guest": - resp = category_router.move_category({ - "cats": cats, - "new_parent_cats": new_parent_cats - }) - if resp['success']: - new_cat = '/'.join(new_parent_cat_str.split('/') + [cat_str.split('/')[-1]]) - display_utils.center_print(f"Successfully moved {cat_str} to {new_parent_cat_str}/.", type="success") - cats_dict = get_categories() - display_utils.display_categories(cats_dict, highlight_cat=new_cat) - else: - display_utils.center_print(resp['detail'], type="error") - - -def get_last_cat_id(cats: list): - mode = auth_utils.get_mode(cfg.config_path) - if mode == "user": - resp = requests.get( - cfg.base_url + "/categories/last-cat-id", - json={ - "cats": cats - }, - headers=auth_utils.build_jwt_header(cfg.config_path) - ) - if resp.status_code == 200: - return general_utils.bytes2dict(resp.content)['cat_id'] - elif resp.status_code == 400: - err_msg = general_utils.bytes2dict(resp.content)['detail'] - display_utils.center_print(err_msg, type="error") - exit(0) - else: - display_utils.center_print(resp.content, type="error") - exit(0) - elif mode == "guest": - resp = category_router.get_last_cat_id({"cats": cats}) - if resp['success']: - return resp['cat_id'] - else: - display_utils.center_print(resp['detail'], type="error") - exit(0) - - - -def get_category_color(cat_id: int): - mode = auth_utils.get_mode(cfg.config_path) - if mode == "user": - resp = requests.get( - cfg.base_url + f"/categories/{cat_id}/color", - headers=auth_utils.build_jwt_header(cfg.config_path) - ) - - if resp.status_code == 200: - return general_utils.bytes2dict(resp.content)['color'] - elif resp.status_code == 400: - err_msg = general_utils.bytes2dict(resp.content)['detail'] - display_utils.center_print(err_msg, type="error") - exit(0) - else: - display_utils.center_print(resp.content, type="error") - exit(0) - elif mode == "guest": - resp = category_router.get_category_color(cat_id) - if resp['success']: - return resp['color'] - else: - display_utils.center_print(resp.content, type="error") - exit(0) - -def get_color_dict(): - mode = auth_utils.get_mode(cfg.config_path) - if mode == "user": - resp = requests.get( - cfg.base_url + "/categories/color", - headers=auth_utils.build_jwt_header(cfg.config_path) - ) - if resp.status_code == 200: - color_dict = general_utils.bytes2dict(resp.content) - return color_dict - elif resp.status_code == 400: - err_msg = general_utils.bytes2dict(resp.content)['detail'] - display_utils.center_print(err_msg, type="error") - else: - display_utils.center_print("Error occurred.", type="error") - elif mode == "guest": - resp = category_router.get_category_colors_dict() - if resp['success']: - return resp['colors'] - else: - display_utils.center_print("Error occurred.", type="error") - exit(0) - \ No newline at end of file +import requests +from requests import HTTPError + +from girok.api.entity import APIResponse +from girok.auth_handler import AuthHandler +from girok.constants import BASE_URL + + +def get_all_categories() -> APIResponse: + access_token = AuthHandler.get_access_token() + resp = requests.get( + url=urljoin(BASE_URL, "categories"), + headers={"Authorization": "Bearer " + access_token}, + ) + + try: + resp.raise_for_status() + return APIResponse(is_success=True, body=resp.json()) + except HTTPError as e: + try: + error_body = resp.json() + error_message = error_body["message"] + except: + error_message = "Operation failed" + + return APIResponse(is_success=False, error_message=error_message) diff --git a/girok/api/entity.py b/girok/api/entity.py new file mode 100644 index 0000000..73adc54 --- /dev/null +++ b/girok/api/entity.py @@ -0,0 +1,18 @@ +from dataclasses import dataclass + + +@dataclass +class APIResponse: + is_success: bool + body: dict = None + error_message: str = None + + def __post_init__(self): + if self.is_success: + if self.error_message: + raise ValueError("Success response should not have an error message.") + else: + if not self.error_message: + raise ValueError("Failure response requires an error message.") + if self.body is not None: + raise ValueError("Failure response should not have a body.") diff --git a/girok/api/task.py b/girok/api/task.py deleted file mode 100644 index 32d9180..0000000 --- a/girok/api/task.py +++ /dev/null @@ -1,291 +0,0 @@ -from typing import Union -from textual import log -from datetime import datetime -import requests - -from girok.config import get_config -import girok.utils.auth as auth_utils -import girok.utils.display as display_utils -import girok.utils.general as general_utils -import girok.utils.task as task_utils -import girok.constants as constants - -import girok.server.src.task.router as task_router - -cfg = get_config() - -def create_task(task_data: dict): - mode = auth_utils.get_mode(cfg.config_path) - if mode == "user": - resp = requests.post( - cfg.base_url + "/tasks", - json=task_data, - headers=auth_utils.build_jwt_header(cfg.config_path) - ) - if resp.status_code == 201: - task = general_utils.bytes2dict(resp.content) - task_id = task['task_id'] - return task_id - elif resp.status_code == 400: - err_msg = general_utils.bytes2dict(resp.content)['detail'] - display_utils.center_print(err_msg, constants.DISPLAY_TERMINAL_COLOR_ERROR) - exit(0) - else: - display_utils.center_print("Error occurred.", constants.DISPLAY_TERMINAL_COLOR_ERROR) - exit(0) - elif mode == "guest": - resp = task_router.create_task(task_data) - if resp['success']: - task = resp['new_task'] - task_id = task['task_id'] - return task_id - else: - display_utils.center_print(resp['detail'], type="error") - exit(0) - exit(0) - - -def get_single_task(task_id: int): - mode = auth_utils.get_mode(cfg.config_path) - if mode == "user": - resp = requests.get( - cfg.base_url + f"/tasks/{task_id}", - headers=auth_utils.build_jwt_header(cfg.config_path), - ) - if resp.status_code == 200: - task = general_utils.bytes2dict(resp.content) - return task - elif resp.status_code == 400: - err_msg = general_utils.bytes2dict(resp.content)['detail'] - display_utils.center_print(err_msg, type="error") - else: - display_utils.center_print(resp.content, type="error") - elif mode == "guest": - resp = task_router.get_single_task(task_id) - if resp['success']: - task = resp['task'] - return task - else: - display_utils.center_print(resp['detail'], type="error") - exit(0) - - -def get_tasks( - cats: Union[list, None] = None, - start_date: Union[str, None] = None, - end_date: Union[str, None] = None, - tag: Union[str, None] = None, - priority: Union[int, None] = None, - no_priority: bool = False, - view: str = "category" -): - mode = auth_utils.get_mode(cfg.config_path) - query_str_obj = { - "category": cats, - "start_date": start_date, - "end_date": end_date, - "tag": tag, - "priority": priority, - "no_priority": no_priority, - "view": view - } - if mode == "user": - resp = requests.get( - cfg.base_url + "/tasks", - headers=auth_utils.build_jwt_header(cfg.config_path), - params=query_str_obj - ) - if resp.status_code == 200: - return general_utils.bytes2dict(resp.content)['tasks'] - if resp.status_code == 400: - err_msg = general_utils.bytes2dict(resp.content)['detail'] - display_utils.center_print(err_msg, type="error") - exit(0) - elif resp.status_code: - display_utils.center_print("Error occurred.", type="title") - exit(0) - elif mode == "guest": - resp = task_router.get_tasks(query_str_obj) - if resp['success']: - return resp['tasks'] - else: - display_utils.center_print(resp['detail'], type="error") - - exit(0) - - -def remove_task(task_id: int): - mode = auth_utils.get_mode(cfg.config_path) - if mode == "user": - resp = requests.delete( - cfg.base_url + f"/tasks/{task_id}", - headers=auth_utils.build_jwt_header(cfg.config_path), - ) - if resp.status_code == 204: - return True - elif resp.status_code == 400: - err_msg = general_utils.bytes2dict(resp.content)['detail'] - display_utils.center_print(err_msg, type="error") - else: - display_utils.center_print(resp.content, type="error") - elif mode == "guest": - resp = task_router.delete_task(task_id) - if resp['success']: - return True - else: - display_utils.center_print(resp['detail'], type="error") - exit(0) - -def get_tags(): - mode = auth_utils.get_mode(cfg.config_path) - if mode == "user": - resp = requests.get( - cfg.base_url + "/tasks/tags", - headers=auth_utils.build_jwt_header(cfg.config_path) - ) - if resp.status_code == 200: - return general_utils.bytes2dict(resp.content)['tags'] - elif resp.status_code == 400: - err_msg = general_utils.bytes2dict(resp.content)['detail'] - display_utils.center_print(err_msg, type="error") - exit(0) - else: - exit(0) - elif mode == "guest": - resp = task_router.get_tags() - if resp['success']: - return resp['tags'] - else: - display_utils.center_print(resp['detail'], type="error") - exit(0) - - -def change_task_tag(task_id: int, new_tag_name: str): - mode = auth_utils.get_mode(cfg.config_path) - if mode == "user": - resp = requests.patch( - cfg.base_url + f"/tasks/{task_id}/tag", - headers=auth_utils.build_jwt_header(cfg.config_path), - json={ - "new_tag_name": new_tag_name - } - ) - - if resp.status_code == 200: - task = general_utils.bytes2dict(resp.content) - task_name = task['name'] - display_utils.center_print(f"Successfully changed [{task_name}]'s tag to {new_tag_name}.", type="success") - return - elif resp.status_code == 400: - err_msg = general_utils.bytes2dict(resp.content)['detail'] - display_utils.center_print(err_msg, type="error") - else: - display_utils.center_print(resp.content, type="error") - elif mode == "guest": - resp = task_router.change_task_tag(task_id, {"new_tag_name": new_tag_name}) - if resp['success']: - task = resp['updated_task'] - task_name = task['name'] - display_utils.center_print(f"Successfully changed [{task_name}]'s tag to {new_tag_name}.", type="success") - return - else: - display_utils.center_print(resp['detail'], constants.DISPLAY_TERMINAL_COLOR_ERROR) - exit(0) - - -def change_task_priority(task_id: int, new_priority: int): - mode = auth_utils.get_mode(cfg.config_path) - if mode == "user": - resp = requests.patch( - cfg.base_url + f"/tasks/{task_id}/priority", - headers=auth_utils.build_jwt_header(cfg.config_path), - json={ - "new_priority": new_priority - } - ) - - if resp.status_code == 200: - task = general_utils.bytes2dict(resp.content) - task_name = task['name'] - display_utils.center_print(f"Successfully changed [{task_name}]'s priority to {new_priority}.", type="success") - return - elif resp.status_code == 400: - err_msg = general_utils.bytes2dict(resp.content)['detail'] - display_utils.center_print(err_msg, type="error") - else: - display_utils.center_print(resp.content, type="error") - elif mode == "guest": - resp = task_router.change_task_priority(task_id, {"new_priority": new_priority}) - if resp['success']: - task = resp['updated_task'] - task_name = task['name'] - display_utils.center_print(f"Successfully changed [{task_name}]'s priority to {new_priority}.", type="success") - return - else: - display_utils.center_print(resp['detail'], type="error") - exit(0) - - - -def change_task_name(task_id: int, new_name: str): - mode = auth_utils.get_mode(cfg.config_path) - if mode == "user": - resp = requests.patch( - cfg.base_url + f"/tasks/{task_id}/name", - headers=auth_utils.build_jwt_header(cfg.config_path), - json={ - "new_name": new_name - } - ) - - if resp.status_code == 200: - task = general_utils.bytes2dict(resp.content) - task_name = task['name'] - display_utils.center_print(f"Successfully changed [{task_name}]'s name to {new_name}.", type="success") - return - elif resp.status_code == 400: - err_msg = general_utils.bytes2dict(resp.content)['detail'] - display_utils.center_print(err_msg, constants.DISPLAY_TERMINAL_COLOR_ERROR) - else: - display_utils.center_print(resp.content, constants.DISPLAY_TERMINAL_COLOR_ERROR) - elif mode == "guest": - resp = task_router.change_task_name(task_id, {"new_name": new_name}) - if resp['success']: - task = resp['updated_task'] - task_name = task['name'] - display_utils.center_print(f"Successfully changed [{task_name}]'s name to {new_name}.", type="success") - return - else: - display_utils.center_print(resp['detail'], constants.DISPLAY_TERMINAL_COLOR_ERROR) - exit(0) - - -def change_task_date(task_id: int, new_date: str): - mode = auth_utils.get_mode(cfg.config_path) - if mode == "user": - resp = requests.patch( - cfg.base_url + f"/tasks/{task_id}/date", - headers=auth_utils.build_jwt_header(cfg.config_path), - json={ - "new_date": new_date - } - ) - - if resp.status_code == 200: - task = general_utils.bytes2dict(resp.content) - task_name = task['name'] - display_utils.center_print(f"Successfully changed [{task_name}]'s date to {new_date.split()[0]}.", type="success") - elif resp.status_code == 400: - err_msg = general_utils.bytes2dict(resp.content)['detail'] - display_utils.center_print(err_msg, type="error") - else: - display_utils.center_print(resp.content, type="error") - elif mode == "guest": - resp = task_router.change_task_date(task_id, {"new_date": new_date}) - if resp['success']: - task = resp['updated_task'] - task_name = task['name'] - display_utils.center_print(f"Successfully changed [{task_name}]'s date to {new_date.split()[0]}.", type="success") - else: - display_utils.center_print(resp['detail'], type="error") - exit(0) \ No newline at end of file diff --git a/girok/api_client.py b/girok/api_client.py new file mode 100644 index 0000000..7ed7eab --- /dev/null +++ b/girok/api_client.py @@ -0,0 +1,10 @@ +from urllib.parse import urljoin + +import requests + +from girok.constants import BASE_URL + +# class APIClient: + +# @classmethod +# def post(endpoint: str, ) diff --git a/girok/auth_handler.py b/girok/auth_handler.py new file mode 100644 index 0000000..5bb427b --- /dev/null +++ b/girok/auth_handler.py @@ -0,0 +1,57 @@ +import os + +import girok.api.auth as auth_api +from girok.constants import APP_DIR, CONFIG_PATH +from girok.utils.json_utils import read_json, update_json, write_json + + +class AuthHandler: + def __init__(self): + pass + + @classmethod + def init(cls) -> None: + # Ensure application directory exists + if not os.path.isdir(APP_DIR): + os.makedirs(APP_DIR) + + # Ensure config.json exists + if not os.path.exists(CONFIG_PATH): + write_json(CONFIG_PATH, {}) + + @classmethod + def is_logged_in(cls) -> bool: + # Ensure config.json exists + if not is_config_exist(): + return False + + # Ensure access_token is present + cfg = read_json(CONFIG_PATH) + if "access_token" not in cfg: + return False + + # Ensure access_token is valid + access_token = cfg["access_token"] + return auth_api.verify_access_token(access_token) + + @classmethod + def login(cls, access_token: str) -> None: + update_json(CONFIG_PATH, {"access_token": access_token}) + + @classmethod + def logout(cls) -> None: + cfg = read_json(CONFIG_PATH) + if "access_token" in cfg: + del cfg["access_token"] + write_json(CONFIG_PATH, cfg) + + @classmethod + def get_access_token(cls) -> str: + cfg = read_json(CONFIG_PATH) + if "access_token" not in cfg: + raise ValueError("Access token not found.") + return cfg["access_token"] + + +def is_config_exist(): + return os.path.exists(CONFIG_PATH) diff --git a/girok/calendar_cli/calendar_app.py b/girok/calendar_cli/calendar_app.py deleted file mode 100644 index 443aba6..0000000 --- a/girok/calendar_cli/calendar_app.py +++ /dev/null @@ -1,37 +0,0 @@ -from textual.app import App, ComposeResult -from textual.containers import Container, Horizontal, Vertical -from textual.widgets import Button, Footer, Header, Static, Label, Placeholder, Tree -from textual.messages import Message -from textual.widget import Widget - -import girok.api.category as category_api -import girok.utils.calendar as calendar_utils -from rich.table import Table -from rich.style import Style -from textual import log - -from girok.calendar_cli.sidebar import SidebarContainer, CategoryTree -from girok.calendar_cli.calendar_container import CalendarContainer -import girok.constants as constants - -class CalendarApp(Horizontal): - CSS_PATH = "./demo_dock.css" - - def compose(self): - yield SidebarContainer(id="sidebar-container") - yield CalendarContainer(id="calendar-container") - - def on_category_tree_category_changed(self, event): - self.query_one(CalendarContainer).update_cat_path(event.cat_path) - cat_tree = self.query_one(CategoryTree) - - def on_tag_tree_tag_changed(self, event): - tag = event.tag - if tag.endswith(" " + constants.LEFT_ARROW_EMOJI): - tag = tag[:-2] - if tag == "All Tags": - tag = "" - self.query_one(CalendarContainer).update_tag(tag) - - def on_category_tree_custom_test_message(self, event): - self.refresh() \ No newline at end of file diff --git a/girok/calendar_cli/calendar_container.py b/girok/calendar_cli/calendar_container.py deleted file mode 100644 index 6003542..0000000 --- a/girok/calendar_cli/calendar_container.py +++ /dev/null @@ -1,315 +0,0 @@ -import asyncio -from datetime import datetime, timedelta -import calendar - -from textual.app import App, ComposeResult -from textual.containers import Container, Horizontal, Vertical -from textual.widgets import Button, Footer, Header, Static, Label, Placeholder, Tree, DataTable -from textual.messages import Message -from textual.widget import Widget -from textual.reactive import var -from rich.text import Text -from rich.style import Style -from rich.segment import Segment -from rich.panel import Panel -from rich.markdown import Markdown -from textual import log - -import girok.api.task as task_api -import girok.api.category as category_api -import girok.utils.calendar as calendar_utils -import girok.utils.general as general_utils -import girok.utils.task as task_utils -import girok.constants as constants -from girok.calendar_cli.sidebar import CategoryTree, TagTree - - -class WeekdayBarContainer(Horizontal): - pass - -class CalendarHeader(Vertical): - year = datetime.now().year - month = datetime.now().month - cat_path = "" - tag = "" - - def on_mount(self): - self.display_date() - - def compose(self): - month_name = task_utils.get_month_name_by_number(self.month) - - with Horizontal(): - with Container(id="calendar-header-category-container"): - yield Static(self.cat_path, id="calendar-header-category") - with Container(id="calendar-header-date-container"): - yield Static(Text(f"{month_name} {self.year}", style=Style(color=constants.CALENDAR_HEADER_DATE_COLOR, bold=True)), id="calendar-header-date") - with Container(id="calendar-header-tag-container"): - yield Static(f"{self.tag}", id="calendar-header-tag") - yield Horizontal() - with WeekdayBarContainer(id="weekday-bar"): - yield Static(Text("Monday", style=Style(color=constants.CALENDAR_WEEKDAY_NAME_COLOR, bold=True)), classes="calendar-weekday-name") - yield Static(Text("Tuesday", style=Style(color=constants.CALENDAR_WEEKDAY_NAME_COLOR, bold=True)), classes="calendar-weekday-name") - yield Static(Text("Wednesday", style=Style(color=constants.CALENDAR_WEEKDAY_NAME_COLOR, bold=True)), classes="calendar-weekday-name") - yield Static(Text("Thursday", style=Style(color=constants.CALENDAR_WEEKDAY_NAME_COLOR, bold=True)), classes="calendar-weekday-name") - yield Static(Text("Friday", style=Style(color=constants.CALENDAR_WEEKDAY_NAME_COLOR, bold=True)), classes="calendar-weekday-name") - yield Static(Text("Saturday", style=Style(color="#87C5FA", bold=True)), classes="calendar-weekday-name") - yield Static(Text("Sunday", style=Style(color="#DB4455", bo