WebFund 2024F Lecture 14
Video
Video from the lecture for November 5, 2024 is now available:
Notes
Lecture 14 ---------- - Assignment 2 grades should be out by end of tomorrow - Midterms should be graded by middle of next week, interviews will be scheduled afterwards - will be scheduled at arbitrary times that work for everyone - TAs will try to keep it to their tutorials & office hours - TAs will do a few each, I will do many - We will go through midterm solutions once they are graded - Tutorial 7 will come out Thursday, A3 soon after - A3 will be based on T7 code - we're going to start developing T7 code today in class A3 is due Nov. 20th T5, 6 due Nov. 13th T7 due around Nov 15th - note how compressed this timeframe is - get caught up on tutorials! Today, the plan is to merge the code from T5 & T6
Code
Note: This code is a work in progress!
domdemo.js
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2024 Anil Somayaji
//
// dbdemo.js
// for COMP 2406 (Fall 2024), Carleton University
//
// Initial version: October 9, 2024
//
// run with the following command:
// deno run --allow-net --allow-read --allow-write domdemo.js
//
import { DB } from "https://deno.land/x/sqlite/mod.ts";
const status_NOT_FOUND = 404;
const status_OK = 200;
const status_NOT_IMPLEMENTED = 501;
const appTitle = "COMP 2406 DOM Demo";
const dbFile = "people.db";
const table = "people";
const db = new DB(dbFile);
db.execute(`
CREATE TABLE IF NOT EXISTS ${table} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
city TEXT,
country TEXT,
birthday TEXT,
email TEXT
)
`);
function addRecordDB(db, table, r) {
return db.query(`INSERT INTO ${table} ` +
"(name, city, country, birthday, email) " +
"VALUES (?, ?, ?, ?, ?)",
[r.name, r.city, r.country, r.birthday, r.email]);
}
function getAllRecordsDB(db, table) {
var state = [];
const query =
db.prepareQuery(
"SELECT id, name, city, country, birthday, email FROM " +
table + " ORDER BY name ASC LIMIT 50");
for (const [id, name, city, country, birthday, email] of query.iter()) {
state.push({id, name, city, country, birthday, email});
}
query.finalize();
return state;
}
function analyzeRecordsDB(db, table) {
var analysis = {};
analysis.count = db.query("SELECT COUNT(*) FROM " + table);
analysis.cityList = db.query("SELECT DISTINCT CITY FROM " + table);
return analysis;
}
function MIMEtype(filename) {
const MIME_TYPES = {
'css': 'text/css',
'gif': 'image/gif',
'htm': 'text/html',
'html': 'text/html',
'ico': 'image/x-icon',
'jpeg': 'image/jpeg',
'jpg': 'image/jpeg',
'js': 'text/javascript',
'json': 'application/json',
'pdf': 'application/pdf',
'png': 'image/png',
'txt': 'text/text'
};
var extension = "";
if (filename) {
extension = filename.slice(filename.lastIndexOf('.')+1).toLowerCase();
}
return MIME_TYPES[extension] || "application/octet-stream";
};
function template_header(title) {
const fullTitle = appTitle + ": " + title;
return `<!DOCTYPE html>
<html>
<head>
<title>${fullTitle}</title>
<link rel="stylesheet" href="/style.css">
</head>
`
}
function template_notFound(path) {
return template_header("Page not found") +
`<body>
<h1>Page not found</h1>
<p>Sorry, the requested page was not found.</p>
</body>
</html>
`
}
function template_addRecord(obj) {
return template_header("Record just added") +
`<body>
<body>
<h1>Person just added</h1>
<p>Name: ${obj.name}</p>
<p>City: ${obj.city}</p>
<p>Country: ${obj.country}</p>
<p>Birthday: ${obj.birthday}</p>
<p>Email: ${obj.email}</p>
<form method="get" action="/">
<button type="submit">Home</button>
</form>
</body>
</html>
`
}
function listRecords() {
var state = getAllRecordsDB(db, table);
var response = { contentType: "application/JSON",
status: status_OK,
contents: JSON.stringify(state),
};
return response;
}
async function routeGet(req) {
const path = new URL(req.url).pathname;
if (path === "/list") {
return listRecords();
} else if (path === "/analyze") {
return await showAnalysis();
} else {
return null;
}
}
async function addRecord(req) {
var body = await req.formData();
var obj = { name: body.get("name"),
city: body.get("city"),
country: body.get("country"),
birthday: body.get("birthday"),
email: body.get("email") };
addRecordDB(db, table, obj);
var response = { contentType: "text/html",
status: status_OK,
contents: template_addRecord(obj),
};
return response;
}
async function showAnalysis() {
var analysis = analyzeRecordsDB(db, table);
var cityList = '<li>' + analysis.cityList.join('</li> <li>') + '</li>';
var analysisBody = ` <body>
<body>
<h1>Database analysis</h1>
<p># Records: ${analysis.count}</p>
<p>Cities:
<ol>
${cityList}
</ol>
</p>
<form method="get" action="/">
<button type="submit">Home</button>
</form>
</body>
</html>`
var contents = template_header("Analysis") + analysisBody;
var response = { contentType: "text/html",
status: status_OK,
contents: contents,
};
return response;
}
async function addSubmission(req) {
//var body = await req.formData();
//var obj = JSON.parse(body);
console.log("Got following request object.");
console.log(req);
var response = { contentType: "text/plain",
status: status_OK,
contents: "Got the data",
};
}
async function routePost(req) {
const path = new URL(req.url).pathname;
if (path === "/add") {
return await addRecord(req);
} else if (path === "/uploadSubmission") {
return await addSubmission(req);
} else {
return null;
}
}
async function route(req) {
if (req.method === "GET") {
return await routeGet(req);
} else if (req.method === "POST") {
return await routePost(req);
} else {
return {
contents: "Method not implemented.",
status: status_NOT_IMPLEMENTED,
contentType: "text/plain"
};
}
}
async function fileData(path) {
var contents, status, contentType;
try {
contents = await Deno.readFile("./static" + path);
status = status_OK;
contentType = MIMEtype(path);
} catch (e) {
contents = template_notFound(path);
status = status_NOT_FOUND;
contentType = "text/html";
}
return { contents, status, contentType };
}
async function handler(req) {
var origpath = new URL(req.url).pathname;
var path = origpath;
var r = await route(req);
if (!r) {
if (path === "/") {
path = "/index.html";
}
r = await fileData(path);
}
console.log(`${r.status} ${req.method} ${r.contentType} ${origpath}`);
return new Response(r.contents,
{status: r.status,
headers: {
"content-type": r.contentType,
}});
}
const ac = new AbortController();
const server = Deno.serve(
{
signal: ac.signal,
port: 8000,
hostname: "0.0.0.0"
},
handler);
Deno.addSignalListener("SIGINT", () => {
console.log("SIGINT received, terminating...");
ac.abort();
});
server.finished.then(() => {
console.log("Server terminating, closing database.")
db.close();
});
validator.js
// validator.js
const filePrefix = "comp2406-tutorial6";
const submissionName = "COMP 2406 2024F Tutorial 6"
const expectedQuestionList = "1,2,3,4,5";
var analysisSection;
function loadAssignment(fileInput, upload) {
analysisSection.hidden = false;
if(fileInput.files[0] == undefined) {
updateTag("status", "ERROR: No files to examine.")
return;
}
var reader = new FileReader();
reader.onload = function(ev) {
var content = ev.target.result;
var q = checkSubmission(fileInput.files[0].name, content);
if (upload) {
console.log("Trying to upload the submission...");
doUploadSubmission(q);
}
};
reader.onerror = function(err) {
updateTag("status", "ERROR: Failed to read file");
}
reader.readAsText(fileInput.files[0]);
}
var numQuestions = expectedQuestionList.split(",").length;
function updateTag(id, val) {
document.getElementById(id).innerHTML = val;
}
function lineEncoding(lines) {
var n, i, s;
for (i = 0; i < lines.length; i++) {
s = lines[i];
n = s.length;
if (s[n-1] === '\r') {
return "DOS/Windows Text (CR/LF)";
}
}
return "UNIX";
}
function checkSubmission(fn, f) {
var lines = f.split('\n')
var c = 0;
var questionList = [];
var questionString;
var q = {};
var e = {};
var i;
var lastQuestion = null;
const fnPattern = filePrefix + "-[a-zA-Z0-9]+\.txt";
const fnRexp = new RegExp(fnPattern);
if (!fnRexp.test(fn)) {
updateTag("status", "ERROR " + fn +
" doesn't follow the pattern " + fnRexp);
return;
}
if (fn === filePrefix + "-template.txt") {
updateTag("status", "ERROR " + fn +
" has the default name, please change template to your mycarletonone username");
return;
}
updateTag("filename", fn);
let encoding = lineEncoding(lines);
if (encoding !== "UNIX") {
updateTag("status", "ERROR " + fn +
" is not a UNIX textfile, it is a "
+ encoding + " file.");
return;
}
if (submissionName !== lines[0]) {
updateTag("status", "ERROR " + fn +
" doesn't start with \"" + submissionName + "\"");
return;
}
try {
e.name = lines[1].match(/^Name:(.+)/m)[1].trim();
e.id = lines[2].match(/^Student ID:(.+)/m)[1].trim();
} catch (error) {
updateTag("status", "ERROR " + fn +
" has bad Name or Student ID field");
return;
}
updateTag("name", e.name);
updateTag("studentID", e.id);
var questionRE = /^([0-9a-g]+)\.(.*)/;
for (i = 4; i < lines.length; i++) {
if (typeof(lines[i]) === 'string') {
lines[i] = lines[i].replace('\r','');
}
let m = lines[i].match(questionRE);
if (m) {
c++;
questionList.push(m[1]);
q[m[1]] = m[2];
lastQuestion = m[1];
} else {
if (lastQuestion !== null) {
if ((q[lastQuestion] === '') || (q[lastQuestion] === ' ')) {
q[lastQuestion] = lines[i];
} else {
q[lastQuestion] = q[lastQuestion] + "\n" + lines[i];
}
}
}
}
console.log(JSON.stringify(q, null, ' '));
questionString = questionList.toString();
if (questionString !== expectedQuestionList) {
updateTag("status", "ERROR expected questions " +
expectedQuestionList + " but got questions " +
questionString);
} else {
updateTag("status", "PASSED " +
fn + ": " + e.name + " (" + e.id + ")");
}
var newP, newText, newPre, newPreText;
let questionDiv = document.getElementById("questions");
for (let qName of questionList) {
let qText = q[qName];
newP = document.createElement("p");
newText = document.createTextNode(qName + ":");
newP.appendChild(newText);
questionDiv.appendChild(newP);
newPre = document.createElement("pre");
newPreText = document.createTextNode(qText);
newPre.appendChild(newPreText);
newP.appendChild(newPre);
}
return q;
}
function hideAnalysis() {
analysisSection = document.getElementById("analysis");
analysisSection.hidden = true;
}
function uploadSubmission() {
var assignmentFile = document.getElementById("assignmentFile");
loadAssignment(assignmentFile, true);
}
function doUploadSubmission(q) {
console.log("sending data to server...");
if (q) {
const request = new Request("/uploadSubmission", {
method: "POST",
body: JSON.stringify(q),
});
console.log(request);
const response = fetch(request).then(() => {
console.log("upload status: " + response.status);
});
} else {
console.log("No data to upload!");
}
}
static/index.html
<!DOCTYPE html>
<html>
<head>
<title>COMP 2406 DOM Demo</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<h1>COMP 2406 DOM Demo</h1>
<p>Please select what you would like to do:
<ol>
<li><a href="/add.html">Add a record.</a></li>
<li><a href="/list.html">List records.</a></li>
<li><a href="/analyze">Analyze records.</a></li>
<li><a href="/tut-validator.html">Tutorial Validator.</a></li>
</ol>
</p>
</body>
</html>
tut-validator.html
<!DOCTYPE html>
<html>
<head>
<title>COMP 2406 2024F Tutorial Validator</title>
<link rel="stylesheet" href="/style.css">
<script type="text/javascript" src="/validator.js"></script>
</head>
<body onload="hideAnalysis()">
<h1>COMP 2406 2024F Tutorial Validator</h1>
<form>
<input type="file" id="assignmentFile"
onchange="loadAssignment(this, false)" />
<button type="button" onclick="uploadSubmission()">Submit</button>
</form>
<div id="analysis">
<p><b>Status:</b> <span id="status">UNKNOWN</span></p>
<p>Filename: <span id="filename"></span></p>
<p>Name: <span id="name"></span><br>
Student ID: <span id="studentID"></span> <i>(Please check that your ID number is correct!)</i></p>
<hr>
<div id="questions"><p><b>Questions:</b> <i>(Please check that your answers are numbered correctly!)</i></p></div>
</div>
</body>
</html>