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