Skip to content

23241a6749/avidus-interactive

Repository files navigation

Avidus interactive

A role-based access control and user activity tracking platform, built as a clean Node + React monorepo.

License: MIT Node 24 React 18 MongoDB Live demo API

Live app: https://avidus-interactive-three.vercel.app API health: https://avidus-interactive-server.onrender.com/api/health Default admin login: admin@avidus.com / Admin@12345 (set via env vars; change in production)


About

Avidus interactive is a full-stack reference application that demonstrates how to ship a real-world JWT-authenticated product with two roles (admin, user), an auditable activity log, and a small but useful task-tracking domain. The backend is a stateless Express API on top of MongoDB; the frontend is a React 18 SPA built with Vite, Tailwind, and shadcn/ui. It is designed to be easy to read, easy to extend, and easy to deploy.

Features

For Users

  • ✅ Self-service registration with email + strong password
  • ✅ Email + password login, with secure refresh-token rotation
  • ✅ "Remember me" via long-lived refresh token (7 days)
  • ✅ Personal profile — change name and password from one screen
  • ✅ Personal dashboard with task statistics (pending / in-progress / completed)
  • ✅ Full CRUD on your own tasks (create, list, view, update, change status, delete)
  • ✅ Filter and search your tasks by status or free-text

For Admins

  • ✅ Everything users can do, plus a separate admin view
  • ✅ User management — list, search, activate / deactivate, delete
  • ✅ Global task oversight — see and delete any user's task
  • ✅ Paginated activity log, filterable by action, user, or date range
  • ✅ Analytics dashboard — user counts, task counts, 14-day task trend
  • ✅ Safety rails — admins cannot delete or self-deactivate their own account
  • ✅ Orphaned tasks (after user deletion) are kept for audit, with owner: null

Tech Stack

Layer Technology
Runtime Node.js 24
API framework Express 4
Database / ODM MongoDB 6+ with Mongoose 8
Auth JWT (access 15m / refresh 7d, hashed at rest) + bcrypt
Validation Joi
Security middleware helmet, express-rate-limit, CORS allow-list
Frontend framework React 18 + React Router 6
Build tool Vite 5
Styling Tailwind CSS 3 + shadcn/ui primitives
Charts Recharts
HTTP client Axios (with auth interceptor)
Toasts Sonner
Linting (server) ESLint 8
Dev runner (server) nodemon

Architecture

+-------------------+      Authorization: Bearer <accessToken>      +-------------------+
|                   |  ------------------------------------------->|                   |
|   React 18 SPA    |  <------------------------------------------- |   Express API     |
|   (Vite, Tailwind,|       { success, message, data, … }          |   (Node 24)       |
|    shadcn/ui)     |                                               |   Port 5000       |
|   Port 5173       |       POST /api/auth/refresh                  |                   |
|                   |  ------------------------------------------->|   helmet, cors,   |
|   Axios client    |       { accessToken, refreshToken }          |   rate-limit,     |
|   - accessToken   |                                               |   Joi validate,   |
|     in            |                                               |   role middleware |
|   localStorage    |                                               |                   |
|   - refreshToken  |                                               +---------+---------+
|     in body or    |                                                         |
|     httpOnly      |                                                         | Mongoose driver
|     cookie        |                                                         v
+-------------------+                                                +-------------------+
                                                                       |    MongoDB 6+     |
                                                                       |  - users          |
                                                                       |  - tasks          |
                                                                       |  - activitylogs   |
                                                                       +-------------------+

Request flow:

  1. The browser Axios client attaches the access token in the Authorization header.
  2. Express validates the JWT, runs Joi input validation, and (for state changes) records an ActivityLog entry through a fire-and-forget helper so logging never blocks the response.
  3. Mongoose talks to MongoDB. Responses are wrapped in the standard envelope { success, message, data } (or data: { items, pagination }).
  4. When the access token expires, the client posts the refresh token to /api/auth/refresh and gets a brand new pair back.

For the full version — data model, request lifecycle diagram, error shape, security boundaries — see docs/architecture.md.

Folder Structure

