Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/HNU-himematsu/HNU-TimeLetter/llms.txt

Use this file to discover all available pages before exploring further.

HNU-TimeLetter reads all external service credentials and runtime tuning parameters from environment variables. In local development these live in .env.local (copied from .env.example); in production they are injected by the host as repository or environment secrets. This page documents every variable in .env.example, explains how to obtain the required values, and highlights the security constraints that must be met before going to production.

Creating Your .env.local

Start by copying the example file that ships with the repository:
cp .env.example .env.local
.env.local is listed in .gitignore and will never be committed to version control. Never commit credentials directly to the repository.

Feishu Credentials

These two variables authenticate every call to the Feishu Open API and are required for both the sync script and the admin-triggered sync endpoint.
VariableRequiredDescription
FEISHU_APP_ID✅ RequiredThe App ID of your Feishu enterprise application, e.g. cli_a1b2c3d4e5f6g7h8.
FEISHU_APP_SECRET✅ RequiredThe App Secret paired with FEISHU_APP_ID. Keep this value confidential.

How to obtain Feishu app credentials

1

Create an enterprise application

Open the Feishu Developer Console and click Create Application → Enterprise Self-built App. Give the app a name (e.g. “HNU-TimeLetter Sync”) and confirm creation. The App ID and App Secret are shown on the application’s Credentials & Basic Info page.
2

Enable Bitable permissions

Navigate to Permissions & Scopes in the left sidebar. Search for and enable the following permission scopes:
  • bitable:app — read Bitable application data
  • bitable:app:readonly — read records and attachments from Bitable tables
Click Apply for Release (or save permissions if publishing is not required for your environment).
3

Add the application to your Bitable document

Open the Feishu Bitable document that contains your content tables. Click ··· (more options) → Add application and search for the app you just created. Grant it Viewer or higher access. The sync script will not be able to read table records unless this step is completed.

Homepage Stories (Feishu Table Identifiers)

These variables identify the specific Bitable tables and views that hold homepage story content and campus location data. All values can be read directly from the URL of each table when viewed in a browser.
VariableRequiredDescriptionExample
FEISHU_APP_TOKEN✅ RequiredThe Bitable application token — the long alphanumeric ID in the Bitable document URL after /base/.xxxxxxxxxxxxxxxxxxxxxx
FEISHU_TABLE_ID✅ RequiredThe table ID of the homepage stories table, found in the URL after ?table=.tblxxxxxxxxxxxxxxxx
FEISHU_VIEW_ID✅ RequiredThe view ID of the homepage stories view, found in the URL after &view=.vewxxxxxxxxxxxxxxxx
FEISHU_LOCATIONS_TABLE_ID✅ RequiredThe table ID of the campus locations / map-pin reference table.tblxxxxxxxxxxxxxxxx
The Bitable document URL typically looks like: https://your-company.feishu.cn/base/{APP_TOKEN}?table={TABLE_ID}&view={VIEW_ID} Copy the three highlighted segments directly into the corresponding environment variables.

Creation Board

The Creation Board (/creation) is powered by its own set of Feishu tables, separate from the homepage stories, allowing the community submission workflow to be managed independently.
VariableRequiredDescriptionExample
FEISHU_CREATION_TABLE_ID✅ RequiredTable ID of the main creation-idea records table.tblxxxxxxxxxxxxxxxx
FEISHU_CREATION_VIEW_ID✅ RequiredView ID within the creation table used for the public board display.vewxxxxxxxxxxxxxxxx
FEISHU_CREATION_HEADER_TABLE_ID✅ RequiredTable ID of the board header/banner configuration table. Controls the announcement or title card shown at the top of the creation board.tblxxxxxxxxxxxxxxxx

Contributors

The Credits section on the homepage and the contributors list are sourced from a dedicated Bitable table.
VariableRequiredDescriptionExample
FEISHU_CONTRIBUTORS_TABLE_ID✅ RequiredTable ID of the contributors / credits table. Records from this table are written to src/data/contributors.json during sync.tblxxxxxxxxxxxxxxxx

Aliyun OSS

During sync, the script downloads image attachments from Feishu and re-uploads them to Aliyun OSS. The resulting public OSS URLs are written into the JSON artifacts and rendered directly in the frontend — the browser never fetches images from Feishu.
VariableRequiredDescriptionExample
ALIYUN_OSS_REGION✅ RequiredThe OSS region endpoint identifier where your bucket resides.oss-cn-hangzhou
ALIYUN_OSS_BUCKET✅ RequiredThe name of the OSS bucket to store uploaded images.hnu-timeletter-assets
ALIYUN_OSS_ACCESS_KEY_ID✅ RequiredAliyun RAM user Access Key ID with oss:PutObject permission.LTAI5txxxxxxxxxxxxxxxx
ALIYUN_OSS_ACCESS_KEY_SECRET✅ RequiredThe secret paired with ALIYUN_OSS_ACCESS_KEY_ID. Keep this value confidential.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

