Compare commits
No commits in common. "BKCH" and "main" have entirely different histories.
43
.gitignore
vendored
|
@ -1,2 +1,41 @@
|
||||||
justfile
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
static/*
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# env files (can opt-in for committing if needed)
|
||||||
|
.env*
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|
40
README.md
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
First, run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
yarn dev
|
||||||
|
# or
|
||||||
|
pnpm dev
|
||||||
|
# or
|
||||||
|
bun dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||||
|
|
||||||
|
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about Next.js, take a look at the following resources:
|
||||||
|
|
||||||
|
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||||
|
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||||
|
|
||||||
|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||||
|
|
||||||
|
## Deploy on Vercel
|
||||||
|
|
||||||
|
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||||
|
|
||||||
|
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||||
|
|
||||||
|
|
||||||
|
# Structure
|
||||||
|
Store on local machine and use a middleware to process (compress) the file and facilitate storage.
|
|
@ -1,100 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>NIMING NewUser</title>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="m-0 grid place-center w-dvw h-dvh bg-zinc-800 text-zinc-900">
|
|
||||||
<script>
|
|
||||||
let key = ""
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
let username = document.getElementById("username")
|
|
||||||
let password = document.getElementById("password")
|
|
||||||
let gen_img = document.getElementById("gen")
|
|
||||||
let img = document.getElementById("totp_img")
|
|
||||||
let create = document.getElementById("create")
|
|
||||||
const get_img = () => {
|
|
||||||
console.log(username.value)
|
|
||||||
if (username.value.length == 0) {
|
|
||||||
alert("缺少名稱")
|
|
||||||
}
|
|
||||||
gen_img.disabled = true
|
|
||||||
fetch(`/api/admin/new_totp?name=${encodeURIComponent(username.value)}`).then(async resp => {
|
|
||||||
const resp_obj = await resp.json()
|
|
||||||
key = resp_obj["key"]
|
|
||||||
img.src = "data:image/png;base64, " + resp_obj["img"]
|
|
||||||
}).finally(() => {
|
|
||||||
gen_img.disabled = false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
gen_img.onclick = get_img;
|
|
||||||
const create_func = () => {
|
|
||||||
if (username.value.length == 0) {
|
|
||||||
alert("username required")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (password.value.length < 8) {
|
|
||||||
alert("password must contain over 8 characters")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (key.length == 0) {
|
|
||||||
alert("Didn't set a totp code")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch("/api/admin/create", {
|
|
||||||
body: JSON.stringify(
|
|
||||||
{
|
|
||||||
"username": username.value
|
|
||||||
, "password": password.value
|
|
||||||
, "totp_secret": key
|
|
||||||
}),
|
|
||||||
headers: {"Content-Type": "application/json"},
|
|
||||||
method: "POST"
|
|
||||||
}).then(resp => {
|
|
||||||
if (resp.status == 201) {
|
|
||||||
alert("created")
|
|
||||||
username.value = ""
|
|
||||||
password.value = ""
|
|
||||||
key = ""
|
|
||||||
img.src = ""
|
|
||||||
} else {
|
|
||||||
alert(`Not succeeded, status: ${resp.statusText}`)
|
|
||||||
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
|
||||||
alert(err)
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
create.onclick = create_func
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<div
|
|
||||||
class="drop-shadow-lg flex flex-col box-border py-5 px-3 rounded-xl bg-zinc-400 m-auto h-fit w-10/12 md:w-5/12 lg:w-1/4 gap-5">
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<label>使用者名稱</label>
|
|
||||||
<input type="text" name="username"
|
|
||||||
class="focus:border-zinc-600 outline-zinc-500 pl-2 border-2 rounded-lg border-zinc-800 caret-zinc-800 outline-none"
|
|
||||||
id="username" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<label>密碼</label>
|
|
||||||
<input type="text" name="password"
|
|
||||||
class="focus:border-zinc-600 outline-zinc-500 pl-2 border-2 rounded-lg border-zinc-800 caret-zinc-800 outline-none"
|
|
||||||
id="password" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<button id="gen">產生TOTP QrCODE</button>
|
|
||||||
<img src="" id="totp_img" class="max-w-30 max-h-30 mx-auto" />
|
|
||||||
</div>
|
|
||||||
<button class="border-2 border-slate-700 w-50 rounded-xl mx-auto" id="create">確認</button>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -1,127 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>NIMING Login</title>
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.4"
|
|
||||||
integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+"
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
|
|
||||||
|
|
||||||
<body class="grid place-center w-dvw h-dvh bg-zinc-900">
|
|
||||||
<script>
|
|
||||||
const totp_length = 6;
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
let totp_cell = document.getElementById("totp_cell");
|
|
||||||
let totp_container = document.getElementById("totp");
|
|
||||||
|
|
||||||
for (let i = 0; i < totp_length; i++) {
|
|
||||||
totp_cell.content.firstElementChild.dataset.indexNumber = i
|
|
||||||
totp_cell.content.firstElementChild.name = `totp-${i}`
|
|
||||||
let node = document.importNode(totp_cell.content, true)
|
|
||||||
totp_container.appendChild(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyup_ev = (ev) => {
|
|
||||||
if (ev.keyCode <= 0x39 && ev.keyCode >= 0x30) {
|
|
||||||
ev.target.value = ev.key
|
|
||||||
if (ev.target.nextElementSibling) {
|
|
||||||
ev.target.nextElementSibling.focus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ev.key = "a" && ev.ctrlKey) {
|
|
||||||
console.log("Select all")
|
|
||||||
for (let i = 1; i <= totp_length; i++) {
|
|
||||||
totp_container.children[i].select()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ev.key == "Backspace" && ev.target.dataset.indexNumber > 0) {
|
|
||||||
const previous = totp_container.querySelector(`[data-index-number='${ev.target.dataset.indexNumber - 1}']`)
|
|
||||||
previous.focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
const paste_input_ev = (ev) => {
|
|
||||||
if (ev.inputType == "insertFromPaste") {
|
|
||||||
let data = ev.data
|
|
||||||
console.log(data)
|
|
||||||
for (let i = 1; i <= totp_length; i++) {
|
|
||||||
totp_container.children[i].value = data.slice(0, 1)
|
|
||||||
data = data.slice(1, data.length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const cell of totp_container.children) {
|
|
||||||
if (cell instanceof HTMLInputElement) {
|
|
||||||
cell.addEventListener("keyup", keyup_ev)
|
|
||||||
cell.addEventListener("input", paste_input_ev)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const form_onsubmit = (ev) => {
|
|
||||||
let form = new FormData(ev.target)
|
|
||||||
let miss = form.entries().find(p => p[1] == "")
|
|
||||||
if (miss) {
|
|
||||||
alert(`Missing ${miss[0]}`)
|
|
||||||
ev.preventDefault()
|
|
||||||
}
|
|
||||||
console.log(form)
|
|
||||||
|
|
||||||
}
|
|
||||||
const form_formdata = (ev) => {
|
|
||||||
const data = ev.formData
|
|
||||||
let totp = new Array(totp_length)
|
|
||||||
for (const [k, v] of data.entries()) {
|
|
||||||
if (k.match(/totp-\d/g)) {
|
|
||||||
totp[parseInt(k.split("-")[1])] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let i = 0; i < totp_length; i++) {
|
|
||||||
data.delete(`totp-${i}`)
|
|
||||||
}
|
|
||||||
data.set("totp_code", totp.join(""))
|
|
||||||
}
|
|
||||||
const form = document.getElementById("login_form")
|
|
||||||
form.addEventListener("submit", form_onsubmit)
|
|
||||||
form.addEventListener("formdata", form_formdata)
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<form id="login_form"
|
|
||||||
class="m-auto h-fit w-10/12 md:w-5/12 lg:w-1/4 bg-zinc-400 drop-shadow-lg flex flex-col px-10 box-border py-5 gap-10 rounded-xl"
|
|
||||||
action="/admin/login" method="post">
|
|
||||||
<h1 class="text-3xl text-center">中工匿名管理</h1>
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<label for="username">名稱</label>
|
|
||||||
<input name="name" id="username" type="text"
|
|
||||||
class="block rounded outline-none ring-3 border-box px-3" />
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<label for="password">密碼</label>
|
|
||||||
<input name="password" id="password" type="password"
|
|
||||||
class="block rounded outline-none ring-3 border-box px-3" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<label for="totp">TOTP</label>
|
|
||||||
<div class="flex flex-row gap-3 w-full px-auto justify-center box-border" id="totp">
|
|
||||||
<template id="totp_cell">
|
|
||||||
<input type="text" inputmode="numeric" pattern="[0-9]{1}"
|
|
||||||
class="block rounded outline-none ring-2 border-box w-7 h-7 text-center totp"
|
|
||||||
data-index-number="0" maxlength="1" minlength="1" />
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="submit"
|
|
||||||
class="w-fit px-5 py-1 mx-auto bg-sky-500 rounded-lg drop-shadow-cyan-500/50 drop-shadow-lg">確認</button>
|
|
||||||
</form>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -1,163 +0,0 @@
|
||||||
class Post {
|
|
||||||
content;
|
|
||||||
signing;
|
|
||||||
post_at;
|
|
||||||
media;
|
|
||||||
id;
|
|
||||||
constructor(id, content, signing, post_at, media) {
|
|
||||||
this.id = id;
|
|
||||||
this.content = content;
|
|
||||||
this.signing = signing;
|
|
||||||
this.post_at = post_at;
|
|
||||||
this.media = media;
|
|
||||||
}
|
|
||||||
verify(check) {
|
|
||||||
this.check = check;
|
|
||||||
}
|
|
||||||
get verify_blk() {
|
|
||||||
return {
|
|
||||||
post: this.id,
|
|
||||||
check: this.check,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const Fetch = async () => {
|
|
||||||
let result = await fetch("/api/admin/fetch_post", {
|
|
||||||
credentials: "include",
|
|
||||||
});
|
|
||||||
return (await result.json()).map((e) => {
|
|
||||||
return new Post(e.id, e.content, e.signing, e.post_at, e.enclosure);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
let sending = [];
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
let verix_container = document.getElementById("verix_container");
|
|
||||||
|
|
||||||
const verify = (id, obj, ck) => {
|
|
||||||
obj.verify(ck);
|
|
||||||
sending.push(obj);
|
|
||||||
let child = document.getElementById(`post-${id}`);
|
|
||||||
verix_container.removeChild(child);
|
|
||||||
};
|
|
||||||
|
|
||||||
let hv = document.getElementById("hv");
|
|
||||||
const disable_display = () => {
|
|
||||||
hv.classList.add("hidden");
|
|
||||||
};
|
|
||||||
hv.onclick = disable_display;
|
|
||||||
|
|
||||||
const hover_display_img = (src) => {
|
|
||||||
hv.classList.remove("hidden");
|
|
||||||
console.log(hv.children);
|
|
||||||
hv.children[0].src = src;
|
|
||||||
};
|
|
||||||
|
|
||||||
Fetch()
|
|
||||||
.then((e) => {
|
|
||||||
if (e.length == 0) {
|
|
||||||
let nothing = document.createElement("div");
|
|
||||||
nothing.appendChild(document.createTextNode("Nothing!!!"));
|
|
||||||
nothing.className = "text-slate-300 text-3xl mx-auto";
|
|
||||||
verix_container.appendChild(nothing);
|
|
||||||
} else
|
|
||||||
e.forEach((x) => {
|
|
||||||
let post = document.createElement("div");
|
|
||||||
post.className =
|
|
||||||
"rounded bg-zinc-800 w-full h-fit text-slate-200 px-2 relative break-all";
|
|
||||||
let post_content = document.createElement("p");
|
|
||||||
post_content.className = "h-fit text-md min-h-20 mr-[30px]";
|
|
||||||
post_content.appendChild(document.createTextNode(x.content));
|
|
||||||
post.appendChild(post_content);
|
|
||||||
|
|
||||||
let media = document.createElement("div");
|
|
||||||
media.className = "w-full flex flex-row gap-2 h-fit overflow-x-auto";
|
|
||||||
post.appendChild(media);
|
|
||||||
|
|
||||||
if (x.media) {
|
|
||||||
x.media.forEach((src) => {
|
|
||||||
let media_cell = document.createElement("img");
|
|
||||||
media_cell.className = "h-30 w-auto my-auto";
|
|
||||||
media_cell.src = `/static/${src}`;
|
|
||||||
media_cell.onclick = () => {
|
|
||||||
hover_display_img(media_cell.src);
|
|
||||||
};
|
|
||||||
media.appendChild(media_cell);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let btm_bar = document.createElement("div");
|
|
||||||
let msg = document.createElement("p");
|
|
||||||
msg.className = "text-sm border-t-3";
|
|
||||||
msg.appendChild(
|
|
||||||
document.createTextNode(
|
|
||||||
`id: ${x.id}, ${x.signing ? "sign: " + x.signing + ", " : ""}post_at: ${new Date(x.post_at).toDateString()}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
btm_bar.appendChild(msg);
|
|
||||||
post.appendChild(btm_bar);
|
|
||||||
|
|
||||||
let right_bar = document.createElement("div");
|
|
||||||
right_bar.classList =
|
|
||||||
"h-10 w-fit absolute right-0 top-0 flex flex-col gap-2 text-lg";
|
|
||||||
post.appendChild(right_bar);
|
|
||||||
let btn_cls = "rounded-2xl w-[30px] h-[30px] bg-zinc-400";
|
|
||||||
let revoke = document.createElement("button");
|
|
||||||
revoke.className = btn_cls;
|
|
||||||
revoke.onclick = () => {
|
|
||||||
verify(x.id, x, false);
|
|
||||||
};
|
|
||||||
revoke.appendChild(document.createTextNode("X"));
|
|
||||||
let issue = document.createElement("button");
|
|
||||||
issue.className = btn_cls;
|
|
||||||
issue.onclick = () => {
|
|
||||||
verify(x.id, x, true);
|
|
||||||
};
|
|
||||||
issue.appendChild(document.createTextNode("Y"));
|
|
||||||
right_bar.appendChild(revoke);
|
|
||||||
right_bar.appendChild(issue);
|
|
||||||
post.id = `post-${x.id}`;
|
|
||||||
|
|
||||||
verix_container.appendChild(post);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
alert(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
const sendbtn = document.getElementById("send");
|
|
||||||
|
|
||||||
const send = () => {
|
|
||||||
sendbtn.disabled = true;
|
|
||||||
sendbtn.classList.remove("text-zinc-200");
|
|
||||||
sendbtn.classList.add("text-zinc-400");
|
|
||||||
if (sending.length > 0) {
|
|
||||||
fetch("/api/admin/verify_post", {
|
|
||||||
body: JSON.stringify({ choice: sending.map((e) => e.verify_blk) }),
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
credentials: "include",
|
|
||||||
method: "PUT",
|
|
||||||
})
|
|
||||||
.then(async (resp) => {
|
|
||||||
if (resp.status == 200) {
|
|
||||||
sending = [];
|
|
||||||
alert("成功");
|
|
||||||
} else {
|
|
||||||
alert(`Err: ${resp.statusText}, ${await resp.text()}`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => alert(err))
|
|
||||||
.finally(() => {
|
|
||||||
sendbtn.disabled = false;
|
|
||||||
sendbtn.classList.add("text-zinc-200");
|
|
||||||
sendbtn.classList.remove("text-zinc-400");
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
alert("Nothing");
|
|
||||||
sendbtn.classList.add("text-zinc-200");
|
|
||||||
sendbtn.classList.remove("text-zinc-400");
|
|
||||||
sendbtn.disabled = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sendbtn.onclick = send;
|
|
||||||
});
|
|
|
@ -1,29 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Panel</title>
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.4"
|
|
||||||
integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+"
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
<script src="/admin/panel/api.js"></script>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="m-0 w-dvw h-dvh bg-zinc-800 grid place-center">
|
|
||||||
<div class="mx-auto h-dvh w-[90dvw] lg:w-[70dvw]">
|
|
||||||
<button class="h-1/20 w-full text-zinc-200 border-2 rounded my-[25px]" id="send">Send</button>
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 bg-zinc-700 px-2 overflow-auto h-[calc(95dvh-50px)] box-border"
|
|
||||||
id="verix_container">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="hidden fixed top-0 left-0 w-dvw h-dvh z-10 backdrop-brightness-30" id="hv">
|
|
||||||
<img src="" class="h-70dvh lg:w-[40dvw] fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-20"
|
|
||||||
alt="img" />
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
63
adminadd.go
|
@ -1,63 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5"
|
|
||||||
"github.com/pquerna/otp/totp"
|
|
||||||
"golang.org/x/crypto/scrypt"
|
|
||||||
"nim.jasinco.work/app/nimdb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
pgurl := os.Getenv("POSTGRES_URL")
|
|
||||||
conn, err := pgx.Connect(context.Background(), pgurl)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
salt := os.Getenv("SALT")
|
|
||||||
|
|
||||||
db := nimdb.New(conn)
|
|
||||||
tx, err := conn.Begin(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err.Error())
|
|
||||||
}
|
|
||||||
qtx := db.WithTx(tx)
|
|
||||||
defer tx.Rollback(context.Background())
|
|
||||||
|
|
||||||
fmt.Print("UserName and password (split by space): ")
|
|
||||||
var name, password string
|
|
||||||
_, err = fmt.Scanf("%s %s", &name, &password)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := totp.Generate(totp.GenerateOpts{Issuer: "TCIVS_NIMING", AccountName: name})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
secret := key.Secret()
|
|
||||||
log.Println(secret, key.Issuer(), key.AccountName())
|
|
||||||
fmt.Print("Verify TOTP Code: ")
|
|
||||||
var code string
|
|
||||||
_, err = fmt.Scanf("%s", &code)
|
|
||||||
if !totp.Validate(code, secret) {
|
|
||||||
gen, err := totp.GenerateCode(secret, time.Now())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln("Validation not succed, can't gen code, err:", err.Error())
|
|
||||||
}
|
|
||||||
log.Fatalln("Velidation not succed, CODE should be: ", gen)
|
|
||||||
}
|
|
||||||
hashed, err := scrypt.Key([]byte(password), []byte(salt), 32768, 8, 1, 32)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err.Error())
|
|
||||||
}
|
|
||||||
qtx.AdminCreateAccount(context.Background(), nimdb.AdminCreateAccountParams{Username: name, Password: base64.StdEncoding.EncodeToString(hashed), Totp: secret})
|
|
||||||
tx.Commit(context.Background())
|
|
||||||
}
|
|
BIN
bun.lockb
Executable file
39
go.mod
|
@ -1,39 +0,0 @@
|
||||||
module nim.jasinco.work/app
|
|
||||||
|
|
||||||
go 1.24.3
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/gofiber/contrib/jwt v1.1.2
|
|
||||||
github.com/gofiber/fiber/v2 v2.52.8
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
|
||||||
github.com/google/uuid v1.6.0
|
|
||||||
github.com/jackc/pgx/v5 v5.7.5
|
|
||||||
github.com/pquerna/otp v1.5.0
|
|
||||||
golang.org/x/crypto v0.38.0
|
|
||||||
google.golang.org/grpc v1.72.2
|
|
||||||
google.golang.org/protobuf v1.36.6
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/MicahParks/keyfunc/v2 v2.1.0 // indirect
|
|
||||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
|
||||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
|
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
|
||||||
github.com/stretchr/testify v1.10.0 // indirect
|
|
||||||
github.com/tinylib/msgp v1.2.5 // indirect
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
|
||||||
github.com/valyala/fasthttp v1.62.0 // indirect
|
|
||||||
golang.org/x/net v0.40.0 // indirect
|
|
||||||
golang.org/x/sync v0.14.0 // indirect
|
|
||||||
golang.org/x/sys v0.33.0 // indirect
|
|
||||||
golang.org/x/text v0.25.0 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
|
|
||||||
)
|
|
96
go.sum
|
@ -1,96 +0,0 @@
|
||||||
github.com/MicahParks/keyfunc/v2 v2.1.0 h1:6ZXKb9Rp6qp1bDbJefnG7cTH8yMN1IC/4nf+GVjO99k=
|
|
||||||
github.com/MicahParks/keyfunc/v2 v2.1.0/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4OeE/yHVMteCkw9k=
|
|
||||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
|
||||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
|
||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
|
||||||
github.com/gofiber/contrib/jwt v1.1.2 h1:GmWnOqT4A15EkA8IPXwSpvNUXZR4u5SMj+geBmyLAjs=
|
|
||||||
github.com/gofiber/contrib/jwt v1.1.2/go.mod h1:CpIwrkUQ3Q6IP8y9n3f0wP9bOnSKx39EDp2fBVgMFVk=
|
|
||||||
github.com/gofiber/fiber/v2 v2.52.8 h1:xl4jJQ0BV5EJTA2aWiKw/VddRpHrKeZLF0QPUxqn0x4=
|
|
||||||
github.com/gofiber/fiber/v2 v2.52.8/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
|
||||||
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
|
|
||||||
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
|
||||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
|
||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
|
||||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
|
|
||||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
|
||||||
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
|
|
||||||
github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
|
||||||
github.com/valyala/fasthttp v1.62.0 h1:8dKRBX/y2rCzyc6903Zu1+3qN0H/d2MsxPPmVNamiH0=
|
|
||||||
github.com/valyala/fasthttp v1.62.0/go.mod h1:FCINgr4GKdKqV8Q0xv8b+UxPV+H/O5nNFo3D+r54Htg=
|
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
|
||||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
|
||||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
|
||||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
|
||||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
|
||||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
|
||||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
|
||||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
|
||||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
|
||||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
|
||||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
|
||||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
|
||||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
|
||||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
|
||||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
|
|
||||||
google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
|
|
||||||
google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
|
|
||||||
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
|
|
||||||
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
|
@ -1,211 +0,0 @@
|
||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// protoc-gen-go v1.36.6
|
|
||||||
// protoc v6.31.0
|
|
||||||
// source: igapi/interface.proto
|
|
||||||
|
|
||||||
package igapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
|
||||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
|
||||||
reflect "reflect"
|
|
||||||
sync "sync"
|
|
||||||
unsafe "unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Verify that this generated code is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
|
||||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
|
||||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Request struct {
|
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
|
||||||
Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
|
||||||
Igid string `protobuf:"bytes,2,opt,name=igid,proto3" json:"igid,omitempty"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Request) Reset() {
|
|
||||||
*x = Request{}
|
|
||||||
mi := &file_igapi_interface_proto_msgTypes[0]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Request) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Request) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *Request) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_igapi_interface_proto_msgTypes[0]
|
|
||||||
if x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use Request.ProtoReflect.Descriptor instead.
|
|
||||||
func (*Request) Descriptor() ([]byte, []int) {
|
|
||||||
return file_igapi_interface_proto_rawDescGZIP(), []int{0}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Request) GetId() int64 {
|
|
||||||
if x != nil {
|
|
||||||
return x.Id
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Request) GetIgid() string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Igid
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
type Reply struct {
|
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
|
||||||
Err int64 `protobuf:"varint,1,opt,name=err,proto3" json:"err,omitempty"`
|
|
||||||
Result map[string]string `protobuf:"bytes,2,rep,name=result,proto3" json:"result,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
|
||||||
unknownFields protoimpl.UnknownFields
|
|
||||||
sizeCache protoimpl.SizeCache
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Reply) Reset() {
|
|
||||||
*x = Reply{}
|
|
||||||
mi := &file_igapi_interface_proto_msgTypes[1]
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Reply) String() string {
|
|
||||||
return protoimpl.X.MessageStringOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*Reply) ProtoMessage() {}
|
|
||||||
|
|
||||||
func (x *Reply) ProtoReflect() protoreflect.Message {
|
|
||||||
mi := &file_igapi_interface_proto_msgTypes[1]
|
|
||||||
if x != nil {
|
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
|
||||||
if ms.LoadMessageInfo() == nil {
|
|
||||||
ms.StoreMessageInfo(mi)
|
|
||||||
}
|
|
||||||
return ms
|
|
||||||
}
|
|
||||||
return mi.MessageOf(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: Use Reply.ProtoReflect.Descriptor instead.
|
|
||||||
func (*Reply) Descriptor() ([]byte, []int) {
|
|
||||||
return file_igapi_interface_proto_rawDescGZIP(), []int{1}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Reply) GetErr() int64 {
|
|
||||||
if x != nil {
|
|
||||||
return x.Err
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Reply) GetResult() map[string]string {
|
|
||||||
if x != nil {
|
|
||||||
return x.Result
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var File_igapi_interface_proto protoreflect.FileDescriptor
|
|
||||||
|
|
||||||
const file_igapi_interface_proto_rawDesc = "" +
|
|
||||||
"\n" +
|
|
||||||
"\x15igapi/interface.proto\"-\n" +
|
|
||||||
"\aRequest\x12\x0e\n" +
|
|
||||||
"\x02id\x18\x01 \x01(\x03R\x02id\x12\x12\n" +
|
|
||||||
"\x04igid\x18\x02 \x01(\tR\x04igid\"\x80\x01\n" +
|
|
||||||
"\x05Reply\x12\x10\n" +
|
|
||||||
"\x03err\x18\x01 \x01(\x03R\x03err\x12*\n" +
|
|
||||||
"\x06result\x18\x02 \x03(\v2\x12.Reply.ResultEntryR\x06result\x1a9\n" +
|
|
||||||
"\vResultEntry\x12\x10\n" +
|
|
||||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
|
||||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x012\xbf\x01\n" +
|
|
||||||
"\x05IGAPI\x12\x1b\n" +
|
|
||||||
"\x05login\x12\b.Request\x1a\x06.Reply\"\x00\x12\"\n" +
|
|
||||||
"\faccount_info\x12\b.Request\x1a\x06.Reply\"\x00\x12\x1c\n" +
|
|
||||||
"\x06upload\x12\b.Request\x1a\x06.Reply\"\x00\x12\x1c\n" +
|
|
||||||
"\x06delete\x12\b.Request\x1a\x06.Reply\"\x00\x12\x1b\n" +
|
|
||||||
"\x05queue\x12\b.Request\x1a\x06.Reply\"\x00\x12\x1c\n" +
|
|
||||||
"\x06search\x12\b.Request\x1a\x06.Reply\"\x00B\x1cZ\x1anim.jasinco.work/app/igapib\x06proto3"
|
|
||||||
|
|
||||||
var (
|
|
||||||
file_igapi_interface_proto_rawDescOnce sync.Once
|
|
||||||
file_igapi_interface_proto_rawDescData []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
func file_igapi_interface_proto_rawDescGZIP() []byte {
|
|
||||||
file_igapi_interface_proto_rawDescOnce.Do(func() {
|
|
||||||
file_igapi_interface_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_igapi_interface_proto_rawDesc), len(file_igapi_interface_proto_rawDesc)))
|
|
||||||
})
|
|
||||||
return file_igapi_interface_proto_rawDescData
|
|
||||||
}
|
|
||||||
|
|
||||||
var file_igapi_interface_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
|
||||||
var file_igapi_interface_proto_goTypes = []any{
|
|
||||||
(*Request)(nil), // 0: Request
|
|
||||||
(*Reply)(nil), // 1: Reply
|
|
||||||
nil, // 2: Reply.ResultEntry
|
|
||||||
}
|
|
||||||
var file_igapi_interface_proto_depIdxs = []int32{
|
|
||||||
2, // 0: Reply.result:type_name -> Reply.ResultEntry
|
|
||||||
0, // 1: IGAPI.login:input_type -> Request
|
|
||||||
0, // 2: IGAPI.account_info:input_type -> Request
|
|
||||||
0, // 3: IGAPI.upload:input_type -> Request
|
|
||||||
0, // 4: IGAPI.delete:input_type -> Request
|
|
||||||
0, // 5: IGAPI.queue:input_type -> Request
|
|
||||||
0, // 6: IGAPI.search:input_type -> Request
|
|
||||||
1, // 7: IGAPI.login:output_type -> Reply
|
|
||||||
1, // 8: IGAPI.account_info:output_type -> Reply
|
|
||||||
1, // 9: IGAPI.upload:output_type -> Reply
|
|
||||||
1, // 10: IGAPI.delete:output_type -> Reply
|
|
||||||
1, // 11: IGAPI.queue:output_type -> Reply
|
|
||||||
1, // 12: IGAPI.search:output_type -> Reply
|
|
||||||
7, // [7:13] is the sub-list for method output_type
|
|
||||||
1, // [1:7] is the sub-list for method input_type
|
|
||||||
1, // [1:1] is the sub-list for extension type_name
|
|
||||||
1, // [1:1] is the sub-list for extension extendee
|
|
||||||
0, // [0:1] is the sub-list for field type_name
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() { file_igapi_interface_proto_init() }
|
|
||||||
func file_igapi_interface_proto_init() {
|
|
||||||
if File_igapi_interface_proto != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
type x struct{}
|
|
||||||
out := protoimpl.TypeBuilder{
|
|
||||||
File: protoimpl.DescBuilder{
|
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_igapi_interface_proto_rawDesc), len(file_igapi_interface_proto_rawDesc)),
|
|
||||||
NumEnums: 0,
|
|
||||||
NumMessages: 3,
|
|
||||||
NumExtensions: 0,
|
|
||||||
NumServices: 1,
|
|
||||||
},
|
|
||||||
GoTypes: file_igapi_interface_proto_goTypes,
|
|
||||||
DependencyIndexes: file_igapi_interface_proto_depIdxs,
|
|
||||||
MessageInfos: file_igapi_interface_proto_msgTypes,
|
|
||||||
}.Build()
|
|
||||||
File_igapi_interface_proto = out.File
|
|
||||||
file_igapi_interface_proto_goTypes = nil
|
|
||||||
file_igapi_interface_proto_depIdxs = nil
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
syntax = "proto3";
|
|
||||||
option go_package = "nim.jasinco.work/app/igapi";
|
|
||||||
|
|
||||||
service IGAPI {
|
|
||||||
rpc login(Request) returns (Reply) {}
|
|
||||||
|
|
||||||
rpc account_info(Request) returns (Reply) {}
|
|
||||||
|
|
||||||
rpc upload(Request) returns (Reply) {}
|
|
||||||
|
|
||||||
rpc delete (Request) returns (Reply) {}
|
|
||||||
|
|
||||||
rpc queue(Request) returns (Reply) {}
|
|
||||||
|
|
||||||
rpc search(Request) returns (Reply) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
message Request {
|
|
||||||
int64 id = 1;
|
|
||||||
string igid = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Reply {
|
|
||||||
int64 err = 1;
|
|
||||||
map<string, string> result = 2;
|
|
||||||
}
|
|
|
@ -1,311 +0,0 @@
|
||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// - protoc-gen-go-grpc v1.5.1
|
|
||||||
// - protoc v6.31.0
|
|
||||||
// source: igapi/interface.proto
|
|
||||||
|
|
||||||
package igapi
|
|
||||||
|
|
||||||
import (
|
|
||||||
context "context"
|
|
||||||
grpc "google.golang.org/grpc"
|
|
||||||
codes "google.golang.org/grpc/codes"
|
|
||||||
status "google.golang.org/grpc/status"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is a compile-time assertion to ensure that this generated file
|
|
||||||
// is compatible with the grpc package it is being compiled against.
|
|
||||||
// Requires gRPC-Go v1.64.0 or later.
|
|
||||||
const _ = grpc.SupportPackageIsVersion9
|
|
||||||
|
|
||||||
const (
|
|
||||||
IGAPI_Login_FullMethodName = "/IGAPI/login"
|
|
||||||
IGAPI_AccountInfo_FullMethodName = "/IGAPI/account_info"
|
|
||||||
IGAPI_Upload_FullMethodName = "/IGAPI/upload"
|
|
||||||
IGAPI_Delete_FullMethodName = "/IGAPI/delete"
|
|
||||||
IGAPI_Queue_FullMethodName = "/IGAPI/queue"
|
|
||||||
IGAPI_Search_FullMethodName = "/IGAPI/search"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IGAPIClient is the client API for IGAPI service.
|
|
||||||
//
|
|
||||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
|
||||||
type IGAPIClient interface {
|
|
||||||
Login(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Reply, error)
|
|
||||||
AccountInfo(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Reply, error)
|
|
||||||
Upload(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Reply, error)
|
|
||||||
Delete(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Reply, error)
|
|
||||||
Queue(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Reply, error)
|
|
||||||
Search(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Reply, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type iGAPIClient struct {
|
|
||||||
cc grpc.ClientConnInterface
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewIGAPIClient(cc grpc.ClientConnInterface) IGAPIClient {
|
|
||||||
return &iGAPIClient{cc}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *iGAPIClient) Login(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Reply, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(Reply)
|
|
||||||
err := c.cc.Invoke(ctx, IGAPI_Login_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *iGAPIClient) AccountInfo(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Reply, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(Reply)
|
|
||||||
err := c.cc.Invoke(ctx, IGAPI_AccountInfo_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *iGAPIClient) Upload(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Reply, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(Reply)
|
|
||||||
err := c.cc.Invoke(ctx, IGAPI_Upload_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *iGAPIClient) Delete(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Reply, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(Reply)
|
|
||||||
err := c.cc.Invoke(ctx, IGAPI_Delete_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *iGAPIClient) Queue(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Reply, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(Reply)
|
|
||||||
err := c.cc.Invoke(ctx, IGAPI_Queue_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *iGAPIClient) Search(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Reply, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(Reply)
|
|
||||||
err := c.cc.Invoke(ctx, IGAPI_Search_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IGAPIServer is the server API for IGAPI service.
|
|
||||||
// All implementations must embed UnimplementedIGAPIServer
|
|
||||||
// for forward compatibility.
|
|
||||||
type IGAPIServer interface {
|
|
||||||
Login(context.Context, *Request) (*Reply, error)
|
|
||||||
AccountInfo(context.Context, *Request) (*Reply, error)
|
|
||||||
Upload(context.Context, *Request) (*Reply, error)
|
|
||||||
Delete(context.Context, *Request) (*Reply, error)
|
|
||||||
Queue(context.Context, *Request) (*Reply, error)
|
|
||||||
Search(context.Context, *Request) (*Reply, error)
|
|
||||||
mustEmbedUnimplementedIGAPIServer()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnimplementedIGAPIServer must be embedded to have
|
|
||||||
// forward compatible implementations.
|
|
||||||
//
|
|
||||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
|
||||||
// pointer dereference when methods are called.
|
|
||||||
type UnimplementedIGAPIServer struct{}
|
|
||||||
|
|
||||||
func (UnimplementedIGAPIServer) Login(context.Context, *Request) (*Reply, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Login not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedIGAPIServer) AccountInfo(context.Context, *Request) (*Reply, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method AccountInfo not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedIGAPIServer) Upload(context.Context, *Request) (*Reply, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Upload not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedIGAPIServer) Delete(context.Context, *Request) (*Reply, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Delete not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedIGAPIServer) Queue(context.Context, *Request) (*Reply, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Queue not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedIGAPIServer) Search(context.Context, *Request) (*Reply, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Search not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedIGAPIServer) mustEmbedUnimplementedIGAPIServer() {}
|
|
||||||
func (UnimplementedIGAPIServer) testEmbeddedByValue() {}
|
|
||||||
|
|
||||||
// UnsafeIGAPIServer may be embedded to opt out of forward compatibility for this service.
|
|
||||||
// Use of this interface is not recommended, as added methods to IGAPIServer will
|
|
||||||
// result in compilation errors.
|
|
||||||
type UnsafeIGAPIServer interface {
|
|
||||||
mustEmbedUnimplementedIGAPIServer()
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterIGAPIServer(s grpc.ServiceRegistrar, srv IGAPIServer) {
|
|
||||||
// If the following call pancis, it indicates UnimplementedIGAPIServer was
|
|
||||||
// embedded by pointer and is nil. This will cause panics if an
|
|
||||||
// unimplemented method is ever invoked, so we test this at initialization
|
|
||||||
// time to prevent it from happening at runtime later due to I/O.
|
|
||||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
|
||||||
t.testEmbeddedByValue()
|
|
||||||
}
|
|
||||||
s.RegisterService(&IGAPI_ServiceDesc, srv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _IGAPI_Login_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(Request)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(IGAPIServer).Login(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: IGAPI_Login_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(IGAPIServer).Login(ctx, req.(*Request))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _IGAPI_AccountInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(Request)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(IGAPIServer).AccountInfo(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: IGAPI_AccountInfo_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(IGAPIServer).AccountInfo(ctx, req.(*Request))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _IGAPI_Upload_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(Request)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(IGAPIServer).Upload(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: IGAPI_Upload_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(IGAPIServer).Upload(ctx, req.(*Request))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _IGAPI_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(Request)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(IGAPIServer).Delete(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: IGAPI_Delete_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(IGAPIServer).Delete(ctx, req.(*Request))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _IGAPI_Queue_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(Request)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(IGAPIServer).Queue(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: IGAPI_Queue_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(IGAPIServer).Queue(ctx, req.(*Request))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _IGAPI_Search_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(Request)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(IGAPIServer).Search(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: IGAPI_Search_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(IGAPIServer).Search(ctx, req.(*Request))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IGAPI_ServiceDesc is the grpc.ServiceDesc for IGAPI service.
|
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
|
||||||
// and not to be introspected or modified (even as a copy)
|
|
||||||
var IGAPI_ServiceDesc = grpc.ServiceDesc{
|
|
||||||
ServiceName: "IGAPI",
|
|
||||||
HandlerType: (*IGAPIServer)(nil),
|
|
||||||
Methods: []grpc.MethodDesc{
|
|
||||||
{
|
|
||||||
MethodName: "login",
|
|
||||||
Handler: _IGAPI_Login_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "account_info",
|
|
||||||
Handler: _IGAPI_AccountInfo_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "upload",
|
|
||||||
Handler: _IGAPI_Upload_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "delete",
|
|
||||||
Handler: _IGAPI_Delete_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "queue",
|
|
||||||
Handler: _IGAPI_Queue_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "search",
|
|
||||||
Handler: _IGAPI_Search_Handler,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Streams: []grpc.StreamDesc{},
|
|
||||||
Metadata: "igapi/interface.proto",
|
|
||||||
}
|
|
|
@ -1,270 +0,0 @@
|
||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
"image/png"
|
|
||||||
"log"
|
|
||||||
"path"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
|
||||||
"github.com/pquerna/otp/totp"
|
|
||||||
"golang.org/x/crypto/scrypt"
|
|
||||||
"nim.jasinco.work/app/internal"
|
|
||||||
"nim.jasinco.work/app/nimdb"
|
|
||||||
)
|
|
||||||
|
|
||||||
type NewAdmin struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
TOTP_Secret string `json:"totp_secret"`
|
|
||||||
}
|
|
||||||
type AdminAuth struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
TOTP_code string `json:"totp_code"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func Admin_create(c *fiber.Ctx) error {
|
|
||||||
cfg := new(NewAdmin)
|
|
||||||
if err := c.BodyParser(cfg); err != nil {
|
|
||||||
return fiber.ErrBadRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
hashed, err := scrypt.Key([]byte(cfg.Password), []byte(internal.SALT), 32768, 8, 1, 32)
|
|
||||||
if err != nil {
|
|
||||||
return c.SendStatus(fiber.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = internal.NIMDB.AdminCreateAccount(c.Context(), nimdb.AdminCreateAccountParams{Username: cfg.Name, Password: base64.StdEncoding.EncodeToString(hashed), Totp: cfg.TOTP_Secret})
|
|
||||||
if err != nil {
|
|
||||||
log.Println(base64.StdEncoding.EncodeToString(hashed), err)
|
|
||||||
return c.SendStatus(fiber.StatusBadRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.SendStatus(fiber.StatusCreated)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Admin_new_totp_code(c *fiber.Ctx) error {
|
|
||||||
name := c.Query("name")
|
|
||||||
if len(name) == 0 {
|
|
||||||
return fiber.ErrBadRequest
|
|
||||||
}
|
|
||||||
key, err := totp.Generate(totp.GenerateOpts{Issuer: "TCIVS_NIMING", AccountName: name})
|
|
||||||
if err != nil {
|
|
||||||
return fiber.ErrInternalServerError
|
|
||||||
}
|
|
||||||
var buf bytes.Buffer
|
|
||||||
img, err := key.Image(200, 200)
|
|
||||||
png.Encode(&buf, img)
|
|
||||||
return c.JSON(fiber.Map{"key": key.Secret(), "img": base64.StdEncoding.EncodeToString(buf.Bytes())})
|
|
||||||
}
|
|
||||||
|
|
||||||
const debug = false
|
|
||||||
|
|
||||||
func Admin_Login_JWT(c *fiber.Ctx) (*string, error) {
|
|
||||||
cred := new(AdminAuth)
|
|
||||||
|
|
||||||
if err := c.BodyParser(cred); err != nil {
|
|
||||||
return nil, fiber.ErrBadRequest
|
|
||||||
}
|
|
||||||
hashed, err := scrypt.Key([]byte(cred.Password), []byte(internal.SALT), 32768, 8, 1, 32)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fiber.ErrInternalServerError
|
|
||||||
}
|
|
||||||
|
|
||||||
var claims jwt.MapClaims
|
|
||||||
|
|
||||||
if !debug {
|
|
||||||
|
|
||||||
rec, err := internal.NIMDB.AdminLoginGetTOTP(c.Context(), nimdb.AdminLoginGetTOTPParams{Username: cred.Name, Password: base64.StdEncoding.EncodeToString(hashed)})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
c.SendString("Failed to login with wrong account or password")
|
|
||||||
return nil, fiber.ErrUnauthorized
|
|
||||||
}
|
|
||||||
if !totp.Validate(cred.TOTP_code, rec.Totp) {
|
|
||||||
log.Println(totp.GenerateCode(rec.Totp, time.Now()))
|
|
||||||
c.SendString("Failed to login with wrong totp")
|
|
||||||
return nil, fiber.ErrUnauthorized
|
|
||||||
}
|
|
||||||
claims = jwt.MapClaims{"name": cred.Name, "admin": rec.Super}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
claims = jwt.MapClaims{"name": "test", "admin": true}
|
|
||||||
}
|
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
|
|
||||||
t, err := token.SignedString([]byte(internal.JWT_SECRET))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fiber.ErrInternalServerError
|
|
||||||
}
|
|
||||||
cookie := new(fiber.Cookie)
|
|
||||||
cookie.Name = "token"
|
|
||||||
cookie.Value = t
|
|
||||||
cookie.Expires = time.Now().Add(5 * time.Hour)
|
|
||||||
c.Cookie(cookie)
|
|
||||||
return &t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Admin_Fetch_Post(c *fiber.Ctx) error {
|
|
||||||
user := c.Locals("user").(*jwt.Token)
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
|
||||||
super := claims["admin"].(bool)
|
|
||||||
ctx := c.Context()
|
|
||||||
if !super {
|
|
||||||
rec, err := internal.NIMDB.AdminGetPost(ctx)
|
|
||||||
if err != nil {
|
|
||||||
|
|
||||||
return c.SendStatus(fiber.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
type recwithimg struct {
|
|
||||||
Id int32 `json:"id"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
Signing pgtype.Text `json:"signing"`
|
|
||||||
Post_at time.Time `json:"post_at"`
|
|
||||||
Enclosue []string `json:"enclosure"`
|
|
||||||
}
|
|
||||||
rec_with_img := make([]recwithimg, len(rec))
|
|
||||||
for id, k := range rec {
|
|
||||||
rec_with_img[id] = recwithimg{Id: k.ID, Content: k.Content, Signing: k.Signing, Post_at: k.PostAt.Time}
|
|
||||||
imgrec, err := internal.NIMDB.AdminGetMedia(ctx, pgtype.Int4{Int32: k.ID, Valid: true})
|
|
||||||
if err != nil {
|
|
||||||
return c.SendStatus(fiber.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
rec_with_img[id].Enclosue = imgrec
|
|
||||||
}
|
|
||||||
return c.JSON(rec_with_img)
|
|
||||||
} else {
|
|
||||||
|
|
||||||
rec, err := internal.NIMDB.SuperAdminGetPost(ctx)
|
|
||||||
if err != nil {
|
|
||||||
|
|
||||||
return c.SendStatus(fiber.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
type recwithimg struct {
|
|
||||||
Id int32 `json:"id"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
Signing pgtype.Text `json:"signing"`
|
|
||||||
Post_at time.Time `json:"post_at"`
|
|
||||||
Enclosue []string `json:"enclosure"`
|
|
||||||
Phase nimdb.PostPhase `json:"phase"`
|
|
||||||
}
|
|
||||||
rec_with_img := make([]recwithimg, len(rec))
|
|
||||||
for id, k := range rec {
|
|
||||||
rec_with_img[id] = recwithimg{Id: k.ID, Content: k.Content, Signing: k.Signing, Post_at: k.PostAt.Time, Phase: k.Phase}
|
|
||||||
imgrec, err := internal.NIMDB.AdminGetMedia(ctx, pgtype.Int4{Int32: k.ID, Valid: true})
|
|
||||||
if err != nil {
|
|
||||||
return c.SendStatus(fiber.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
rec_with_img[id].Enclosue = imgrec
|
|
||||||
}
|
|
||||||
return c.JSON(rec_with_img)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
type Verify struct {
|
|
||||||
Id int32 `json:"post"`
|
|
||||||
Check bool `json:"check"`
|
|
||||||
}
|
|
||||||
type VerifyList struct {
|
|
||||||
Choice []Verify `json:"choice"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func AdminVerify(c *fiber.Ctx) error {
|
|
||||||
user := c.Locals("user").(*jwt.Token)
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
|
||||||
super := claims["admin"].(bool)
|
|
||||||
ctx := c.Context()
|
|
||||||
tx, err := internal.POOL.Begin(ctx)
|
|
||||||
defer tx.Rollback(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return c.SendStatus(fiber.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
qtx := internal.NIMDB.WithTx(tx)
|
|
||||||
post_verify_list := new(VerifyList)
|
|
||||||
if err = c.BodyParser(post_verify_list); err != nil {
|
|
||||||
log.Printf("Can't parse verify body: %s", err.Error())
|
|
||||||
return c.SendStatus(fiber.StatusBadRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !super {
|
|
||||||
for _, post_verify := range post_verify_list.Choice {
|
|
||||||
if post_verify.Check {
|
|
||||||
_, err = qtx.AdminVerify(ctx, nimdb.AdminVerifyParams{ID: post_verify.Id, Phase: nimdb.PostPhaseOk})
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err.Error(), post_verify.Id)
|
|
||||||
return c.SendStatus(fiber.StatusBadRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = qtx.AdminUpdateImage(ctx, pgtype.Int4{Int32: post_verify.Id, Valid: true})
|
|
||||||
if err != nil {
|
|
||||||
return c.SendStatus(fiber.StatusInternalServerError)
|
|
||||||
|
|
||||||
}
|
|
||||||
if internal.IGAPI_ACTIVATE {
|
|
||||||
internal.IGAPI_CHAN <- internal.PR{ACT_TYPE: 0, ID: post_verify.Id}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
_, err = qtx.AdminVerify(ctx, nimdb.AdminVerifyParams{ID: post_verify.Id, Phase: nimdb.PostPhaseRejected})
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err.Error())
|
|
||||||
return c.SendStatus(fiber.StatusBadRequest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for _, post_verify := range post_verify_list.Choice {
|
|
||||||
if post_verify.Check {
|
|
||||||
_, err = qtx.SuperAdminVerify(ctx, nimdb.SuperAdminVerifyParams{ID: post_verify.Id, Phase: nimdb.PostPhaseOk})
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err.Error(), post_verify.Id)
|
|
||||||
return c.SendStatus(fiber.StatusBadRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = qtx.AdminUpdateImage(ctx, pgtype.Int4{Int32: post_verify.Id, Valid: true})
|
|
||||||
if err != nil {
|
|
||||||
return c.SendStatus(fiber.StatusInternalServerError)
|
|
||||||
|
|
||||||
}
|
|
||||||
if internal.IGAPI_ACTIVATE {
|
|
||||||
internal.IGAPI_CHAN <- internal.PR{ACT_TYPE: 0, ID: post_verify.Id}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
_, err = qtx.SuperAdminVerify(ctx, nimdb.SuperAdminVerifyParams{ID: post_verify.Id, Phase: nimdb.PostPhaseAdminRejected})
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err.Error())
|
|
||||||
return c.SendStatus(fiber.StatusBadRequest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tx.Commit(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
const adminpage_basepath = "./admin_panel/dist"
|
|
||||||
|
|
||||||
func AdminSendPage(c *fiber.Ctx) error {
|
|
||||||
pagepath := c.Params("*")
|
|
||||||
if pagepath == "login" || pagepath == "panel" || pagepath == "new_account" {
|
|
||||||
err := c.SendFile(path.Join(adminpage_basepath, "index.html"))
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return c.SendStatus(fiber.StatusNotFound)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err := c.SendFile(path.Join(adminpage_basepath, pagepath))
|
|
||||||
if err != nil {
|
|
||||||
c.SendStatus(404)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"nim.jasinco.work/app/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Add_heart(c *fiber.Ctx) error {
|
|
||||||
hearts, err := strconv.Atoi(c.Query("post"))
|
|
||||||
if err != nil {
|
|
||||||
return fiber.ErrBadRequest
|
|
||||||
}
|
|
||||||
dbhearts, err := internal.NIMDB.AddPostHeart(c.Context(), int32(hearts))
|
|
||||||
return c.JSON(fiber.Map{"hearts": dbhearts})
|
|
||||||
}
|
|
|
@ -1,182 +0,0 @@
|
||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/base64"
|
|
||||||
"log"
|
|
||||||
"mime"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
|
||||||
"nim.jasinco.work/app/internal"
|
|
||||||
"nim.jasinco.work/app/nimdb"
|
|
||||||
)
|
|
||||||
|
|
||||||
var supported_filetype = []string{"image/png", "image/jpeg", "image/webp", "image/gif", "image/heif", "video/H264", "video/H265", "video/mp4"}
|
|
||||||
|
|
||||||
func find_supported(mimetype string) bool {
|
|
||||||
for _, supported := range supported_filetype {
|
|
||||||
if strings.Compare(supported, mimetype) == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func Fetch_post(c *fiber.Ctx) error {
|
|
||||||
cursor := c.Query("cursor")
|
|
||||||
|
|
||||||
ctx := c.Context()
|
|
||||||
|
|
||||||
var rec []nimdb.GetPostRow
|
|
||||||
var err error
|
|
||||||
if len(cursor) == 0 {
|
|
||||||
rec, err = internal.NIMDB.GetPost(ctx, internal.FETCH_LENGTH)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return fiber.ErrInternalServerError
|
|
||||||
}
|
|
||||||
} else if cursor_num, err := strconv.Atoi(cursor); err == nil {
|
|
||||||
rec_cur, err := internal.NIMDB.GetPostWithCursor(ctx, nimdb.GetPostWithCursorParams{ID: int32(cursor_num), Limit: internal.FETCH_LENGTH})
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return c.Status(500).SendString("Failed to Query DB")
|
|
||||||
}
|
|
||||||
rec = make([]nimdb.GetPostRow, len(rec_cur))
|
|
||||||
for id, val := range rec_cur {
|
|
||||||
rec[id] = nimdb.GetPostRow(val)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return c.Status(fiber.StatusBadRequest).SendString("Can't parse cursor")
|
|
||||||
}
|
|
||||||
if len(rec) == 0 {
|
|
||||||
return c.SendStatus(fiber.StatusNoContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(rec)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Delete_post(c *fiber.Ctx) error {
|
|
||||||
post := c.Query("post")
|
|
||||||
hash := c.Query("hash")
|
|
||||||
ctx := c.Context()
|
|
||||||
post_id, err := strconv.Atoi(post)
|
|
||||||
post_id_i32 := int32(post_id)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(500).SendString("Failed to Parse Int")
|
|
||||||
}
|
|
||||||
|
|
||||||
tx, err := internal.POOL.Begin(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(500).SendString("Failed to Start Transaction")
|
|
||||||
}
|
|
||||||
defer tx.Rollback(ctx)
|
|
||||||
qtx := internal.NIMDB.WithTx(tx)
|
|
||||||
if err = qtx.DeletePost(ctx, nimdb.DeletePostParams{ID: post_id_i32, Hash: hash}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = qtx.InvalidateMedia(ctx, pgtype.Int4{Int32: post_id_i32}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if tx.Commit(ctx) != nil {
|
|
||||||
return c.Status(500).SendString("Failed to Commit")
|
|
||||||
}
|
|
||||||
|
|
||||||
if internal.IGAPI_ACTIVATE {
|
|
||||||
internal.IGAPI_CHAN <- internal.PR{ACT_TYPE: 1, ID: post_id_i32}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.SendStatus(200)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Insert_Post(c *fiber.Ctx) error {
|
|
||||||
ctx := c.Context()
|
|
||||||
|
|
||||||
tx, err := internal.POOL.Begin(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(500).SendString("Failed to Start Transaction")
|
|
||||||
}
|
|
||||||
defer tx.Rollback(ctx)
|
|
||||||
qtx := internal.NIMDB.WithTx(tx)
|
|
||||||
|
|
||||||
if form, err := c.MultipartForm(); err == nil {
|
|
||||||
|
|
||||||
content := form.Value["content"]
|
|
||||||
signing := form.Value["signing"]
|
|
||||||
files := form.File["enclosure"]
|
|
||||||
if len(content) == 0 || len(content[0]) == 0 {
|
|
||||||
return fiber.ErrBadRequest
|
|
||||||
}
|
|
||||||
post_hash := sha256.New()
|
|
||||||
post_hash.Write([]byte(content[0]))
|
|
||||||
signing_pgt := new(pgtype.Text)
|
|
||||||
signing_pgt.Valid = false
|
|
||||||
if len(signing) > 0 && len(signing[0]) > 0 {
|
|
||||||
post_hash.Write([]byte(signing[0]))
|
|
||||||
signing_pgt.Valid = true
|
|
||||||
signing_pgt.String = signing[0]
|
|
||||||
}
|
|
||||||
now, err := time.Now().MarshalBinary()
|
|
||||||
if err != nil {
|
|
||||||
c.SendString("Failed to get time")
|
|
||||||
return c.SendStatus(500)
|
|
||||||
}
|
|
||||||
|
|
||||||
credential, err := qtx.InsertPost(ctx,
|
|
||||||
nimdb.InsertPostParams{
|
|
||||||
Content: content[0],
|
|
||||||
Signing: *signing_pgt,
|
|
||||||
Hash: base64.StdEncoding.EncodeToString(post_hash.Sum(now)),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err.Error())
|
|
||||||
return c.Status(500).SendString("Failed to insert post")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range files {
|
|
||||||
filemime, _, err := mime.ParseMediaType(file.Header.Get("Content-Type"))
|
|
||||||
if err != nil {
|
|
||||||
return c.Status(fiber.ErrBadRequest.Code).SendString("Failed to parse content type")
|
|
||||||
}
|
|
||||||
if !find_supported(filemime) {
|
|
||||||
return c.Status(fiber.ErrBadRequest.Code).SendString("Unsupported Type")
|
|
||||||
}
|
|
||||||
|
|
||||||
filetype := strings.Split(filemime, "/")[0]
|
|
||||||
mid, err := uuid.NewRandom()
|
|
||||||
if err != nil {
|
|
||||||
c.SendString("Faild to generate UUIDv4")
|
|
||||||
return c.SendStatus(500)
|
|
||||||
}
|
|
||||||
|
|
||||||
fxnamepath := filetype + "/" + mid.String()
|
|
||||||
if err = c.SaveFile(file, path.Join("./static", fxnamepath)); err != nil {
|
|
||||||
log.Println("Can't save to ./static", err.Error())
|
|
||||||
return fiber.ErrInternalServerError
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := qtx.InsertPostImage(ctx, nimdb.InsertPostImageParams{
|
|
||||||
Url: fxnamepath,
|
|
||||||
PostID: pgtype.Int4{Int32: credential.ID, Valid: true},
|
|
||||||
}); err != nil {
|
|
||||||
log.Println(err.Error())
|
|
||||||
return c.Status(500).SendString("Failed to insert image")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = tx.Commit(ctx); err != nil {
|
|
||||||
log.Println(err.Error())
|
|
||||||
c.SendString("Failed to send Commit")
|
|
||||||
return c.SendStatus(500)
|
|
||||||
}
|
|
||||||
return c.JSON(credential)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fiber.ErrBadRequest
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
|
||||||
"nim.jasinco.work/app/nimdb"
|
|
||||||
)
|
|
||||||
|
|
||||||
var POOL *pgxpool.Pool = nil
|
|
||||||
var NIMDB *nimdb.Queries = nil
|
|
|
@ -1,97 +0,0 @@
|
||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
|
||||||
"nim.jasinco.work/app/igapi"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PR struct {
|
|
||||||
ACT_TYPE int //0 for post, 1 for revoke
|
|
||||||
ID int32
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
SALT string
|
|
||||||
POSTGRES_URL string
|
|
||||||
JWT_SECRET string
|
|
||||||
FETCH_LENGTH int32
|
|
||||||
PREFORK bool
|
|
||||||
IGAPI_HOST string
|
|
||||||
CORS_ALLOW string
|
|
||||||
IGAPI_ACTIVATE bool
|
|
||||||
IGAPI_CHAN = make(chan PR, 20)
|
|
||||||
err error
|
|
||||||
conv int64
|
|
||||||
)
|
|
||||||
|
|
||||||
func ReadFromENV() error {
|
|
||||||
SALT = os.Getenv("SALT")
|
|
||||||
if len(SALT) < 8 {
|
|
||||||
return errors.New("Invalid Salt")
|
|
||||||
}
|
|
||||||
POSTGRES_URL = os.Getenv("POSTGRES_URL")
|
|
||||||
if len(POSTGRES_URL) < 1 {
|
|
||||||
return errors.New("POSTGRES_URL NOT FOUND")
|
|
||||||
}
|
|
||||||
JWT_SECRET = os.Getenv("JWT_SECRET")
|
|
||||||
if len(JWT_SECRET) < 10 {
|
|
||||||
return errors.New("INVALID JWT SECRET")
|
|
||||||
}
|
|
||||||
fetch_len_str := os.Getenv("FETCH_LENGTH")
|
|
||||||
if len(fetch_len_str) == 0 {
|
|
||||||
FETCH_LENGTH = 10
|
|
||||||
} else {
|
|
||||||
conv, err = strconv.ParseInt(fetch_len_str, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if conv < 1 {
|
|
||||||
return errors.New("FETCH_LENGTH should be a positive number as well over 0")
|
|
||||||
}
|
|
||||||
FETCH_LENGTH = int32(conv)
|
|
||||||
|
|
||||||
}
|
|
||||||
prefork_str := os.Getenv("PREFORK")
|
|
||||||
if len(prefork_str) == 0 {
|
|
||||||
PREFORK = false
|
|
||||||
} else {
|
|
||||||
PREFORK, err = strconv.ParseBool(prefork_str)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IGAPI_HOST = os.Getenv("IGAPI_HOST")
|
|
||||||
IGAPI_ACTIVATE = true
|
|
||||||
if len(IGAPI_HOST) == 0 {
|
|
||||||
log.Println("Didn't get IGAPI_HOST, it will work without it")
|
|
||||||
IGAPI_ACTIVATE = false
|
|
||||||
}
|
|
||||||
CORS_ALLOW = os.Getenv("CORS_ALLOW")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func IGAPI_establish() (*grpc.ClientConn, igapi.IGAPIClient) {
|
|
||||||
conn, err := grpc.NewClient(IGAPI_HOST, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
|
||||||
if err != nil {
|
|
||||||
log.Panicf("Can't connect to igapi: %s", err.Error())
|
|
||||||
}
|
|
||||||
c := igapi.NewIGAPIClient(conn)
|
|
||||||
return conn, c
|
|
||||||
}
|
|
||||||
func IGAPI_chan_exec(c igapi.IGAPIClient) {
|
|
||||||
for id := range IGAPI_CHAN {
|
|
||||||
if id.ACT_TYPE == 0 {
|
|
||||||
c.Upload(context.Background(), &igapi.Request{Id: int64(id.ID)})
|
|
||||||
}
|
|
||||||
if id.ACT_TYPE == 1 {
|
|
||||||
c.Delete(context.Background(), &igapi.Request{Id: int64(id.ID)})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
6
justfile
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
fake:
|
||||||
|
POSTGRES_URL="postgres://test:test@192.168.50.14:5432/posts" bun run ./tools/gen_fake.ts
|
||||||
|
fetch_post:
|
||||||
|
POSTGRES_URL="postgres://test:test@192.168.50.14:5432/posts" bun run ./tools/post_db.ts
|
||||||
|
create_schema:
|
||||||
|
POSTGRES_URL="postgres://test:test@192.168.50.14:5432/posts" bun run ./tools/create_schema.ts
|
7
next.config.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
/* config options here */
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
32
nimdb/db.go
|
@ -1,32 +0,0 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// sqlc v1.29.0
|
|
||||||
|
|
||||||
package nimdb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5"
|
|
||||||
"github.com/jackc/pgx/v5/pgconn"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DBTX interface {
|
|
||||||
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
|
|
||||||
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
|
|
||||||
QueryRow(context.Context, string, ...interface{}) pgx.Row
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(db DBTX) *Queries {
|
|
||||||
return &Queries{db: db}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Queries struct {
|
|
||||||
db DBTX
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
|
|
||||||
return &Queries{
|
|
||||||
db: tx,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// sqlc v1.29.0
|
|
||||||
|
|
||||||
package nimdb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql/driver"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PostPhase string
|
|
||||||
|
|
||||||
const (
|
|
||||||
PostPhasePending PostPhase = "pending"
|
|
||||||
PostPhaseRejected PostPhase = "rejected"
|
|
||||||
PostPhaseAdminRejected PostPhase = "admin_rejected"
|
|
||||||
PostPhaseOk PostPhase = "ok"
|
|
||||||
PostPhaseDeleted PostPhase = "deleted"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (e *PostPhase) Scan(src interface{}) error {
|
|
||||||
switch s := src.(type) {
|
|
||||||
case []byte:
|
|
||||||
*e = PostPhase(s)
|
|
||||||
case string:
|
|
||||||
*e = PostPhase(s)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported scan type for PostPhase: %T", src)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type NullPostPhase struct {
|
|
||||||
PostPhase PostPhase `json:"post_phase"`
|
|
||||||
Valid bool `json:"valid"` // Valid is true if PostPhase is not NULL
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan implements the Scanner interface.
|
|
||||||
func (ns *NullPostPhase) Scan(value interface{}) error {
|
|
||||||
if value == nil {
|
|
||||||
ns.PostPhase, ns.Valid = "", false
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
ns.Valid = true
|
|
||||||
return ns.PostPhase.Scan(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value implements the driver Valuer interface.
|
|
||||||
func (ns NullPostPhase) Value() (driver.Value, error) {
|
|
||||||
if !ns.Valid {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return string(ns.PostPhase), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Admin struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
Totp string `json:"totp"`
|
|
||||||
Super bool `json:"super"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Comment struct {
|
|
||||||
ID int32 `json:"id"`
|
|
||||||
PostID int32 `json:"post_id"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
Signing pgtype.Text `json:"signing"`
|
|
||||||
Hash string `json:"hash"`
|
|
||||||
PostAt pgtype.Timestamptz `json:"post_at"`
|
|
||||||
Heart int32 `json:"heart"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Medium struct {
|
|
||||||
Url string `json:"url"`
|
|
||||||
PostID pgtype.Int4 `json:"post_id"`
|
|
||||||
CommentID pgtype.Int4 `json:"comment_id"`
|
|
||||||
Visible pgtype.Bool `json:"visible"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Post struct {
|
|
||||||
ID int32 `json:"id"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
Signing pgtype.Text `json:"signing"`
|
|
||||||
Hash string `json:"hash"`
|
|
||||||
PostAt pgtype.Timestamptz `json:"post_at"`
|
|
||||||
Heart int32 `json:"heart"`
|
|
||||||
Phase PostPhase `json:"phase"`
|
|
||||||
Igid pgtype.Text `json:"igid"`
|
|
||||||
}
|
|
|
@ -1,490 +0,0 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// sqlc v1.29.0
|
|
||||||
// source: query.sql
|
|
||||||
|
|
||||||
package nimdb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
|
||||||
)
|
|
||||||
|
|
||||||
const addCommentHeart = `-- name: AddCommentHeart :one
|
|
||||||
UPDATE comment SET heart = heart + 1 WHERE id=$1 RETURNING heart
|
|
||||||
`
|
|
||||||
|
|
||||||
func (q *Queries) AddCommentHeart(ctx context.Context, id int32) (int32, error) {
|
|
||||||
row := q.db.QueryRow(ctx, addCommentHeart, id)
|
|
||||||
var heart int32
|
|
||||||
err := row.Scan(&heart)
|
|
||||||
return heart, err
|
|
||||||
}
|
|
||||||
|
|
||||||
const addPostHeart = `-- name: AddPostHeart :one
|
|
||||||
UPDATE posts SET heart = heart + 1 WHERE id=$1 RETURNING heart
|
|
||||||
`
|
|
||||||
|
|
||||||
func (q *Queries) AddPostHeart(ctx context.Context, id int32) (int32, error) {
|
|
||||||
row := q.db.QueryRow(ctx, addPostHeart, id)
|
|
||||||
var heart int32
|
|
||||||
err := row.Scan(&heart)
|
|
||||||
return heart, err
|
|
||||||
}
|
|
||||||
|
|
||||||
const adminCreateAccount = `-- name: AdminCreateAccount :exec
|
|
||||||
INSERT INTO admin (username, password, totp) VALUES ($1, $2,$3)
|
|
||||||
`
|
|
||||||
|
|
||||||
type AdminCreateAccountParams struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
Totp string `json:"totp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) AdminCreateAccount(ctx context.Context, arg AdminCreateAccountParams) error {
|
|
||||||
_, err := q.db.Exec(ctx, adminCreateAccount, arg.Username, arg.Password, arg.Totp)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
const adminGetMedia = `-- name: AdminGetMedia :many
|
|
||||||
SELECT url from media WHERE post_id = $1
|
|
||||||
`
|
|
||||||
|
|
||||||
func (q *Queries) AdminGetMedia(ctx context.Context, postID pgtype.Int4) ([]string, error) {
|
|
||||||
rows, err := q.db.Query(ctx, adminGetMedia, postID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
var items []string
|
|
||||||
for rows.Next() {
|
|
||||||
var url string
|
|
||||||
if err := rows.Scan(&url); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
items = append(items, url)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const adminGetPost = `-- name: AdminGetPost :many
|
|
||||||
SELECT id, content, signing, post_at FROM posts WHERE phase = 'pending' ORDER BY id DESC LIMIT 100
|
|
||||||
`
|
|
||||||
|
|
||||||
type AdminGetPostRow struct {
|
|
||||||
ID int32 `json:"id"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
Signing pgtype.Text `json:"signing"`
|
|
||||||
PostAt pgtype.Timestamptz `json:"post_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) AdminGetPost(ctx context.Context) ([]AdminGetPostRow, error) {
|
|
||||||
rows, err := q.db.Query(ctx, adminGetPost)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
var items []AdminGetPostRow
|
|
||||||
for rows.Next() {
|
|
||||||
var i AdminGetPostRow
|
|
||||||
if err := rows.Scan(
|
|
||||||
&i.ID,
|
|
||||||
&i.Content,
|
|
||||||
&i.Signing,
|
|
||||||
&i.PostAt,
|
|
||||||
); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
items = append(items, i)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const adminLoginGetTOTP = `-- name: AdminLoginGetTOTP :one
|
|
||||||
SELECT totp, super FROM admin WHERE username = $1 AND password = $2
|
|
||||||
`
|
|
||||||
|
|
||||||
type AdminLoginGetTOTPParams struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AdminLoginGetTOTPRow struct {
|
|
||||||
Totp string `json:"totp"`
|
|
||||||
Super bool `json:"super"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) AdminLoginGetTOTP(ctx context.Context, arg AdminLoginGetTOTPParams) (AdminLoginGetTOTPRow, error) {
|
|
||||||
row := q.db.QueryRow(ctx, adminLoginGetTOTP, arg.Username, arg.Password)
|
|
||||||
var i AdminLoginGetTOTPRow
|
|
||||||
err := row.Scan(&i.Totp, &i.Super)
|
|
||||||
return i, err
|
|
||||||
}
|
|
||||||
|
|
||||||
const adminUpdateImage = `-- name: AdminUpdateImage :exec
|
|
||||||
UPDATE media SET visible = true WHERE post_id=$1
|
|
||||||
`
|
|
||||||
|
|
||||||
func (q *Queries) AdminUpdateImage(ctx context.Context, postID pgtype.Int4) error {
|
|
||||||
_, err := q.db.Exec(ctx, adminUpdateImage, postID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
const adminVerify = `-- name: AdminVerify :one
|
|
||||||
UPDATE posts SET phase = $1 WHERE id=$2 AND phase = 'pending' RETURNING id
|
|
||||||
`
|
|
||||||
|
|
||||||
type AdminVerifyParams struct {
|
|
||||||
Phase PostPhase `json:"phase"`
|
|
||||||
ID int32 `json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) AdminVerify(ctx context.Context, arg AdminVerifyParams) (int32, error) {
|
|
||||||
row := q.db.QueryRow(ctx, adminVerify, arg.Phase, arg.ID)
|
|
||||||
var id int32
|
|
||||||
err := row.Scan(&id)
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
|
|
||||||
const deletePost = `-- name: DeletePost :exec
|
|
||||||
UPDATE posts SET phase = 'deleted' WHERE id = $1 AND hash=$2
|
|
||||||
`
|
|
||||||
|
|
||||||
type DeletePostParams struct {
|
|
||||||
ID int32 `json:"id"`
|
|
||||||
Hash string `json:"hash"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) DeletePost(ctx context.Context, arg DeletePostParams) error {
|
|
||||||
_, err := q.db.Exec(ctx, deletePost, arg.ID, arg.Hash)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
const getComment = `-- name: GetComment :many
|
|
||||||
SELECT id, content, signing, post_at
|
|
||||||
FROM comment
|
|
||||||
WHERE phase = 'ok' AND post_id = $1
|
|
||||||
ORDER BY id DESC
|
|
||||||
LIMIT $2
|
|
||||||
`
|
|
||||||
|
|
||||||
type GetCommentParams struct {
|
|
||||||
PostID int32 `json:"post_id"`
|
|
||||||
Limit int32 `json:"limit"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetCommentRow struct {
|
|
||||||
ID int32 `json:"id"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
Signing pgtype.Text `json:"signing"`
|
|
||||||
PostAt pgtype.Timestamptz `json:"post_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) GetComment(ctx context.Context, arg GetCommentParams) ([]GetCommentRow, error) {
|
|
||||||
rows, err := q.db.Query(ctx, getComment, arg.PostID, arg.Limit)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
var items []GetCommentRow
|
|
||||||
for rows.Next() {
|
|
||||||
var i GetCommentRow
|
|
||||||
if err := rows.Scan(
|
|
||||||
&i.ID,
|
|
||||||
&i.Content,
|
|
||||||
&i.Signing,
|
|
||||||
&i.PostAt,
|
|
||||||
); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
items = append(items, i)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const getCommentWithCursor = `-- name: GetCommentWithCursor :many
|
|
||||||
SELECT id, content, signing, post_at
|
|
||||||
FROM comment
|
|
||||||
WHERE id <= $1 AND phase = 'ok' AND post_id = $2
|
|
||||||
ORDER BY id DESC
|
|
||||||
LIMIT $3
|
|
||||||
`
|
|
||||||
|
|
||||||
type GetCommentWithCursorParams struct {
|
|
||||||
ID int32 `json:"id"`
|
|
||||||
PostID int32 `json:"post_id"`
|
|
||||||
Limit int32 `json:"limit"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetCommentWithCursorRow struct {
|
|
||||||
ID int32 `json:"id"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
Signing pgtype.Text `json:"signing"`
|
|
||||||
PostAt pgtype.Timestamptz `json:"post_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) GetCommentWithCursor(ctx context.Context, arg GetCommentWithCursorParams) ([]GetCommentWithCursorRow, error) {
|
|
||||||
rows, err := q.db.Query(ctx, getCommentWithCursor, arg.ID, arg.PostID, arg.Limit)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
var items []GetCommentWithCursorRow
|
|
||||||
for rows.Next() {
|
|
||||||
var i GetCommentWithCursorRow
|
|
||||||
if err := rows.Scan(
|
|
||||||
&i.ID,
|
|
||||||
&i.Content,
|
|
||||||
&i.Signing,
|
|
||||||
&i.PostAt,
|
|
||||||
); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
items = append(items, i)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const getPost = `-- name: GetPost :many
|
|
||||||
SELECT id, content, signing, post_at, heart, igid, array_agg(url) as enclosure
|
|
||||||
FROM posts
|
|
||||||
LEFT JOIN media ON post_id is not null AND post_id = id AND visible
|
|
||||||
WHERE phase = 'ok'
|
|
||||||
GROUP BY id
|
|
||||||
ORDER BY id DESC
|
|
||||||
LIMIT $1
|
|
||||||
`
|
|
||||||
|
|
||||||
type GetPostRow struct {
|
|
||||||
ID int32 `json:"id"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
Signing pgtype.Text `json:"signing"`
|
|
||||||
PostAt pgtype.Timestamptz `json:"post_at"`
|
|
||||||
Heart int32 `json:"heart"`
|
|
||||||
Igid pgtype.Text `json:"igid"`
|
|
||||||
Enclosure interface{} `json:"enclosure"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) GetPost(ctx context.Context, limit int32) ([]GetPostRow, error) {
|
|
||||||
rows, err := q.db.Query(ctx, getPost, limit)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
var items []GetPostRow
|
|
||||||
for rows.Next() {
|
|
||||||
var i GetPostRow
|
|
||||||
if err := rows.Scan(
|
|
||||||
&i.ID,
|
|
||||||
&i.Content,
|
|
||||||
&i.Signing,
|
|
||||||
&i.PostAt,
|
|
||||||
&i.Heart,
|
|
||||||
&i.Igid,
|
|
||||||
&i.Enclosure,
|
|
||||||
); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
items = append(items, i)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const getPostWithCursor = `-- name: GetPostWithCursor :many
|
|
||||||
SELECT id, content, signing, post_at, heart, igid, array_agg(url) as enclosure
|
|
||||||
FROM posts
|
|
||||||
LEFT JOIN media ON post_id is not null AND post_id = id AND visible
|
|
||||||
WHERE id <= $1 AND phase = 'ok'
|
|
||||||
GROUP BY id
|
|
||||||
ORDER BY id DESC
|
|
||||||
LIMIT $2
|
|
||||||
`
|
|
||||||
|
|
||||||
type GetPostWithCursorParams struct {
|
|
||||||
ID int32 `json:"id"`
|
|
||||||
Limit int32 `json:"limit"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetPostWithCursorRow struct {
|
|
||||||
ID int32 `json:"id"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
Signing pgtype.Text `json:"signing"`
|
|
||||||
PostAt pgtype.Timestamptz `json:"post_at"`
|
|
||||||
Heart int32 `json:"heart"`
|
|
||||||
Igid pgtype.Text `json:"igid"`
|
|
||||||
Enclosure interface{} `json:"enclosure"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) GetPostWithCursor(ctx context.Context, arg GetPostWithCursorParams) ([]GetPostWithCursorRow, error) {
|
|
||||||
rows, err := q.db.Query(ctx, getPostWithCursor, arg.ID, arg.Limit)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
var items []GetPostWithCursorRow
|
|
||||||
for rows.Next() {
|
|
||||||
var i GetPostWithCursorRow
|
|
||||||
if err := rows.Scan(
|
|
||||||
&i.ID,
|
|
||||||
&i.Content,
|
|
||||||
&i.Signing,
|
|
||||||
&i.PostAt,
|
|
||||||
&i.Heart,
|
|
||||||
&i.Igid,
|
|
||||||
&i.Enclosure,
|
|
||||||
); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
items = append(items, i)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const insertCommentImage = `-- name: InsertCommentImage :exec
|
|
||||||
INSERT INTO media (url, comment_id) VALUES ($1, $2)
|
|
||||||
`
|
|
||||||
|
|
||||||
type InsertCommentImageParams struct {
|
|
||||||
Url string `json:"url"`
|
|
||||||
CommentID pgtype.Int4 `json:"comment_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) InsertCommentImage(ctx context.Context, arg InsertCommentImageParams) error {
|
|
||||||
_, err := q.db.Exec(ctx, insertCommentImage, arg.Url, arg.CommentID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
const insertPost = `-- name: InsertPost :one
|
|
||||||
INSERT INTO posts (content,signing,hash)
|
|
||||||
VALUES ($1, $2 ,$3)
|
|
||||||
RETURNING id,hash
|
|
||||||
`
|
|
||||||
|
|
||||||
type InsertPostParams struct {
|
|
||||||
Content string `json:"content"`
|
|
||||||
Signing pgtype.Text `json:"signing"`
|
|
||||||
Hash string `json:"hash"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type InsertPostRow struct {
|
|
||||||
ID int32 `json:"id"`
|
|
||||||
Hash string `json:"hash"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) InsertPost(ctx context.Context, arg InsertPostParams) (InsertPostRow, error) {
|
|
||||||
row := q.db.QueryRow(ctx, insertPost, arg.Content, arg.Signing, arg.Hash)
|
|
||||||
var i InsertPostRow
|
|
||||||
err := row.Scan(&i.ID, &i.Hash)
|
|
||||||
return i, err
|
|
||||||
}
|
|
||||||
|
|
||||||
const insertPostImage = `-- name: InsertPostImage :exec
|
|
||||||
INSERT INTO media (url, post_id) VALUES ($1, $2)
|
|
||||||
`
|
|
||||||
|
|
||||||
type InsertPostImageParams struct {
|
|
||||||
Url string `json:"url"`
|
|
||||||
PostID pgtype.Int4 `json:"post_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) InsertPostImage(ctx context.Context, arg InsertPostImageParams) error {
|
|
||||||
_, err := q.db.Exec(ctx, insertPostImage, arg.Url, arg.PostID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
const invalidateMedia = `-- name: InvalidateMedia :exec
|
|
||||||
UPDATE media SET visible = false WHERE post_id = $1
|
|
||||||
`
|
|
||||||
|
|
||||||
func (q *Queries) InvalidateMedia(ctx context.Context, postID pgtype.Int4) error {
|
|
||||||
_, err := q.db.Exec(ctx, invalidateMedia, postID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
const superAdminGetPost = `-- name: SuperAdminGetPost :many
|
|
||||||
SELECT id, content, signing, post_at, phase FROM posts WHERE phase = 'pending' OR phase = 'rejected' ORDER BY id DESC LIMIT 100
|
|
||||||
`
|
|
||||||
|
|
||||||
type SuperAdminGetPostRow struct {
|
|
||||||
ID int32 `json:"id"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
Signing pgtype.Text `json:"signing"`
|
|
||||||
PostAt pgtype.Timestamptz `json:"post_at"`
|
|
||||||
Phase PostPhase `json:"phase"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) SuperAdminGetPost(ctx context.Context) ([]SuperAdminGetPostRow, error) {
|
|
||||||
rows, err := q.db.Query(ctx, superAdminGetPost)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
var items []SuperAdminGetPostRow
|
|
||||||
for rows.Next() {
|
|
||||||
var i SuperAdminGetPostRow
|
|
||||||
if err := rows.Scan(
|
|
||||||
&i.ID,
|
|
||||||
&i.Content,
|
|
||||||
&i.Signing,
|
|
||||||
&i.PostAt,
|
|
||||||
&i.Phase,
|
|
||||||
); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
items = append(items, i)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const superAdminVerify = `-- name: SuperAdminVerify :one
|
|
||||||
UPDATE posts SET phase = $1 WHERE id=$2 AND (phase = 'pending' OR phase = 'rejected') RETURNING id
|
|
||||||
`
|
|
||||||
|
|
||||||
type SuperAdminVerifyParams struct {
|
|
||||||
Phase PostPhase `json:"phase"`
|
|
||||||
ID int32 `json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) SuperAdminVerify(ctx context.Context, arg SuperAdminVerifyParams) (int32, error) {
|
|
||||||
row := q.db.QueryRow(ctx, superAdminVerify, arg.Phase, arg.ID)
|
|
||||||
var id int32
|
|
||||||
err := row.Scan(&id)
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatePostIGID = `-- name: UpdatePostIGID :exec
|
|
||||||
UPDATE posts set igid = $1 WHERE id = $2
|
|
||||||
`
|
|
||||||
|
|
||||||
type UpdatePostIGIDParams struct {
|
|
||||||
Igid pgtype.Text `json:"igid"`
|
|
||||||
ID int32 `json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) UpdatePostIGID(ctx context.Context, arg UpdatePostIGIDParams) error {
|
|
||||||
_, err := q.db.Exec(ctx, updatePostIGID, arg.Igid, arg.ID)
|
|
||||||
return err
|
|
||||||
}
|
|
26
package.json
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"name": "nim",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev --turbopack",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@faker-js/faker": "^9.7.0",
|
||||||
|
"bun-types": "^1.2.9",
|
||||||
|
"next": "15.3.0",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5",
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@types/react-dom": "^19",
|
||||||
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"tailwindcss": "^4"
|
||||||
|
}
|
||||||
|
}
|
5
postcss.config.mjs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
const config = {
|
||||||
|
plugins: ["@tailwindcss/postcss"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
1
public/file.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 391 B |
1
public/globe.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
After Width: | Height: | Size: 1 KiB |
1
public/next.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
1
public/vercel.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
After Width: | Height: | Size: 128 B |
1
public/window.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
After Width: | Height: | Size: 385 B |
129
server.go
|
@ -1,129 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
jwtware "github.com/gofiber/contrib/jwt"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
|
||||||
"github.com/gofiber/fiber/v2/middleware/limiter"
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
|
||||||
"nim.jasinco.work/app/internal"
|
|
||||||
"nim.jasinco.work/app/internal/handlers"
|
|
||||||
"nim.jasinco.work/app/nimdb"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
err := internal.ReadFromENV()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
dbpool, err := pgxpool.New(context.Background(), internal.POSTGRES_URL)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Failed to open connection to db")
|
|
||||||
}
|
|
||||||
defer dbpool.Close()
|
|
||||||
internal.POOL = dbpool
|
|
||||||
internal.NIMDB = nimdb.New(dbpool)
|
|
||||||
|
|
||||||
if internal.IGAPI_ACTIVATE {
|
|
||||||
conn, client := internal.IGAPI_establish()
|
|
||||||
defer conn.Close()
|
|
||||||
go internal.IGAPI_chan_exec(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
app := fiber.New(fiber.Config{Prefork: internal.PREFORK})
|
|
||||||
app.Static("/", "./static/webpage/")
|
|
||||||
|
|
||||||
app.Use(limiter.New(limiter.Config{
|
|
||||||
Next: func(c *fiber.Ctx) bool {
|
|
||||||
return c.IP() == "127.0.0.1"
|
|
||||||
},
|
|
||||||
Max: 50,
|
|
||||||
Expiration: 30 * time.Second,
|
|
||||||
KeyGenerator: func(c *fiber.Ctx) string {
|
|
||||||
return c.Get("x-forwarded-for")
|
|
||||||
},
|
|
||||||
LimitReached: func(c *fiber.Ctx) error {
|
|
||||||
return c.SendFile("./toofast.html")
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
if len(internal.CORS_ALLOW) > 0 {
|
|
||||||
app.Use(cors.New(cors.Config{
|
|
||||||
AllowOrigins: internal.CORS_ALLOW,
|
|
||||||
AllowCredentials: true,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
app.Get("/api/post", handlers.Fetch_post)
|
|
||||||
app.Post("/api/post", handlers.Insert_Post)
|
|
||||||
app.Delete("/api/post", handlers.Delete_post)
|
|
||||||
app.Get("/api/heart", handlers.Add_heart)
|
|
||||||
app.Static("/static", "./static/")
|
|
||||||
|
|
||||||
app.Get("/admin", func(c *fiber.Ctx) error {
|
|
||||||
return c.Redirect("/admin/login")
|
|
||||||
})
|
|
||||||
app.Post("/api/admin/login", func(c *fiber.Ctx) error {
|
|
||||||
token, err := handlers.Admin_Login_JWT(c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return c.JSON(fiber.Map{"token": *token})
|
|
||||||
})
|
|
||||||
app.Post("/admin/login", func(c *fiber.Ctx) error {
|
|
||||||
_, err := handlers.Admin_Login_JWT(c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return c.Redirect("/admin/panel")
|
|
||||||
})
|
|
||||||
app.Static("/admin/login", "./admin/login", fiber.Static{Index: "index.html"})
|
|
||||||
app.Use(jwtware.New(jwtware.Config{
|
|
||||||
SigningKey: jwtware.SigningKey{Key: []byte(internal.JWT_SECRET)},
|
|
||||||
TokenLookup: "cookie:token",
|
|
||||||
ErrorHandler: func(ctx *fiber.Ctx, err error) error {
|
|
||||||
webpanel, regxerr := regexp.Match("^/admin/.*", []byte(ctx.Path()))
|
|
||||||
if regxerr == nil && webpanel {
|
|
||||||
return ctx.Redirect("/admin/login")
|
|
||||||
} else if regxerr != nil {
|
|
||||||
log.Printf("RegErr %s\n", regxerr.Error())
|
|
||||||
}
|
|
||||||
if !strings.ContainsAny(ctx.Path(), "admin") {
|
|
||||||
return ctx.SendStatus(404)
|
|
||||||
}
|
|
||||||
if err == jwtware.ErrJWTMissingOrMalformed {
|
|
||||||
return ctx.Status(400).SendString(err.Error())
|
|
||||||
}
|
|
||||||
if err == jwtware.ErrJWTAlg {
|
|
||||||
return ctx.Status(401).SendString(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
app.Static("/admin/panel", "./admin/panel", fiber.Static{Index: "index.html"})
|
|
||||||
app.Use("/admin/create", func(c *fiber.Ctx) error {
|
|
||||||
user := c.Locals("user").(*jwt.Token)
|
|
||||||
claims := user.Claims.(jwt.MapClaims)
|
|
||||||
super := claims["admin"].(bool)
|
|
||||||
if !super {
|
|
||||||
return c.SendStatus(fiber.StatusNotFound)
|
|
||||||
}
|
|
||||||
return c.Next()
|
|
||||||
})
|
|
||||||
app.Static("/admin/create", "./admin/create", fiber.Static{Index: "index.html"})
|
|
||||||
|
|
||||||
app.Get("/api/admin/new_totp", handlers.Admin_new_totp_code)
|
|
||||||
app.Get("/api/admin/fetch_post", handlers.Admin_Fetch_Post)
|
|
||||||
app.Put("/api/admin/verify_post", handlers.AdminVerify)
|
|
||||||
app.Post("/api/admin/create", handlers.Admin_create)
|
|
||||||
|
|
||||||
log.Panic(app.Listen(":3000"))
|
|
||||||
}
|
|
15
services/docker-compose.yml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:17.4-alpine3.21
|
||||||
|
restart: always
|
||||||
|
shm_size: 128mb
|
||||||
|
environment:
|
||||||
|
- POSTGRES_PASSWORD=test
|
||||||
|
- POSTGRES_USER=test
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
adminer:
|
||||||
|
image: adminer
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
|
@ -1,82 +0,0 @@
|
||||||
-- name: GetPost :many
|
|
||||||
SELECT id, content, signing, post_at, heart, igid, array_agg(url) as enclosure
|
|
||||||
FROM posts
|
|
||||||
LEFT JOIN media ON post_id is not null AND post_id = id AND visible
|
|
||||||
WHERE phase = 'ok'
|
|
||||||
GROUP BY id
|
|
||||||
ORDER BY id DESC
|
|
||||||
LIMIT $1;
|
|
||||||
|
|
||||||
-- name: GetPostWithCursor :many
|
|
||||||
SELECT id, content, signing, post_at, heart, igid, array_agg(url) as enclosure
|
|
||||||
FROM posts
|
|
||||||
LEFT JOIN media ON post_id is not null AND post_id = id AND visible
|
|
||||||
WHERE id <= $1 AND phase = 'ok'
|
|
||||||
GROUP BY id
|
|
||||||
ORDER BY id DESC
|
|
||||||
LIMIT $2;
|
|
||||||
|
|
||||||
-- name: InsertPost :one
|
|
||||||
INSERT INTO posts (content,signing,hash)
|
|
||||||
VALUES ($1, $2 ,$3)
|
|
||||||
RETURNING id,hash;
|
|
||||||
|
|
||||||
-- name: UpdatePostIGID :exec
|
|
||||||
UPDATE posts set igid = $1 WHERE id = $2;
|
|
||||||
|
|
||||||
-- name: DeletePost :exec
|
|
||||||
UPDATE posts SET phase = 'deleted' WHERE id = $1 AND hash=$2;
|
|
||||||
|
|
||||||
-- name: InvalidateMedia :exec
|
|
||||||
UPDATE media SET visible = false WHERE post_id = $1;
|
|
||||||
|
|
||||||
-- name: GetComment :many
|
|
||||||
SELECT id, content, signing, post_at
|
|
||||||
FROM comment
|
|
||||||
WHERE phase = 'ok' AND post_id = $1
|
|
||||||
ORDER BY id DESC
|
|
||||||
LIMIT $2;
|
|
||||||
|
|
||||||
-- name: GetCommentWithCursor :many
|
|
||||||
SELECT id, content, signing, post_at
|
|
||||||
FROM comment
|
|
||||||
WHERE id <= $1 AND phase = 'ok' AND post_id = $2
|
|
||||||
ORDER BY id DESC
|
|
||||||
LIMIT $3;
|
|
||||||
|
|
||||||
|
|
||||||
-- name: InsertPostImage :exec
|
|
||||||
INSERT INTO media (url, post_id) VALUES ($1, $2);
|
|
||||||
|
|
||||||
-- name: InsertCommentImage :exec
|
|
||||||
INSERT INTO media (url, comment_id) VALUES ($1, $2);
|
|
||||||
|
|
||||||
-- name: AddPostHeart :one
|
|
||||||
UPDATE posts SET heart = heart + 1 WHERE id=$1 RETURNING heart;
|
|
||||||
|
|
||||||
-- name: AddCommentHeart :one
|
|
||||||
UPDATE comment SET heart = heart + 1 WHERE id=$1 RETURNING heart;
|
|
||||||
|
|
||||||
-- name: AdminGetPost :many
|
|
||||||
SELECT id, content, signing, post_at FROM posts WHERE phase = 'pending' ORDER BY id DESC LIMIT 100;
|
|
||||||
|
|
||||||
-- name: SuperAdminGetPost :many
|
|
||||||
SELECT id, content, signing, post_at, phase FROM posts WHERE phase = 'pending' OR phase = 'rejected' ORDER BY id DESC LIMIT 100;
|
|
||||||
|
|
||||||
-- name: AdminGetMedia :many
|
|
||||||
SELECT url from media WHERE post_id = $1;
|
|
||||||
|
|
||||||
-- name: AdminVerify :one
|
|
||||||
UPDATE posts SET phase = $1 WHERE id=$2 AND phase = 'pending' RETURNING id;
|
|
||||||
|
|
||||||
-- name: SuperAdminVerify :one
|
|
||||||
UPDATE posts SET phase = $1 WHERE id=$2 AND (phase = 'pending' OR phase = 'rejected') RETURNING id;
|
|
||||||
|
|
||||||
-- name: AdminUpdateImage :exec
|
|
||||||
UPDATE media SET visible = true WHERE post_id=$1;
|
|
||||||
|
|
||||||
-- name: AdminLoginGetTOTP :one
|
|
||||||
SELECT totp, super FROM admin WHERE username = $1 AND password = $2;
|
|
||||||
|
|
||||||
-- name: AdminCreateAccount :exec
|
|
||||||
INSERT INTO admin (username, password, totp) VALUES ($1, $2,$3);
|
|
|
@ -1,37 +0,0 @@
|
||||||
CREATE TYPE post_phase AS ENUM('pending', 'rejected', 'admin_rejected','ok', 'deleted');
|
|
||||||
|
|
||||||
CREATE TABLE posts (
|
|
||||||
id serial PRIMARY KEY,
|
|
||||||
content varchar(500) not null,
|
|
||||||
signing varchar(20),
|
|
||||||
hash char(64) UNIQUE NOT NULL,
|
|
||||||
post_at timestamp with time zone DEFAULT now(),
|
|
||||||
heart integer NOT NULL DEFAULT 0,
|
|
||||||
phase post_phase NOT NULL DEFAULT 'pending',
|
|
||||||
igid varchar(22)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE comment (
|
|
||||||
id serial PRIMARY KEY,
|
|
||||||
post_id integer not null REFERENCES posts (id),
|
|
||||||
content varchar(200) not null,
|
|
||||||
signing varchar(20),
|
|
||||||
hash char(64) UNIQUE NOT NULL,
|
|
||||||
post_at timestamp with time zone DEFAULT now(),
|
|
||||||
heart integer NOT NULL DEFAULT 0
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE media (
|
|
||||||
url varchar(59) NOT NULL UNIQUE,
|
|
||||||
post_id integer REFERENCES posts (id),
|
|
||||||
comment_id integer REFERENCES comment (id),
|
|
||||||
visible boolean DEFAULT false,
|
|
||||||
CONSTRAINT uni_id CHECK ((post_id IS NULL) <> (comment_id IS NULL))
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE admin (
|
|
||||||
username varchar(20) NOT NULL UNIQUE PRIMARY KEY,
|
|
||||||
password char(45) NOT NULL,
|
|
||||||
totp char(33) NOT NULL,
|
|
||||||
super bool NOT NULL DEFAULT false
|
|
||||||
);
|
|
11
sqlc.yml
|
@ -1,11 +0,0 @@
|
||||||
version: "2"
|
|
||||||
sql:
|
|
||||||
- engine: "postgresql"
|
|
||||||
queries: "./sql/query.sql"
|
|
||||||
schema: "./sql/schema.sql"
|
|
||||||
gen:
|
|
||||||
go:
|
|
||||||
package: "nimdb"
|
|
||||||
out: "nimdb"
|
|
||||||
sql_package: "pgx/v5"
|
|
||||||
emit_json_tags: true
|
|
BIN
src/app/favicon.ico
Normal file
After Width: | Height: | Size: 25 KiB |
26
src/app/globals.css
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--background: #ffffff;
|
||||||
|
--foreground: #171717;
|
||||||
|
}
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--color-background: var(--background);
|
||||||
|
--color-foreground: var(--foreground);
|
||||||
|
--font-sans: var(--font-geist-sans);
|
||||||
|
--font-mono: var(--font-geist-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--background: #0a0a0a;
|
||||||
|
--foreground: #ededed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: var(--background);
|
||||||
|
color: var(--foreground);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
}
|
34
src/app/layout.tsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import { Geist, Geist_Mono } from "next/font/google";
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
const geistSans = Geist({
|
||||||
|
variable: "--font-geist-sans",
|
||||||
|
subsets: ["latin"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const geistMono = Geist_Mono({
|
||||||
|
variable: "--font-geist-mono",
|
||||||
|
subsets: ["latin"],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Create Next App",
|
||||||
|
description: "Generated by create next app",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body
|
||||||
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
15
src/app/new_post/route.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { insert_post, NewPost } from "@/db";
|
||||||
|
import { Blob } from "buffer";
|
||||||
|
import type { NextRequest } from "next/server";
|
||||||
|
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
let form = await req.formData();
|
||||||
|
let text = form.get("content")?.toString()
|
||||||
|
if (!text) {
|
||||||
|
return Response.error()
|
||||||
|
}
|
||||||
|
let request: NewPost = { content: text, image: [] };
|
||||||
|
let [id, hash] = await insert_post(request);
|
||||||
|
|
||||||
|
return Response.json({ id: id, hash: hash })
|
||||||
|
}
|
6
src/app/new_user/route.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { NextRequest } from "next/server";
|
||||||
|
|
||||||
|
export function Post(req: NextRequest) {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
14
src/app/page.tsx
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative min-h-screen px-8 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||||
|
<div className="w-full h-5dvh fixed top-0 left-0">
|
||||||
|
<h1 className="text-4xl mt-4 ml-3">匿名中工</h1>
|
||||||
|
</div>
|
||||||
|
<div className="h-[90dvh] w-[85dvw] absolute left-1/2 top-1/2 mt-5 -translate-1/2">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
35
src/app/post.tsx
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
|
||||||
|
import { Suspense } from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { Attachment, MultiMediaType } from "@/db";
|
||||||
|
|
||||||
|
export default function Post(post_content: string, attachments: Attachment[]) {
|
||||||
|
let images = [];
|
||||||
|
let videos = [];
|
||||||
|
attachments.forEach(attachment => {
|
||||||
|
if (attachment.type == MultiMediaType.video) {
|
||||||
|
attachment.urls.forEach(url => {
|
||||||
|
videos.push(
|
||||||
|
<Suspense fallback={<p>加載中</p>}>
|
||||||
|
<video controls preload="none" aria-label="Video player">
|
||||||
|
<source src={url} type={attachment.type.toString()} />
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
</Suspense>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
} else if (attachment.type == MultiMediaType.image) {
|
||||||
|
attachment.urls.forEach(url => {
|
||||||
|
images.push(<Image src={url} alt="Uploaded" width={300} height={200}></Image>)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return (<div className="w-full h-fit">
|
||||||
|
<div>
|
||||||
|
{post_content}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
</div>)
|
||||||
|
}
|
93
src/db.ts
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
import { env, ReservedSQL, CryptoHasher, sql } from 'bun'
|
||||||
|
import { MIMEType } from 'util';
|
||||||
|
|
||||||
|
export enum MultiMediaType {
|
||||||
|
video, image
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Attachment {
|
||||||
|
urls: string[],
|
||||||
|
type: MultiMediaType
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface Post {
|
||||||
|
post: string,
|
||||||
|
post_time: string,
|
||||||
|
hash: string,
|
||||||
|
attachments: Attachment[],
|
||||||
|
}
|
||||||
|
interface SQLPostCast {
|
||||||
|
hash: string, post: string, post_time: string, images: number[]
|
||||||
|
};
|
||||||
|
const SQLPostCast2Post = (obj: SQLPostCast) => {
|
||||||
|
let x: Post =
|
||||||
|
{
|
||||||
|
post: obj.post,
|
||||||
|
hash: obj.hash,
|
||||||
|
post_time: obj.post_time,
|
||||||
|
attachments: []
|
||||||
|
};
|
||||||
|
if (obj.images && obj.images.length > 0) {
|
||||||
|
x.attachments.push(
|
||||||
|
{
|
||||||
|
type: MultiMediaType.image, urls: obj.images.map(img => {
|
||||||
|
return `/img/${obj.hash}_${img}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface NewPost {
|
||||||
|
image: Blob[]
|
||||||
|
content: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function insert_post(post: NewPost): Promise<[number, string]> {
|
||||||
|
let post_hash = new CryptoHasher("sha256");
|
||||||
|
post_hash.update(post.content);
|
||||||
|
post_hash.update(Date.now().toString())
|
||||||
|
let populated = post_hash.digest("base64");
|
||||||
|
let [{ id, hash }] = await sql`INSERT INTO niming.posts (post, hash) VALUES (${post.content}, ${populated}) RETURNING id, hash`
|
||||||
|
return [id, hash]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function init_db() {
|
||||||
|
await sql.begin(async sql => {
|
||||||
|
await sql`CREATE SCHEMA niming`
|
||||||
|
await sql`CREATE TABLE niming.posts (id SERIAL PRIMARY KEY, hash char(44) UNIQUE, post VARCHAR(500), post_time TIMESTAMPTZ DEFAULT now())`
|
||||||
|
await sql`CREATE TABLE niming.images (id INTEGER PRIMARY KEY REFERENCES niming.posts (id), fileid integer[] )`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PostFetcher {
|
||||||
|
conn: Promise<ReservedSQL>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.conn = sql.reserve();
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
await this.conn.then(async e => {
|
||||||
|
await e`DECLARE post_ptr CURSOR WITH HOLD FOR SELECT niming.posts.*, niming.images.fileid
|
||||||
|
AS images FROM niming.posts
|
||||||
|
LEFT JOIN niming.images ON niming.images.hash=niming.posts.hash ORDER BY niming.posts.post_time DESC, niming.posts.hash ASC`
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async postgres_fetch() {
|
||||||
|
let x: SQLPostCast[] = []
|
||||||
|
if (this.conn) {
|
||||||
|
x = await (await this.conn)`FETCH 10 IN post_ptr`;
|
||||||
|
}
|
||||||
|
return x.map(e => {
|
||||||
|
return SQLPostCast2Post(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
3
tools/create_schema.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import { init_db } from "@/db";
|
||||||
|
|
||||||
|
init_db()
|
13
tools/gen_fake.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { insert_post, NewPost } from '@/db';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
let text = faker.string.alpha(20);
|
||||||
|
let x: NewPost = { image: [], content: text };
|
||||||
|
let [p_id, p_hash] = await insert_post(x)
|
||||||
|
console.log(p_id, p_hash)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
POST http://127.0.0.1:3000/api/post
|
|
||||||
[Multipart]
|
|
||||||
content: Test
|
|
||||||
signing: Test
|
|
13
tools/post_db.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { PostFetcher, Post } from "@/db"
|
||||||
|
import { sql } from "bun";
|
||||||
|
|
||||||
|
let x: Post[] = [];
|
||||||
|
const fetcher = new PostFetcher();
|
||||||
|
await fetcher.init()
|
||||||
|
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
x = await fetcher.postgres_fetch()
|
||||||
|
|
||||||
|
console.log(x)
|
||||||
|
|
||||||
|
}
|
BIN
tools/test.png
Before Width: | Height: | Size: 85 KiB |
28
tsconfig.json
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
"types":["bun-types"]
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|