Avidus interactive/
├── README.md                 ← you are here
├── LICENSE                   ← MIT
├── CONTRIBUTING.md           ← contribution guide
├── .editorconfig             ← cross-OS editor config
├── .gitattributes
├── .gitignore
├── postman_collection.json   ← importable Postman v2.1 collection
├── docs/
│   ├── architecture.md       ← longer architecture write-up
│   └── screenshots/          ← (empty; populate with app screenshots)
│       └── .gitkeep
├── server/                   ← Node 24 + Express + MongoDB
│   ├── .env.example
│   ├── package.json
│   ├── scripts/
│   │   └── seedAdmin.js      ← `npm run seed:admin`
│   └── src/
│       ├── app.js            ← Express app, CORS, helmet, rate limit, mount routers
│       ├── server.js         ← Entrypoint (connect DB, listen)
│       ├── config/
│       │   ├── db.js
│       │   └── env.js
│       ├── models/
│       │   ├── User.js
│       │   ├── Task.js
│       │   └── ActivityLog.js
│       ├── routes/
│       │   ├── auth.routes.js
│       │   ├── user.routes.js
│       │   ├── task.routes.js
│       │   └── admin.routes.js
│       ├── controllers/
│       │   ├── auth.controller.js
│       │   ├── user.controller.js
│       │   ├── task.controller.js
│       │   ├── admin.controller.js
│       │   └── activity.controller.js
│       ├── middlewares/
│       │   ├── auth.middleware.js
│       │   ├── admin.middleware.js
│       │   ├── activity.middleware.js
│       │   └── error.middleware.js
│       ├── validators/
│       │   ├── auth.validator.js
│       │   └── task.validator.js
│       ├── services/
│       │   ├── token.service.js
│       │   └── activity.service.js
│       └── utils/
│           ├── ApiError.js
│           ├── ApiResponse.js
│           ├── asyncHandler.js
│           └── logger.js
└── client/                   ← React 18 + Vite + Tailwind + shadcn/ui
    ├── .env.example
    ├── package.json
    └── (src/, public/, index.html — populated by Vite scaffold)

Getting Started

