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": { "dependencies": {
"argparse": "^2.0.1", "argparse": "^2.0.1",
"axios": "^1.3.3", "axios": "^1.3.3",
@ -11,5 +14,8 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "node src/main.js" "start": "node src/main.js"
},
"bin": {
"aggietimed": "cli.js"
} }
} }

View File

@ -2,6 +2,10 @@ import * as aggietime from "./aggietime.js";
const ACTIONS = { const ACTIONS = {
"clock-in": aggietime.clock_in, "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) => { export const do_action = async (body) => {

View File

@ -4,12 +4,19 @@ import {
USER_PATH, USER_PATH,
USER_CACHE_EXP_SEC, USER_CACHE_EXP_SEC,
CLOCKIN_PATH, CLOCKIN_PATH,
CLOCKOUT_PATH,
OPEN_SHIFT_PATH,
OPEN_SHIFT_EXP_SEC,
} from "./constants.js"; } from "./constants.js";
import { client } from "./axios_client.js"; import { client } from "./axios_client.js";
import expireCache from "expire-cache"; import expireCache from "expire-cache";
const aggietime = client.create({
baseURL: AGGIETIME_URI,
});
const replace_path_args = (path, map) => const replace_path_args = (path, map) =>
path.replaceAll(/:([a-zA-Z0-9_]+)/g, (_, key) => map[key]); path.replaceAll(/:([a-zA-Z0-9_]+)/g, (_, key) => map[key]);
@ -27,9 +34,7 @@ const get_user_position_or_specified = async (position) => {
export const get_user_info = async () => { export const get_user_info = async () => {
if (!expireCache.get("user")) { if (!expireCache.get("user")) {
const user = await client const user = await aggietime.get(USER_PATH).then(({ data, config }) => {
.get(`${AGGIETIME_URI}/${USER_PATH}`)
.then(({ data, config }) => {
const csrf_token = config.jar const csrf_token = config.jar
.toJSON() .toJSON()
.cookies.find( .cookies.find(
@ -48,8 +53,9 @@ export const get_user_info = async () => {
export const clock_in = async ({ position } = {}) => { export const clock_in = async ({ position } = {}) => {
position = await get_user_position_or_specified(position); position = await get_user_position_or_specified(position);
return await client.post( return await aggietime
`${AGGIETIME_URI}/${replace_path_args(CLOCKIN_PATH, { position })}`, .post(
replace_path_args(CLOCKIN_PATH, { position }),
{ {
comment: "", comment: "",
}, },
@ -58,5 +64,71 @@ export const clock_in = async ({ position } = {}) => {
"X-XSRF-TOKEN": expireCache.get("aggietime-csrf"), "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_DOMAIN = "aggietimeultra.usu.edu";
export const AGGIETIME_URI = `https://${AGGIETIME_DOMAIN}`; 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 LOGIN_PATH = "api/v1/auth/login";
export const LOGOUT_PATH = "api/v1/auth/logout"; export const LOGOUT_PATH = "api/v1/auth/logout";
export const CLOCKIN_PATH = "api/v1/positions/:position/clock_in"; 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 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 EXECUTION_SELECTOR = "input[type=hidden][name=execution]";
export const DUO_IFRAME_SELECTOR = "#duo_iframe"; export const DUO_IFRAME_SELECTOR = "#duo_iframe";

View File

@ -1,3 +1,5 @@
#!/usr/bin/env node
import { import {
DEFAULT_SOCKET_PATH, DEFAULT_SOCKET_PATH,
KILL_SIGNALS, KILL_SIGNALS,
@ -10,7 +12,7 @@ import * as net from "net";
import * as dotenv from "dotenv"; import * as dotenv from "dotenv";
import * as fs from "fs"; import * as fs from "fs";
const main = async () => { export default async () => {
dotenv.config(); dotenv.config();
const args = build_args(); const args = build_args();
@ -20,7 +22,26 @@ const main = async () => {
} catch { } catch {
fs.unlinkSync(args.socket_path); 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 build_args = () => {
@ -29,7 +50,7 @@ const build_args = () => {
parser.add_argument("-d", "--daemon", { parser.add_argument("-d", "--daemon", {
help: "Start server as a process blocking daemon", help: "Start server as a process blocking daemon",
action: argparse.BooleanOptionalAction, action: argparse.BooleanOptionalAction,
default: true, default: false,
}); });
parser.add_argument("-s", "--socket_path", { parser.add_argument("-s", "--socket_path", {
@ -37,6 +58,10 @@ const build_args = () => {
help: `Set server socket path, defaults to ${DEFAULT_SOCKET_PATH}`, 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(); return parser.parse_args();
}; };
@ -76,7 +101,16 @@ specify another socket path with --socket_path`
return; 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)) process.on(signal, () => kill_server(unix_server, socket_path))
); );
}; };
main();