Compare commits
2 commits
dc71938bd8
...
1d358a9ea9
Author | SHA1 | Date | |
---|---|---|---|
1d358a9ea9 | |||
0745298257 |
12 changed files with 174 additions and 81 deletions
BIN
bun.lockb
Normal file → Executable file
BIN
bun.lockb
Normal file → Executable file
Binary file not shown.
6
justfile
Normal file
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
|
|
@ -9,6 +9,7 @@
|
|||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "^9.7.0",
|
||||
"bun-types": "^1.2.9",
|
||||
"next": "15.3.0",
|
||||
"react": "^19.0.0",
|
||||
|
|
15
src/app/new_post/route.ts
Normal file
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
6
src/app/new_user/route.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import { NextRequest } from "next/server";
|
||||
|
||||
export function Post(req: NextRequest) {
|
||||
|
||||
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
import { retrieve_post } from "@/db";
|
||||
|
||||
export default function Home() {
|
||||
|
||||
|
|
|
@ -1,35 +1,35 @@
|
|||
|
||||
import { Suspense } from "react";
|
||||
import Image from "next/image";
|
||||
import { Attachment } from "@/db";
|
||||
import { Attachment, MultiMediaType } from "@/db";
|
||||
|
||||
export default function Post(post_content: string, attachments: Attachment[]) {
|
||||
let images = [];
|
||||
let videos = [];
|
||||
attachments.forEach(attachment => {
|
||||
if (attachment.type.type == "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.type="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>)
|
||||
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>)
|
||||
}
|
130
src/db.ts
130
src/db.ts
|
@ -1,57 +1,93 @@
|
|||
import { main } from "bun";
|
||||
import { sql } from "bun";
|
||||
import { MIMEType } from "util"
|
||||
import { env, ReservedSQL, CryptoHasher, sql } from 'bun'
|
||||
import { MIMEType } from 'util';
|
||||
|
||||
export enum MultiMediaType {
|
||||
video, image
|
||||
}
|
||||
|
||||
export interface Attachment {
|
||||
type: MIMEType;
|
||||
urls: string[];
|
||||
}
|
||||
urls: string[],
|
||||
type: MultiMediaType
|
||||
};
|
||||
|
||||
export interface Post {
|
||||
content: string,
|
||||
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 async function setup_func() {
|
||||
const rows = await sql`
|
||||
CREATE OR REPLACE FUNCTION fetch_post(
|
||||
OUT p_text VARCHAR(1000),
|
||||
OUT p_hash CHAR(32),
|
||||
OUT p_date TIMESTAMP,
|
||||
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 interface NewPost {
|
||||
image: Blob[]
|
||||
content: string,
|
||||
}
|
||||
|
||||
/* retrieve the latest post with posts table */
|
||||
export async function retrieve_post(offset: Number) {
|
||||
const res = await sql`SELECT * FROM fetch_post();`
|
||||
console.log(res)
|
||||
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]
|
||||
|
||||
}
|
||||
setup_func()
|
||||
retrieve_post(0)
|
||||
|
||||
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
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
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)
|
||||
|
||||
}
|
||||
|
13
tools/post_db.ts
Normal file
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)
|
||||
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
|
@ -20,7 +20,8 @@
|
|||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"types":["bun-types"]
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
|
|
Loading…
Add table
Reference in a new issue