Compare commits

...

8 Commits

Author SHA1 Message Date
5dd8b228f4 update readme 2026-03-21 14:35:30 +00:00
ec0da2ab2f update to user feedback 2026-03-21 14:34:05 +00:00
b636009422 Bugfix 2026-03-19 20:26:03 +00:00
2d15819a70 cookies bug fix 2026-03-19 19:00:17 +00:00
644e45434e project cleanup 2026-03-19 17:57:58 +00:00
e5a5911f86 added style sheet 2026-03-19 17:55:48 +00:00
fd5121be80 updated readme 2026-03-19 17:49:06 +00:00
b4add42f2e deployment setup 2026-03-19 17:47:37 +00:00
13 changed files with 145 additions and 52 deletions

View File

@@ -1,3 +1,17 @@
# whatsfreeinmyfree # whatsfreeinmyfree
Record of what classrooms are free when I have a free. CS Practice Record of what classrooms are free when I have a free. CS Practice
## Use
To run this, do the following:
- in `frontend` execute `npm run build`
- in `backend` execute `npm run setup`
- in `backend` execute `npm run dev`
If reseting, do the following:
- in `backend` execute `npm run clean`
- in `backend` execute `npm run setup`

1
backend/.gitignore vendored
View File

@@ -25,3 +25,4 @@ dist-ssr
adduser.sql adduser.sql
database.db

Binary file not shown.

View File

