Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules/
.env
15 changes: 14 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
FROM node:alpine
WORKDIR /app

# Copy package files
COPY package*.json ./
RUN npm install --omit=dev

# Install all dependencies (including dev for Vite build)
RUN npm install

# Copy source files
COPY . .

# Build React app
RUN npx vite build

# Remove dev dependencies to slim down image (optional)
# RUN npm prune --production

EXPOSE 8081
CMD ["node", "api/server.js"]
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@ Club's internal alumni directory
## Prerequisites

- [Docker Desktop](https://www.docker.com/products/docker-desktop/) installed and running
- **Firecrawl API key** (contact the dev team for this.)

## Getting Started
## Configuration

Create a `.env` file in the project root:

```bash
# Clone the repo
git clone https://github.com/SCE-Development/sce-linkedin.git
cd sce-linkedin
DATABASE_HOST=127.0.0.1
FIRECRAWL_API_KEY=fc-your-key-here
```

# Start the dev environment (Express server + MongoDB)
## Getting Started

```bash
# Start the dev environment (Express + MongoDB)
docker compose -f docker-compose.dev.yml up --build

# Access the app at http://localhost:8081
```
25 changes: 25 additions & 0 deletions api/models/Alumni.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ const ExperienceSchema = new Schema({

const AlumniSchema = new Schema(
{
name: {
type: String,
required: true
},
userId: {
type: Schema.Types.ObjectId,
ref: 'User',
Expand Down Expand Up @@ -61,6 +65,27 @@ const AlumniSchema = new Schema(
type: String,
default: ''
},
currentCompany: {
type: String,
default: ''
},
currentJobTitle: {
type: String,
default: ''
},
location: {
type: String,
default: ''
},
enrichmentStatus: {
type: String,
enum: ['pending', 'completed', 'failed'],
default: null
},
enrichmentJobId: {
type: String,
default: ''
},
experiences: {
type: [ExperienceSchema],
default: []
Expand Down
68 changes: 63 additions & 5 deletions api/routes/Alumni.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
const express = require('express');
const Alumni = require('../models/Alumni');
const { enrichAlumni, getEnrichedData } = require('../services/enrichmentService');

// Generate a random 24-character hex string (MongoDB ObjectId format)
function generateObjectId() {
const chars = '0123456789abcdef';
let result = '';
for (let i = 0; i < 24; i++) {
result += chars[Math.floor(Math.random() * chars.length)];
}
return result;
}

const router = express.Router();

// List all alumni profiles
router.get('/', async (req, res) => {
router.get('/alumni', async (req, res) => {
try {
const alumni = await Alumni.find();
res.json(alumni);
Expand All @@ -14,7 +25,7 @@ router.get('/', async (req, res) => {
});

// Get a single alumni profile by ID
router.get('/:id', async (req, res) => {
router.get('/alumni/:id', async (req, res) => {
try {
const alumni = await Alumni.findById(req.params.id);
if (!alumni) return res.status(404).json({ error: 'Alumni not found' });
Expand All @@ -25,17 +36,64 @@ router.get('/:id', async (req, res) => {
});

// Create a new alumni profile
router.post('/', async (req, res) => {
router.post('/alumni', async (req, res) => {
try {
// Ensure name is provided
if (!req.body.name) {
return res.status(400).json({ error: 'Name is required for alumni record' });
}

// Auto-generate userId if not provided
if (!req.body.userId) {
req.body.userId = generateObjectId();
}

const alumni = await new Alumni(req.body).save();
res.status(201).json(alumni);
} catch (err) {
res.status(500).json({ error: err.message });
}
});

// Start enrichment job (preview mode - doesn't save to DB)
router.post('/alumni/enrich', async (req, res) => {
try {
const { name, graduationYear } = req.body;

if (!name) {
return res.status(400).json({ error: 'Name is required for enrichment' });
}

const alumni = { name, graduationYear };
const result = await enrichAlumni(alumni);

if (!result.success) {
return res.status(500).json({ error: result.error });
}

res.json({ jobId: result.jobId, status: 'pending' });
} catch (err) {
res.status(500).json({ error: err.message });
}
});

// Poll for enrichment job status
router.get('/alumni/enrich/:jobId', async (req, res) => {
try {
const result = await getEnrichedData(req.params.jobId);

if (!result.success) {
return res.status(500).json({ error: result.error });
}

res.json(result);
} catch (err) {
res.status(500).json({ error: err.message });
}
});

// Update an existing alumni profile
router.put('/:id', async (req, res) => {
router.put('/alumni/:id', async (req, res) => {
try {
const alumni = await Alumni.findByIdAndUpdate(req.params.id, req.body, {
new: true,
Expand All @@ -49,7 +107,7 @@ router.put('/:id', async (req, res) => {
});

// Delete an alumni profile
router.delete('/:id', async (req, res) => {
router.delete('/alumni/:id', async (req, res) => {
try {
const alumni = await Alumni.findByIdAndDelete(req.params.id);
if (!alumni) return res.status(404).json({ error: 'Alumni not found' });
Expand Down
15 changes: 15 additions & 0 deletions api/server.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
require('dotenv').config();

const express = require('express');
const mongoose = require('mongoose');
const alumniRouter = require('./routes/Alumni');
const path = require('path');

const app = express();
const PORT = 8081;

app.use(express.json());

// Serve React build from dist/
app.use(express.static('dist'));

// API routes
app.use('/api', alumniRouter);

// Fallback to index.html for React - must come after static and API routes
app.use((req, res) => {
if (!req.path.startsWith('/api')) {
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
}
});

const dbHost = process.env.DATABASE_HOST || '127.0.0.1';
mongoose
.connect(`mongodb://${dbHost}:27017/sce_linkedin`)
Expand Down
Loading