add game
This commit is contained in:
		@@ -0,0 +1,4 @@
 | 
			
		||||
            <Button asChild>
 | 
			
		||||
              <Link href="/post/game">Game</Link>
 | 
			
		||||
            </Button>
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,26 @@
 | 
			
		||||
"use server";
 | 
			
		||||
 | 
			
		||||
import { DID } from "@/lib/data/atproto/did";
 | 
			
		||||
import { getVerifiedHandle } from "@/lib/data/atproto/identity";
 | 
			
		||||
import { putPost } from "@/lib/data/atproto/game";
 | 
			
		||||
import { uncached_doesPostExist } from "@/lib/data/db/post";
 | 
			
		||||
import { DataLayerError } from "@/lib/data/error";
 | 
			
		||||
import { ensureUser } from "@/lib/data/user";
 | 
			
		||||
import { redirect } from "next/navigation";
 | 
			
		||||
 | 
			
		||||
export async function newPostAction(_prevState: unknown) {
 | 
			
		||||
	"use server";
 | 
			
		||||
	const user = await ensureUser();
 | 
			
		||||
	const [handle] = await Promise.all([
 | 
			
		||||
		getVerifiedHandle(user.did),
 | 
			
		||||
	]);
 | 
			
		||||
 | 
			
		||||
	const account = `at://did:plc:4hqjfn7m6n5hno3doamuhgef/ai.syui.game.user/${handle}`;
 | 
			
		||||
	try {
 | 
			
		||||
		const { rkey } = await putPost({ account });
 | 
			
		||||
		redirect(`https://at.syu.is/at/${user.did}/ai.syui.game/self`);
 | 
			
		||||
	} catch (error) {
 | 
			
		||||
		if (!(error instanceof DataLayerError)) throw error;
 | 
			
		||||
		return { error: "Failed to create post" };
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,36 @@
 | 
			
		||||
"use client";
 | 
			
		||||
 | 
			
		||||
import { startTransition, useActionState, useId, useState } from "react";
 | 
			
		||||
import { newPostAction } from "./_action";
 | 
			
		||||
import { Label } from "@/lib/components/ui/label";
 | 
			
		||||
import { Input } from "@/lib/components/ui/input";
 | 
			
		||||
import { Button } from "@/lib/components/ui/button";
 | 
			
		||||
import { Alert, AlertDescription, AlertTitle } from "@/lib/components/ui/alert";
 | 
			
		||||
import { Spinner } from "@/lib/components/ui/spinner";
 | 
			
		||||
import { InputLengthIndicator } from "@/lib/components/input-length-indicator";
 | 
			
		||||
 | 
			
		||||
export function NewPostButton() {
 | 
			
		||||
  const [state, action, isPending] = useActionState(newPostAction, null);
 | 
			
		||||
  const [error, setError] = useState<string | null>(null);
 | 
			
		||||
 | 
			
		||||
  const handleClick = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
      const result = await action({});
 | 
			
		||||
      if (result && 'error' in result) {
 | 
			
		||||
        setError(result.error);
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.error('Error creating post:', e);
 | 
			
		||||
      setError('予期しないエラーが発生しました');
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      <button onClick={handleClick} disabled={isPending}>
 | 
			
		||||
        {isPending ? '投稿中...' : '新規投稿'}
 | 
			
		||||
      </button>
 | 
			
		||||
      {error && <p style={{ color: 'red' }}>{error}</p>}
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,17 @@
 | 
			
		||||
import { Metadata } from "next";
 | 
			
		||||
import { NewPostButton } from "./_client";
 | 
			
		||||
 | 
			
		||||
export const metadata: Metadata = {
 | 
			
		||||
  title: "New post | Frontpage",
 | 
			
		||||
  robots: "noindex, nofollow",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function NewPost() {
 | 
			
		||||
  return (
 | 
			
		||||
    <main className="flex flex-col gap-3">
 | 
			
		||||
      <h2 className="text-3xl font-bold tracking-tight text-gray-900 dark:text-gray-100">
 | 
			
		||||
      </h2>
 | 
			
		||||
      <NewPostButton />
 | 
			
		||||
    </main>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,62 @@
 | 
			
		||||
import "server-only";
 | 
			
		||||
import {
 | 
			
		||||
  atprotoPutRecord,
 | 
			
		||||
  atprotoCreateRecord,
 | 
			
		||||
  atprotoDeleteRecord,
 | 
			
		||||
  atprotoGetRecord,
 | 
			
		||||
} from "./record";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { DataLayerError } from "../error";
 | 
			
		||||
import { DID, getPdsUrl } from "./did";
 | 
			
		||||
 | 
			
		||||
export const PostCollection = "ai.syui.game";
 | 
			
		||||
 | 
			
		||||
export const PostRecord = z.object({
 | 
			
		||||
  account: z.string(),
 | 
			
		||||
  createdAt: z.string(),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export type Post = z.infer<typeof PostRecord>;
 | 
			
		||||
 | 
			
		||||
type PostInput = {
 | 
			
		||||
  account: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export async function putPost({ account }: PostInput) {
 | 
			
		||||
  const record = { account, createdAt: new Date().toISOString() };
 | 
			
		||||
  PostRecord.parse(record);
 | 
			
		||||
 | 
			
		||||
  const result = await atprotoPutRecord({
 | 
			
		||||
    rkey: "self",
 | 
			
		||||
    record,
 | 
			
		||||
    collection: PostCollection,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    rkey: "self",
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function deletePost(rkey: string) {
 | 
			
		||||
  await atprotoDeleteRecord({
 | 
			
		||||
    rkey,
 | 
			
		||||
    collection: PostCollection,
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getPost({ rkey, repo }: { rkey: string; repo: DID }) {
 | 
			
		||||
  const service = await getPdsUrl(repo);
 | 
			
		||||
 | 
			
		||||
  if (!service) {
 | 
			
		||||
    throw new DataLayerError("Failed to get service url");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const { value } = await atprotoGetRecord({
 | 
			
		||||
    serviceEndpoint: service,
 | 
			
		||||
    repo,
 | 
			
		||||
    collection: PostCollection,
 | 
			
		||||
    rkey,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return PostRecord.parse(value);
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user