@@ -4,12 +4,19 @@ import bodyParser from "body-parser";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import Database from "better-sqlite3"; import Database from "better-sqlite3";
const app = e();
const port = 3000;
const db = new Database("./database.db");
app.use(cors());
app.use(e.json());
app.use(bodyParser.json());
Date.prototype.addHours= function(h){ Date.prototype.addHours= function(h){
this.setTime(this.getTime() + (h*60*60*1000)); this.setTime(this.getTime() + (h*60*60*1000));
return this; return this;
} }
function getPeriod() { function getPeriod() {
const now = new Date(); const now = new Date();
let hours = now.getHours(); let hours = now.getHours();
@@ -18,7 +25,7 @@ function getPeriod() {
// P1 // P1
return 1; return 1;
} }
if ((hours == 9 && minutes >= 45) || (hours < 11 && minutes < 5)) { if ((hours == 9 && minutes >= 45) || (hours == 11 && minutes < 5) || (hours == 10)) {
// P2 / Break // P2 / Break
return 2; return 2;
} }
@@ -30,7 +37,7 @@ function getPeriod() {
// P4 // P4
return 4; return 4;
} }
if ((hours == 13 && minutes >= 5) || (hours == 15 && minutes < 15)) { if ((hours == 13 && minutes >= 5) || (hours == 15 && minutes < 15) || (hours == 14)) {
// Lunch to P5 // Lunch to P5
return 5; return 5;
} else { } else {
@@ -39,17 +46,12 @@ function getPeriod() {
} }
} }
const app = e(); function incrementUserSubmissions(userid) {
const port = 3000; const stmt = db.prepare(`UPDATE Users SET Submissions = Submissions + 1 WHERE Id=${parseInt(userid)};`);
const db = new Database("./database.db"); stmt.run();
}
app.use(cors()); app.use('/', e.static('../frontend/dist'));
app.use(e.json());
app.use(bodyParser.json());
app.get('/', (req, res) => {
res.send("Nothing Here");
});
app.get('/getPeriod', (req, res) => { app.get('/getPeriod', (req, res) => {
// req has nothing // req has nothing
@@ -67,7 +69,7 @@ app.post('/currentRooms', (req, res) => {
return; return;
} }
const today = req.body.day; const today = req.body.day;
let stmt = db.prepare(`SELECT * FROM TimeSlots INNER JOIN Rooms ON Rooms.Id=TimeSlots.Room WHERE Period BETWEEN ${currentPeriod - 1} AND ${currentPeriod + 1} AND Day=${today};`); let stmt = db.prepare(`SELECT TimeSlots.Id, TimeSlots.Period, TimeSlots.Day, Timeslots.Room, Rooms.RoomName FROM TimeSlots INNER JOIN Rooms ON Rooms.Id=TimeSlots.Room WHERE TimeSlots.Period BETWEEN ${currentPeriod - 1} AND ${currentPeriod + 1} AND Timeslots.Day=${today} ORDER BY Rooms.RoomName ASC;`);
let records = stmt.all(); let records = stmt.all();
res.status(200).json({records: records}); res.status(200).json({records: records});
}); });
@@ -81,11 +83,23 @@ app.post('/addTimeSlot', (req, res) => {
let userid = parseInt(body.userid); let userid = parseInt(body.userid);
let stmt = db.prepare(`INSERT INTO TimeSlots (Period, Day, Room) VALUES (${body.period}, ${body.day}, ${roomid});`); let stmt = db.prepare(`INSERT INTO TimeSlots (Period, Day, Room) VALUES (${body.period}, ${body.day}, ${roomid});`);
stmt.run(); stmt.run();
stmt = db.prepare(`UPDATE Users SET Submissions = Submissions + 1 WHERE Id=${userid};`); incrementUserSubmissions(userid)
stmt.run();
res.status(200).send("added timeslot"); res.status(200).send("added timeslot");
}); });
app.post('/removeTimeSlot', (req, res) => {
// req has roomid and userid
// ress has success or faliure
console.log("removeTimeSlot");
const body = req.body;
const slotid = parseInt(body.Id);
const userid = parseInt(body.userId);
let stmt = db.prepare(`DELETE FROM TimeSlots WHERE Id=${slotid};`);
stmt.run();
incrementUserSubmissions(userid);
res.status(200).send("removed timeslot");
})
app.post('/addRoom', (req, res) => { app.post('/addRoom', (req, res) => {
// req has userid and roomname // req has userid and roomname
// res has success or faliure // res has success or faliure
@@ -98,10 +112,9 @@ app.post('/addRoom', (req, res) => {
res.status(418).send("room already exists"); res.status(418).send("room already exists");
return; return;
} }
stmt = db.prepare(`INSERT INTO Rooms (RoomName) VALUES (${name})`); stmt = db.prepare(`INSERT INTO Rooms (RoomName) VALUES ('${name}')`);
stmt.run();
stmt = db.prepare(`UPDATE Users SET Submissions = Submissions + 1 WHERE Id=${parseInt(body.userid)};`);
stmt.run(); stmt.run();
incrementUserSubmissions(parseInt(body.userid));
res.status(200).send("added room"); res.status(200).send("added room");
}); });
@@ -109,7 +122,7 @@ app.get('/getRooms', (req, res) => {
// req has no data // req has no data
// res has success or faliure // res has success or faliure
console.log("getRooms"); console.log("getRooms");
let stmt = db.prepare(`SELECT * FROM Rooms`); let stmt = db.prepare(`SELECT * FROM Rooms ORDER BY RoomName ASC;`);
let records = stmt.all(); let records = stmt.all();
res.status(200).send({records: records}); res.status(200).send({records: records});
}); });
@@ -126,7 +139,7 @@ app.post('/createUser', async (req, res) => {
return; return;
} }
const generatedHash = await bcrypt.hash(body.pass, 10); const generatedHash = await bcrypt.hash(body.pass, 10);
stmt = db.prepare(`INSERT INTO Users (Email, Pass, Username, Submissions) VALUES ('${body.email}', '${generatedHash}', '${body.name}', 0)`); stmt = db.prepare(`INSERT INTO Users (Email, Pass, Username, Submissions) VALUES ('${body.email}', '${generatedHash}', '${body.name}', 0);`);
stmt.run(); stmt.run();
stmt = db.prepare(`SELECT Id FROM Users WHERE Email='${body.email}';`); stmt = db.prepare(`SELECT Id FROM Users WHERE Email='${body.email}';`);
const uid = stmt.get(); const uid = stmt.get();

View File

@@ -6,7 +6,8 @@
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"dev": "node index.js", "dev": "node index.js",
"setup": "rm ./database.db; touch ./database.db; node setup.js" "setup": "touch ./database.db; node setup.js",
"clean": "rm ./database.db"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",

View File

@@ -8,6 +8,7 @@
"name": "frontend", "name": "frontend",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"picnic": "^7.1.0",
"vue": "^3.5.30", "vue": "^3.5.30",
"vue-cookies": "^1.8.6" "vue-cookies": "^1.8.6"
}, },
@@ -994,6 +995,18 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/picnic": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/picnic/-/picnic-7.1.0.tgz",
"integrity": "sha512-OFoJClCDSaCnavs7QZPMIIjrodUO8m6V+GBNOWaIN4kk3IjzSAd2kp/RfkpZRwXK2u71q5mJ3UQaQjHJRr3dnw==",
"license": "MIT",
"engines": {
"node": ">=4.0.0"
},
"funding": {
"url": "https://www.paypal.me/franciscopresencia/19"
}
},
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",

View File

@@ -9,6 +9,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"picnic": "^7.1.0",
"vue": "^3.5.30", "vue": "^3.5.30",
"vue-cookies": "^1.8.6" "vue-cookies": "^1.8.6"
}, },

View File

@@ -1,6 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { inject, ref } from 'vue';
import type { VueCookies } from 'vue-cookies';
const $cookies = inject<VueCookies>("$cookies") as VueCookies;
const emit = defineEmits(['goBack']); const emit = defineEmits(['goBack']);
@@ -8,10 +10,11 @@ const roomName = ref<string>();
async function addRoom(e: SubmitEvent) { async function addRoom(e: SubmitEvent) {
e.preventDefault(); e.preventDefault();
let uid = await cookieStore.get("userId"); let uid = await $cookies.get("userId");
const res = await fetch("http://localhost:3000/addRoom", { console.log(uid);
const res = await fetch("/addRoom", {
method: "POST", method: "POST",
body: JSON.stringify({ roomName: roomName.value, userid: uid?.value }), body: JSON.stringify({ roomName: roomName.value, userid: uid }),
headers: new Headers({'content-type': 'application/json'}) headers: new Headers({'content-type': 'application/json'})
}); });
if (res.ok) { if (res.ok) {

View File

@@ -1,6 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { onBeforeMount, ref } from 'vue'; import { inject, onBeforeMount, ref } from 'vue';
import type { VueCookies } from 'vue-cookies';
const $cookies = inject<VueCookies>("$cookies") as VueCookies;
const emit = defineEmits(['goHome', 'addRoom']); const emit = defineEmits(['goHome', 'addRoom']);
const rooms = ref(); const rooms = ref();
@@ -10,18 +12,23 @@ const day = ref<number>(1);
const period = ref<number>(1); const period = ref<number>(1);
onBeforeMount(async () => { onBeforeMount(async () => {
let res = await fetch("http://localhost:3000/getRooms", { let res = await fetch("/getRooms", {
method: "GET" method: "GET"
}); });
rooms.value = await res.json(); rooms.value = await res.json();
const date = new Date();
day.value = date.getDay();
res = await fetch("getPeriod", { method: "GET" });
const json = await res.json();
period.value = json.period;
}); });
async function addTimeSlot(e: SubmitEvent) { async function addTimeSlot(e: SubmitEvent) {
e.preventDefault(); e.preventDefault();
let uid = await cookieStore.get("userId"); let uid = await $cookies.get("userId");
await fetch("http://localhost:3000/addTimeSlot", { await fetch("/addTimeSlot", {
method: "POST", method: "POST",
body: JSON.stringify({ roomid: room.value.Id, userid: uid?.value, day: (day.value * weekNumber.value), period: period.value }), body: JSON.stringify({ roomid: room.value.Id, userid: uid, day: (day.value * weekNumber.value), period: period.value }),
headers: new Headers({'content-type': 'application/json'}) headers: new Headers({'content-type': 'application/json'})
}); });
emit('goHome'); emit('goHome');

View File

@@ -1,5 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { onBeforeMount, ref } from 'vue'; import { inject, onBeforeMount, ref } from 'vue';
import type { VueCookies } from 'vue-cookies';
type timeSlot_t = {Id: number, Period: number, Day: number, Room: number, RoomName: string};
const $cookies = inject<VueCookies>("$cookies") as VueCookies;
const rooms = ref(); const rooms = ref();
const showRooms = ref<boolean>(false); const showRooms = ref<boolean>(false);
@@ -8,21 +13,38 @@ const weekNumber = ref<number>(1);
async function getData() { async function getData() {
showRooms.value = false; showRooms.value = false;
const today = new Date(); const today = new Date();
if (today.getDay() == 0 || today.getDay() == 6) { // if (today.getDay() == 0 || today.getDay() == 6) {
return; // return;
} // }
const day = today.getDay() * weekNumber.value; const day = today.getDay() * weekNumber.value;
const res = await fetch("http://localhost:3000/currentRooms", { const res = await fetch("/currentRooms", {
method: "POST", method: "POST",
body: JSON.stringify({day: day}), body: JSON.stringify({day: day}),
headers: new Headers({'content-type': 'application/json'}) headers: new Headers({'content-type': 'application/json'})
}); });
if (res.ok) { if (res.ok) {
rooms.value = await res.json(); let json = await res.json();
if (json.records.length == 0) {
showRooms.value = false;
} else {
rooms.value = json;
showRooms.value = true; showRooms.value = true;
return;
} }
}
}
async function deleteTimeSlot(timeSlot: timeSlot_t) {
const uid = await $cookies.get("userId");
const res = await fetch("/removeTimeSlot", {
method: "POST",
body: JSON.stringify({ Id: timeSlot.Id, userId: uid }),
headers: new Headers({ "content-type": "application/json" })
});
if (res.ok) {
await getData();
} else {
console.log(await res.text()); console.log(await res.text());
}
} }
defineEmits(['addSlot']); defineEmits(['addSlot']);
@@ -34,7 +56,8 @@ onBeforeMount(async () => {
<header> <header>
<h1>What's free in my free?</h1> <h1>What's free in my free?</h1>
<button @click="$emit('addSlot')">Add new slot</button> <br> <button @click="$emit('addSlot')">Add new slot</button> <br>
<select v-model="weekNumber" @change="getData"> <label for="weekNumber">Week Number</label>
<select v-model="weekNumber" @change="getData" id="weekNumber">
<option :value="1">1</option> <option :value="1">1</option>
<option :value="2">2</option> <option :value="2">2</option>
</select> </select>
@@ -43,7 +66,7 @@ onBeforeMount(async () => {
<h2>Free Rooms:</h2> <h2>Free Rooms:</h2>
<p v-if="!showRooms">No free rooms right now</p> <p v-if="!showRooms">No free rooms right now</p>
<ul v-else v-for="slot in rooms.records"> <ul v-else v-for="slot in rooms.records">
<li>Room: {{ slot.RoomName }}, in {{ slot.Period }}</li> <li>Room: {{ slot.RoomName }}, in Period {{ slot.Period }}, <button type="button" @click="deleteTimeSlot(slot)">X</button></li>
</ul> </ul>
</article> </article>
</template> </template>

View File

@@ -1,5 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { inject, ref } from 'vue';
import type { VueCookies } from 'vue-cookies';
const $cookies = inject<VueCookies>("$cookies") as VueCookies;
const email = ref<string>(""); const email = ref<string>("");
const pass = ref<string>(""); const pass = ref<string>("");
@@ -10,15 +13,15 @@ const emit = defineEmits(['nextPage']);
async function login(e: SubmitEvent) { async function login(e: SubmitEvent) {
text.value = ""; text.value = "";
e.preventDefault(); e.preventDefault();
const res = await fetch("http://localhost:3000/login", { const res = await fetch("/login", {
method: "POST", method: "POST",
body: JSON.stringify({ email: email.value, pass: pass.value }), body: JSON.stringify({ email: email.value, pass: pass.value }),
headers: new Headers({'content-type': 'application/json'}) headers: new Headers({'content-type': 'application/json'})
}); });
if (res.ok) { if (res.ok) {
cookieStore.set("loggedIn", "true"); $cookies.set("loggedIn", "true");
let uid = await res.json(); let uid = await res.json();
cookieStore.set("userId", uid.uid.Id); $cookies.set("userId", uid.uid.Id);
emit('nextPage'); emit('nextPage');
} else { } else {
text.value = await res.text(); text.value = await res.text();
@@ -28,7 +31,7 @@ async function login(e: SubmitEvent) {
async function signup(e: Event) { async function signup(e: Event) {
text.value = ""; text.value = "";
e.preventDefault(); e.preventDefault();
const res = await fetch("http://localhost:3000/createUser", { const res = await fetch("/createUser", {
method: "POST", method: "POST",
body: JSON.stringify({ email: email.value, pass: pass.value, name: email.value.split("@")[0] }), body: JSON.stringify({ email: email.value, pass: pass.value, name: email.value.split("@")[0] }),
headers: new Headers({'content-type': 'application/json'}) headers: new Headers({'content-type': 'application/json'})

View File

@@ -1,5 +1,5 @@
import { createApp } from 'vue'; import { createApp } from 'vue';
// import './style.css' import './style.css';
import App from './App.vue'; import App from './App.vue';
import VueCookies from 'vue-cookies'; import VueCookies from 'vue-cookies';

View File

@@ -1,4 +1,18 @@
:root { @import "../node_modules/picnic/picnic.min.css";
body {
@media (min-width: 800px) {
margin: 0% 25%;
}
@media (max-width: 800px) {
margin: 0 5%;
}
}
/* :root {
--text: #6b6375; --text: #6b6375;
--text-h: #08060d; --text-h: #08060d;
--bg: #fff; --bg: #fff;
@@ -293,4 +307,4 @@ code {
right: 0; right: 0;
border-right-color: var(--border); border-right-color: var(--border);
} }
} } */