medal management

This commit is contained in:
xiaomlove
2022-01-19 23:54:55 +08:00
parent 3fa8fd19c0
commit 64a1f2bb0c
39 changed files with 1282 additions and 171 deletions

View File

@@ -7,12 +7,14 @@ use App\Models\Exam;
use App\Models\ExamProgress;
use App\Models\ExamUser;
use App\Models\HitAndRun;
use App\Models\Medal;
use App\Models\SearchBox;
use App\Models\Snatch;
use App\Models\User;
use App\Repositories\ExamRepository;
use App\Repositories\SearchBoxRepository;
use App\Repositories\TorrentRepository;
use App\Repositories\UserRepository;
use Carbon\Carbon;
use GeoIp2\Database\Reader;
use Illuminate\Console\Command;
@@ -55,11 +57,8 @@ class Test extends Command
*/
public function handle()
{
$user = User::query()->first();
$user->update([
'page' => DB::raw('dddd')
]);
dd(last_query());
$rep = new UserRepository();
$r = $rep->getDetail(1);
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace App\Http\Controllers;
use App\Http\Resources\MedalResource;
use App\Repositories\MedalRepository;
use Illuminate\Http\Request;
class MedalController extends Controller
{
private $repository;
public function __construct(MedalRepository $repository)
{
$this->repository = $repository;
}
/**
* Display a listing of the resource.
*
* @param Request $request
* @return array
*/
public function index(Request $request)
{
$result = $this->repository->getList($request->all());
$resource = MedalResource::collection($result);
$resource->additional([
'page_title' => nexus_trans('medal.admin.list.page_title'),
]);
return $this->success($resource);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function store(Request $request)
{
$rules = [
'name' => 'required|string',
'price' => 'required|integer|min:1',
'image_large' => 'required|url',
'image_small' => 'required|url',
'duration' => 'nullable|integer|min:-1',
];
$request->validate($rules);
$result = $this->repository->store($request->all());
$resource = new MedalResource($result);
return $this->success($resource);
}
/**
* Display the specified resource.
*
* @param int $id
* @return array
*/
public function show($id)
{
$result = $this->repository->getDetail($id);
$resource = new MedalResource($result);
return $this->success($resource);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return array
*/
public function update(Request $request, $id)
{
$rules = [
'name' => 'required|string',
'price' => 'required|integer|min:1',
'image_large' => 'required|url',
'image_small' => 'required|url',
'duration' => 'nullable|integer|min:-1',
];
$request->validate($rules);
$result = $this->repository->update($request->all(), $id);
$resource = new MedalResource($result);
return $this->success($resource);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return array
*/
public function destroy($id)
{
$result = $this->repository->delete($id);
return $this->success($result, 'Delete medal success!');
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace App\Http\Controllers;
use App\Http\Resources\MedalResource;
use App\Models\UserMedal;
use App\Repositories\MedalRepository;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class UserMedalController extends Controller
{
private $repository;
public function __construct(MedalRepository $repository)
{
$this->repository = $repository;
}
/**
* Display a listing of the resource.
*
* @param Request $request
* @return array
*/
public function index(Request $request)
{
$result = $this->repository->getList($request->all());
$resource = MedalResource::collection($result);
$resource->additional([
'page_title' => nexus_trans('medal.admin.list.page_title'),
]);
return $this->success($resource);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function store(Request $request)
{
$rules = [
'name' => 'required|string',
'price' => 'required|integer|min:1',
'image_large' => 'required|url',
'image_small' => 'required|url',
'duration' => 'nullable|integer|min:-1',
];
$request->validate($rules);
$result = $this->repository->store($request->all());
$resource = new MedalResource($result);
return $this->success($resource);
}
/**
* Display the specified resource.
*
* @param int $id
* @return array
*/
public function show($id)
{
$result = $this->repository->getDetail($id);
$resource = new MedalResource($result);
return $this->success($resource);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return array
*/
public function update(Request $request, $id)
{
$rules = [
'name' => 'required|string',
'price' => 'required|integer|min:1',
'image_large' => 'required|url',
'image_small' => 'required|url',
'duration' => 'nullable|integer|min:-1',
];
$request->validate($rules);
$result = $this->repository->update($request->all(), $id);
$resource = new MedalResource($result);
return $this->success($resource);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return array
*/
public function destroy($id)
{
$userMedal = UserMedal::query()->findOrFail($id);
$result = $userMedal->delete();
return $this->success($result, 'Remove user medal success!');
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class MedalResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'get_type' => $this->get_type,
'get_type_text' => $this->getTypeText,
'image_large' => $this->image_large,
'image_small' => $this->image_small,
'price' => $this->price,
'duration' => $this->duration,
'description' => $this->description,
'expire_at' => $this->whenPivotLoaded('user_medals', function () {return $this->pivot->expire_at;}),
'user_medal_id' => $this->whenPivotLoaded('user_medals', function () {return $this->pivot->id;}),
];
}
}

View File

@@ -34,6 +34,7 @@ class UserResource extends JsonResource
'leechtime' => $this->leechtime,
'leechtime_text' => mkprettytime($this->leechtime),
'inviter' => new UserResource($this->whenLoaded('inviter')),
'valid_medals' => MedalResource::collection($this->whenLoaded('valid_medals')),
];
if ($request->routeIs('user.me')) {
$out['downloaded_human'] = mksize($this->downloaded);

View File

@@ -12,9 +12,11 @@ class BonusLogs extends NexusModel
const DEFAULT_BONUS_CANCEL_ONE_HIT_AND_RUN = 10000;
const BUSINESS_TYPE_CANCEL_HIT_AND_RUN = 1;
const BUSINESS_TYPE_BUY_MEDAL = 2;
public static $businessTypes = [
self::BUSINESS_TYPE_CANCEL_HIT_AND_RUN => ['text' => 'Cancel H&R'],
self::BUSINESS_TYPE_BUY_MEDAL => ['text' => 'Buy medal'],
];
public static function getBonusForCancelHitAndRun()

39
app/Models/Medal.php Normal file
View File

@@ -0,0 +1,39 @@
<?php
namespace App\Models;
use Carbon\Carbon;
class Medal extends NexusModel
{
const GET_TYPE_EXCHANGE = 1;
const GET_TYPE_GRANT = 2;
public static $getTypeText = [
self::GET_TYPE_EXCHANGE => ['text' => 'Exchange'],
self::GET_TYPE_GRANT => ['text' => 'Grant'],
];
protected $fillable = ['name', 'description', 'image_large', 'image_small', 'price', 'duration', 'get_type'];
public $timestamps = true;
public function getGetTypeTextAttribute($value): string
{
return self::$getTypeText[$this->get_type]['text'] ?? '';
}
public function users(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(User::class, 'user_medals', 'medal_id', 'uid')->withTimestamps();
}
public function valid_users(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->users()->where(function ($query) {
$query->whereNull('user_medals.expire_at')->orWhere('user_medals.expire_at', '>=', Carbon::now());
});
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Models;
use App\Http\Middleware\Locale;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
@@ -263,6 +264,20 @@ class User extends Authenticatable
return $this->hasMany(HitAndRun::class, 'uid');
}
public function medals(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Medal::class, 'user_medals', 'uid', 'medal_id')
->withPivot(['id', 'expire_at'])
->withTimestamps();
}
public function valid_medals(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->medals()->where(function ($query) {
$query->whereNull('user_medals.expire_at')->orWhere('user_medals.expire_at', '>=', Carbon::now());
});
}
public function getAvatarAttribute($value)
{
if ($value) {

8
app/Models/UserMedal.php Normal file
View File

@@ -0,0 +1,8 @@
<?php
namespace App\Models;
class UserMedal extends NexusModel
{
protected $fillable = ['uid', 'medal_id', 'expire_at'];
}

View File

@@ -3,17 +3,16 @@ namespace App\Repositories;
use App\Models\BonusLogs;
use App\Models\HitAndRun;
use App\Models\Medal;
use App\Models\Setting;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\Expression;
use Illuminate\Support\Facades\DB;
use Nexus\Database\NexusDB;
class BonusRepository extends BaseRepository
{
public function consumeToCancelHitAndRun($uid, $hitAndRunId)
public function consumeToCancelHitAndRun($uid, $hitAndRunId): bool
{
if (!HitAndRun::getIsEnabled()) {
throw new \LogicException("H&R not enabled.");
@@ -24,47 +23,23 @@ class BonusRepository extends BaseRepository
throw new \LogicException("H&R: $hitAndRunId not belongs to user: $uid.");
}
$requireBonus = BonusLogs::getBonusForCancelHitAndRun();
if ($user->seedbonus < $requireBonus) {
do_log("user: $uid, bonus: {$user->seedbonus} < requireBonus: $requireBonus", 'error');
throw new \LogicException("User bonus point not enough.");
}
$result = NexusDB::transaction(function () use ($user, $hitAndRun, $requireBonus) {
$oldUserBonus = $user->seedbonus;
$newUserBonus = bcsub($oldUserBonus, $requireBonus);
$log = "user: {$user->id}, hitAndRun: {$hitAndRun->id}, requireBonus: $requireBonus, oldUserBonus: $oldUserBonus, newUserBonus: $newUserBonus";
do_log($log);
$affectedRows = NexusDB::table($user->getTable())
->where('id', $user->id)
->where('seedbonus', $oldUserBonus)
->update(['seedbonus' => $newUserBonus]);
if ($affectedRows != 1) {
do_log("update user seedbonus affected rows != 1, query: " . last_query(), 'error');
throw new \RuntimeException("Update user seedbonus fail.");
}
NexusDB::transaction(function () use ($user, $hitAndRun, $requireBonus) {
$comment = nexus_trans('hr.bonus_cancel_comment', [
'now' => Carbon::now()->toDateTimeString(),
'bonus' => $requireBonus,
], $user->locale);
$comment = addslashes($comment);
do_log("comment: $comment");
$this->consumeUserBonus($user, $requireBonus, BonusLogs::BUSINESS_TYPE_CANCEL_HIT_AND_RUN, "$comment(H&R ID: {$hitAndRun->id})");
$hitAndRun->update([
'status' => HitAndRun::STATUS_PARDONED,
'comment' => new Expression("concat(comment, '\n$comment')"),
'comment' => NexusDB::raw("concat(comment, '\n$comment')"),
]);
$bonusLog = [
'business_type' => BonusLogs::BUSINESS_TYPE_CANCEL_HIT_AND_RUN,
'uid' => $user->id,
'old_total_value' => $oldUserBonus,
'value' => $requireBonus,
'new_total_value' => $newUserBonus,
'comment' => "$comment(H&R ID: {$hitAndRun->id})",
];
BonusLogs::query()->insert($bonusLog);
do_log("bonusLog: " . json_encode($bonusLog));
return true;
});
return $result;
return true;
}
@@ -74,11 +49,11 @@ class BonusRepository extends BaseRepository
$tableName = (new User())->getTable();
$result = 0;
do {
$affectedRows = DB::table($tableName)
$affectedRows = NexusDB::table($tableName)
->whereNull('seed_points')
->limit($size)
->update([
'seed_points' => DB::raw('seed_points = seedbonus')
'seed_points' => NexusDB::raw('seed_points = seedbonus')
]);
$result += $affectedRows;
do_log("affectedRows: $affectedRows, query: " . last_query());
@@ -88,4 +63,67 @@ class BonusRepository extends BaseRepository
}
public function consumeToBuyMedal($uid, $medalId): bool
{
$user = User::query()->findOrFail($uid);
$medal = Medal::query()->findOrFail($medalId);
$exists = $user->valid_medals()->where('medal_id', $medalId)->exists();
do_log(last_query());
if ($exists) {
throw new \LogicException("user: $uid already own this medal: $medalId.");
}
$requireBonus = $medal->price;
NexusDB::transaction(function () use ($user, $medal, $requireBonus) {
$comment = nexus_trans('bonus.comment_buy_medal', [
'bonus' => $requireBonus,
'medal_name' => $medal->name,
], $user->locale);
$comment = addslashes($comment);
do_log("comment: $comment");
$this->consumeUserBonus($user, $requireBonus, BonusLogs::BUSINESS_TYPE_BUY_MEDAL, "$comment(medal ID: {$medal->id})");
$expireAt = null;
if ($medal->duration > 0) {
$expireAt = Carbon::now()->addDays($medal->duration)->toDateTimeString();
}
$user->medals()->attach([$medal->id => ['expire_at' => $expireAt]]);
});
return true;
}
private function consumeUserBonus($user, $requireBonus, $logBusinessType, $logComment = '')
{
if ($user->seedbonus < $requireBonus) {
do_log("user: {$user->id}, bonus: {$user->seedbonus} < requireBonus: $requireBonus", 'error');
throw new \LogicException("User bonus point not enough.");
}
NexusDB::transaction(function () use ($user, $requireBonus, $logBusinessType, $logComment) {
$oldUserBonus = $user->seedbonus;
$newUserBonus = bcsub($oldUserBonus, $requireBonus);
$log = "user: {$user->id}, requireBonus: $requireBonus, oldUserBonus: $oldUserBonus, newUserBonus: $newUserBonus, logBusinessType: $logBusinessType, logComment: $logComment";
do_log($log);
$affectedRows = NexusDB::table($user->getTable())
->where('id', $user->id)
->where('seedbonus', $oldUserBonus)
->update(['seedbonus' => $newUserBonus]);
if ($affectedRows != 1) {
do_log("update user seedbonus affected rows != 1, query: " . last_query(), 'error');
throw new \RuntimeException("Update user seedbonus fail.");
}
$bonusLog = [
'business_type' => $logBusinessType,
'uid' => $user->id,
'old_total_value' => $oldUserBonus,
'value' => $requireBonus,
'new_total_value' => $newUserBonus,
'comment' => $logComment,
];
BonusLogs::query()->insert($bonusLog);
do_log("bonusLog: " . nexus_json_encode($bonusLog));
});
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Repositories;
use App\Models\Medal;
use App\Models\UserMedal;
use Nexus\Database\NexusDB;
class MedalRepository extends BaseRepository
{
public function getList(array $params): \Illuminate\Contracts\Pagination\LengthAwarePaginator
{
$query = Medal::query();
list($sortField, $sortType) = $this->getSortFieldAndType($params);
$query->orderBy($sortField, $sortType);
return $query->paginate();
}
public function store(array $params)
{
return Medal::query()->create($params);
}
public function update(array $params, $id)
{
$medal = Medal::query()->findOrFail($id);
$medal->update($params);
return $medal;
}
public function getDetail($id)
{
return Medal::query()->findOrFail($id);
}
/**
* delete a medal, also will delete all user medal.
*
* @param $id
* @return bool
*/
public function delete($id): bool
{
$medal = Medal::query()->findOrFail($id);
NexusDB::transaction(function () use ($medal) {
do {
$deleted = UserMedal::query()->where('medal_id', $medal->id)->limit(10000)->delete();
} while ($deleted > 0);
$medal->delete();
});
return true;
}
}

View File

@@ -30,14 +30,15 @@ class UserRepository extends BaseRepository
public function getDetail($id)
{
$with = [
'inviter' => function ($query) {return $query->select(User::$commonFields);}
'inviter' => function ($query) {return $query->select(User::$commonFields);},
'valid_medals'
];
$user = User::query()->with($with)->findOrFail($id, User::$commonFields);
$userResource = new UserResource($user);
$baseInfo = $userResource->response()->getData(true)['data'];
$examRep = new ExamRepository();
$examProgress = $examRep->getUserExamProgress($id, null, ['exam']);
$examProgress = $examRep->getUserExamProgress($id, null);
if ($examProgress) {
$examResource = new ExamUserResource($examProgress);
$examInfo = $examResource->response()->getData(true)['data'];
@@ -45,6 +46,8 @@ class UserRepository extends BaseRepository
$examInfo = null;
}
return [
'base_info' => $baseInfo,
'exam_info' => $examInfo,
@@ -156,4 +159,7 @@ class UserRepository extends BaseRepository
$user = User::query()->findOrFail($id, ['modcomment']);
return $user->modcomment;
}
}