feat: 설문조사를 새 방식으로 구현
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\API\Vote;
|
||||
|
||||
use DateTimeInterface;
|
||||
use sammo\DB;
|
||||
use sammo\DTO\VoteComment;
|
||||
use sammo\General;
|
||||
use sammo\Session;
|
||||
use sammo\Validator;
|
||||
|
||||
class AddComment extends \sammo\BaseAPI
|
||||
{
|
||||
function getRequiredSessionMode(): int
|
||||
{
|
||||
return static::REQ_GAME_LOGIN | static::REQ_READ_ONLY;
|
||||
}
|
||||
|
||||
function validateArgs(): ?string
|
||||
{
|
||||
$v = new Validator($this->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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\API\Vote;
|
||||
|
||||
use sammo\Session;
|
||||
use DateTimeInterface;
|
||||
use sammo\DB;
|
||||
use sammo\DTO\VoteComment;
|
||||
use sammo\DTO\VoteInfo;
|
||||
use sammo\Json;
|
||||
use sammo\KVStorage;
|
||||
use sammo\Validator;
|
||||
|
||||
class GetVoteDetail extends \sammo\BaseAPI
|
||||
{
|
||||
public function validateArgs(): ?string
|
||||
{
|
||||
$v = new Validator($this->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
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\API\Vote;
|
||||
|
||||
use DateTimeInterface;
|
||||
use sammo\DB;
|
||||
use sammo\DTO\VoteInfo;
|
||||
use sammo\KVStorage;
|
||||
use sammo\Session;
|
||||
|
||||
class GetVoteList extends \sammo\BaseAPI
|
||||
{
|
||||
|
||||
public function validateArgs(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getRequiredSessionMode(): int
|
||||
{
|
||||
return static::REQ_LOGIN | static::REQ_READ_ONLY;
|
||||
}
|
||||
|
||||
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag)
|
||||
{
|
||||
$db = DB::db();
|
||||
|
||||
$voteStor = KVStorage::getStorage($db, 'vote');
|
||||
|
||||
$votes = [];
|
||||
foreach($voteStor->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
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\API\Vote;
|
||||
|
||||
use DateTimeInterface;
|
||||
use sammo\DB;
|
||||
use sammo\DTO\VoteInfo;
|
||||
use sammo\KVStorage;
|
||||
use sammo\Session;
|
||||
use sammo\Util;
|
||||
use sammo\Validator;
|
||||
|
||||
class NewVote extends \sammo\BaseAPI
|
||||
{
|
||||
function getRequiredSessionMode(): int
|
||||
{
|
||||
return static::REQ_GAME_LOGIN;
|
||||
}
|
||||
|
||||
function validateArgs(): ?string
|
||||
{
|
||||
$v = new Validator($this->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
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\API\Vote;
|
||||
|
||||
use DateTimeInterface;
|
||||
use sammo\DB;
|
||||
use sammo\DTO\VoteInfo;
|
||||
use sammo\General;
|
||||
use sammo\Json;
|
||||
use sammo\KVStorage;
|
||||
use sammo\Session;
|
||||
use sammo\Validator;
|
||||
use Symfony\Component\Lock\LockFactory;
|
||||
use Symfony\Component\Lock\Store\SemaphoreStore;
|
||||
|
||||
use function sammo\tryUniqueItemLottery;
|
||||
|
||||
class Vote extends \sammo\BaseAPI
|
||||
{
|
||||
function getRequiredSessionMode(): int
|
||||
{
|
||||
return static::REQ_GAME_LOGIN | static::REQ_READ_ONLY;
|
||||
}
|
||||
|
||||
function validateArgs(): ?string
|
||||
{
|
||||
$v = new Validator($this->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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\DTO;
|
||||
|
||||
use Spatie\DataTransferObject\Attributes\MapFrom;
|
||||
use Spatie\DataTransferObject\Attributes\MapTo;
|
||||
use Spatie\DataTransferObject\Attributes\Strict;
|
||||
use Spatie\DataTransferObject\DataTransferObject;
|
||||
|
||||
#[Strict]
|
||||
class VoteComment extends DataTransferObject
|
||||
{
|
||||
public ?int $id;
|
||||
|
||||
#[MapFrom('vote_id')]
|
||||
#[MapTo('vote_id')]
|
||||
public int $voteID;
|
||||
|
||||
#[MapFrom('general_id')]
|
||||
#[MapTo('general_id')]
|
||||
public int $generalID;
|
||||
|
||||
#[MapFrom('nation_id')]
|
||||
#[MapTo('nation_id')]
|
||||
public int $nationID;
|
||||
|
||||
#[MapFrom('nation_name')]
|
||||
#[MapTo('nation_name')]
|
||||
public string $nationName;
|
||||
|
||||
#[MapFrom('general_name')]
|
||||
#[MapTo('general_name')]
|
||||
public string $generalName;
|
||||
|
||||
public string $text;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\DTO;
|
||||
|
||||
use Spatie\DataTransferObject\Attributes\CastWith;
|
||||
use Spatie\DataTransferObject\Attributes\Strict;
|
||||
use Spatie\DataTransferObject\DataTransferObject;
|
||||
use Spatie\DataTransferObject\Casters\ArrayCaster;
|
||||
|
||||
//https://json2dto.atymic.dev/
|
||||
|
||||
|
||||
|
||||
#[Strict]
|
||||
class VoteInfo extends DataTransferObject
|
||||
{
|
||||
public int $id;
|
||||
public string $title;
|
||||
public int $multipleOptions;
|
||||
public \DateTimeImmutable $startDate;
|
||||
public ?\DateTimeImmutable $endDate;
|
||||
|
||||
/** @var string[] */
|
||||
public array $options;
|
||||
}
|
||||
+2
-1
@@ -65,4 +65,5 @@ ENGINE=Aria;
|
||||
|
||||
DROP TABLE IF EXISTS ng_betting;
|
||||
|
||||
DROP TABLE IF EXISTS vote;
|
||||
DROP TABLE IF EXISTS vote;
|
||||
DROP TABLE IF EXISTS vote_comment;
|
||||
+13
-1
@@ -667,9 +667,21 @@ CREATE TABLE `vote` (
|
||||
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||
`vote_id` INT(11) NOT NULL,
|
||||
`general_id` INT(11) NOT NULL,
|
||||
`nation_id` INT(11) NOT NULL,
|
||||
`selection` VARCHAR(100) NOT NULL COLLATE 'utf8mb4_bin',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE INDEX `by_general` (`general_id`, `vote_id`),
|
||||
UNIQUE INDEX `by_vote` (`vote_id`, `selection`, `general_id`),
|
||||
INDEX `by_vote` (`vote_id`, `selection`),
|
||||
CONSTRAINT `json` CHECK (json_valid(`selection`))
|
||||
) COLLATE = 'utf8mb4_general_ci' ENGINE = Aria;
|
||||
CREATE TABLE `vote_comment` (
|
||||
`id` INT(11) NOT NULL AUTO_INCREMENT,
|
||||
`vote_id` INT(11) NOT NULL,
|
||||
`general_id` INT(11) NOT NULL,
|
||||
`nation_id` INT(11) NOT NULL,
|
||||
`general_name` VARCHAR(32) NOT NULL COLLATE 'utf8mb4_bin',
|
||||
`nation_name` VARCHAR(64) NOT NULL COLLATE 'utf8mb4_bin',
|
||||
`text` TEXT NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `by_vote` (`vote_id`)
|
||||
) COLLATE = 'utf8mb4_general_ci' ENGINE = Aria;
|
||||
@@ -14,6 +14,7 @@ import type { UploadImageResponse } from "./defs/API/Misc";
|
||||
import type { GeneralLogType, GetGeneralLogResponse, JoinArgs } from "./defs/API/General";
|
||||
import type { GetConstResponse, GetCurrentHistoryResponse, GetDiplomacyResponse, GetHistoryResponse } from "./defs/API/Global";
|
||||
import type { CachedMapResult, GeneralListResponse, ItemTypeKey, MapResult } from "./defs";
|
||||
import type { VoteDetailResult, VoteListResult } from "./defs/API/Vote";
|
||||
|
||||
const apiRealPath = {
|
||||
Betting: {
|
||||
@@ -144,6 +145,27 @@ const apiRealPath = {
|
||||
reqTo?: number
|
||||
}, GetGeneralLogResponse>
|
||||
},
|
||||
Vote: {
|
||||
AddComment: POST as APICallT<{
|
||||
voteID: number,
|
||||
text: string,
|
||||
}>,
|
||||
GetVoteList: GET as APICallT<undefined, VoteListResult>,
|
||||
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) => {
|
||||
|
||||
@@ -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<string, VoteInfo>;
|
||||
}
|
||||
|
||||
export type VoteDetailResult = ValidResponse & {
|
||||
voteInfo: VoteInfo,
|
||||
votes: [number[], number][],
|
||||
comments: VoteComment[],
|
||||
}
|
||||
Reference in New Issue
Block a user