Skip to content
Merged
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
42 changes: 28 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@opentelemetry/sdk-trace-base": "^1.30.1",
"@opentelemetry/sdk-trace-node": "^1.30.1",
"@opentelemetry/semantic-conventions": "^1.34.0",
"airtable": "^0.12.2",
"archiver": "^7.0.1",
"axios": "^1.9.0",
"body-parser": "^2.2.0",
Expand Down
130 changes: 114 additions & 16 deletions src/controller/NfcController.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import axios from "axios";
import { mongoose } from "../services/mongoose_service";
const ObjectId = mongoose.Types.ObjectId;
import Airtable from "airtable";
import { getModels } from "./util/resources";

const NfcSchema = new mongoose.Schema({
nfcId: { type: String, required: true },
userId: { type: String, ref: 'User', required: true },
createdAt: { type: Date, default: Date.now }
});

const UserSchema = new mongoose.Schema({
checkIns: { type: Object, default: {} }
})

const NfcModel = mongoose.model('nfc-user-assignments', NfcSchema);

delete mongoose.models.User;
const UserModel = mongoose.model('User', UserSchema);
const NfcUserModel = getModels().user.mongoose;

export const assignNFCToUser = async (nfcId: string, userId: string) => {
if (!nfcId || !userId) {
Expand Down Expand Up @@ -95,7 +93,7 @@ export const getUserFromNfcId = async (nfcId: string) => {
}

try {
const user = await UserModel.findById(userId);
const user = await NfcUserModel.findById(userId);
return user;
} catch (error: any) {
console.log(error);
Expand All @@ -104,28 +102,128 @@ export const getUserFromNfcId = async (nfcId: string) => {

}

export const updateCheckInField = async (nfcId: string, field: string, value: boolean) => {
export const checkIn = async (nfcId: string, field: string) => {
const userId = await getUserIdFromNfcId(nfcId);

const existingUser = await UserModel.findById(userId);
const existingUser = await NfcUserModel.findById(userId);
if (!existingUser) {
throw new Error(`User with ID ${userId} does not exist.`);
}

if (!existingUser.checkIns) {
throw new Error(`User with ID ${userId} does not have any events.`);
}

const newCheckIn = new Date().toISOString();

try {
const response = await UserModel.updateOne(
const response = await NfcUserModel.updateOne(
{ _id: new ObjectId(userId), checkIns: { $exists: true } },
{ $set: {
checkIns: {
...existingUser.checkIns,
[field]: value
}

checkIns: existingUser.checkIns.map((checkIn) => {
if (checkIn.event.name === field) {
return {
...checkIn,
checkIns: [...checkIn.checkIns, newCheckIn]
}
}
return checkIn;
})
} },
);
return response;


return newCheckIn;
} catch (error: any) {
console.log(error);
throw new Error(`Error updating check-ins: ${error.message}`);
}
}

export const removeLastCheckIn = async (nfcId: string, field: string) => {
const userId = await getUserIdFromNfcId(nfcId);

const existingUser = await NfcUserModel.findById(userId);
if (!existingUser) {
throw new Error(`User with ID ${userId} does not exist.`);
}

if (!existingUser.checkIns) {
throw new Error(`User with ID ${userId} does not have any events.`);
}

const newCheckIns = existingUser.checkIns.map((checkIn: any) => {
if (checkIn.event.name === field) {
return {
...checkIn,
checkIns: checkIn.checkIns.slice(0, -1)
}
}
return checkIn;
});
try {
const response = await NfcUserModel.updateOne(
{ _id: new ObjectId(userId), checkIns: { $exists: true } },
{ $set: { checkIns: newCheckIns } }
);
} catch (error: any) {
console.log(error);
throw new Error(`Error removing last check-in: ${error.message}`);
}
}

export const populateEvents = async (userId: string) => {
const airtableToken = process.env.AIRTABLE_TOKEN;

let events: any[] = [];
try {

console.log('airtableToken', airtableToken);

Airtable.configure({
endpointUrl: 'https://api.airtable.com',
apiKey: airtableToken
});

const base = Airtable.base("app8WOptWZhwtlUam");

const records = await base('Events').select({
fields: ['Name', 'Start', 'End']
}).all();
events = [...records];
} catch (error: any) {
console.log(error);
throw new Error(`Error populating events: ${error.message}`);
}

console.log(events);

const eventsArray = events.map((event: any) => {
return {
event: {
name: event.fields.Name,
start: event.fields.Start,
end: event.fields.End
},
checkIns: [] // when hackers check in append a some sort of date/time to the checkIns array
}
});

try {

const user = await NfcUserModel.findByIdAndUpdate(
userId,
{
$set: {
checkIns: eventsArray
}
},
{ new: true }
);

return user;
} catch (error: any) {
console.log(error);
throw new Error(`Error populating events: ${error.message}`);
}
}
15 changes: 15 additions & 0 deletions src/models/user/fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -916,6 +916,13 @@ export const fields = {
readCheck: true,
},

checkIns: {
type: Array,
required: false,
default: [],
readCheck: true,
},

lastLogout: {
type: Number,
required: true,
Expand Down Expand Up @@ -1151,6 +1158,14 @@ export interface IUser extends BasicUser {
computedApplicationDeadline: number;
computedRSVPDeadline: number;
mailingListSubcriberID?: number;
checkIns?: {
event: {
name: string;
start: string; // "2025-07-18T21:00:00.000Z"
end: string;
};
checkIns: string[]; // ISO string
}[];
}

export interface IMailMerge {
Expand Down
50 changes: 33 additions & 17 deletions src/routes/nfc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@ import express, {Request, Response} from "express";

import { isConnected } from "../services/mongoose_service";

import { assignNFCToUser, getUserIdFromNfcId, getUserFromNfcId, deleteAssignmentByNfc, deleteAssignmentByUser, updateCheckInField } from "../controller/NfcController";
import {
assignNFCToUser,
getUserIdFromNfcId,
getUserFromNfcId,
deleteAssignmentByNfc,
deleteAssignmentByUser,
checkIn,
populateEvents,
removeLastCheckIn
} from "../controller/NfcController";

import { isVolunteer } from "../models/validator";

Expand Down Expand Up @@ -57,30 +66,37 @@ nfcRouter.get('/getUser/:nfcId', async (req: Request, res: Response) => {
}
});

nfcRouter.post('/updateCheckInsFromNFC', async (req: Request, res: Response) => {
const { nfcId, checkInEvent, value } = req.body;

const events = [
'hackerCheckIn',
'lunchOne',
'dinnerOne',
'eventOne',
'snackOne',
'lunchTwo',
'dinnerTwo'
]

if (checkInEvent === undefined || !events.includes(checkInEvent)) {
return res.status(400).json({ error: 'Invalid check-in event' });
nfcRouter.post('/checkInFromNFC', async (req: Request, res: Response) => {
const { nfcId, checkInEvent } = req.body;

try {
const response = await checkIn(nfcId, checkInEvent);
return res.status(200).json({ response });
} catch (error) {
return res.status(500).json({ error: 'Failed to update check-ins' });
}
});

nfcRouter.post('/removeLastCheckIn', async (req: Request, res: Response) => {
const { nfcId, checkInEvent } = req.body;

try {
const response = await updateCheckInField(nfcId, checkInEvent, value);
const response = await removeLastCheckIn(nfcId, checkInEvent);
return res.status(200).json({ response });
} catch (error) {
return res.status(500).json({ error: 'Failed to update check-ins' });
}
});

nfcRouter.post('/populateEvents', async (req: Request, res: Response) => {
const { userId } = req.body;

try {
const response = await populateEvents(userId);
return res.status(200).json({ response });
} catch (error) {
return res.status(500).json({ error: 'Failed to populate events' });
}
});

export default nfcRouter;
Loading