You need Node.js 24+, npm 10+, and a running MongoDB 6+ instance (default connection string in the example is mongodb://127.0.0.1:27017/avidus_interactive).

The two apps run as siblings — open two terminals, one for each.

  1. Clone the repo

    git clone <your-fork-url> avidus-interactive
    cd avidus-interactive
  2. Backend — server/

    cd server
    cp .env.example .env
    # open .env and set at least:
    #   MONGODB_URI, JWT_ACCESS_SECRET, JWT_REFRESH_SECRET,
    #   ADMIN_EMAIL, ADMIN_PASSWORD
    npm install
    npm run seed:admin        # creates / promotes the admin from .env
    npm run dev               # http://localhost:5000
  3. Frontend — client/

    cd client
    cp .env.example .env      # contains VITE_API_BASE_URL=http://localhost:5000/api
    npm install
    npm run dev               # http://localhost:5173
  4. Open the app at http://localhost:5173, sign in with the seeded admin credentials (see below), and you're in.

Environment Variables

Server — server/.env

Variable Required Default Description
PORT no 5000 HTTP port the API listens on
NODE_ENV no development development / production / test; controls CORS cookie secure flag
MONGODB_URI yes Full MongoDB connection string, e.g. mongodb://127.0.0.1:27017/avidus_interactive
CLIENT_ORIGIN no http://localhost:5173 Comma-separated list of allowed CORS origins
JWT_ACCESS_SECRET yes Long random string used to sign access tokens
JWT_REFRESH_SECRET yes Long random string used to sign refresh tokens
JWT_ACCESS_EXPIRY no 15m Access-token lifetime (ms, s, m, h, d per jsonwebtoken)
JWT_REFRESH_EXPIRY no 7d Refresh-token lifetime
ADMIN_NAME no Super Admin Display name used by npm run seed:admin
ADMIN_EMAIL yes (for seeding) Email of the seeded admin
ADMIN_PASSWORD yes (for seeding) Password of the seeded admin (min 8 chars)

Client — client/.env

Variable Required Default Description
VITE_API_BASE_URL yes http://localhost:5000/api Base URL the Axios client prefixes on calls

Vite only exposes variables prefixed with VITE_ to the browser bundle. Don't put secrets in the client .env.

Default Admin Credentials

After running npm run seed:admin, the following account exists in your database — taken straight from server/.env.example:

Field Value
Email admin@avidus.com
Password Admin@12345

⚠️ Change these immediately in any non-local environment. The example values exist purely to make first-time setup friction-free. In production, set strong unique values for ADMIN_PASSWORD and the two JWT_*_SECRETs, and rotate them regularly.

API Documentation

Base URL: http://localhost:5000/api

All requests and responses use JSON. The response envelope is:

{ "success": true, "message": "OK", "data": { /* … */ } }

For paginated lists, data is shaped as:

{
  "items": [ /* … */ ],
  "pagination": { "page": 1, "limit": 25, "total": 42, "totalPages": 2 }
}

Errors use the same envelope with success: false and an optional details object containing per-field validation messages.

Looking for an importable Postman v2.1 collection? See postman_collection.json at the repo root.

Auth

POST /api/auth/register

Property Value
Auth None
Role Public
Rate limit 20 / hour / IP

Request body:

{
  "name": "Jane Doe",
  "email": "jane.doe@example.com",
  "password": "S3cret!Pass"
}

Response 201 Created:

{
  "success": true,
  "message": "Registration successful",
  "data": {
    "user": {
      "_id": "65f0a1b2c3d4e5f6a7b8c9d0",
      "name": "Jane Doe",
      "email": "jane.doe@example.com",
      "role": "user",
      "status": "active",
      "lastLoginAt": null,
      "createdAt": "2026-06-04T10:00:00.000Z",
      "updatedAt": "2026-06-04T10:00:00.000Z"
    },
    "accessToken": "eyJhbGciOi…",
    "refreshToken": "eyJhbGciOi…"
  }
}

POST /api/auth/login

Property Value
Auth None
Role Public
Rate limit 10 / 15 min / IP

Request body:

{
  "email": "admin@avidus.com",
  "password": "Admin@12345"
}

Response 200 OK:

{
  "success": true,
  "message": "Login successful",
  "data": {
    "user": { "…": "same shape as register" },
    "accessToken": "eyJhbGciOi…",
    "refreshToken": "eyJhbGciOi…"
  }
}

Inactive accounts return 403 Account is inactive.

POST /api/auth/refresh

Property Value
Auth None
Role Public (valid refresh token)

Request body:

{ "refreshToken": "eyJhbGciOi…" }

Response 200 OK:

{
  "success": true,
  "message": "Token refreshed",
  "data": {
    "accessToken": "eyJhbGciOi…",
    "refreshToken": "eyJhbGciOi…"
  }
}

The server rotates the refresh token: the old hash is replaced with a new bcrypt hash, so a previously-leaked refresh token is invalidated.

POST /api/auth/logout

Property Value
Auth Bearer
Role Any authenticated

No request body.

Response 200 OK:

{ "success": true, "message": "Logged out", "data": null }

Clears the stored refreshTokenHash on the user and the httpOnly refreshToken cookie (if the cookie is in use).

GET /api/auth/me

Property Value
Auth Bearer
Role Any authenticated

Response 200 OK:

{
  "success": true,
  "message": "Current user",
  "data": {
    "user": {
      "_id": "",
      "name": "Jane Doe",
      "email": "jane.doe@example.com",
      "role": "user",
      "status": "active",
      "lastLoginAt": "2026-06-04T10:15:00.000Z",
      "createdAt": "2026-05-01T08:00:00.000Z",
      "updatedAt": "2026-06-04T10:15:00.000Z"
    }
  }
}

Users (self)

All endpoints in this section require a Bearer access token. None require the admin role — any authenticated user can call them.

GET /api/users/profile

Returns the current user's profile.

Response 200 OK:

{
  "success": true,
  "message": "Profile",
  "data": {
    "user": {
      "_id": "", "name": "", "email": "",
      "role": "user", "status": "active",
      "lastLoginAt": "", "createdAt": "", "updatedAt": ""
    }
  }
}

PATCH /api/users/profile

Update the current user's name and/or password. To change the password, both currentPassword and newPassword must be supplied.

Request body (at least one of name or currentPassword required):

{
  "name": "Jane M. Doe",
  "currentPassword": "S3cret!Pass",
  "newPassword": "N3wer!Pass"
}

Response 200 OK:

{
  "success": true,
  "message": "Profile updated",
  "data": { "user": { "…": "same shape as GET profile" } }
}

GET /api/users/me/stats

Returns task counts for the current user.

Response 200 OK:

{
  "success": true,
  "message": "My task stats",
  "data": {
    "total": 12,
    "pending": 5,
    "inProgress": 4,
    "completed": 3
  }
}

GET /api/users/me/tasks

Paginated list of the current user's tasks. Query params:

Param Type Default Notes
page int 1 1-indexed
limit int 25 capped at 100
status string One of pending, in-progress, completed
q string Free-text match on title or description

Response 200 OK:

{
  "success": true,
  "message": "My tasks",
  "data": {
    "items": [
      {
        "_id": "",
        "title": "Ship Q2 release",
        "description": "",
        "status": "in-progress",
        "priority": "high",
        "owner": "",
        "createdAt": "", "updatedAt": ""
      }
    ],
    "pagination": { "page": 1, "limit": 25, "total": 1, "totalPages": 1 }
  }
}

Tasks

All endpoints require a Bearer access token. Users may only mutate their own tasks; admins may view/delete any task.

POST /api/tasks

Create a new task owned by the current user.

Request body:

{
  "title": "Ship Q2 release",
  "description": "Cut the Q2 release branch and publish the changelog.",
  "status": "pending",
  "priority": "high"
}

status defaults to pending; priority defaults to medium. Allowed statuses: pending | in-progress | completed. Allowed priorities: low | medium | high.

Response 201 Created:

{
  "success": true,
  "message": "Task created",
  "data": {
    "task": {
      "_id": "",
      "title": "Ship Q2 release",
      "description": "",
      "status": "pending",
      "priority": "high",
      "owner": "65f0a1b2…",
      "createdAt": "", "updatedAt": ""
    }
  }
}

GET /api/tasks

Paginated list of the current user's tasks. Same query params as GET /api/users/me/tasks.

Response 200 OK: same paginated envelope shape as above.

GET /api/tasks/:id

Fetch a single task by id. Owners and admins can read; everyone else gets 403.

Response 200 OK:

{
  "success": true,
  "message": "Task",
  "data": { "task": { "…": "see POST /api/tasks shape" } }
}

PUT /api/tasks/:id

Replace mutable fields on a task. Owner-only (403 otherwise).

Request body (any subset — at least one field required):

{
  "title": "Ship Q2 release (revised)",
  "description": "Cut the branch, publish notes, announce on Slack.",
  "status": "in-progress",
  "priority": "high"
}

Response 200 OK:

{
  "success": true,
  "message": "Task updated",
  "data": { "task": { "…": "updated task" } }
}

PATCH /api/tasks/:id/status

Slim status-only update. Owner-only.

Request body:

{ "status": "completed" }

Response 200 OK:

{
  "success": true,
  "message": "Task status updated",
  "data": { "task": { "…": "task with new status" } }
}

DELETE /api/tasks/:id

Delete a task. Owners and admins can delete.

Response 200 OK:

{ "success": true, "message": "Task deleted", "data": null }

Admin

All endpoints in this section require a Bearer access token with role: "admin". Non-admins get 403 Admin privileges required.

GET /api/admin/users

Paginated list of users. Query params:

Param Type Default Notes
page int 1 1-indexed
limit int 25 capped at 100
role string admin or user
status string active or inactive
q string Free-text match on name or email

Response 200 OK:

{
  "success": true,
  "message": "Users",
  "data": {
    "items": [ { "_id": "", "name": "", "email": "", "role": "user", "status": "active", "…": "" } ],
    "pagination": { "page": 1, "limit": 25, "total": 1, "totalPages": 1 }
  }
}

DELETE /api/admin/users/:id

Permanently delete a user other than the calling admin (400 if you target yourself). All of the user's tasks are orphaned (owner: null) for audit — they are not cascade-deleted.

Response 200 OK:

{ "success": true, "message": "User deleted", "data": null }

PATCH /api/admin/users/:id/status

Activate or deactivate any user other than yourself with status: "inactive".

Request body:

{ "status": "inactive" }

Response 200 OK:

{
  "success": true,
  "message": "User status updated",
  "data": { "user": { "…": "user with new status" } }
}

GET /api/admin/tasks

Paginated list of all tasks across the system. Query params:

Param Type Default Notes
page int 1 1-indexed
limit int 25 capped at 100
status string pending, in-progress, completed
priority string low, medium, high
owner string Filter by owner _id (or null for orphaned)
q string Free-text match on title or description

Each item in data.items is populated with owner: { _id, name, email, role }.

DELETE /api/admin/tasks/:id

Admin-only hard delete. Logs TASK_DELETED with description Admin deleted task: "<title>".

Response 200 OK:

{ "success": true, "message": "Task deleted", "data": null }

GET /api/admin/activity-logs

Paginated, filterable view of the activitylogs collection. Query params:

Param Type Notes
page int 1-indexed
limit int capped at 100
action string One of LOGIN, LOGOUT, REGISTER, TASK_CREATED, TASK_UPDATED, TASK_DELETED, USER_DELETED, USER_STATUS_UPDATED
user string User _id
from ISO date Lower bound for createdAt
to ISO date Upper bound for createdAt

Response 200 OK:

{
  "success": true,
  "message": "Activity logs",
  "data": {
    "items": [
      {
        "_id": "",
        "user": { "_id": "", "name": "Jane Doe", "email": "jane@example.com", "role": "user" },
        "userEmail": "jane@example.com",
        "action": "TASK_CREATED",
        "targetType": "Task",
        "targetId": "",
        "description": "Task created: \"Ship Q2 release\"",
        "ip": "127.0.0.1",
        "userAgent": "Mozilla/5.0 …",
        "createdAt": "2026-06-04T10:00:00.000Z"
      }
    ],
    "pagination": { "page": 1, "limit": 25, "total": 1, "totalPages": 1 }
  }
}

GET /api/admin/analytics

Aggregate metrics for the admin dashboard. No query params.

Response 200 OK:

{
  "success": true,
  "message": "Analytics",
  "data": {
    "users": {
      "total": 42, "active": 37, "inactive": 5,
      "admins": 2, "regular": 40
    },
    "tasks": {
      "total": 318, "pending": 121, "inProgress": 84, "completed": 113
    },
    "tasksByDay": [
      { "_id": "2026-05-23", "count": 9 },
      { "_id": "2026-05-24", "count": 14 }
    ],
    "recentActivity": [ { "…": "same shape as a log entry" } ]
  }
}

Role Matrix

✅ = allowed · ❌ = forbidden · ⚠️ = allowed with caveat

Action user admin
Register / login / refresh / logout / me
Read & update own profile
Read own stats and task list
Create / read / update / delete own task
Read another user's task
Delete another user's task
List all users
Delete a user ⚠️ cannot delete self
Activate / deactivate a user ⚠️ cannot deactivate self
List all tasks across the system
View activity logs
View analytics

Activity Log

Every state-mutating endpoint passes through the activity middleware, which exposes a req.log(action, targetType, targetId, description) helper. The log is written after the response is prepared, so it never blocks the user.

Action Fired by
REGISTER POST /api/auth/register — new user created
LOGIN POST /api/auth/login — successful login (success and failure paths)
LOGOUT POST /api/auth/logout — refresh-token hash cleared
TASK_CREATED POST /api/tasks
TASK_UPDATED PUT /api/tasks/:id and PATCH /api/tasks/:id/status
TASK_DELETED DELETE /api/tasks/:id (owner) and DELETE /api/admin/tasks/:id (admin)
USER_DELETED DELETE /api/admin/users/:id
USER_STATUS_UPDATED PATCH /api/admin/users/:id/status

Each log row stores user, userEmail, action, targetType, targetId, free-text description, request ip and userAgent, and a createdAt timestamp.

Screenshots

Coming soon — drop exported PNGs into docs/screenshots/ and the placeholders below will pick them up automatically.

Login User Dashboard Admin Users Admin Analytics Activity Logs

Tech Choices

  • JWT over server-side sessions — keeps the API stateless and easy to scale horizontally. Refresh tokens are bcrypt-hashed at rest (User.refreshTokenHash) so a database leak does not immediately leak live sessions.
  • 15-minute access tokens, 7-day refresh tokens — short enough to limit the blast radius of a stolen access token, long enough that active users are not constantly re-authenticating.
  • localStorage for the access token by default, with the refresh token also exposed as an httpOnly cookie — the cookie path is available for frontends that prefer stricter XSS isolation. The body is always returned so the SPA can pick either storage strategy without server changes.
  • Role-based middleware (auth + adminOnly) — declarative, composable, and easy to apply to a new route in one line. Avoids sprinkling if (user.role !== 'admin') checks in controllers.
  • Joi for input validation — declarative schemas per endpoint, with stripUnknown: true so extra fields are dropped silently instead of leaking 400s on harmless clients.
  • { success, message, data } envelope — single, predictable response shape for the whole API. Errors add an optional details object with per-field validation messages.
  • Activity logging is fire-and-forget — the service swallows DB errors and never awaits a write on the hot path, so a logging failure cannot bring down the API.
  • Orphaned tasks (not cascade delete) when a user is removed — preserves the audit trail in the activity log and in the admin/tasks list.
  • shadcn/ui + Tailwind on the frontend — copy-in components, full ownership of the source, no locked-in dependency upgrade treadmill.
  • Recharts for analytics — declarative React-friendly chart library; small bundle, no D3 wrangling.

Deployment (5 minutes to live)

The app is wired for one-click deploys to MongoDB Atlas (database) + Render (backend) + Vercel (frontend). All three have generous free tiers.

0. Prerequisite: MongoDB Atlas

  1. Create a free cluster at https://www.mongodb.com/cloud/atlas.
  2. Add a database user (username + password).
  3. In Network Access, add 0.0.0.0/0 (or your deployment IP).
  4. Grab the connection string from Database → Connect → Drivers → Node.js, with the database name avidus_interactive inserted before the ?:
    mongodb+srv://<user>:<password>@cluster0.xxxxx.mongodb.net/avidus_interactive?retryWrites=true&w=majority
    

1. Backend → Render (Blueprint, ~5 min)

  1. Sign in to https://render.com with GitHub.
  2. New +Blueprint → connect the avidus-interactive repo.
  3. Render auto-detects server/render.yaml and shows the planned Web Service.
  4. Before deploying, open the service and set these env vars (the rest are pre-filled by the Blueprint):
    • MONGODB_URI — your Atlas connection string
    • ADMIN_EMAIL — e.g. admin@avidus.com
    • ADMIN_PASSWORD — a strong password
    • JWT_ACCESS_SECRET and JWT_REFRESH_SECRET can be auto-generated (Render will fill these) or paste your own long random strings.
  5. Click Apply. Render builds, deploys, and starts the server. The start command is npm run seed:admin && node src/server.js, so the first admin is created/refreshed on every start — idempotent, safe to re-run. (Render's free tier doesn't support a separate releaseCommand, so we chain the seed into the start command instead.)
  6. Copy the service URL — looks like https://avidus-interactive-server.onrender.com.

2. Frontend → Vercel (~3 min)

  1. Sign in to https://vercel.com with GitHub.
  2. Add New → Project → import avidus-interactive.
  3. Configure:
    • Root Directory: client
    • Framework Preset: Vite (auto-detected)
    • Build Command: npm run build
    • Output Directory: dist
    • Environment Variables → add VITE_API_BASE_URL = https://avidus-interactive-server.onrender.com/api
  4. Deploy. You'll get a URL like https://avidus-interactive.vercel.appthis is your deployment link.

3. Wire CORS

Go back to Render → avidus-interactive-server → Environment, set CLIENT_ORIGIN to the Vercel URL (no trailing slash), and save. Render auto-redeploys.

4. Verify

Open the Vercel URL. Log in with the admin email/password you set in step 1. You should land on the admin dashboard backed by Atlas.

Render's free tier sleeps the service after 15 min of inactivity. The first request after sleep takes ~30 s. For a submission demo, ping it once before sharing the link.

Roadmap / Future Improvements

  • 🔁 Refresh-token rotation via httpOnly cookies — move refresh tokens entirely out of localStorage to close the XSS-read window.
  • 📧 Email verification on registration (signed token + 24h expiry).
  • 🔑 Password reset flow over email (one-time signed token).
  • 🛡️ Account lockout after N failed logins (in addition to the current IP-based rate limit).
  • 📜 Per-user audit trail — expose a GET /api/users/me/activity endpoint that returns only the current user's log rows.
  • 🧑‍🤝‍🧑 Team / organization model so users can share tasks and admins can be scoped to a single tenant.
  • 🧪 Automated test suite — Jest + Supertest for the API, Vitest
    • React Testing Library for the client.
  • 🐳 Docker Compose for one-command local dev (Mongo + API + client).
  • 🚀 CI pipeline (lint, test, build) on every PR.
  • 📦 OpenAPI 3.1 spec generated from Joi schemas, served at /api/docs.
  • 🌍 i18n in the React app.
  • 🔔 Real-time updates over Server-Sent Events for the admin activity log and the user's own task list.

License

This project is released under the MIT License — see LICENSE for the full text.

Author

Maintained by the Avidus interactive team. Drop a line at hello@avidus.com (placeholder) or open an issue on the repo.

About

Role-based access control and user activity tracking application (Node + Express + MongoDB, React + Vite + Tailwind + shadcn/ui)

Resources

License

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors