Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
918e974
replaced imports from shared submodule to http links
Jaz-spec Jun 9, 2025
63a1b77
deno deploy fix
Jaz-spec Jun 9, 2025
ecc21a6
experimental delete of shred folder
Jaz-spec Jun 9, 2025
b8fef6f
updated one import
Jaz-spec Jun 9, 2025
db2bf03
changed a few more imports
Jaz-spec Jun 9, 2025
75c187e
changed url
Jaz-spec Jun 9, 2025
f0cf123
attempt to solve corse error
Jaz-spec Jun 9, 2025
76691eb
trial deploy bug fix -> using a more stable version of bycrypt
Jaz-spec Jun 9, 2025
dd2dfb1
changed hash to hashSync
Jaz-spec Jun 11, 2025
a6e6020
Changed an uncaught hash
Jaz-spec Jun 11, 2025
ea2fefc
added more explicit console logs
Jaz-spec Jun 11, 2025
163ced8
added cron
Jaz-spec Jun 11, 2025
f0da16c
changed cron timimgs for testing
Jaz-spec Jun 11, 2025
1bd496c
updated cron to update at midnight
Jaz-spec Jun 11, 2025
4e5b173
added dev url to backend
Jaz-spec Jun 11, 2025
b99cf5b
fixing silly mistake
Jaz-spec Jun 11, 2025
4665eaa
trying out a new ai prompt
Jaz-spec Jun 11, 2025
be6132b
changed cron job for production
Jaz-spec Jun 11, 2025
351912c
cron targets cron route
Jaz-spec Jun 11, 2025
b84f7ac
imported cron to app
Jaz-spec Jun 11, 2025
a863eab
added console logs
Jaz-spec Jun 11, 2025
dd924dd
added dailt token to cron job
Jaz-spec Jun 11, 2025
faab68b
calling cron job internally
Jaz-spec Jun 11, 2025
34c4340
feat: :sparkles: Set up back end routes for user Events
Oggie112 Jun 12, 2025
28b2db7
style: :art: Ran lint, check and fmt.
Oggie112 Jun 12, 2025
4f7b6b0
Change to update event to make its fullevent a partial to match front…
Oggie112 Jun 12, 2025
54bc436
Ran check, fmt, lint
Oggie112 Jun 12, 2025
023241c
Change to console.log
Oggie112 Jun 12, 2025
c6d741b
Simplified handleUserEvents auth
Oggie112 Jun 12, 2025
bc48ef1
Ran deno fmt
Oggie112 Jun 12, 2025
f965db2
set cron to midnight
Jaz-spec Jun 13, 2025
cd3dc4f
merged savedEvents
Jaz-spec Jun 14, 2025
53d52f8
merged in saveUserEvents
Jaz-spec Jun 14, 2025
d7ee995
linting
Jaz-spec Jun 14, 2025
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ node_modules
PROMPTS.md
To-do.md
Claude/
.claude/
.claude/
8 changes: 4 additions & 4 deletions api-examples/signup.rest
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
POST http://localhost:3000/auth/signup
POST https://the-locals.deno.dev/auth/signup
Content-Type: application/json

{
"email": "Dkjdhsbfjewrbk@gmail.com",
"password": "securePassword123",
"username": "Donald-kjhsdbvcck"
"email": "Dkjdhsbfje@gmail.com",
"password": "securePasswod1",
"username": "Donald-kjhsdb"
}
1 change: 0 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
"event-test": "deno run --allow-read --allow-write testing/testScripts/event.utils.test.ts"
},
"imports": {
"models/": "./shared/src/models/",
"mongodb": "npm:mongodb",
"zod": "npm:zod",
"@openai/openai": "jsr:@openai/openai@^4.98.0"
Expand Down
11 changes: 10 additions & 1 deletion deno.lock

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

6 changes: 5 additions & 1 deletion deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,8 @@ export type { Payload } from 'https://deno.land/x/djwt@v3.0.2/mod.ts';
export { Db, MongoClient, ObjectId } from 'npm:mongodb@6.1.0';
export type { OptionalId } from 'npm:mongodb@6.1.0';

export { compare, hash } from 'https://deno.land/x/bcrypt@v0.4.1/mod.ts';
//more stable version of bycrypt
export {
compareSync,
hashSync,
} from 'https://deno.land/x/bcrypt@v0.2.4/mod.ts';
1 change: 0 additions & 1 deletion shared
Submodule shared deleted from bddcaa
7 changes: 6 additions & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import 'https://deno.land/std@0.224.0/dotenv/load.ts';
import { Application, oakCors } from '../deps.ts';
import './database/connect.ts';
import './cron.ts';
import router from './routes/index.ts';
import logRequest from './middleware/logRequest.ts';

const app = new Application();

app.use(
oakCors({ origin: ['http://localhost:5173', 'http://127.0.0.1:5173'] }),
oakCors({
origin: ['https://the-locals.netlify.app', 'http://localhost:5173'],
credentials: true,
optionsSuccessStatus: 200, // For legacy browser support
}),
); // Allow local frontend to bypass cors requirement
app.use(logRequest);
app.use(router.routes());
Expand Down
10 changes: 9 additions & 1 deletion src/controllers/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// deno-lint-ignore-file require-await
import { Context, RouterContext, Status } from '../../deps.ts';
import { UserLogInSchema, UserSignUpSchema } from 'models/user.model.ts';
import {
UserLogInSchema,
UserSignUpSchema,
} from 'https://raw.githubusercontent.com/fac-31/Pro0428-LocalEventShared/main/src/models/user.model.ts';
import { authService } from '../services/auth.service.ts';

export const getCurrentUser = async (ctx: Context) => {
Expand All @@ -19,9 +22,13 @@ export const getCurrentUser = async (ctx: Context) => {

export const signUpUser = async (ctx: RouterContext<'/signup'>) => {
const body = await ctx.request.body.json();
console.log('Request body:', body);

const userInput = UserSignUpSchema.safeParse(body);
console.log('Validtion result:', userInput.success);

if (!userInput.success) {
console.log('Validation Errors:', userInput.error);
ctx.response.status = Status.BadRequest;
ctx.response.body = { errors: userInput.error.flatten() };
return;
Expand All @@ -33,6 +40,7 @@ export const signUpUser = async (ctx: RouterContext<'/signup'>) => {
} catch (error) {
ctx.response.status = Status.InternalServerError;
if (error instanceof Error) {
console.log('Sign up user error:', error);
ctx.response.body = { error: error.message };
} else {
ctx.response.body = { error: 'Unkown error creating user' };
Expand Down
123 changes: 80 additions & 43 deletions src/controllers/event.controller.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,40 @@
import { Context, ObjectId, RouterContext, Status } from '../../deps.ts';
import { eventService } from '../services/event.service.ts';
import { generateEvents } from '../services/openai.service.ts';
import { eventFilterSchema, FullEvent } from 'models/event.model.ts';
//import { generateEvents } from '../services/openai.service.ts';
import {
eventFilterSchema,
FullEvent,
} from 'https://raw.githubusercontent.com/fac-31/Pro0428-LocalEventShared/main/src/models/event.model.ts';
import { verifyToken } from '../utils/token.utils.ts';
import { Payload } from '../../deps.ts';
import { userService } from '../services/user.service.ts';

export const getAllEvents = async (ctx: Context) => {
const params = ctx.request.url.searchParams;
const auth = ctx.request.headers.get('Authorization');
const token = auth && auth.split(' ')[1];

let userId: string | null = null;
if (token) {
try {
const user: Payload = await verifyToken(token);
userId = user._id as string;
} catch (error) {
console.warn('Invalid token in /events: ' + error);
}
}

const params = ctx.request.url.searchParams;
const rawModes = params.getAll('mode');

const normalizedModes = rawModes
.flatMap((param) => param.split(','))
.map((mode) => mode.trim().toLowerCase())
.filter(Boolean); // removes empty strings
.filter(Boolean);

const parseResult = eventFilterSchema.safeParse({
mode: normalizedModes.length > 0 ? normalizedModes : undefined,
});

let allEvents;
if (parseResult.success) {
console.log(parseResult.data);
allEvents = await eventService.getAllEvents(parseResult.data);
} else {
if (!parseResult.success) {
ctx.response.status = Status.BadRequest;
ctx.response.body = {
error: 'Invalid query parameters',
Expand All @@ -30,7 +43,21 @@ export const getAllEvents = async (ctx: Context) => {
return;
}

ctx.response.body = allEvents;
try {
const [allEvents, savedEvents] = await Promise.all([
eventService.getAllEvents(parseResult.data),
userId ? userService.getUserEvents(userId) : Promise.resolve([]),
]);

ctx.response.body = {
events: allEvents,
savedEventIds: savedEvents.map((x) => x._id),
};
} catch (err) {
console.error('Error fetching combined event data:', err);
ctx.response.status = Status.InternalServerError;
ctx.response.body = { error: 'Internal server error' };
}
};

export const getEventById = async (ctx: RouterContext<'/:id'>) => {
Expand Down Expand Up @@ -64,8 +91,8 @@ export const updateEventById = async (ctx: RouterContext<'/:id'>) => {
}

const id: string = ctx.params.id;
const event: FullEvent = await ctx.request.body.json();

const event: Partial<FullEvent> = await ctx.request.body.json();
console.log(event);
if (!ObjectId.isValid(id)) {
ctx.response.status = Status.BadRequest;
ctx.response.body = `Invalid event id "${id}"`;
Expand Down Expand Up @@ -129,33 +156,43 @@ export const saveNewEvent = async (ctx: Context) => {
}
};

export const saveEventsCronHandler = async (ctx: Context) => {
console.log('Server pinged');
const token = ctx.request.headers.get('X-Daily-Token');

// Change this to appropriate .env or github secret
const expectedToken = Deno.env.get('DAILY_JOB_TOKEN');

if (token !== expectedToken) {
ctx.response.status = Status.Forbidden;
ctx.response.body = 'Forbidden';
return;
}

console.log('Daily task triggered');

const events = await generateEvents(
['music', 'charity', 'sports', 'other'],
'Finsbury Park',
);

if (events !== null) {
await eventService.saveEvents(events);
ctx.response.body = 'Events saved sucessfully';
console.log('Events saved');
} else {
ctx.response.status = Status.NoContent;
ctx.response.body = 'No events to save';
console.log('There were no events to save');
}
};
// export const saveEventsCronHandler = async (ctx: Context) => {
// console.log('Called saveEventsCronHandler()');
// const token = ctx.request.headers.get('X-Daily-Token');

// // Change this to appropriate .env or github secret
// const expectedToken = Deno.env.get('DAILY_JOB_TOKEN');

// if (token !== expectedToken) {
// ctx.response.status = Status.Forbidden;
// ctx.response.body = 'Forbidden';
// return;
// }

// console.log('Daily task triggered');

// const events = await generateEvents(
// ['music', 'charity', 'sports', 'other'],
// 'Finsbury Park',
// );

// if (events !== null) {
// await eventService.saveEvents(events);
// ctx.response.body = 'Events saved sucessfully';
// console.log('Events saved');
// } else {
// ctx.response.status = Status.NoContent;
// ctx.response.body = 'No events to save';
// console.log('There were no events to save');
// }
// };

// export const updateEvent = async (ctx: Context) => {
// // TODO: Update event in DB
// ctx.response.body = { message: `Update event of params.id` };
// };

// export const deleteEvent = async (ctx: Context) => {
// // TODO: Delete event from DB
// ctx.response.body = { message: `Delete event params.id` };
// };
26 changes: 23 additions & 3 deletions src/controllers/user.controller.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// deno-lint-ignore-file require-await
import { Context, RouterContext } from '../../deps.ts';
import { Context, Payload, RouterContext } from '../../deps.ts';
import { userService } from '../services/user.service.ts';
import { Status } from '../../deps.ts';
import { toSafeUser } from 'models/user.model.ts';
import { toSafeUser } from 'https://raw.githubusercontent.com/fac-31/Pro0428-LocalEventShared/main/src/models/user.model.ts';
export const getUserProfile = async (ctx: Context) => {
// TODO: Get user data from ctx.state.user
ctx.response.body = { message: 'Get user profile' };
Expand All @@ -17,7 +17,7 @@ export const deleteUserAccount = async (ctx: Context) => {
// TODO: Call userService.deleteUser(ctx.state.user.id)
ctx.response.body = { message: 'Delete user account' };
};
export const getAllUsers = async (ctx: RouterContext<'/:role'>) => {
export const getAllUsers = async (ctx: RouterContext<'/getUsers:role'>) => {
const role = ctx.params.role;

if (role !== 'user' && role !== 'admin' && role !== 'all') {
Expand All @@ -40,3 +40,23 @@ export const getAllUsers = async (ctx: RouterContext<'/:role'>) => {
}
}
};

export const handleUserEvents = async (ctx: Context) => {
try {
const user: Payload = ctx.state.user;

if (!user || !user._id) {
ctx.response.status = Status.Unauthorized;
ctx.response.body = { message: 'Unauthorized: Invalid token' };
return;
}
const body = await ctx.request.body.json();
const { eventId, active } = body;
const userId = user._id as string;

await userService.handleUserEvents(eventId, userId, active);
return ctx.response.body = 'User event handled';
} catch (error) {
console.error('Issue saving user events: ' + error);
}
};
29 changes: 29 additions & 0 deletions src/cron.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { eventService } from './services/event.service.ts';
import { generateEvents } from './services/openai.service.ts';

Deno.cron('save events cron', '0 0 * * *', async () => {
console.log('executing cron job...');
try {
console.log('Daily task triggered');

const events = await generateEvents(
['music', 'charity', 'sports', 'other'],
'Finsbury Park',
);

if (events !== null) {
await eventService.saveEvents(events);
console.log('Events saved successfully');
} else {
console.log('There were no events to save');
}
} catch (error) {
console.error('Cron job failed:', error);
}
});

//cron for every 10mins
// "*/10 * * * *"

// cron for midnight
// "0 0 * * *"
4 changes: 2 additions & 2 deletions src/routes/event.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
deleteEventById,
getAllEvents,
getEventById,
saveEventsCronHandler,
//saveEventsCronHandler,
saveNewEvent,
updateEventById,
} from '../controllers/event.controller.ts';
Expand All @@ -20,7 +20,7 @@ router.get('/:id', getEventById);
router.put('/:id', ProtectRoute, protectAdmin, updateEventById);
router.delete('/:id', ProtectRoute, protectAdmin, deleteEventById);
router.post('/save-event', saveNewEvent);
router.post('/cron/save-events', saveEventsCronHandler);
//router.post('/cron/save-events', saveEventsCronHandler);
// router.post("/")

// router.post("/generate", generateEvents) (using the openAi service)
Expand Down
20 changes: 7 additions & 13 deletions src/routes/user.routes.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
import { Router } from '../../deps.ts';
import protectAdmin from '../middleware/requireAdmin.ts';
import ProtectRoute from '../middleware/protectRoute.ts';
import { getAllUsers } from '../controllers/user.controller.ts';
const router = new Router();

// Routes under /users
import {
getAllUsers,
handleUserEvents,
} from '../controllers/user.controller.ts';

// Temporary stub for testing
router.get('/', (ctx) => {
ctx.response.body = 'User route root';
});
router.get('/:role', ProtectRoute, protectAdmin, getAllUsers);
// -> to controllers
// router.get("/me", getUserProfile)
// router.put("/me", updateUserProfile)
// router.delete("/me", deleteUserAccount)
const router = new Router();

router.get('/getUsers:role', ProtectRoute, protectAdmin, getAllUsers);
router.post('/saveUserEvents', ProtectRoute, handleUserEvents);
export default router;
Loading