Add flag to connect over socket and retrieve action response, simple systemd service, and more actions

This commit is contained in:
Lizzy Hunt 2023-02-16 13:55:40 -07:00
parent 32803c4416
commit 312af9bc10
No known key found for this signature in database
GPG Key ID: 8AC6A4B840C0EC49
8 changed files with 162 additions and 28 deletions

2
.env.example Normal file
View File

@ -0,0 +1,2 @@
A_NUMBER=A12345671
PASSWORD=

11
aggietimed.service Normal file
View File

@ -0,0 +1,11 @@
[Unit]
Description=aggietimed
After=network.target
[Service]
Type=simple
EnvironmentFile=/home/lizzy/work/simple_scripts/aggietime_cli/.env
ExecStart=/usr/bin/aggietimed -d -s /tmp/aggietimed.sock
[Install]
WantedBy=multi-user.target

4
cli.js Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env node
import main from './src/main.js';
main();

View File

@ -1,4 +1,7 @@
{
"name": "aggietimed",
"version": "0.0.1",
"author": "Lizzy Hunt (@simponic)",
"dependencies": {
"argparse": "^2.0.1",
"axios": "^1.3.3",
@ -11,5 +14,8 @@
"type": "module",
"scripts": {
"start": "node src/main.js"
},
"bin": {
"aggietimed": "cli.js"
}
}

View File

@ -2,6 +2,10 @@ import * as aggietime from "./aggietime.js";
const ACTIONS = {
"clock-in": aggietime.clock_in,
"clock-out": aggietime.clock_out,
"current-shift": aggietime.current_shift,
"current-user": aggietime.get_user_info,
"status-line": aggietime.get_status_line,
};
export const do_action = async (body) => {

View File

@ -4,12 +4,19 @@ import {
USER_PATH,
USER_CACHE_EXP_SEC,
CLOCKIN_PATH,
CLOCKOUT_PATH,
OPEN_SHIFT_PATH,
OPEN_SHIFT_EXP_SEC,
} from "./constants.js";
import { client } from "./axios_client.js";
import expireCache from "expire-cache";
const aggietime = client.create({
baseURL: AGGIETIME_URI,
});
const replace_path_args = (path, map) =>
path.replaceAll(/:([a-zA-Z0-9_]+)/g, (_, key) => map[key]);
@ -27,18 +34,16 @@ const get_user_position_or_specified = async (position) => {
export const get_user_info = async () => {
if (!expireCache.get("user")) {
const user = await client
.get(`${AGGIETIME_URI}/${USER_PATH}`)
.then(({ data, config }) => {
const csrf_token = config.jar
.toJSON()
.cookies.find(
({ domain, key }) =>
domain === AGGIETIME_DOMAIN && key === "XSRF-TOKEN"
).value;
expireCache.set("aggietime-csrf", csrf_token);
return data;
});
const user = await aggietime.get(USER_PATH).then(({ data, config }) => {
const csrf_token = config.jar
.toJSON()
.cookies.find(
({ domain, key }) =>
domain === AGGIETIME_DOMAIN && key === "XSRF-TOKEN"
).value;
expireCache.set("aggietime-csrf", csrf_token);
return data;
});
expireCache.set("user", user, USER_CACHE_EXP_SEC);
}
@ -48,15 +53,82 @@ export const get_user_info = async () => {
export const clock_in = async ({ position } = {}) => {
position = await get_user_position_or_specified(position);
return await client.post(
`${AGGIETIME_URI}/${replace_path_args(CLOCKIN_PATH, { position })}`,
{
comment: "",
},
{
headers: {
"X-XSRF-TOKEN": expireCache.get("aggietime-csrf"),
return await aggietime
.post(
replace_path_args(CLOCKIN_PATH, { position }),
{
comment: "",
},
}
);
{
headers: {
"X-XSRF-TOKEN": expireCache.get("aggietime-csrf"),
},
}
)
.then(({ data }) => {
expireCache.remove("status_line");
return data;
});
};
export const clock_out = async ({ position } = {}) => {
position = await get_user_position_or_specified(position);
return await aggietime
.post(
replace_path_args(CLOCKOUT_PATH, { position }),
{
comment: "",
},
{
headers: {
"X-XSRF-TOKEN": expireCache.get("aggietime-csrf"),
},
}
)
.then(({ data }) => {
expireCache.remove("status_line");
return data;
});
};
export const current_shift = async () => {
const req_path = replace_path_args(OPEN_SHIFT_PATH, await get_user_info());
const {
request: {
res: { responseUrl: response_url },
},
data,
} = await aggietime.get(req_path);
if (`${AGGIETIME_URI}/${req_path}` != response_url) {
// IMO a very weird decision - when there is no open shift the api redirects
// home instead of sending back a 404 or something actually *reasonable* :3
return null;
}
return data;
};
export const get_status_line = async () => {
if (!expireCache.get("status_line")) {
const { anumber } = await get_user_info();
const shift = await current_shift();
let status_line = "No Shift";
if (shift && shift?.start) {
const start = new Date(shift?.start);
status_line =
((new Date().getTime() - start.getTime()) / (1000 * 60 * 60)).toFixed(
2
) + " hours";
}
expireCache.set(
"status_line",
`${anumber} - ${status_line}`,
OPEN_SHIFT_EXP_SEC
);
}
return { status: expireCache.get("status_line") };
};

View File

@ -3,11 +3,14 @@ export const KILL_SIGNALS = ["SIGINT", "SIGTERM", "SIGQUIT"];
export const AGGIETIME_DOMAIN = "aggietimeultra.usu.edu";
export const AGGIETIME_URI = `https://${AGGIETIME_DOMAIN}`;
export const REFRESH_JWT_MS = 5 * 1000 * 60;
export const LOGIN_PATH = "api/v1/auth/login";
export const LOGOUT_PATH = "api/v1/auth/logout";
export const CLOCKIN_PATH = "api/v1/positions/:position/clock_in";
export const CLOCKOUT_PATH = "api/v1/positions/:position/clock_out";
export const USER_PATH = "api/v1/auth/get_user_info";
export const REFRESH_JWT_MS = 5 * 1000 * 60;
export const OPEN_SHIFT_PATH = "api/v1/users/:anumber/open_shift";
export const OPEN_SHIFT_EXP_SEC = 60;
export const EXECUTION_SELECTOR = "input[type=hidden][name=execution]";
export const DUO_IFRAME_SELECTOR = "#duo_iframe";

View File

@ -1,3 +1,5 @@
#!/usr/bin/env node
import {
DEFAULT_SOCKET_PATH,
KILL_SIGNALS,
@ -10,7 +12,7 @@ import * as net from "net";
import * as dotenv from "dotenv";
import * as fs from "fs";
const main = async () => {
export default async () => {
dotenv.config();
const args = build_args();
@ -20,16 +22,35 @@ const main = async () => {
} catch {
fs.unlinkSync(args.socket_path);
}
} else if (args.action) {
if (fs.existsSync(args.socket_path)) {
run_action(args.socket_path, args.action);
} else {
console.error(`ERR: No such socket '${args.socket_path}'`);
}
}
};
const run_action = (socket_path, action) => {
const connection = net.connect(socket_path);
connection.on("data", (data) => {
if (Buffer.isBuffer(data)) {
console.log(data.toString().trim());
} else {
console.log(data.trim());
}
connection.end();
});
connection.write(JSON.stringify({ action }));
};
const build_args = () => {
const parser = new argparse.ArgumentParser({ description: "AggieTime CLI" });
parser.add_argument("-d", "--daemon", {
help: "Start server as a process blocking daemon",
action: argparse.BooleanOptionalAction,
default: true,
default: false,
});
parser.add_argument("-s", "--socket_path", {
@ -37,6 +58,10 @@ const build_args = () => {
help: `Set server socket path, defaults to ${DEFAULT_SOCKET_PATH}`,
});
parser.add_argument("-a", "--action", {
help: `Ignored when daemon flag is set. Returns the value of action (see actions.js) when sent over the socket.`,
});
return parser.parse_args();
};
@ -76,7 +101,16 @@ specify another socket path with --socket_path`
return;
}
actions.do_action(body);
actions
.do_action(body)
.then((resp) => {
client.write(JSON.stringify(resp) + "\r\n");
})
.catch((e) => {
console.error(e);
client.write(JSON.stringify({ err: true }) + "\r\n");
});
});
});
@ -90,5 +124,3 @@ specify another socket path with --socket_path`
process.on(signal, () => kill_server(unix_server, socket_path))
);
};
main();