Shushoku Assen
A placement portal that streamlines the hiring process, helping companies find the best talent and students the best opportunities.
[!NOTE] This project is kind of like
Linkedin - Social Networkingfor internal placements in college.
Highlights
- Role based access for Admin, Company and Student (
role_requiredon endpoints) - Browse live placement drives and apply to a drive with a single click
- Track every application and placement offer in one place
- Companies can post drives, shortlist candidates, and manage the entire recruitment pipeline
- Admin approves drives and company registrations
- Visualization dashboards built with Chart.js
- Filter-driven analytics (students, companies, drives, applications and placements)
- On-demand PDF reports for admin, student, company, drive and placement offer
- Periodic report emails dispatched to students with their placement activity attached
- Google Chat notification when a new drive opens
- Secure password hashing (werkzeug) and JWT cookie-based session handling
- Background scheduling with Celery + Redis (report mails and drive cleanup)
Tech Stack
Backend (backend/)
- Flask + Flask-Smorest — REST API with OpenAPI/Swagger docs (
/swagger-ui) - Flask-SQLAlchemy — ORM
- Flask-JWT-Extended — JWT auth with cookie + CSRF support
- Flask-Caching — response caching
- Flask-CORS — cross-origin support
- Celery + Redis — async tasks, webhooks and scheduled jobs
- WeasyPrint — PDF report generation
- httpx — Google Chat webhook calls
- Werkzeug — password hashing
Frontend (frontend/)
- Vue 3 + Vite
- Pinia — state management
- Vue Router — routing
- Chart.js + vue-chartjs — dashboards (incl. funnel chart)
- Axios — HTTP client
- dayjs — date handling
Project Structure
.
├── backend/ # Flask API + Celery tasks
│ ├── app.py # App factory, blueprints, celery schedule
│ ├── celery_config.py # Broker / result backend config
│ ├── pyproject.toml # Python deps (uv)
│ └── application/
│ ├── api/ # Blueprints: auth, utils, student, company,
│ │ # drive, applied, placement, analytics, download
│ ├── models.py # SQLAlchemy models
│ ├── schema.py # Marshmallow schemas
│ ├── tasks.py # Celery tasks (reports, mail, webhooks, cleanup)
│ ├── mail.py # SMTP mail helper
│ ├── dummy_data.py # Seeds DB with demo data on startup
│ └── factory.py # Extension init + role_required decorator
└── frontend/ # Vue 3 + Vite SPA
├── index.html
└── src/
├── router.js # Vue Router routes
├── stores/auth.js # Pinia auth store
├── axios.js # Axios instance with CSRF/cookie handling
└── components/ # Views & reusable components
Getting Started
Prerequisites
- Docker with Docker Compose (full-stack deployment), or for local development:
Docker (full stack)
One command builds and starts every service — Redis, the Flask API, Celery worker + beat, and the frontend (production build served by nginx):
docker compose up --build
| Service | URL |
|---|---|
| Frontend | http://localhost:5173 |
| API | http://localhost:3000 |
| Swagger | http://localhost:3000/swagger-ui |
How it works:
- Services start in order:
redis→backend(API) →worker+beat(reuse the backend image with overridden commands) →frontend. workerandbeatwait on the API healthcheck (GET /openapi.json) so the SQLite database is seeded exactly once.- Data persists across restarts in named volumes:
sqlite-data→/app/instance/database.db(SQLite database)reports→/app/static/reports(generated PDFs, written by the worker and served by the API)
- Celery connects to Redis via
CELERY_BROKER_URL/CELERY_RESULT_BACKEND(redis://redis:6379/{0,1}). - The frontend container serves the built SPA same-origin and reverse-proxies
/apiand/authto the backend, so JWT cookies + CSRF work without CORS changes.
Useful commands:
docker compose logs -f backend # follow one service
docker compose down # stop (named volumes persist)
docker compose down -v # stop and wipe the database + reports
Backend (local development)
cd backend
uv sync # install dependencies
uv run python app.py # start the API on http://localhost:3000
- On startup the app creates the SQLite database (
database.db) and seeds it with demo data. - Interactive API docs are available at
http://localhost:3000/swagger-ui.
Celery (worker + beat)
# terminal 1 - worker
uv run celery -A app.celery worker --loglevel=info
# terminal 2 - beat (scheduled tasks)
uv run celery -A app.celery beat --loglevel=info
Scheduled tasks (defined in app.py):
- Monthly — emails each student their placement activity report.
- Daily — closes drives whose deadline has passed.
Mail sink
Report emails use SMTP on localhost:1025 (see application/mail.py). Point a local mail catcher like MailHog or aiosmtpd at that port to inspect outgoing mail.
Google Chat webhook
The drive_notification task posts to a Google Chat webhook when a drive is created. Set the webhook URL in backend/application/tasks.py.
Frontend
cd frontend
npm install
npm run dev # start the Vite dev server on http://localhost:5173
The Vite dev server proxies /api and /auth to the backend at http://127.0.0.1:3000 (see frontend/vite.config.js).
Demo Accounts
Seeded by backend/application/dummy_data.py, password password for all:
| Role | Notes | |
|---|---|---|
| Admin | user@admin.com |
Full access |
| Student | user@student1.com |
Branch: Computer Science |
| Student | user@student2.com |
Branch: Data Science |
| Student | user@student3.com |
Branch: Mechanical Engineering |
| Company | hr@techcorp.com |
Approved — can host drives |
| Company | hr@financeltd.com |
Pending approval |
| Company | hr@badcompany.com |
Rejected |
Entities
- Student — applies to drives.
- Company — hosts drives.
- Admin — approves drives and company registrations, manages utilities.
Resources
- Drive — campaign started by a company, approved by Admin, participated in by students.
- Application — submitted by a student for a drive.
- Placement — offer made to a selected student.
API Endpoints
All endpoints except POST /auth/v1/login, POST /auth/v1/logout, POST /api/v1/students/ and POST /api/v1/company/ require authentication (JWT). * indicates any authenticated user.
Auth — prefix /auth/v1
- POST
/auth/v1/login— Log in with email + password. -> All - POST
/auth/v1/logout— Log out and clear the access cookie. -> All
Utilities — prefix /api/v1/utils
- GET
/api/v1/utils/roles: Get all roles. -> All - POST
/api/v1/utils/roles: Create a role. -> Admin - GET
/api/v1/utils/branch: Get all branches. -> All - POST
/api/v1/utils/branch: Create a branch. -> Admin - GET
/api/v1/utils/academic-degree: Get all academic degrees. -> All - POST
/api/v1/utils/academic-degree: Create an academic degree. -> Admin - GET
/api/v1/utils/industry: Get all industries. -> All - POST
/api/v1/utils/industry: Create an industry. -> Admin - GET
/api/v1/utils/skills: Get all skills. -> All - POST
/api/v1/utils/skills: Create a skill. -> Admin
Students — prefix /api/v1/students
- GET
/api/v1/students/: Get all students (filter by branch, year, degree, name, blacklisted). -> Admin - POST
/api/v1/students/: Register a student. -> All (open) - GET
/api/v1/students/<student_id>: Retrieve a student’s profile. -> All authenticated - PATCH
/api/v1/students/<student_id>: Update the profile (only Admin may setblacklisted). -> Student & Admin - DELETE
/api/v1/students/<student_id>: Delete a student. -> Student & Admin
Companies — prefix /api/v1/company
- GET
/api/v1/company/: Get all companies (filter by industry, status, name, blacklisted). -> Admin & Student - POST
/api/v1/company/: Register a company. -> All (open) - GET
/api/v1/company/<company_id>: Retrieve a company’s profile. -> All authenticated - PATCH
/api/v1/company/<company_id>: Update company info (only Admin may setstatus/blacklisted). -> Admin & Company - DELETE
/api/v1/company/<company_id>: Delete a company. -> Admin & Company
Drives — prefix /api/v1/drives
- GET
/api/v1/drives/: Get all drives (filter by company, title, job_type, status). -> All authenticated - POST
/api/v1/drives/: Create a drive (triggers Google Chat notification). -> Company - GET
/api/v1/drives/<drive_id>: Retrieve drive info. -> All authenticated - PATCH
/api/v1/drives/<drive_id>: Update drive info (e.g. approve, change status). -> Admin & Company - DELETE
/api/v1/drives/<drive_id>: Delete a drive. -> Admin
Applications — prefix /api/v1/applications
- GET
/api/v1/applications/: Get applications (filter by student, drive, company). -> All authenticated - POST
/api/v1/applications/: Apply to a drive. -> Student - GET
/api/v1/applications/<application_id>: Retrieve an application. -> All authenticated - PATCH
/api/v1/applications/<application_id>: Update application status (shortlist / select / reject / offer). -> Admin & Company
Placements — prefix /api/v1/placements
- GET
/api/v1/placements/: Get placement offers (filter by student, company, drive). -> All authenticated - POST
/api/v1/placements/: Offer a placement. -> Company - GET
/api/v1/placements/<placement_id>: Retrieve a placement offer. -> All authenticated
Analytics — prefix /api/v1/analytics
- GET
/api/v1/analytics/students: Students by branch / academic degree / total. -> All authenticated - GET
/api/v1/analytics/company: Companies by industry / total. -> All authenticated - GET
/api/v1/analytics/drives: Drives by status / job type / top companies / total. -> All authenticated - GET
/api/v1/analytics/application: Applications by status / total (filter by student, drive, company). -> All authenticated - GET
/api/v1/analytics/placements: Placements by company / total (filter by student, company, drive). -> All authenticated
Downloads — prefix /api/v1/downloads
Report endpoints dispatch a Celery task and return a task id; fetch the finished file from GET /api/v1/downloads/<id>.
- GET
/api/v1/downloads/report: Generate the admin report (PDF). -> Admin - GET
/api/v1/downloads/student/<student_id>/report: Generate a student report (PDF). -> Admin & Student - GET
/api/v1/downloads/company/<company_id>/report: Generate a company report (PDF). -> Admin & Company - GET
/api/v1/downloads/drive/<drive_id>/report: Generate a drive report (PDF). -> Admin & Company - GET
/api/v1/downloads/placement/<placement_id>/report: Generate a placement offer (PDF). -> All authenticated - GET
/api/v1/downloads/<id>: Retrieve a generated report by task id. -> All authenticated
Models and Fields
Base Model (abstract)
id— UUID string (primary key)created_at— timestampupdated_at— timestamp
Utility Models
-
Role : Base Model
name,descriptionusers— one-many, users with this role
-
Branch : Base Model
name,descriptionstudents— one-many, students in this branch
-
AcademicDegree : Base Model
name,descriptionstudents— one-many, students of this degree
-
Industry : Base Model
name,descriptioncompanies— one-many, companies in this industry
-
Skill : Base Model
name,description- linked to students (
student_skills) and drives (skills_required) via many-many join tables
User Models
-
User : Base Model
email— uniquepassword— hashed (werkzeug)blacklisted— booleanrole_id— FK toroles.idstudent_details/company_details— one-one, role dependentdetails— property resolving the role-appropriate details object
-
Details (abstract)
id— FK tousers.id
-
StudentDetails : Details
first_name,last_nameaboutgithub,linkedinbranch_id— FK tobranches.idyearacademic_degree_id— FK toacademic_degrees.idcgpaskills— many-many withskillsapplications— one-many withapplicationsplacements— one-many withplacements
-
CompanyDetails : Details
registered_namedescriptionindustry_id— FK toindustries.idlocationcontact_emailcontact_phonewebsitestatus— enum:pending|approved|rejecteddrives— one-many withdrivesplacements— one-many withplacements
Operational Models
-
Drive : Base Model
company_id— FK tocompany_details.idtitle,descriptionopeningssalaryjob_type— enum:internship|part-time|full-timedeadlinestatus— enum:pending|open|closed|rejectedskills_required— many-many withskillsapplications— one-many withapplicationsplacements— one-many withplacements
-
Application : Base Model
drive_id— FK todrives.idstudent_id— FK tostudent_details.idstatus— enum:applied|shortlisted|selected|rejected|offered
-
Placement : Base Model
student_id— FK tostudent_details.idcompany_id— FK tocompany_details.iddrive_id— FK todrives.idjoining_date