diff --git a/hwe/sammo/API/Vote/AddComment.php b/hwe/sammo/API/Vote/AddComment.php new file mode 100644 index 00000000..03d8a8f9 --- /dev/null +++ b/hwe/sammo/API/Vote/AddComment.php @@ -0,0 +1,59 @@ +args); + $v->rule('required', [ + 'voteID', + 'text', + ])->rule('lengthMin', 'text', 1) + ->rule('int', 'multipleOptions'); + + if (!$v->validate()) { + return $v->errorStr(); + } + return null; + } + + function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag) + { + $voteID = $this->args['voteID']; + $text = $this->args['text']; + + $generalID = $session->generalID; + $general = General::createGeneralObjFromDB($generalID, [], 0); + $generalName = $general->getName(); + $nationID = $general->getNationID(); + $nationName = $general->getStaticNation()['name']; + + $comment = new VoteComment( + voteID: $voteID, + generalID: $generalID, + nationID: $nationID, + nationName: $nationName, + generalName: $generalName, + text: $text, + ); + + $db = DB::db(); + $db->insert('vote_comment', $comment->toArray()); + + return null; + } +} diff --git a/hwe/sammo/API/Vote/GetVoteDetail.php b/hwe/sammo/API/Vote/GetVoteDetail.php new file mode 100644 index 00000000..a1189cfa --- /dev/null +++ b/hwe/sammo/API/Vote/GetVoteDetail.php @@ -0,0 +1,60 @@ +args); + $v->rule('required', 'voteID') + ->rule('integer', 'voteID'); + if (!$v->validate()) { + return $v->errorStr(); + } + $this->args['voteID'] = (int)$this->args['voteID']; + return null; + } + + public function getRequiredSessionMode(): int + { + return static::REQ_LOGIN | static::REQ_READ_ONLY; + } + + public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag) + { + $voteID = $this->args['voteID']; + $db = DB::db(); + + $voteStor = KVStorage::getStorage($db, 'vote'); + $rawVote = $voteStor->getValue("vote_{$voteID}"); + if (!$rawVote) { + return '설문조사가 없습니다.'; + } + $voteInfo = new VoteInfo(...$rawVote); + + + $votes = array_map(fn ($arr) => [Json::decode($arr[0]), $arr[1]], $db->queryAllLists( + 'SELECT selection, count(*) AS cnt FROM vote_result WHERE voteID = %i GROUP BY selection', + $voteID + )); + + $comments = VoteComment::arrayOf($db->query('SELECT * FROM vote_comment WHERE voteID = %i ORDER BY `id` ASC', $voteID)); + + return [ + 'result' => true, + 'voteInfo' => $voteInfo, + 'votes' => $votes, + 'comments' => $comments + ]; + } +} diff --git a/hwe/sammo/API/Vote/GetVoteList.php b/hwe/sammo/API/Vote/GetVoteList.php new file mode 100644 index 00000000..a772fd90 --- /dev/null +++ b/hwe/sammo/API/Vote/GetVoteList.php @@ -0,0 +1,44 @@ +getAll() as $voteKey => $rawVote){ + if(!str_starts_with($voteKey, 'vote_')){ + continue; + } + $voteID = (int)substr($voteKey, 5); + $votes[$voteID] = new VoteInfo(...$rawVote); + } + + return [ + 'result'=>true, + 'votes'=>$votes + ]; + } +} diff --git a/hwe/sammo/API/Vote/NewVote.php b/hwe/sammo/API/Vote/NewVote.php new file mode 100644 index 00000000..2279d205 --- /dev/null +++ b/hwe/sammo/API/Vote/NewVote.php @@ -0,0 +1,110 @@ +args); + $v->rule('required', [ + 'title', + ])->rule('lengthMin', 'title', 1) + ->rule('int', 'multipleOptions') + ->rule('date', 'endDate') + ->rule('stringArray', 'options') + ->rule('boolean', 'keepOldVote'); + + if (!$v->validate()) { + return $v->errorStr(); + } + return null; + } + + function closeOldVote(int $voteID, KVStorage $voteStor) + { + $db = DB::db(); + $voteStor = KVStorage::getStorage($db, 'vote'); + $rawLastVoteInfo = $voteStor->getValue("vote_{$voteID}"); + if (!$rawLastVoteInfo) { + return; + } + $lastVoteInfo = new VoteInfo(...$rawLastVoteInfo); + if ($lastVoteInfo->endDate) { + return; + } + + $lastVoteInfo->endDate = new \DateTimeImmutable();; + $voteStor->setValue("vote_{$voteID}", $lastVoteInfo->toArray()); + } + + function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag) + { + $isVoteAdmin = in_array('vote', $session->acl[DB::prefix()] ?? []); + $isVoteAdmin = $isVoteAdmin || $session->userGrade >= 5; + + if (!$isVoteAdmin) { + return "권한이 부족합니다."; + } + + /** @var string */ + $title = $this->args['title']; + /** @var int */ + $multipleOptions = $this->args['multipleOptions'] ?? 1; + if ($multipleOptions < 0) { + $multipleOptions = 0; + } + + + $now = new \DateTimeImmutable(); + $endDate = $this->args['endDate'] ?? null; + /** @var string[] */ + $options = $this->args['options'] ?? []; + + $db = DB::db(); + $gameStor = KVStorage::getStorage($db, 'game_env'); + + + $lastVote = $gameStor->getValue('lastVote') ?? 0; + $voteID = $lastVote + 1; + + $voteStor = KVStorage::getStorage($db, 'vote'); + + if (!($this->args['keepOldVote'] ?? false)) { + $this->closeOldVote($lastVote, $voteStor); + } + + $multipleOptions = Util::valueFit($multipleOptions, 0, count($options)); + + $voteInfo = new VoteInfo( + id: $voteID, + title: $title, + multipleOptions: $multipleOptions, + startDate: $now, + endDate: $endDate, + options: $options, + ); + + $voteStor->setValue("vote_{$voteID}", $voteInfo->toArray()); + $db->update('general', [ + 'newvote' => 1 + ], true); + + return [ + 'result' => true + ]; + } +} diff --git a/hwe/sammo/API/Vote/Vote.php b/hwe/sammo/API/Vote/Vote.php new file mode 100644 index 00000000..62c1a3be --- /dev/null +++ b/hwe/sammo/API/Vote/Vote.php @@ -0,0 +1,114 @@ +args); + $v->rule('required', [ + 'voteID', + 'selection' + ])->rule('lengthMin', 'title', 1) + ->rule('int', 'voteID') + ->rule('integerArray', 'seletion'); + + if (!$v->validate()) { + return $v->errorStr(); + } + return null; + } + + function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag) + { + + $voteID = $this->args['voteID']; + /** @var int[] */ + $selection = $this->args['selection']; + if (!$selection) { + return '선택한 항목이 없습니다.'; + } + $db = DB::db(); + $voteStor = KVStorage::getStorage($db, 'vote'); + + $rawVoteInfo = $voteStor->getValue("vote_{$voteID}"); + if (!$rawVoteInfo) { + return '설문조사가 없습니다.'; + } + $voteInfo = new VoteInfo(...$rawVoteInfo); + + if ($voteInfo->endDate && $voteInfo->endDate < new \DateTimeImmutable()) { + return '설문조사가 종료되었습니다.'; + } + + if ($voteInfo->multipleOptions >= 1 && count($selection) > $voteInfo->multipleOptions) { + return '선택한 항목이 너무 많습니다.'; + } + + $optionsCnt = count($voteInfo->options); + foreach ($selection as $sel) { + if ($sel >= $optionsCnt) { + return '선택한 항목이 없습니다.'; + } + } + + sort($selection, SORT_NUMERIC); + + $userID = $session->userID; + $generalID = $session->generalID; + + $serverPrefix = DB::prefix(); + $lockFactory = new LockFactory(new SemaphoreStore()); + + $nationID = $db->query('SELECT nation FROM general WHERE no = %i', $generalID); + + + $lock = $lockFactory->createLock("api_{$serverPrefix}_{$userID}"); + $lock->acquire(true); + + + $db->insertIgnore('vote', [ + 'vote_id' => $voteID, + 'general_id' => $generalID, + 'nation_id' => $nationID, + 'selection' => Json::encode($selection), + ]); + + if ($db->affectedRows() == 0) { + return '이미 설문조사를 완료하였습니다.'; + } + + $gameStor = KVStorage::getStorage($db, 'game_env'); + $voteReward = $gameStor->getValue('develcost') * 5; + + $general = General::createGeneralObjFromDB($generalID, ['vote', 'horse', 'weapon', 'book', 'item', 'npc', 'imgsvr', 'picture', 'aux'], 2); + $general->increaseVar('gold', $voteReward); + $wonLottery = tryUniqueItemLottery($general, '설문조사'); + + $general->applyDB($db); + + return [ + 'result' => true, + 'wonLottery' => $wonLottery, + ]; + } +} diff --git a/hwe/sammo/DTO/VoteComment.php b/hwe/sammo/DTO/VoteComment.php new file mode 100644 index 00000000..6d48c1ed --- /dev/null +++ b/hwe/sammo/DTO/VoteComment.php @@ -0,0 +1,36 @@ + }, + Vote: { + AddComment: POST as APICallT<{ + voteID: number, + text: string, + }>, + GetVoteList: GET as APICallT, + GetVoteDetail: GET as APICallT<{ + voteID: number + }, VoteDetailResult>, + NewVote: POST as APICallT<{ + title: string, + multipleOptions?: number, + endDate?: string, + options: string[], + keepOldVote?: boolean, + }>, + Vote: POST as APICallT<{ + voteID: number, + selection: number[], + }, ValidResponse & {wonLottery: boolean}>, + } } as const; export const SammoAPI = APIPathGen(apiRealPath, (path: string[], tail: APITail, pathParam) => { diff --git a/hwe/ts/defs/API/Vote.ts b/hwe/ts/defs/API/Vote.ts new file mode 100644 index 00000000..3ed5f6d1 --- /dev/null +++ b/hwe/ts/defs/API/Vote.ts @@ -0,0 +1,29 @@ +import type { ValidResponse } from "@/util/callSammoAPI"; + +export type VoteInfo = { + id: number; + title: string; + multipleOptions: number; + startDate: string; + endDate: string; + options: string[]; +} + +export type VoteComment = { + id: number; + voteID: number; + generalID: number; + nationName: string; + generalName: string; + text: string; +} + +export type VoteListResult = ValidResponse & { + votes: Record; +} + +export type VoteDetailResult = ValidResponse & { + voteInfo: VoteInfo, + votes: [number[], number][], + comments: VoteComment[], +} \ No newline at end of file