Skip to content
This repository has been archived by the owner on Apr 25, 2024. It is now read-only.

Commit

Permalink
feat: add upload notion links
Browse files Browse the repository at this point in the history
In order i have done these things with this commit:
1. Fix svg import creating a custom d.ts module and adding includes in tscondig.json 2. Add interfaces and models for the database (added stuff for remote database
3. Added logic and VALIDATORS for uploading notion links
4. fixed a bug described here vercel/next.js#10439 (comment), for sections ts and courses ts, Senza questa promise arrivava il warning: ```API resolved without sending a response for /api/sections, this may result in stalled requests.``` Ossia non sapeva se sta ancora aspettando o no, sta cosa risolve.

I made many todos: Like moving the validators to model level
  • Loading branch information
Angelo Huang committed Nov 7, 2021
1 parent 2778610 commit 18e0924
Show file tree
Hide file tree
Showing 6 changed files with 285 additions and 27 deletions.
171 changes: 171 additions & 0 deletions src/pages/api/appunti.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import type { NextApiRequest, NextApiResponse } from 'next'
import { Section, Appunti } from "../../types/models";
import type {noteRequestExistance, standardResponse} from "../../types/customTypes"

export default async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method != "POST") {
res.status(400).json({error: "Invalid request, only POST is supported"});
return;
}
// TODO: check if user is authenticated with Telegram before letting him posting
// The field author should be found from this middleware
const checkRes = checkRequest(req.query);
if (checkRes.error != undefined) {
res.status(checkRes.code).json(checkRes);
return;
}

const updateStatus = await addNote(req.query);

res.status(updateStatus.code).json(updateStatus);
}

async function addNote(note: NextApiRequest["query"]) {
let doc = await Section.findOne({id: note.sectionId}, "appunti").exec();
if (!doc) {
return {error: {message: "Could not find the document, probably bad id"}, code: 404, code_meaning:"Not Found"};
} else if (doc.$error) {
return {error: {message: doc.$error.reason}, code: 500, code_meaning: "Internal error, contact server administrator"}
}

const newNote: Appunti = getNewNote(note, doc.appunti);
const addNoteResult = await Section.updateOne(
{id: note.sectionId},
{$push: {appunti: newNote}}
);
if (addNoteResult.modifiedCount != 1) {
return {error: {message: "Couldn't add the note to the database"}, code: 500, code_meaning: "Unknown error"};
}

await Section.findOneAndUpdate({id: note.sectionId}, {$inc: {n_appunti: 1}});
return {data: {message: "Note added correctly to database"}, code: 201, code_meaning: "Fullfilled"};
}


function getNewNote(note: NextApiRequest["query"], appunti: Appunti[]): Appunti {
let noteId: string = getNoteId(appunti, note.sectionId);

let newNote: Appunti = {
id: noteId,
author: note.author as string,
likes: 0,
isValid: false,
last_modified: new Date().toISOString(),
type: note.type as string,
link: note.link as string,
pdf: note.pdf as string,
video: note.video as string
}

return newNote;
}


function getNoteId(appunti: Appunti[], sectionId: string|string[]):string {
let lastNote = appunti[appunti.length - 1] || undefined;
let noteId: string;
if (lastNote) {
// Ricorda il formato di ID appunto:
// 1-1-00013 (id_appunto)-(id_sezione)-(id_corso)
let currentId = lastNote.id.split("-");
currentId[0] = String(Number(currentId[0]) + 1);
noteId = currentId.join("-");
} else {
noteId = "1-" + sectionId;
}

return noteId;
}


function checkRequest(query: NextApiRequest["query"]): standardResponse {
// TODO: adapt this function to mongoose model level, not API level, there is a mix of responsibilities
// Check https://mongoosejs.com/docs/validation.html
const fieldExists = getFieldExistance(query);
if (hasMissingFields(fieldExists, query.type)) {
const errorMessage = getErrorMessage(fieldExists, query.type);
return {error: {message: errorMessage}, code: 400, code_meaning: "Bad Request"};
}

if (query.type == "link" && !isValidUrl(query.link)) {
return {error: {message: "This is not a notion link"}, code: 400, code_meaning: "Bad Request"}
}

return {data: {message: "ok"}, code: 200, code_meaning: "All fine"};
}


function isValidUrl(url: string|string[]): boolean {
let isValid = true;
const splittedUrl = (url as string).split("/");

// Example of notion link
// https://flecart-notes.notion.site/Limiti-b3b1b15767a74696b48d7166604d8898
if (splittedUrl.length < 3 || splittedUrl[0] != "https:" || splittedUrl[1] != "" || !splittedUrl[2].endsWith("notion.site")) {
isValid = false;
}
return isValid
}


function getFieldExistance(query: NextApiRequest["query"]): noteRequestExistance {
// STYLEISSUE: should make enums and then a switch in the filter object?
return ({
sectionId: query.hasOwnProperty("sectionId"),
author: query.hasOwnProperty("author"),
name: query.hasOwnProperty("name"),
type: query.hasOwnProperty("type"),
pdf: query.hasOwnProperty("pdf"),
link: query.hasOwnProperty("link"),
video: query.hasOwnProperty("video"),
})
}

function hasMissingFields(fieldExists: noteRequestExistance, type: string | string[]): boolean {
let fieldIsMissing = true;
if (
fieldExists.sectionId &&
fieldExists.author &&
fieldExists.name &&
type != undefined &&
( (type == "pdf" && fieldExists.pdf) ||
(type == "link" && fieldExists.link) ||
(type == "video" && fieldExists.video) )
){
fieldIsMissing = false;
}
return fieldIsMissing;
}

