Skip to content

owlCoder/tapiz-excel-api

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tapiz Excel Service

Serverless Excel export microservice for the Tapiz academic management platform. Handles score sheet workbooks and form response exports.

Built with Hono + TypeScript on Node.js, deployed as a Vercel serverless function.


Tech Stack

HTTP framework Hono
Excel engine ExcelJS
Rate limiting hono-rate-limiter (Valkey-backed)
Cache / ephemeral Valkey (Redis-compatible, via ioredis)
Runtime Node.js ≥ 18
Deployment Vercel Serverless

Project Structure

src/
├── app.ts                      # Hono app — route mounting, middleware, docs
├── index.ts                    # Local dev entry point
├── core/
│   ├── colors.ts               # Warm brown RGB palette (matches frontend CSS vars)
│   ├── docs.ts                 # OpenAPI-style HTML docs page
│   ├── Result.ts               # Ok / Err result type
│   └── valkeyClient.ts         # ioredis singleton
├── middleware/
│   ├── errorHandler.ts         # Global Hono error handler
│   └── rateLimiter.ts          # 10 req / 15 min per IP, Valkey-backed
└── routes/
    ├── scoresheetRoute.ts      # POST /api/excel/score-sheet
    └── formsRoute.ts           # POST /api/excel/forms
api/
└── index.ts                    # Vercel serverless handler (Node → Web Request bridge)

Getting Started

Prerequisites

  • Node.js ≥ 18
  • A running Valkey / Redis instance (optional — rate limiter falls back to in-memory if unavailable)

Install

npm install

Environment Variables

Copy the example and fill in your values:

cp .env.example .env
Variable Required Description
PORT Local dev port (default: 3003)
CLIENT_URL CORS allowed origin(s), comma-separated (default: *)
VALKEY_URL Redis-compatible URL, e.g. redis://localhost:6379 or rediss://... for TLS

Development

npm run dev

Server starts at http://localhost:3003.
Interactive API docs are available at http://localhost:3003/.

Type Check

npm run typecheck

Production Build

This project is designed for Vercel serverless deployment — no build step is needed locally. For a standalone Node.js build, tsc output goes to dist/.


API Reference

All endpoints are prefixed with /api/excel. A health check is available at:

GET /api/excel/health  →  { "status": "ok", "ts": "..." }

Interactive docs with full request/response schemas are served at the root URL:

GET /  →  HTML docs page
GET /api/docs  →  HTML docs page

Endpoints

Method Path Description Output
POST /api/excel/score-sheet Score sheet workbook with stats and dashboard .xlsx
POST /api/excel/forms Form responses export with frequency summary .xlsx

All endpoints:

  • Accept Content-Type: application/json with a 50 MB body limit
  • Return Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet with Content-Disposition: attachment
  • Return 429 when the rate limit is exceeded

Rate Limiting

10 requests per IP per 15 minutes, applied across all /api/excel/* routes.
Backed by Valkey for consistency across serverless instances. Degrades gracefully to in-memory if Valkey is unavailable.


POST /api/excel/score-sheet

Generates a .xlsx workbook with three sheets:

  • Tabela poena — main data table with a max-points sub-header row and per-column average row. Numeric cells are color-coded green / amber / red by percentage of maxPoints. formula columns are rendered bold in primary color.
  • Statistike — per-column statistics block: count, sum, average, median, standard deviation, min, max, average percentage, and pass rate (≥ 60%).
  • Pregled — KPI summary (total students, overall average %, pass rate, number of graded activities) and a per-student points breakdown table with a Položio / Uslovno / Pao status column.
{
  "sheetName": "Matematika 1",
  "subjectName": "Matematika 1",
  "academicYear": "2024/25",
  "columns": [
    { "id": "col1", "name": "Kolokvijum 1", "maxPoints": 30 },
    { "id": "col2", "name": "Kolokvijum 2", "maxPoints": 30 },
    { "id": "total", "name": "Ukupno", "maxPoints": 60, "type": "formula" }
  ],
  "rows": [
    {
      "studentName": "Ana Petrović",
      "indexNumber": "1234/24",
      "computedCells": { "col1": 24, "col2": 27, "total": 51 }
    }
  ]
}
Field Type Required Description
sheetName string Main worksheet tab name (default: "Tabela poena")
subjectName string Subject name shown in the title row
academicYear string Academic year label, e.g. "2024/25"
columns Column[] Column definitions
rows Row[] Student data rows

Column object

Field Type Description
id string Unique column identifier
name string Column header label
maxPoints number? Maximum achievable points — used for color coding and pass rate
type string? "formula" renders the cell bold in primary color
isHidden boolean? If true, column is excluded from the export

Row object

Field Type Description
studentName string Full student name
indexNumber string Student index / ID number
computedCells Record<string, unknown> Map of columnId → cell value

POST /api/excel/forms

Generates a .xlsx workbook with two sheets:

  • Odgovori — one row per response, frozen header, alternating row shading.
  • Pregled — metadata summary (total responses, number of questions, generation timestamp) followed by a frequency table for every question with type: "select", "radio", or "checkbox". Options are sorted by frequency descending.
{
  "formTitle": "Anketa o zadovoljstvu",
  "questions": [
    { "id": "q1", "label": "Ime i prezime" },
    { "id": "q2", "label": "Ocena kursa", "type": "select" },
    { "id": "q3", "label": "Komentar" }
  ],
  "responses": [
    {
      "submittedAt": "2025-06-01T10:30:00.000Z",
      "answers": { "q1": "Ana Petrović", "q2": "5", "q3": "Odlično!" }
    }
  ]
}
Field Type Required Description
formTitle string Form title shown in the workbook header
questions Question[] Question definitions
responses Response[] Submitted response records

Question object

Field Type Description
id string Unique question identifier
label string Column header in the responses sheet
type string? "select" / "radio" / "checkbox" — triggers a frequency table in the summary sheet

Response object

Field Type Description
submittedAt string ISO 8601 submission timestamp
answers Record<string, unknown> Map of questionId → value. Array values (multi-select) are joined with ,

Excel Design

All workbooks share a consistent visual language that matches the Tapiz frontend:

  • Primary color: Warm brown #a08040 (matches --color-primary-500 CSS variable)
  • Title rows: Primary background with white bold text
  • Header rows: gray-50 background, medium bottom border in primary color, frozen pane
  • Data rows: Alternating white / gray-50 row shading
  • Numeric cells: Color-coded green / amber / red relative to maxPoints
  • Formula cells: Bold text in primary color
  • Statistics sheets: Per-column blocks with labeled key-value rows and bordered cells

Deployment

The project is configured for Vercel via vercel.json. The api/index.ts file acts as the serverless handler — it bridges Node.js IncomingMessage / ServerResponse to the Hono Web Request API, matching the same pattern used across Tapiz microservices.

vercel deploy

No additional configuration is needed beyond setting the environment variables in the Vercel dashboard.

About

Serverless Excel export microservice for the Tapiz academic management platform. Handles score sheet workbooks and form response exports.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors