Add scheduled GitHub Action task

This commit is contained in:
lansonsam
2026-03-15 21:06:58 +08:00
parent 072d0004b1
commit aac5ac4a9b
3 changed files with 170 additions and 0 deletions

34
.github/workflows/daily-task.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Daily Task
on:
schedule:
- cron: '0 2 * * *'
workflow_dispatch:
jobs:
run-daily-task:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Run scheduled task
env:
TARGET_URL: ${{ secrets.TARGET_URL }}
HTTP_METHOD: ${{ vars.HTTP_METHOD }}
REQUEST_BODY: ${{ secrets.REQUEST_BODY }}
AUTH_TOKEN: ${{ secrets.AUTH_TOKEN }}
HEADERS_JSON: ${{ secrets.HEADERS_JSON }}
EXPECTED_STATUS: ${{ vars.EXPECTED_STATUS }}
RETRY_COUNT: ${{ vars.RETRY_COUNT }}
RETRY_DELAY_MS: ${{ vars.RETRY_DELAY_MS }}
run: node scripts/daily-task.js

View File

@@ -1,2 +1,41 @@
# card-api # card-api
## GitHub Action: daily task
This repository includes a scheduled GitHub Action at .github/workflows/daily-task.yml.
### Trigger mode
- Runs every day at 02:00 UTC
- Can also be started manually from the GitHub Actions page
### Required configuration
Configure these repository secrets before enabling the workflow:
- TARGET_URL: target API endpoint to call
Optional secrets:
- AUTH_TOKEN: bearer token appended to the Authorization header
- REQUEST_BODY: raw JSON string sent as the request body
- HEADERS_JSON: JSON object string for extra request headers
Optional repository variables:
- HTTP_METHOD: defaults to GET
- EXPECTED_STATUS: defaults to 200
- RETRY_COUNT: defaults to 3
- RETRY_DELAY_MS: defaults to 5000
### Behavior
The workflow runs scripts/daily-task.js, which:
- calls the configured endpoint
- validates the returned status code
- retries on failure
- exits with a non-zero code when all retries fail
If you want this action to do something more specific, such as syncing data, generating reports, or updating files in the repository, the script can be extended from the same entry point.

97
scripts/daily-task.js Normal file
View File

@@ -0,0 +1,97 @@
const DEFAULT_METHOD = 'GET';
const DEFAULT_EXPECTED_STATUS = 200;
const DEFAULT_RETRY_COUNT = 3;
const DEFAULT_RETRY_DELAY_MS = 5000;
function readNumber(value, fallback) {
if (!value) {
return fallback;
}
const parsed = Number.parseInt(value, 10);
return Number.isNaN(parsed) ? fallback : parsed;
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function buildHeaders() {
const headers = {
'content-type': 'application/json',
};
if (process.env.HEADERS_JSON) {
const customHeaders = JSON.parse(process.env.HEADERS_JSON);
Object.assign(headers, customHeaders);
}
if (process.env.AUTH_TOKEN) {
headers.authorization = `Bearer ${process.env.AUTH_TOKEN}`;
}
if (!process.env.REQUEST_BODY) {
delete headers['content-type'];
}
return headers;
}
async function runTask() {
const targetUrl = process.env.TARGET_URL;
if (!targetUrl) {
throw new Error('Missing TARGET_URL secret.');
}
const method = (process.env.HTTP_METHOD || DEFAULT_METHOD).toUpperCase();
const expectedStatus = readNumber(process.env.EXPECTED_STATUS, DEFAULT_EXPECTED_STATUS);
const retryCount = readNumber(process.env.RETRY_COUNT, DEFAULT_RETRY_COUNT);
const retryDelayMs = readNumber(process.env.RETRY_DELAY_MS, DEFAULT_RETRY_DELAY_MS);
const headers = buildHeaders();
const requestOptions = {
method,
headers,
};
if (process.env.REQUEST_BODY) {
requestOptions.body = process.env.REQUEST_BODY;
}
for (let attempt = 1; attempt <= retryCount; attempt += 1) {
console.log(`Attempt ${attempt}/${retryCount}: ${method} ${targetUrl}`);
try {
const response = await fetch(targetUrl, requestOptions);
const responseText = await response.text();
console.log(`Response status: ${response.status}`);
if (responseText) {
console.log(`Response body: ${responseText.slice(0, 1000)}`);
}
if (response.status !== expectedStatus) {
throw new Error(`Expected status ${expectedStatus}, received ${response.status}.`);
}
console.log('Scheduled task completed successfully.');
return;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error(`Attempt ${attempt} failed: ${message}`);
if (attempt === retryCount) {
throw error;
}
console.log(`Waiting ${retryDelayMs}ms before retry.`);
await sleep(retryDelayMs);
}
}
}
runTask().catch((error) => {
console.error('Scheduled task failed.');
console.error(error);
process.exit(1);
});