Merge remote-tracking branch 'refs/remotes/origin/php8' into php8

This commit is contained in:
xiaomlove
2021-04-21 00:11:11 +08:00
160 changed files with 127343 additions and 10808 deletions

View File

@@ -2,7 +2,6 @@
namespace App\Http\Controllers;
use App\Http\Requests\AgentAllowRequest;
use App\Http\Resources\AgentAllowResource;
use App\Models\AgentAllow;
use Illuminate\Http\Request;
@@ -16,33 +15,33 @@ class AgentAllowController extends Controller
*/
public function index()
{
$result = AgentAllow::query()->paginate();
$result = AgentAllow::query()->orderBy('id', 'desc')->paginate();
$resource = AgentAllowResource::collection($result);
return success('agent allow list', $resource);
return $this->success($resource);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return array
* @return \Illuminate\Http\Response
*/
public function store(AgentAllowRequest $request)
public function store(Request $request)
{
$result = AgentAllow::query()->create($request->all());
$resource = new AgentAllowResource($result);
return success('agent allow store', $resource);
//
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
* @return array
*/
public function show($id)
{
//
$result = AgentAllow::query()->findOrFail($id);
$resource = new AgentAllowResource($result);
return $this->success($resource);
}
/**
@@ -57,7 +56,7 @@ class AgentAllowController extends Controller
$result = AgentAllow::query()->findOrFail($id);
$result->update($request->all());
$resource = new AgentAllowResource($result);
return success('agent allow update', $resource);
return $this->success($resource);
}
/**
@@ -69,7 +68,7 @@ class AgentAllowController extends Controller
public function destroy($id)
{
$result = AgentAllow::query()->findOrFail($id);
$success = $result->delete();
return success('agent allow delete', $success);
$deleted = $result->delete();
return $this->success([$deleted]);
}
}

View File

@@ -6,8 +6,51 @@ use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Str;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
public function success($data, $msg = null)
{
if (is_null($msg)) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$caller = $backtrace[1];
$msg = $this->getReturnMsg($caller);
}
return success($msg, $data);
}
public function fail($data, $msg = null)
{
if (is_null($msg)) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$caller = $backtrace[1];
$msg = $this->getReturnMsg($caller);
}
return fail($msg, $data);
}
protected function getReturnMsg(array $backtrace)
{
$title = $this->title ?? '';
if (empty($title)) {
$title = $backtrace['class'];
$pos = strripos($title, '\\');
$title = substr($title, $pos + 1);
$title = str_replace('Controller', '', $title);
}
$action = $backtrace['function'];
$map = [
'index' => 'list',
'show' => 'detail',
'update' => 'update',
'destroy' => 'delete',
];
if (isset($map[$action])) {
$action = $map[$action];
}
return Str::slug("$title.$action", '.');
}
}

View File

@@ -0,0 +1,104 @@
<?php
namespace App\Http\Controllers;
use App\Http\Resources\ExamResource;
use App\Http\Resources\UserResource;
use App\Repositories\ExamRepository;
use App\Repositories\UserRepository;
use Illuminate\Http\Request;
class ExamController extends Controller
{
private $repository;
public function __construct(ExamRepository $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 = ExamResource::collection($result);
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',
'begin' => 'required|date_format:Y-m-d H:i:s',
'end' => 'required|date_format:Y-m-d H:i:s',
'requires' => 'required|array|min:1',
'filters' => 'required|array|min:1',
];
$request->validate($rules);
$result = $this->repository->store($request->all());
$resource = new ExamResource($result);
return $this->success($resource);
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* 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',
'begin' => 'required|date_format:Y-m-d H:i:s',
'end' => 'required|date_format:Y-m-d H:i:s',
'requires' => 'required|array|min:1',
'filters' => 'required|array|min:1',
];
$request->validate($rules);
$result = $this->repository->update($request->all(), $id);
$resource = new ExamResource($result);
return $this->success($resource);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
public function indexes()
{
$result = $this->repository->listIndexes();
return $this->success($result);
}
}

View File

@@ -3,22 +3,29 @@
namespace App\Http\Controllers;
use App\Http\Resources\UserResource;
use App\Models\User;
use App\Repositories\UserRepository;
use Illuminate\Http\Request;
class UserController extends Controller
{
private $repository;
public function __construct(UserRepository $repository)
{
$this->repository = $repository;
}
/**
* Display a listing of the resource.
*
* @param Request $request
* @return array
*/
public function index(Request $request)
{
$result = (new UserRepository())->getList($request->all());
$result = $this->repository->getList($request->all());
$resource = UserResource::collection($result);
return success('user list', $resource);
return $this->success($resource);
}
/**
@@ -29,9 +36,16 @@ class UserController extends Controller
*/
public function store(Request $request)
{
$result = (new UserRepository())->store($request->all());
$rules = [
'username' => 'required|string',
'email' => 'required|email|unique:users',
'password' => 'required|string|min:6|max:40',
'password_confirmation' => 'required|string|same:password'
];
$request->validate($rules);
$result = $this->repository->store($request->all());
$resource = new UserResource($result);
return success('user store', $resource);
return $this->success($resource);
}
/**
@@ -50,21 +64,40 @@ class UserController extends Controller
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return array
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return array
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
public function resetPassword(Request $request)
{
$rules = [
'username' => 'required|string|exists:users',
'password' => 'required|string|min:6|max:40',
'password_confirmation' => 'required|same:password',
];
$request->validate($rules);
$result = $this->repository->resetPassword($request->repositoryname, $request->password, $request->password_confirmation);
$resource = new UserResource($result);
return $this->success($resource);
}
public function classes()
{
$result = $this->repository->listClass();
return $this->success($result);
}
}

View File

@@ -16,6 +16,7 @@ class AgentAllowResource extends JsonResource
{
return [
'id' => $this->id,
'family' => $this->family,
'start_name' => $this->start_name,
'peer_id_pattern' => $this->peer_id_pattern,
'peer_id_match_num' => $this->peer_id_match_num,
@@ -27,6 +28,7 @@ class AgentAllowResource extends JsonResource
'agent_start' => $this->agent_start,
'exception' => $this->exception,
'comment' => $this->comment,
'allowhttps' => $this->allowhttps,
'hits' => $this->hits,
];
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class ExamResource 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,
'description' => $this->description,
'begin' => $this->begin,
'end' => $this->end,
'filters' => $this->filters,
'requires' => $this->requires,
'status' => $this->status,
];
}
}

View File

@@ -16,9 +16,16 @@ class UserResource extends JsonResource
{
return [
'id' => $this->id,
'email' => $this->email,
'username' => $this->username,
'status' => $this->status,
'added' => $this->added,
'class' => $this->class,
'avatar' => $this->avatar,
'uploaded' => $this->uploaded,
'downloaded' => $this->downloaded,
'seedtime' => $this->seedtime,
'leechtime' => $this->leechtime,
];
}
}

View File

@@ -7,7 +7,7 @@ class AgentAllow extends NexusModel
protected $table = 'agent_allowed_family';
protected $fillable = [
'family', 'start_name', 'exception', 'allowhttps', 'comment', 'hits',
'family', 'start_name', 'exception', 'allowhttps', 'comment',
'peer_id_pattern', 'peer_id_match_num', 'peer_id_matchtype', 'peer_id_start',
'agent_pattern', 'agent_match_num', 'agent_matchtype', 'agent_start',
];

40
app/Models/Exam.php Normal file
View File

@@ -0,0 +1,40 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Exam extends NexusModel
{
use HasFactory;
protected $fillable = ['name', 'description', 'begin', 'end', 'status'];
const STATUS_ENABLED = 0;
const STATUS_DISABLED = 1;
public static $status = [
self::STATUS_ENABLED => ['text' => '启用中'],
self::STATUS_DISABLED => ['text' => '已禁用'],
];
const INDEX_UPLOADED = 1;
const INDEX_SEED_TIME = 2;
const INDEX_DOWNLOADED = 3;
const INDEX_LEECH_TIME = 4;
const INDEX_BONUS = 5;
public static $indexes = [
self::INDEX_UPLOADED => ['text' => 'Uploaded'],
self::INDEX_SEED_TIME => ['text' => 'Seed time'],
self::INDEX_DOWNLOADED => ['text' => 'Download'],
self::INDEX_LEECH_TIME => ['text' => 'Leech time'],
self::INDEX_BONUS => ['text' => 'Bonus'],
];
public function getStatusTextAttribute()
{
return self::$status[$this->status]['text'] ?? '';
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ExamProgress extends NexusModel
{
use HasFactory;
protected $fillable = ['exam_id', 'uid', 'type_id', 'value'];
}

View File

@@ -11,7 +11,15 @@ class NexusModel extends Model
public $timestamps = false;
protected $casts = [
'added' => 'datetime'
];
/**
* 为数组 / JSON 序列化准备日期。
*
* @param \DateTimeInterface $date
* @return string
*/
protected function serializeDate(\DateTimeInterface $date)
{
return $date->format($this->dateFormat ?: 'Y-m-d H:i:s');
}
}

33
app/Models/Setting.php Normal file
View File

@@ -0,0 +1,33 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Arr;
class Setting extends NexusModel
{
use HasFactory;
protected $fillable = ['name', 'value'];
public static function get($name = null)
{
static $settings;
if (is_null($settings)) {
$rows = self::query()->get(['name', 'value']);
foreach ($rows as $row) {
$value = $row->value;
$arr = json_decode($value, true);
if (is_array($arr)) {
$value = $arr;
}
Arr::set($settings, $row->name, $value);
}
}
if (is_null($name)) {
return $settings;
}
return Arr::get($settings, $name);
}
}

View File

@@ -2,7 +2,6 @@
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
@@ -11,14 +10,75 @@ class User extends Authenticatable
{
use HasFactory, Notifiable;
public $timestamps = false;
const STATUS_CONFIRMED = 'confirmed';
const STATUS_PENDING = 'pending';
const CLASS_PEASANT = "0";
const CLASS_USER = "1";
const CLASS_POWER_USER = "2";
const CLASS_ELITE_USER = "3";
const CLASS_CRAZY_USER = "4";
const CLASS_INSANE_USER = "5";
const CLASS_VETERAN_USER = "6";
const CLASS_EXTREME_USER = "7";
const CLASS_ULTIMATE_USER = "8";
const CLASS_NEXUS_MASTER = "9";
const CLASS_VIP = "10";
const CLASS_RETIREE = "11";
const CLASS_UPLOADER = "12";
const CLASS_FORUM_MODERATOR = "12.1";
const CLASS_MODERATOR = "13";
const CLASS_ADMINISTRATOR = "14";
const CLASS_SYSOP = "15";
const CLASS_STAFFLEADER = "16";
public static $classes = [
self::CLASS_PEASANT => ['text' => 'Peasant'],
self::CLASS_USER => ['text' => 'User'],
self::CLASS_POWER_USER => ['text' => 'Power User'],
self::CLASS_ELITE_USER => ['text' => 'Elite User'],
self::CLASS_CRAZY_USER => ['text' => 'Crazy User'],
self::CLASS_INSANE_USER => ['text' => 'Insane User'],
self::CLASS_VETERAN_USER => ['text' => 'Veteran User'],
self::CLASS_EXTREME_USER => ['text' => 'Extreme User'],
self::CLASS_ULTIMATE_USER => ['text' => 'Eltimate User'],
self::CLASS_NEXUS_MASTER => ['text' => 'Nexus Master'],
self::CLASS_VIP => ['text' => 'Vip'],
self::CLASS_RETIREE => ['text' => 'Retiree'],
self::CLASS_UPLOADER => ['text' => 'Uploader'],
self::CLASS_FORUM_MODERATOR => ['text' => 'Forum Moderator'],
self::CLASS_MODERATOR => ['text' => 'Moderator'],
self::CLASS_ADMINISTRATOR => ['text' => 'Administrator'],
self::CLASS_SYSOP => ['text' => 'Sysop'],
self::CLASS_STAFFLEADER => ['text' => 'Staff Leader'],
];
public function getClassTextAttribute()
{
return self::$classes[$this->class]['text'] ?? '';
}
/**
* 为数组 / JSON 序列化准备日期。
*
* @param \DateTimeInterface $date
* @return string
*/
protected function serializeDate(\DateTimeInterface $date)
{
return $date->format($this->dateFormat ?: 'Y-m-d H:i:s');
}
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'username', 'email', 'passhash', 'secret', 'status', 'added'
];
protected $fillable = ['username', 'email', 'passhash', 'secret', 'editsecret', 'added'];
/**
* The attributes that should be hidden for arrays.
@@ -26,7 +86,8 @@ class User extends Authenticatable
* @var array
*/
protected $hidden = [
'passhash', 'secret'
'password',
'remember_token',
];
/**
@@ -35,6 +96,6 @@ class User extends Authenticatable
* @var array
*/
protected $casts = [
'added' => 'datetime',
];
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Repositories;
use Illuminate\Support\Str;
class BaseRepository
{
protected function getSortFieldAndType(array $params)
{
$field = $params['sort_field'] ?? 'id';
$type = 'desc';
if (!empty($params['sort_type']) && Str::startsWith($params['sort_type'], 'asc')) {
$type = 'asc';
}
return [$field, $type];
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace App\Repositories;
use App\Models\Exam;
use App\Models\Setting;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
class ExamRepository extends BaseRepository
{
public function getList(array $params)
{
$query = Exam::query();
list($sortField, $sortType) = $this->getSortFieldAndType($params);
$query->orderBy($sortField, $sortType);
return $query->paginate();
}
public function store(array $params)
{
$exam = Exam::query()->create($params);
return $exam;
}
public function update(array $params, $id)
{
$exam = Exam::query()->findOrFail($id);
$exam->update($params);
return $exam;
}
public function listIndexes()
{
$out = [];
foreach(Exam::$indexes as $key => $value) {
$out[$key] = $value['text'];
}
return $out;
}
public function listMatchExam($uid)
{
$now = Carbon::now();
$user = User::query()->findOrFail($uid, ['id', 'username', 'added', 'class']);
$exams = Exam::query()
->where('begin', '<=', $now)
->where('end', '>=', $now)
->where('status', Exam::STATUS_ENABLED)
->get();
$result = [];
$logPrefix = "uid: $uid";
foreach ($exams as $exam) {
$filters = $exam->filters;
if (!in_array($user->class, $filters['classes'])) {
Log::info("$logPrefix, class: {$user->class} not in: " . json_encode($filters));
continue;
}
$added = $user->added->toDateTimeString();
if (!empty($filters['register_time_begin']) && $added < $filters['register_time_begin']) {
Log::info("$logPrefix, added: $added not after: " . $filters['register_time_begin']);
continue;
}
if (!empty($filters['register_time_end']) && $added > $filters['register_time_end']) {
Log::info("$logPrefix, added: $added not before: " . $filters['register_time_end']);
continue;
}
$result[] = $exam;
}
return $result;
}
}

View File

@@ -1,86 +1,65 @@
<?php
namespace App\Repositories;
use App\Models\Setting;
use App\Models\User;
use Illuminate\Support\Facades\Log;
class UserRepository
class UserRepository extends BaseRepository
{
public function store(array $params)
{
$required = ['username', 'email', 'password', 'password_confirmation'];
foreach ($required as $field) {
if (empty($params[$field])) {
throw new \InvalidArgumentException("Require $field");
}
}
$username = $params['username'];
$email = $params['email'];
$password = $params['password'];
$confirmPassword = $params['password_confirmation'];
if (!validusername($username)) {
throw new \InvalidArgumentException("Invalid username: $username");
}
$email = htmlspecialchars(trim($email));
$email = safe_email($email);
if (!check_email($email)) {
throw new \InvalidArgumentException("Invalid email: $email");
}
$exists = User::query()->where('email', $email)->exists();
if ($exists) {
throw new \InvalidArgumentException("The email address: $email is already in use");
}
if (mb_strlen($password) < 6 || mb_strlen($password) > 40) {
throw new \InvalidArgumentException("Invalid password: $password, it should be more than 6 character and less than 40 character");
}
if ($password != $confirmPassword) {
throw new \InvalidArgumentException("confirmPassword: $confirmPassword != password: $password");
}
$setting = get_setting('main');
$secret = mksecret();
$passhash = md5($secret . $password . $secret);
$insert = [
'username' => $username,
'passhash' => $passhash,
'secret' => $secret,
'email' => $email,
'stylesheet' => $setting['defstylesheet'],
'status' => 'confirmed',
'added' => now()->toDateTimeString(),
];
Log::info("create user: " . nexus_json_encode($insert));
return User::query()->create($insert);
}
public function getList(array $params)
{
$query = User::query();
$sortField = 'id';
$validSortFields = ['uploaded', 'downloaded', ];
if (!empty($params['sort']) && in_array($params['sort'], $validSortFields)) {
$sortField = $params['sort'];
}
$fields = ['id', 'username', 'avatar', 'email', 'uploaded', 'downloaded', 'class', 'added'];
if (!empty($params['fields'])) {
$fields = $params['fields'];
}
if (!empty($params['id'])) {
$query->where('id', $params['id']);
}
if (!empty($params['username'])) {
$query->where('username', $params['username']);
}
if (!empty($params['email'])) {
$query->where('email', $params['email']);
}
$result = $query->orderBy($sortField, 'desc')->select($fields)->paginate();
return $result;
list($sortField, $sortType) = $this->getSortFieldAndType($params);
$query->orderBy($sortField, $sortType);
return $query->paginate();
}
}
public function store(array $params)
{
$password = $params['password'];
if ($password != $params['password_confirmation']) {
throw new \InvalidArgumentException("password confirmation != password");
}
$setting = Setting::get('main');
$secret = mksecret();
$passhash = md5($secret . $password . $secret);
$data = [
'username' => $params['username'],
'email' => $params['email'],
'secret' => $secret,
'editsecret' => '',
'passhash' => $passhash,
'stylesheet' => $setting['defstylesheet'],
'added' => now()->toDateTimeString(),
'status' => User::STATUS_CONFIRMED,
];
$user = User::query()->create($data);
return $user;
}
public function resetPassword($username, $password, $passwordConfirmation)
{
if ($password != $passwordConfirmation) {
throw new \InvalidArgumentException("password confirmation != password");
}
$user = User::query()->where('username', $username)->firstOrFail();
$secret = mksecret();
$passhash = md5($secret . $password . $secret);
$update = [
'secret' => $secret,
'passhash' => $passhash,
];
$user->update($update);
return $user;
}
public function listClass()
{
$out = [];
foreach(User::$classes as $key => $value) {
$out[(string)$key] = $value['text'];
}
return $out;
}
}