[feat] solution: SNS 연동 확인용 테스트 게시 기능 추가
This commit is contained in:
parent
d77f2aa14c
commit
921c85df25
@ -30,6 +30,22 @@ async def account(response: Response, user: UserInfo = Depends(IsValidAccessToke
|
||||
return await service.account_state(UUID(user.user_id))
|
||||
|
||||
|
||||
class TestPost(BaseModel):
|
||||
text: str = Field(min_length=1, max_length=500)
|
||||
|
||||
|
||||
@router.post("/test-post")
|
||||
async def test_post(
|
||||
req: TestPost, response: Response, user: UserInfo = Depends(IsValidAccessToken)
|
||||
):
|
||||
"""연동 확인용 즉시 게시 — 승인 없이 바로 연결된 계정으로 올라간다."""
|
||||
private_response(response)
|
||||
try:
|
||||
return await service.test_post(UUID(user.user_id), req.text)
|
||||
except SocialError as ex:
|
||||
raise HTTPException(409, str(ex)) from ex
|
||||
|
||||
|
||||
@router.get("/place/{place_id}")
|
||||
async def list_posts(
|
||||
place_id: UUID, response: Response, user: UserInfo = Depends(IsValidAccessToken)
|
||||
|
||||
@ -149,6 +149,26 @@ def posting_enabled():
|
||||
return accounts.configured() and config.get("SOCIAL_POSTING_ENABLED") == "1"
|
||||
|
||||
|
||||
async def test_post(user_id, text, provider=2):
|
||||
"""사장님이 자기 계정 연결이 실제로 되는지 확인하려고 한 줄 올려 보는 것.
|
||||
|
||||
★ 왜 posting_enabled()·승인 절차를 거치지 않는가 — 그 게이트는 승인 기반
|
||||
"소식 발행" 상품을 계약·해지 안내 정책이 끝나기 전에 열지 않으려는 것이다.
|
||||
여기서 사장님이 자기 계정에 테스트 한 줄을 올리는 것은 그 상품과 무관하다.
|
||||
"""
|
||||
|
||||
async def run(s):
|
||||
await accounts.lock_user(s, user_id, provider)
|
||||
row = await accounts.account(s, user_id, provider)
|
||||
if not row:
|
||||
raise SocialError("ACCOUNT_CONNECTION_REQUIRED")
|
||||
async with httpx.AsyncClient(timeout=20) as client:
|
||||
token = await accounts.get_usable_token(s, row, client)
|
||||
return await adapter(provider).publish(text, token, client=client)
|
||||
|
||||
return await db.transaction(run)
|
||||
|
||||
|
||||
async def create_draft(user_id, place_id, provider=2):
|
||||
async def run(s):
|
||||
place, site, url = await target(s, user_id, place_id)
|
||||
|
||||
@ -23,6 +23,11 @@ export function SocialConnectionCard() {
|
||||
const [state, setState] = useState<SocialState | null>(null);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [testText, setTestText] = useState('');
|
||||
const [testBusy, setTestBusy] = useState(false);
|
||||
const [testError, setTestError] = useState('');
|
||||
const [testPosted, setTestPosted] = useState(false);
|
||||
const [testPermalink, setTestPermalink] = useState<string | null>(null);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
try {
|
||||
@ -64,6 +69,25 @@ export function SocialConnectionCard() {
|
||||
window.location.assign(url);
|
||||
});
|
||||
|
||||
async function testPost() {
|
||||
setTestBusy(true);
|
||||
setTestError('');
|
||||
setTestPosted(false);
|
||||
setTestPermalink(null);
|
||||
try {
|
||||
const result = await socialApi<{id: string; permalink: string | null}>('/test-post', {
|
||||
text: testText,
|
||||
});
|
||||
setTestPosted(true);
|
||||
setTestPermalink(result.permalink);
|
||||
setTestText('');
|
||||
} catch (e) {
|
||||
setTestError(e instanceof Error ? e.message : '요청을 처리하지 못했습니다.');
|
||||
} finally {
|
||||
setTestBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="mb-4 rounded-xl border border-border p-4">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
@ -116,6 +140,58 @@ export function SocialConnectionCard() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{account && !needsReauth && (
|
||||
<div className="mt-3 border-t border-border pt-3">
|
||||
<label htmlFor="social-test-text" className="text-xs font-bold">
|
||||
연동 확인용 테스트 게시
|
||||
</label>
|
||||
<div className="mt-1 flex gap-2">
|
||||
<input
|
||||
id="social-test-text"
|
||||
type="text"
|
||||
value={testText}
|
||||
onChange={(e) => setTestText(e.target.value)}
|
||||
placeholder="이 계정으로 바로 올라갑니다"
|
||||
maxLength={500}
|
||||
className="min-w-0 flex-1 rounded-md border border-border px-2 py-1 text-xs"
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={testBusy || !testText.trim()}
|
||||
onClick={testPost}
|
||||
>
|
||||
{testBusy ? <Loader2 className="size-4 animate-spin" /> : null}
|
||||
<span>테스트 게시</span>
|
||||
</Button>
|
||||
</div>
|
||||
{testPosted && (
|
||||
<p className="mt-1 text-xs">
|
||||
{testPermalink ? (
|
||||
<>
|
||||
게시됐습니다:{' '}
|
||||
<a
|
||||
href={testPermalink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-bold underline underline-offset-4"
|
||||
>
|
||||
확인하기 →
|
||||
</a>
|
||||
</>
|
||||
) : (
|
||||
'게시됐습니다. 잠시 후 계정에서 직접 확인해 주세요.'
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
{testError && (
|
||||
<p role="alert" className="mt-1 text-xs text-destructive">
|
||||
{testError}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 게재가 아직 안 열린 상태를 숨기지 않는다 — 연결만 해 두고 기다리는 것도 사장님의 선택이다. */}
|
||||
{ready && !state.posting_enabled && (
|
||||
<p className="mt-3 text-xs text-muted-foreground">
|
||||
|
||||
@ -19,6 +19,8 @@ const messages: Record<string, string> = {
|
||||
PUBLISH_URL_CHANGED: '홈페이지 주소가 변경되었습니다. 기존 승인으로 게재할 수 없습니다.',
|
||||
APPROVAL_NOT_FOUND: '유효하지 않거나 교체된 승인 링크입니다.',
|
||||
PLACE_NOT_FOUND: '가게를 찾을 수 없습니다.',
|
||||
ACCOUNT_NEEDS_REAUTH: '연결이 만료됐습니다. 다시 연결해 주세요.',
|
||||
TEXT_TOO_LONG: '글이 너무 깁니다.',
|
||||
};
|
||||
export async function socialApi<T>(path: string, data?: unknown, publicAccess = false): Promise<T> {
|
||||
const token = publicAccess ? null : getAccessToken();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user