function getErrorMessage(fieldExists: noteRequestExistance, type: string | string[]): string {
let errorMessage: string = "These fields are requested but not present: ";
if (!fieldExists.sectionId) {
errorMessage += "sectionId "
}

if (!fieldExists.author) {
errorMessage += "author "
}

if (!fieldExists.name) {
errorMessage += "name "
}

if (!type) {
errorMessage += "type "
} else {
if (type == "pdf" && !fieldExists.pdf) {
errorMessage += " pdf";
}

if (type == "link" && !fieldExists.link) {
errorMessage += " link";
}

if (type == "video" && !fieldExists.video) {
errorMessage += " video";
}
}

return errorMessage;
}
18 changes: 13 additions & 5 deletions src/pages/api/courses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@ import type { NextApiRequest, NextApiResponse } from 'next'
import { Course } from "../../types/models";

export default async (req: NextApiRequest, res: NextApiResponse) => {
Course.find(req.query, (err, data) => {
if (err) {
res.status(404).json(err);
} else {
res.status(200).json(data);
return new Promise(_ => {
if (req.method != "GET") {
res.status(400).json({error: "Invalid request, only GET is supported"});
return;
}

Course.find(req.query, (err, data) => {
if (err) {
res.status(404).json({error: err});
} else {
res.status(200).json({data: data});
}
})
return;
})
}
18 changes: 13 additions & 5 deletions src/pages/api/sections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@ import type { NextApiRequest, NextApiResponse } from 'next'
import { Section } from "../../types/models";

export default async (req: NextApiRequest, res: NextApiResponse) => {
Section.find(req.query, (err, data) => {
if (err) {
res.status(404).json(err);
} else {
res.status(200).json(data);
return new Promise(_ => {
if (req.method != "GET") {
res.status(400).json({error: "Invalid request, only GET is supported"});
return;
}

Section.find(req.query, (err, data) => {
if (err) {
res.status(404).json({error: err});
} else {
res.status(200).json({data: data});
}
})
return;
})
}
30 changes: 30 additions & 0 deletions src/types/customTypes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import type {Document} from "mongodb"

// Study this file to have a better use of modules
// https://stackoverflow.com/questions/37377731/extend-express-request-object-using-typescript
// Io(flecart) non lo ho ancora indagato per bene del tutto, dovrei farlo.

export type mongoResponse = {
code: number,
message: {
Expand All @@ -10,3 +14,29 @@ export type mongoResponse = {
data?: never
}
}

export type standardResponse = {
error: {
message: String;
};
code: number;
code_meaning: string;
data?: never;
} | {
data: {
message: string;
};
code: number;
code_meaning: string;
error?: never;
}

export type noteRequestExistance = {
sectionId: boolean,
author: boolean,
name: boolean,
type: boolean,
pdf: boolean,
link: boolean,
video: boolean
}
72 changes: 56 additions & 16 deletions src/types/models.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import mongoose, { Date } from "mongoose";
import mongoose from "mongoose";

const isLocal = process.env.LOCAL;
const MONGODB_URI = isLocal ? process.env.DB_LOCAL_CONN_STRING! : process.env.DB_CONN_STRING!
if (!isLocal || !MONGODB_URI) {
console.error("Local environment variables not set, quitting");
if (!MONGODB_URI) {
console.error("Environment variables not set, quitting...");
process.exit(1);
}

Expand All @@ -12,37 +12,77 @@ if (!mongoose.connections[0].readyState) {
.catch(error => console.error(error));
}

// TODO: add pre(save) and pre(validate) to the schemas
// see here https://mongoosejs.com/docs/subdocs.html#what-is-a-subdocument
// TODO: dividere i modelli in più file, in una cartella models, non unico file
// così puoi gestire meglio i save e validate
// TODO: mettere required agli schema:
// es: invece di
// nome: String
// fare
// nome: {type: String, required: true, default?} // e simili

const CourseSchema = new mongoose.Schema(
{
export interface Courses {
nome: string;
id: string;
n_sezioni: number;
virtuale: string;
teams: string;
website: string;
last_update: string;
professors: string[]
}
const CourseSchema = new mongoose.Schema<Courses>({
nome: String,
id: String,
n_sezioni: Number,
virtuale: String,
teams: String,
website: String,
last_update: Date,
last_update: String,
professors: [String]
},
);
});

// Prevent OverwriteModelError with existing models
// Prevent OverwriteModelError with existing models thanks to ||
export const Course = mongoose.models.courses || mongoose.model("courses", CourseSchema);

// TODO: type should only be link pdf or vide
// should be exclusive but this interface allows doubles
export interface Appunti {
id: String,
author: String,
likes: Number,
last_modified: Date
type: String, // TODO add types for this
// ADD some field depending on the type
isValid: boolean,
last_modified: String,
type: String,
link?: String,
pdf?: String,
video?: String
};

const SectionsSchema = new mongoose.Schema({
const AppuntiSchema = new mongoose.Schema<Appunti>({
id: String,
author: String,
likes: Number,
isValid: Boolean,
last_modified: String,
type: String,
link: String,
pdf: String,
video: String
});

export interface Sections {
nome: string;
id: string;
n_appunti: number;
appunti: Appunti[];
}

const SectionsSchema = new mongoose.Schema<Sections>({
nome: String,
id: String,
n_appunti: Number
// Add appunti, i have error that tells me appunti in only a type
// but this needs value
n_appunti: Number,
appunti: [AppuntiSchema]
});
export const Section = mongoose.models.sections || mongoose.model("sections", SectionsSchema);
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
},
"include": [
"src",
["images", "src/custom.d.ts"]
"src/images",
"src/custom.d.ts"
],
"exclude": [
"node_modules"
Expand Down

0 comments on commit 18e0924

Please sign in to comment.