Compare commits
No commits in common. "1d358a9ea9fcc8168a07ea07f144038da6f387c7" and "dc71938bd888613877d38b9e6499789af533e317" have entirely different histories.
1d358a9ea9
...
dc71938bd8
12 changed files with 81 additions and 174 deletions
BIN
bun.lockb
Executable file → Normal file
BIN
bun.lockb
Executable file → Normal file
Binary file not shown.
6
justfile
6
justfile
|
@ -1,6 +0,0 @@
|
||||||
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
|
|
|
@ -9,7 +9,6 @@
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@faker-js/faker": "^9.7.0",
|
|
||||||
"bun-types": "^1.2.9",
|
"bun-types": "^1.2.9",
|
||||||
"next": "15.3.0",
|
"next": "15.3.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
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 })
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { NextRequest } from "next/server";
|
|
||||||
|
|
||||||
export function Post(req: NextRequest) {
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { retrieve_post } from "@/db";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
|
||||||
|
@ -7,7 +8,7 @@ export default function Home() {
|
||||||
<h1 className="text-4xl mt-4 ml-3">匿名中工</h1>
|
<h1 className="text-4xl mt-4 ml-3">匿名中工</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-[90dvh] w-[85dvw] absolute left-1/2 top-1/2 mt-5 -translate-1/2">
|
<div className="h-[90dvh] w-[85dvw] absolute left-1/2 top-1/2 mt-5 -translate-1/2">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,35 +1,35 @@
|
||||||
|
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { Attachment, MultiMediaType } from "@/db";
|
import { Attachment } from "@/db";
|
||||||
|
|
||||||
export default function Post(post_content: string, attachments: Attachment[]) {
|
export default function Post(post_content: string, attachments: Attachment[]) {
|
||||||
let images = [];
|
let images = [];
|
||||||
let videos = [];
|
let videos = [];
|
||||||
attachments.forEach(attachment => {
|
attachments.forEach(attachment => {
|
||||||
if (attachment.type == MultiMediaType.video) {
|
if (attachment.type.type == "video") {
|
||||||
attachment.urls.forEach(url => {
|
attachment.urls.forEach(url => {
|
||||||
videos.push(
|
videos.push(
|
||||||
<Suspense fallback={<p>加載中</p>}>
|
<Suspense fallback={<p>加載中</p>}>
|
||||||
<video controls preload="none" aria-label="Video player">
|
<video controls preload="none" aria-label="Video player">
|
||||||
<source src={url} type={attachment.type.toString()} />
|
<source src={url} type={attachment.type.toString()} />
|
||||||
Your browser does not support the video tag.
|
Your browser does not support the video tag.
|
||||||
</video>
|
</video>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
} else if (attachment.type == MultiMediaType.image) {
|
}else if (attachment.type.type="image"){
|
||||||
attachment.urls.forEach(url => {
|
attachment.urls.forEach(url => {
|
||||||
images.push(<Image src={url} alt="Uploaded" width={300} height={200}></Image>)
|
images.push(<Image src={url} alt="Uploaded" width={300} height={200}></Image>)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return (<div className="w-full h-fit">
|
return (<div className="w-full h-fit">
|
||||||
<div>
|
<div>
|
||||||
{post_content}
|
{post_content}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>)
|
</div>)
|
||||||
}
|
}
|
130
src/db.ts
130
src/db.ts
|
@ -1,93 +1,57 @@
|
||||||
import { env, ReservedSQL, CryptoHasher, sql } from 'bun'
|
import { main } from "bun";
|
||||||
import { MIMEType } from 'util';
|
import { sql } from "bun";
|
||||||
|
import { MIMEType } from "util"
|
||||||
export enum MultiMediaType {
|
|
||||||
video, image
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Attachment {
|
export interface Attachment {
|
||||||
urls: string[],
|
type: MIMEType;
|
||||||
type: MultiMediaType
|
urls: string[];
|
||||||
};
|
}
|
||||||
|
|
||||||
export interface Post {
|
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,
|
content: string,
|
||||||
|
hash: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function insert_post(post: NewPost): Promise<[number, string]> {
|
export async function setup_func() {
|
||||||
let post_hash = new CryptoHasher("sha256");
|
const rows = await sql`
|
||||||
post_hash.update(post.content);
|
CREATE OR REPLACE FUNCTION fetch_post(
|
||||||
post_hash.update(Date.now().toString())
|
OUT p_text VARCHAR(1000),
|
||||||
let populated = post_hash.digest("base64");
|
OUT p_hash CHAR(32),
|
||||||
let [{ id, hash }] = await sql`INSERT INTO niming.posts (post, hash) VALUES (${post.content}, ${populated}) RETURNING id, hash`
|
OUT p_date TIMESTAMP,
|
||||||
return [id, hash]
|
OUT p_pics INT[]
|
||||||
|
)
|
||||||
|
RETURNS SETOF RECORD AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
post_cursor CURSOR FOR
|
||||||
|
SELECT content, hash, date
|
||||||
|
FROM posts;
|
||||||
|
post_record RECORD;
|
||||||
|
BEGIN
|
||||||
|
-- Open cursor
|
||||||
|
OPEN post_cursor;
|
||||||
|
|
||||||
|
-- Fetch rows and return
|
||||||
|
LOOP
|
||||||
|
FETCH NEXT FROM post_cursor INTO post_record;
|
||||||
|
EXIT WHEN NOT FOUND;
|
||||||
|
|
||||||
|
p_text = post_record.content;
|
||||||
|
p_date = post_record.date;
|
||||||
|
p_hash = post_record.hash;
|
||||||
|
RETURN NEXT;
|
||||||
|
END LOOP;
|
||||||
|
|
||||||
|
-- Close cursor
|
||||||
|
CLOSE post_cursor;
|
||||||
|
END;
|
||||||
|
$$
|
||||||
|
LANGUAGE PLPGSQL;`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function init_db() {
|
/* retrieve the latest post with posts table */
|
||||||
await sql.begin(async sql => {
|
export async function retrieve_post(offset: Number) {
|
||||||
await sql`CREATE SCHEMA niming`
|
const res = await sql`SELECT * FROM fetch_post();`
|
||||||
await sql`CREATE TABLE niming.posts (id SERIAL PRIMARY KEY, hash char(44) UNIQUE, post VARCHAR(500), post_time TIMESTAMPTZ DEFAULT now())`
|
console.log(res)
|
||||||
await sql`CREATE TABLE niming.images (id INTEGER PRIMARY KEY REFERENCES niming.posts (id), fileid integer[] )`
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
setup_func()
|
||||||
export class PostFetcher {
|
retrieve_post(0)
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
import { init_db } from "@/db";
|
|
||||||
|
|
||||||
init_db()
|
|
|
@ -1,13 +0,0 @@
|
||||||
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,13 +0,0 @@
|
||||||
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)
|
|
||||||
|
|
||||||
}
|
|
|
@ -8,7 +8,7 @@
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "bundler",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
|
@ -20,8 +20,7 @@
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
},
|
}
|
||||||
"types":["bun-types"]
|
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
|
|
Loading…
Add table
Reference in a new issue