Initial commit
commit
a28f9a90c4
@ -0,0 +1,2 @@
|
|||||||
|
config.json
|
||||||
|
deno.lock
|
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"deno.enable": true,
|
||||||
|
"deno.unstable": true,
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"deno.importMap": "import_map.json",
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.tabSize": 4,
|
||||||
|
"editor.detectIndentation": false,
|
||||||
|
"editor.defaultFormatter": "denoland.vscode-deno"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
# Wackylytics Server
|
||||||
|
|
||||||
|
The server for the wackiest (and jankiest) analytics suite ever.
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
**Q**: Should I use this?\
|
||||||
|
**A**: No. (seriously, don't) ((your cartoon bendy privileges will be taken away if you do))
|
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"fmt": {
|
||||||
|
"options": {
|
||||||
|
"indentWidth": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"importMap": "import_map.json"
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"@oak/": "https://deno.land/x/oak@v11.1.0/",
|
||||||
|
"@std/": "https://deno.land/std@0.163.0/",
|
||||||
|
"@surrealdb/": "https://deno.land/x/surrealdb@v0.5.0/"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
import init from "./src/index.ts";
|
||||||
|
|
||||||
|
const app = await init();
|
||||||
|
|
||||||
|
app.addEventListener("listen", ({
|
||||||
|
hostname,
|
||||||
|
port,
|
||||||
|
}) => {
|
||||||
|
console.log(
|
||||||
|
`Wackylytics server listening on http://${
|
||||||
|
hostname ?? "localhost"
|
||||||
|
}:${port}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await app.listen({
|
||||||
|
port: Number(Deno.env.get("PORT")) ?? 8080,
|
||||||
|
});
|
@ -0,0 +1,13 @@
|
|||||||
|
import config from "../config.json" assert { type: "json" };
|
||||||
|
|
||||||
|
export type configType = typeof config;
|
||||||
|
|
||||||
|
export interface Site {
|
||||||
|
metadata: {
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { config };
|
||||||
|
|
||||||
|
export default config;
|
@ -0,0 +1,24 @@
|
|||||||
|
import Surreal from "@surrealdb/mod.ts";
|
||||||
|
import config from "./config.ts";
|
||||||
|
import { Location } from "./utils.ts";
|
||||||
|
|
||||||
|
export interface IP {
|
||||||
|
location: Location;
|
||||||
|
history: {
|
||||||
|
timestamp: Date;
|
||||||
|
path: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const db = new Surreal(`${config.db.url}/rpc`);
|
||||||
|
|
||||||
|
export default async function init() {
|
||||||
|
try {
|
||||||
|
await db.signin({
|
||||||
|
user: config.db.user,
|
||||||
|
pass: config.db.pass,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(e);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
import { Application, Router } from "@oak/mod.ts";
|
||||||
|
import initDB, { db, IP } from "./database.ts";
|
||||||
|
import { deviousLick, hash } from "./utils.ts";
|
||||||
|
import config, { Site } from "./config.ts";
|
||||||
|
|
||||||
|
interface Value {
|
||||||
|
site: string;
|
||||||
|
id: string;
|
||||||
|
ip: string;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function init() {
|
||||||
|
await initDB();
|
||||||
|
|
||||||
|
const router = new Router();
|
||||||
|
|
||||||
|
router.post("/add", async (ctx) => {
|
||||||
|
const value: Value = await ctx.request.body({ type: "json" }).value;
|
||||||
|
const hashedIP = await hash(value.ip);
|
||||||
|
const site: Site = (config.sites as Record<string, Site>)[value.site];
|
||||||
|
|
||||||
|
if (!site) {
|
||||||
|
ctx.response.status = 406;
|
||||||
|
ctx.response.body = "Unknown site!";
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ipDB: IP;
|
||||||
|
|
||||||
|
await db.use(config.db.namespace, value.site);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ipDB = (await db.select("ip:" + hashedIP))[0] as IP;
|
||||||
|
} catch {
|
||||||
|
const geolocation = await deviousLick(value.ip);
|
||||||
|
|
||||||
|
await db.create("ip:" + hashedIP, {
|
||||||
|
location: geolocation,
|
||||||
|
history: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
ipDB = (await db.select("ip:" + hashedIP))[0] as IP;
|
||||||
|
}
|
||||||
|
|
||||||
|
ipDB.history.push({
|
||||||
|
timestamp: new Date(),
|
||||||
|
path: value.path,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`${hashedIP} ${value.path}`);
|
||||||
|
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
await db.update(`ip:${hashedIP}`, ipDB as Record<string, any>);
|
||||||
|
|
||||||
|
ctx.response.status = 200;
|
||||||
|
ctx.response.body = "Successfully added request to history!";
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/sites", (ctx) => {
|
||||||
|
ctx.response.status = 200;
|
||||||
|
ctx.response.type = "application/json";
|
||||||
|
ctx.response.body = JSON.stringify(config.sites);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/site/:site", (ctx) => {
|
||||||
|
const siteName: string = ctx.params.site;
|
||||||
|
const site = (config.sites as Record<string, Site>)[siteName];
|
||||||
|
|
||||||
|
ctx.response.status = 200;
|
||||||
|
ctx.response.type = "application/json";
|
||||||
|
ctx.response.body = JSON.stringify(site);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/site/:site/analytics", async (ctx) => {
|
||||||
|
const siteName: string = ctx.params.site;
|
||||||
|
|
||||||
|
await db.use(config.db.namespace, siteName);
|
||||||
|
|
||||||
|
const database = await db.select("ip");
|
||||||
|
|
||||||
|
ctx.response.status = 200;
|
||||||
|
ctx.response.type = "application/json";
|
||||||
|
ctx.response.body = JSON.stringify(database);
|
||||||
|
});
|
||||||
|
|
||||||
|
const app = new Application();
|
||||||
|
app.use(router.routes());
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
import { crypto, toHashString } from "@std/crypto/mod.ts";
|
||||||
|
|
||||||
|
export interface Location {
|
||||||
|
country: string;
|
||||||
|
region: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InternalLocation {
|
||||||
|
status: string;
|
||||||
|
country: string;
|
||||||
|
countryCode: string;
|
||||||
|
region: string;
|
||||||
|
regionName: string;
|
||||||
|
city: string;
|
||||||
|
zip: string;
|
||||||
|
lat: number;
|
||||||
|
lon: number;
|
||||||
|
timezone: string;
|
||||||
|
isp: string;
|
||||||
|
org: string;
|
||||||
|
as: string;
|
||||||
|
query: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** deviously lick (an ip's geolocation) */
|
||||||
|
export async function deviousLick(ip: string): Promise<Location> {
|
||||||
|
const res = await fetch(`http://ip-api.com/json/${ip}`);
|
||||||
|
|
||||||
|
const data: InternalLocation = await res.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
country: data.country,
|
||||||
|
region: data.regionName,
|
||||||
|
} as Location;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function hash(str: string): Promise<string> {
|
||||||
|
const digest = await crypto.subtle.digest(
|
||||||
|
"SHA3-256",
|
||||||
|
new TextEncoder().encode(str),
|
||||||
|
);
|
||||||
|
return toHashString(digest, "hex");
|
||||||
|
}
|
Loading…
Reference in new issue