Implement querying emails in mailbox.
Next step is auth implementation. Yay.
This commit is contained in:
parent
87c785505d
commit
dada829e6e
6 changed files with 1863 additions and 21 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -2,3 +2,5 @@ sendgrid.env
|
|||
messages/
|
||||
node_modules/
|
||||
state.integer
|
||||
messageStore
|
||||
dev.sqlite3
|
29
knexfile.js
Normal file
29
knexfile.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Update with your config settings.
|
||||
|
||||
/**
|
||||
* @type { Object.<string, import("knex").Knex.Config> }
|
||||
*/
|
||||
module.exports = {
|
||||
|
||||
development: {
|
||||
client: 'better-sqlite3',
|
||||
connection: {
|
||||
filename: './dev.sqlite3'
|
||||
}
|
||||
},
|
||||
|
||||
staging: {
|
||||
client: 'better-sqlite3',
|
||||
connection: {
|
||||
filename: './staging.sqlite3'
|
||||
}
|
||||
},
|
||||
|
||||
production: {
|
||||
client: 'better-sqlite3',
|
||||
connection: {
|
||||
filename: './production.sqlite3'
|
||||
}
|
||||
}
|
||||
|
||||
};
|
20
migrations/20250120214711_messageOwnerAndTag.js
Normal file
20
migrations/20250120214711_messageOwnerAndTag.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.up = function(knex) {
|
||||
return knex.schema.createTable("messages", (table) => {
|
||||
table.increments("id", {primaryKey: true});
|
||||
table.string("mailbox");
|
||||
table.integer("state");
|
||||
table.boolean("read");
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
* @param { import("knex").Knex } knex
|
||||
* @returns { Promise<void> }
|
||||
*/
|
||||
exports.down = function(knex) {
|
||||
return knex.schema.dropTable("messages");
|
||||
};
|
1738
package-lock.json
generated
1738
package-lock.json
generated
File diff suppressed because it is too large
Load diff
24
package.json
24
package.json
|
@ -1,10 +1,30 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@sendgrid/mail": "^8.1.4",
|
||||
"better-sqlite3": "^11.8.1",
|
||||
"body-parser": "^1.20.3",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.21.2",
|
||||
"formidable": "^3.5.2"
|
||||
}
|
||||
"formidable": "^3.5.2",
|
||||
"knex": "^3.1.0",
|
||||
"sqlite3": "^5.1.7"
|
||||
},
|
||||
"name": "andrewpietiladotcom",
|
||||
"version": "0.0.1",
|
||||
"description": "Website for andrewpietila.com",
|
||||
"main": "server.js",
|
||||
"directories": {
|
||||
"lib": "lib"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node server.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "ssh://git@forgejo.hax.social:2222/rallias/andrewpietiladotcom.git"
|
||||
},
|
||||
"author": "Andrew Pietila <andrew@andrewpietila.com>",
|
||||
"license": "WTFPL"
|
||||
}
|
||||
|
|
67
server.js
67
server.js
|
@ -18,6 +18,14 @@ import MimeMessage from "./lib/MimeMessage.js";
|
|||
import MimeHeader from "./lib/MimeHeader.js";
|
||||
|
||||
import dns from 'node:dns';
|
||||
import Knex from "knex";
|
||||
|
||||
const knex = Knex({
|
||||
client: 'better-sqlite3',
|
||||
connection: {
|
||||
filename: './dev.sqlite3'
|
||||
}
|
||||
})
|
||||
|
||||
app.use(cors());
|
||||
|
||||
|
@ -26,18 +34,22 @@ app.use(cookieParser());
|
|||
let state = +fs.readFileSync("state.integer", "utf-8");
|
||||
console.log(`State: ${state.toString()}`);
|
||||
|
||||
// app.use((req, res, next) => {
|
||||
// console.log({url: req.url, headers: req.headers});
|
||||
app.use((req, res, next) => {
|
||||
console.log({url: req.url, headers: req.headers});
|
||||
// req.on("data", (data) => {
|
||||
// console.log(data.toString("utf8"));
|
||||
// })
|
||||
// next();
|
||||
// })
|
||||
next();
|
||||
})
|
||||
|
||||
app.post('/sendgrid/ingress', (req, res) => {
|
||||
const form = formidable({});
|
||||
|
||||
form.parse(req, (err, fields, files) => {
|
||||
var thisState = state;
|
||||
state++;
|
||||
fs.writeFileSync("state.integer", state.toString(), { encoding: "utf-8" });
|
||||
|
||||
var message = fields.email?.[0];
|
||||
|
||||
var messageObj = new MimeMessage(message);
|
||||
|
@ -56,12 +68,11 @@ app.post('/sendgrid/ingress', (req, res) => {
|
|||
fs.writeFileSync(`messages/${new Date().toISOString()}.eml`, messageObj.toString());
|
||||
fs.writeFileSync(`messages/${new Date().toISOString()}-headers.json`, JSON.stringify(req.headers, null, 4));
|
||||
fs.writeFileSync(`messages/${new Date().toISOString()}.json`, JSON.stringify(fields, null, 4));
|
||||
fs.writeFileSync(`messageStore/${thisState}.eml`, messageObj.toString());
|
||||
await knex('messages').insert({mailbox: "INBOX", state: thisState, read: false});
|
||||
res.status(204);
|
||||
res.end();
|
||||
state++;
|
||||
fs.writeFileSync("state.integer", state.toString(), { encoding: "utf-8" });
|
||||
})
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
|
@ -128,7 +139,7 @@ app.get("/jmap/session", (req, res) => {
|
|||
"downloadUrl": "https://andrewpietila.com/api/jmap/download/{accountId}/{blobId}/{name}?accept={type}",
|
||||
"uploadUrl": "https://andrewpietila.com/api/jmap/upload/{accountId}/",
|
||||
"eventSourceUrl": "https://andrewpietila.com/api/jmap/eventsource/?types={types}&closeafter={closeafter}&ping={ping}",
|
||||
"state": "75128aab4b1b"
|
||||
"state": state.toString()
|
||||
});
|
||||
// else {
|
||||
// res.status(404);
|
||||
|
@ -146,10 +157,16 @@ function isEmpty(obj) {
|
|||
return true;
|
||||
}
|
||||
|
||||
app.post("/api/jmap/api/", bodyParser.json(), (req, res) => {
|
||||
const responses = req.body.methodCalls.map(element => {
|
||||
if (element[0] === "Identity/get" && isEmpty(element[1]) ) {
|
||||
return [ "Identity/get",
|
||||
app.post("/api/jmap/api/", bodyParser.json(), async (req, res) => {
|
||||
if ( req.header("authorization") !== "SUPER SECRET KEY" ) {
|
||||
res.status(401);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
console.log(JSON.stringify(req.body));
|
||||
const responsesPromise = req.body.methodCalls.map(async element => {
|
||||
if (element[0] === "Identity/get" && isEmpty(element[1])) {
|
||||
return ["Identity/get",
|
||||
{
|
||||
accountId: "andrew@andrewpietila.com",
|
||||
state: state.toString(),
|
||||
|
@ -174,13 +191,13 @@ app.post("/api/jmap/api/", bodyParser.json(), (req, res) => {
|
|||
accountId: "andrew@andrewpietila.com",
|
||||
state: state.toString(),
|
||||
list: [{
|
||||
id: "Inbox",
|
||||
id: "INBOX",
|
||||
name: "Inbox",
|
||||
parentId: null,
|
||||
role: "inbox",
|
||||
sortOrder: 0,
|
||||
totalEmails: 0, // TODO: Index messages.
|
||||
unreadEmails: 0, // TODO: Index messages.
|
||||
totalEmails: +(await knex("messages").select().where("mailbox", "like", "INBOX").count('state')),
|
||||
unreadEmails: +(await knex("messages").select().where("mailbox", "like", "INBOX").andWhere("read", "=", false).count('state')),
|
||||
totalThreads: 0, // TODO: Index messages.
|
||||
unreadThreads: 0, // TODO: Index messages.
|
||||
myRights: {
|
||||
|
@ -199,6 +216,21 @@ app.post("/api/jmap/api/", bodyParser.json(), (req, res) => {
|
|||
},
|
||||
element[2]
|
||||
]
|
||||
} else if ( element[0] === "Email/query" ) {
|
||||
if ( element[1]["filter"] && element[1]["filter"]["inMailbox"] === "INBOX" ) {
|
||||
// TODO: Implement proper sorting.
|
||||
const messageIds = await knex("messages").select().where("mailbox", "like", "INBOX").orderBy("id", "asc");
|
||||
return ["Email/query",
|
||||
{
|
||||
accountId: "andrew@andrewpietila.com",
|
||||
queryState: state.toString(), // TODO: Maybe not a great idea.
|
||||
canCalculateChanges: false,
|
||||
position: 0,
|
||||
ids: messageIds.map((messageId) => messageId.state.toString()),
|
||||
},
|
||||
element[2]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
|
@ -210,7 +242,10 @@ app.post("/api/jmap/api/", bodyParser.json(), (req, res) => {
|
|||
]
|
||||
});
|
||||
|
||||
res.json({"methodResponses": responses, sessionState: "1"});
|
||||
const responses = await Promise.all(responsesPromise);
|
||||
|
||||
console.log(JSON.stringify({ "methodResponses": responses, sessionState: state.toString() }));
|
||||
res.json({ "methodResponses": responses, sessionState: state.toString() });
|
||||
})
|
||||
|
||||
app.use("/static", express.static(__dirname + "/static"));
|
||||
|
|
Loading…
Add table
Reference in a new issue