Skip to content

Commit

Permalink
Attendance command
Browse files Browse the repository at this point in the history
  • Loading branch information
el7cosmos committed Sep 17, 2020
1 parent 12b8d00 commit 57a3e77
Show file tree
Hide file tree
Showing 10 changed files with 222 additions and 8 deletions.
19 changes: 19 additions & 0 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Publish

on:
push:
tags:
- '*'

env:
CARGO_TERM_COLOR: always
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- run: cargo publish
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:
- name: Archive
run: |
cp target/${{ matrix.target }}/release/${{ matrix.bin }} .
tar czf talenta.tar.gz ${{ matrix.bin }} README.md
tar czf talenta.tar.gz ${{ matrix.bin }} LICENSE-APACHE LICENSE-MIT README.md
- name: Upload Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "talenta"
version = "0.1.0"
version = "0.2.0"
authors = ["el7cosmos <[email protected]>"]
edition = "2018"
description = "Talenta CLI"
Expand All @@ -10,6 +10,10 @@ license = "MIT OR Apache-2.0"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

exclude = [
".github/*"
]

[dependencies]
ansi_term = "0.12"
chrono = "0.4"
Expand Down
5 changes: 5 additions & 0 deletions src/attendance/checkin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use structopt::StructOpt;

#[derive(Default, Debug, StructOpt)]
#[structopt(about = "Request checkin only attendance")]
pub(super) struct Checkin {}
5 changes: 5 additions & 0 deletions src/attendance/checkout.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use structopt::StructOpt;

#[derive(Default, Debug, StructOpt)]
#[structopt(about = "Request checkout only attendance")]
pub(super) struct Checkout {}
117 changes: 117 additions & 0 deletions src/attendance/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
mod checkin;
mod checkout;

use crate::attendance::checkin::Checkin;
use crate::attendance::checkout::Checkout;
use crate::client::Client;
use crate::config::Config;
use crate::date::Date;
use crate::time::Time;
use crate::Command;
use ansi_term::Colour;
use dialoguer::theme::ColorfulTheme;
use dialoguer::Input;
use reqwest::StatusCode;
use structopt::clap::{Error, ErrorKind};
use structopt::StructOpt;

#[derive(Debug, StructOpt)]
enum AttendanceCommand {
Checkin(Checkin),
Checkout(Checkout),
}

#[derive(Default, StructOpt)]
#[structopt(about = "Request attendance")]
pub(super) struct Attendance {
#[structopt(skip)]
client: Client,
#[structopt(skip)]
theme: ColorfulTheme,

#[structopt(long)]
reason: Option<String>,
#[structopt(
default_value,
long,
help = "Effective date (yyyy-mm-dd)",
value_name = "DATE"
)]
date: Date,
#[structopt(long, value_name = "TIME")]
checkin_time: Option<Time>,
#[structopt(long, value_name = "TIME")]
checkout_time: Option<Time>,

#[structopt(subcommand)]
cmd: Option<AttendanceCommand>,
}

impl Command for Attendance {
fn run(&self) {
let config = Config::load().unwrap();
let token = match config.token() {
None => Error::with_description(
&format!(
"Not logged in yet. Try: {}",
Colour::Blue.bold().paint("talenta login")
),
ErrorKind::ValueValidation,
)
.exit(),
Some(token) => token,
};

match self.cmd.as_ref() {
None => {
let checkin_time = self.checkin_time.unwrap_or_else(|| {
Input::with_theme(&self.theme)
.with_prompt("Checkin time (HH:mm)")
.interact()
.unwrap()
});

let checkout_time = self.checkout_time.unwrap_or_else(|| {
Input::with_theme(&self.theme)
.with_prompt("Checkout time (HH:mm)")
.interact()
.unwrap()
});

let reason = self.reason.clone().unwrap_or_else(|| {
Input::with_theme(&self.theme)
.with_prompt("Reason")
.interact()
.unwrap()
});

match self.client.attendance_request(
token,
&reason,
self.date.into(),
checkin_time.into(),
checkout_time.into(),
) {
Ok(response) => {
let status = StatusCode::from_u16(response.status()).unwrap();
match status.is_success() {
true => Attendance::success(response.message()),
false => Error::with_description(
&response.message(),
ErrorKind::ValueValidation,
)
.exit(),
}
}
Err(err) => {
Error::with_description(&err.to_string(), ErrorKind::ValueValidation).exit()
}
};
}
Some(cmd) => match cmd {
AttendanceCommand::Checkin(_checkin) => unimplemented!(),
AttendanceCommand::Checkout(_checkout) => unimplemented!(),
},
}
}
}
62 changes: 60 additions & 2 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use chrono::{NaiveDate, NaiveTime};
use reqwest::blocking;
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::error::Error;
use url::{ParseError, Url};

#[derive(Deserialize, Debug)]
Expand All @@ -22,6 +24,9 @@ impl<T> ApiResponse<T> {
}
}

#[derive(Deserialize, Debug)]
pub(super) struct ResponseData {}

#[derive(Deserialize, Debug)]
pub(super) struct LoginData {
token: String,
Expand All @@ -33,6 +38,19 @@ impl LoginData {
}
}

#[allow(non_snake_case)]
#[derive(Serialize, Debug)]
struct AttendanceRequestBody {
datepicker_request_submit: String,
hour_checkin: Option<String>,
minute_checkin: Option<String>,
hour_checkout: Option<String>,
minute_checkout: Option<String>,
reason: String,
useCheckIn: bool,
useCheckOut: bool,
}

#[derive(Default, Debug)]
pub(super) struct Client {
client: blocking::Client,
Expand All @@ -43,7 +61,7 @@ impl Client {
&self,
email: &str,
password: &str,
) -> Result<ApiResponse<LoginData>, Box<dyn std::error::Error>> {
) -> Result<ApiResponse<LoginData>, Box<dyn Error>> {
let mut map = HashMap::new();
map.insert("email", email);
map.insert("password", password);
Expand All @@ -55,6 +73,46 @@ impl Client {
.json()?)
}

pub(super) fn attendance_request(
&self,
token: &str,
reason: &str,
date: NaiveDate,
checkin: Option<NaiveTime>,
checkout: Option<NaiveTime>,
) -> Result<ApiResponse<ResponseData>, Box<dyn Error>> {
let json = AttendanceRequestBody {
datepicker_request_submit: date.to_string(),
hour_checkin: match checkin {
Some(time) => Some(time.format("%H").to_string()),
None => None,
},
minute_checkin: match checkin {
Some(time) => Some(time.format("%M").to_string()),
None => None,
},
hour_checkout: match checkout {
Some(time) => Some(time.format("%H").to_string()),
None => None,
},
minute_checkout: match checkout {
Some(time) => Some(time.format("%M").to_string()),
None => None,
},
reason: reason.into(),
useCheckIn: checkin.is_some(),
useCheckOut: checkout.is_some(),
};

Ok(self
.client
.post(Client::build_url("attendance-request")?)
.bearer_auth(token)
.json(&json)
.send()?
.json()?)
}

fn build_url(path: &str) -> Result<Url, ParseError> {
const BASE_URL: &str = "https://api-mobile.talenta.co/api/v1/";
let base = Url::parse(BASE_URL).expect("hardcoded URL is known to be valid");
Expand Down
6 changes: 3 additions & 3 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const PKG_NAME: &str = env!("CARGO_PKG_NAME");

#[derive(Default, Serialize, Deserialize, Debug)]
pub(super) struct Config {
token: String,
token: Option<String>,
}

impl Config {
Expand All @@ -14,14 +14,14 @@ impl Config {
}

pub(super) fn with_token(token: String) -> Config {
Config { token }
Config { token: Some(token) }
}

pub(super) fn store(&self) -> Result<(), ConfyError> {
confy::store(PKG_NAME, self)
}

pub(super) fn token(&self) -> &str {
pub fn token(&self) -> &Option<String> {
&self.token
}
}
6 changes: 6 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use crate::attendance::Attendance;
use crate::login::Login;
use ansi_term::Colour;
use std::io::Write;
use std::{io, process};
use structopt::clap::AppSettings;
use structopt::StructOpt;

mod attendance;
mod client;
mod config;
mod date;
mod login;
mod time;

trait Command {
fn run(&self);
Expand All @@ -30,12 +34,14 @@ struct App {
#[derive(StructOpt)]
enum RootCommand {
Login(Login),
Attendance(Attendance),
}

fn main() {
let app: App = App::from_args();

match app.cmd {
RootCommand::Login(login) => login.run(),
RootCommand::Attendance(attendance) => attendance.run(),
}
}

0 comments on commit 57a3e77

Please sign in to comment.