OSS bucket setup checklist

  1. Create a bucket in the Aliyun OSS Console. Choose the same region as your deployment server for lowest latency.
  2. Set the bucket’s Access Control to Public Read (public-read). This allows the Next.js frontend to render images via their direct OSS URLs without signed tokens.
  3. Under Bucket Settings → CORS, add a rule that allows * for Allowed Origins, GET, HEAD for Allowed Methods, and * for Allowed Headers. This ensures browsers can load images cross-origin if your domain differs from the OSS endpoint.
  4. Create a dedicated RAM sub-user with only the oss:PutObject and oss:GetObject policies scoped to your specific bucket. Do not use your root account Access Key.
Do not set the OSS bucket to Private access unless you implement signed URL generation in src/lib/sync-service.ts. The current implementation writes bare public OSS URLs into the JSON artifacts; private-bucket images will return 403 errors in the browser.

Admin Authentication

The /admin route and all api/admin/ endpoints are protected by session-based authentication. Three secrets govern this subsystem.
VariableRequiredDescriptionExample
ADMIN_PASSWORD✅ RequiredThe plaintext password for the admin login form at /admin. Only one admin account exists; there is no user database.a-strong-passphrase-here
ADMIN_SESSION_SECRET✅ RequiredA random string used to sign the admin session cookie. Must be at least 32 bytes (characters) long in production.s3cr3t-r4nd0m-str1ng-min-32-ch4rs!!
SYNC_WEBHOOK_SECRET✅ RequiredA separate random secret used to authenticate webhook-triggered sync requests. Must differ from ADMIN_PASSWORD.wh-secr3t-independent-v4lue-here
ADMIN_SESSION_SECRET must be at least 32 bytes long in production. A shorter secret weakens the HMAC signature on the session cookie and may allow session forgery. Generate a suitable value with:
# macOS / Linux
openssl rand -base64 32

# Node.js (any platform)
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
Store the output as ADMIN_SESSION_SECRET. Never reuse this value for any other variable.
Keep SYNC_WEBHOOK_SECRET separate from ADMIN_PASSWORD. The webhook secret may appear in server access logs as a query parameter or request header, depending on how the sync trigger is integrated with external CI/CD systems. If it were the same value as ADMIN_PASSWORD, a leaked webhook log entry would also compromise admin access. Use independently generated random values for both.

Sync Runtime

These variables control the behaviour of the sync process itself — where it writes lock files and logs, and how long it waits before considering a sync run stalled.
VariableRequiredDefaultDescription
SYNC_RUNTIME_DIROptionalruntimeDirectory (relative to the project root) where the sync lock file is written. The lock file prevents two concurrent sync processes from running simultaneously.
SYNC_LOG_DIROptionallogs/syncDirectory (relative to the project root) where per-run sync log files are written. The admin panel reads these logs to display the sync history.
SYNC_LOCK_TIMEOUT_MSOptional1800000Maximum time in milliseconds a sync lock is considered valid before it is forcibly released. Defaults to 30 minutes (1 800 000 ms). Increase this value if your Feishu table has a very large number of image attachments.
The default values (runtime, logs/sync, 1800000) are pre-filled in .env.example and are safe for both local development and production use. You only need to override them if your deployment environment restricts writes to specific directories or imposes stricter concurrency constraints.

Image Optimisation

VariableRequiredDefaultDescription
NEXT_PUBLIC_DISABLE_IMAGE_OPTIMIZATIONOptionalfalseSet to true to bypass Next.js <Image> optimisation entirely. Useful on constrained self-hosted environments without a compatible sharp binary, or when all images are already optimised before upload to OSS. Setting this to true means images are served at their original OSS dimensions with no resizing or format conversion.

Complete .env.example Reference

For quick copy-paste, here is the full .env.example as it ships in the repository:
.env.example
# 飞书应用凭证
FEISHU_APP_ID=
FEISHU_APP_SECRET=

# 首页故事同步
FEISHU_APP_TOKEN=
FEISHU_TABLE_ID=
FEISHU_VIEW_ID=
FEISHU_LOCATIONS_TABLE_ID=

# 创作公示板同步
FEISHU_CREATION_TABLE_ID=
FEISHU_CREATION_VIEW_ID=
FEISHU_CREATION_HEADER_TABLE_ID=
FEISHU_CONTRIBUTORS_TABLE_ID=

# 阿里云 OSS
ALIYUN_OSS_REGION=
ALIYUN_OSS_BUCKET=
ALIYUN_OSS_ACCESS_KEY_ID=
ALIYUN_OSS_ACCESS_KEY_SECRET=

# 后台鉴权
ADMIN_PASSWORD=
ADMIN_SESSION_SECRET=
SYNC_WEBHOOK_SECRET=

# 同步运行时
SYNC_RUNTIME_DIR=runtime
SYNC_LOG_DIR=logs/sync
SYNC_LOCK_TIMEOUT_MS=1800000

# 图片优化
NEXT_PUBLIC_DISABLE_IMAGE_OPTIMIZATION=false
When deploying on Vercel, add each variable to Project Settings → Environment Variables, scoping secrets like ADMIN_SESSION_SECRET and ALIYUN_OSS_ACCESS_KEY_SECRET to Production only. For GitHub Actions (self-hosted pipeline), store them as Repository Secrets or Environment Secrets under the release environment and reference them in your workflow YAML as ${{ secrets.VARIABLE_NAME }}.

Build docs developers (and LLMs) love