From 8fbff51809092fb58b8c8cf4c4f5ea1dcf13acbe Mon Sep 17 00:00:00 2001 From: Xavier Wang Date: Mon, 24 Jun 2019 15:13:44 +0800 Subject: [PATCH 1/4] create speaker controller --- app/Http/Controllers/SpeakerController.php | 159 ++++++++++++++++++ app/Http/Middleware/VerifyCsrfToken.php | 3 + app/Http/Requests/SpeakerRequest.php | 97 +++++++++++ app/Speaker.php | 70 ++++---- database/factories/SpeakerFactory.php | 39 +++++ ...dd_and_modify_fields_to_speakers_table.php | 38 +++++ database/seeds/DatabaseSeeder.php | 1 + database/seeds/SpeakersTableSeeder.php | 16 ++ routes/web.php | 3 + 9 files changed, 394 insertions(+), 32 deletions(-) create mode 100644 app/Http/Controllers/SpeakerController.php create mode 100644 app/Http/Requests/SpeakerRequest.php create mode 100644 database/factories/SpeakerFactory.php create mode 100644 database/migrations/2019_06_21_115922_add_and_modify_fields_to_speakers_table.php create mode 100644 database/seeds/SpeakersTableSeeder.php diff --git a/app/Http/Controllers/SpeakerController.php b/app/Http/Controllers/SpeakerController.php new file mode 100644 index 00000000..b24665c5 --- /dev/null +++ b/app/Http/Controllers/SpeakerController.php @@ -0,0 +1,159 @@ +input('search', ''); + $order_field = $request->input('orderby_field', 'id'); + $order_method = $request->input('orderby_method', 'desc'); + if ($request->input('all', false)) { + $speaker = Speaker::orderBy($order_field, $order_method)->get(); + } else { + $limit = $request->input('limit', 15); + $speaker = Speaker::Where(function ($query) use ($search) { + $query->orWhere('name', 'LIKE', '%' . $search . '%') + ->orWhere('company', 'LIKE', '%' . $search . '%') + ->orWhere('topic', 'LIKE', '%' . $search . '%'); + }) + ->orderBy($order_field, $order_method) + ->paginate($limit); + } + + return $this->returnSuccess('Success.', $speaker); + } + + /** + * Store a newly created resource in storage. + * + * @param SpeakerRequest $request + * @return \Illuminate\Http\JsonResponse + */ + public function store(SpeakerRequest $request) + { + $data = $request->only(['name', 'speaker_type']); + $data['last_edited_by'] = auth()->user()->name; + $speaker = Speaker::create($data); + + return $this->returnSuccess('Store success.', $speaker); + } + + /** + * @param Speaker $speaker + * @return \Illuminate\Http\JsonResponse + */ + public function show(Speaker $speaker) + { + return $this->returnSuccess('Show success.', $speaker); + } + + /** + * Update the specified resource in storage. + * + * @param SpeakerRequest $request + * @param Speaker $speaker + * @return \Illuminate\Http\JsonResponse + */ + public function update(SpeakerRequest $request, Speaker $speaker) + { + $data = $request->except('file', 'last_edited_by'); + $data['last_edited_by'] = auth()->user()->name; + $speaker->update($data); + + if ($request->hasFile('file')) { + $speaker = $this->savePhoto($request->file('file'), $speaker); + } + + return $this->returnSuccess('Update success.', $speaker); + } + + /** + * @param Speaker $speaker + * @return \Illuminate\Http\JsonResponse + */ + public function destroy(Speaker $speaker) + { + $speaker->delete(); + + return $this->returnSuccess('destroy success.'); + } + + /** + * @param $accessKey + * @return \Illuminate\Http\JsonResponse + */ + public function externalShow($accessKey) + { + $speaker = Speaker::where('access_key', '=', $accessKey)->first(); + return $this->returnSuccess('Success.', $speaker); + } + + /** + * @param SpeakerRequest $request + * @param $accessKey + * @return \Illuminate\Http\JsonResponse + */ + public function externalUpdate(SpeakerRequest $request, $accessKey) + { + $speaker = Speaker::where('access_key', '=', $accessKey)->first(); + if ($speaker) { + $data = $request->except('file', 'speaker_status', 'speaker_type', 'last_edited_by'); + $data['last_edited_by'] = $speaker->name; + $speaker->update($data); + + if ($request->hasFile('file')) { + $speaker = $this->savePhoto($request->file('file'), $speaker); + } + } + + return $this->returnSuccess('Update success.', $speaker); + } + + + /** + * Update the specified resource in storage. + * + * @param UploadedFile $image + * @param Speaker $speaker + * @return Speaker + */ + public function savePhoto(UploadedFile $image, Speaker $speaker) + { + $newFileName = $speaker->name . '-' . Str::random(8) . '.' . $image->getClientOriginalExtension(); + $image->move(public_path(Speaker::$photoPath), $newFileName); + + $speaker->photo = Speaker::$photoPath . '/' . $newFileName; + $speaker->save(); + + return $speaker; + } + + public function savePhotoTest(Request $request, $accessKey) + { + // print_r($request->file('file')); + // die(); + $speaker = Speaker::where('access_key', '=', $accessKey)->first(); + if ($speaker) { + $speaker = $this->savePhoto($request->file('file'), $speaker); + } + + return $this->returnSuccess('Update success.', $speaker); + } +} diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 51608b1d..4824c0e0 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -20,5 +20,8 @@ class VerifyCsrfToken extends Middleware */ protected $except = [ '/telegram/web/hook/*', + '/api/speaker', + '/api/speaker/*', + '/speaker/*', ]; } diff --git a/app/Http/Requests/SpeakerRequest.php b/app/Http/Requests/SpeakerRequest.php new file mode 100644 index 00000000..c3546a50 --- /dev/null +++ b/app/Http/Requests/SpeakerRequest.php @@ -0,0 +1,97 @@ + 'required|string', + 'name_e' => 'nullable|string|max:100', + 'company' => 'nullable|string|max:100', + 'job_title' => 'nullable|string|max:100', + 'bio' => 'nullable|string|max:120', + 'bio_e' => 'nullable|string|max:240', + 'file' => 'nullable|image', + 'link_fb' => 'nullable|url', + 'link_github' => 'nullable|url', + 'link_twitter' => 'nullable|url', + 'link_other' => 'nullable|url', + 'topic' => 'nullable|string|max:32', + 'topic_e' => 'nullable|string|max:64', + 'summary' => 'nullable|string|max:240', + 'summary_e' => 'nullable|string|max:480', + 'tag' => 'nullable|array', + 'level' => 'nullable|integer|min:0|max:' . count(Speaker::$levelItem), + 'license' => 'nullable|integer|min:0|max:' . count(Speaker::$licenseItem), + 'promotion' => 'nullable|boolean', + 'tshirt_size' => 'nullable|integer|min:0|max:' . count(Speaker::$tshirtSizeItem), + 'need_parking_space' => 'nullable|boolean', + 'has_dinner' => 'nullable|boolean', + 'meal_preference' => 'nullable|integer|min:0|max:' . count(Speaker::$mealPreferenceItem), + 'has_companion' => 'nullable|integer|min:0|max:10', + 'speaker_status' => 'nullable|integer|min:0|max:' . count(Speaker::$speakerStatusItem), + 'speaker_type' => 'nullable|integer|min:0|max:' . count(Speaker::$speakerTypeItem), + 'note' => 'nullable|string', + 'last_edited_by' => 'nullable|string', + ]; + } + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + // public function attributes() + // { + // return [ + // 'name' => '姓名', + // 'name_e' => '英文名稱', + // 'company' => '公司/組織', + // 'job_title' => '職稱', + // 'bio' => '個人介紹', + // 'bio_e' => '個人介紹(英文)', + // 'photo' => '照片', + // 'link_fb' => 'Facebook', + // 'link_github' => 'Github', + // 'link_twitter' => 'Twitter', + // 'link_other' => '其他(如Website/Blog)', + // 'topic' => '演講主題', + // 'topic_e' => '演講主題(英文)', + // 'summary' => '演講摘要', + // 'summary_e' => '演講摘要(英文)', + // 'tag' => '標籤', + // 'level' => '難易度', + // 'license' => '授權方式', + // 'promotion' => '是否同意公開宣傳', + // 'tshirt_size' => 'T-shirt 尺寸', + // 'need_parking_space' => '您是否需有停車需求', + // 'has_dinner' => '敬邀參加講者晚宴', + // 'meal_preference' => '葷素食偏好', + // 'has_companion' => '晚宴攜伴人數', + // 'speaker_status' => '狀態', + // 'speaker_type' => '類型', + // 'note' => '備註', + // 'last_edited_by' => '最後更新者', + // ]; + // } +} diff --git a/app/Speaker.php b/app/Speaker.php index 79d0480f..0000e21f 100644 --- a/app/Speaker.php +++ b/app/Speaker.php @@ -7,6 +7,7 @@ class Speaker extends Model { + public static $photoPath = '/images/speaker'; public static $tagItem = [ 'AI', 'AR/VR', @@ -48,32 +49,25 @@ class Speaker extends Model '全素', '奶蛋素', ]; + public static $speakerStatusItem = [ + '待確認', + '確認中', + '已確認', + '下架', + ]; + public static $speakerTypeItem = [ + '贊助商', + 'CFP', + 'CFR', + '內推', + '其他', + ]; + protected $table = 'speakers'; - protected $fillable = [ - 'name', - 'name_e', - 'company', - 'job_title', - 'bio', - 'bio_e', + protected $guarded = [ + 'id', 'photo', - 'link_fb', - 'link_github', - 'link_twitter', - 'link_other', - 'topic', - 'topic_e', - 'summary', - 'summary_e', - 'tag', - 'level', - 'license', - 'promotion', - 'tshirt_size', - 'need_parking_space', - 'has_dinner', - 'meal_preference', - 'has_companion', + 'access_key', 'access_secret', ]; protected $appends = [ @@ -81,7 +75,9 @@ class Speaker extends Model 'level_text', 'license_text', 'tshirt_size_text', - 'meal_preference_text' + 'meal_preference_text', + 'speaker_status_text', + 'speaker_type_text', ]; protected $casts = ['tag' => 'array']; @@ -94,11 +90,11 @@ public static function boot() }); } - public function getTaxTextAttribute() + public function getTagTextAttribute() { $tmp_collection = collect($this->tag); $new_collection = $tmp_collection->map(function ($item) { - return $this->tagItem[$item] ?? ''; + return self::$tagItem[$item] ?? ''; })->reject(function ($item) { return empty($item); }); @@ -108,21 +104,31 @@ public function getTaxTextAttribute() public function getLevelTextAttribute() { - return $this->levelItem[$this->level] ?? ''; + return self::$levelItem[$this->level] ?? ''; } public function getLicenseTextAttribute() { - return $this->licenseItem[$this->license] ?? ''; + return self::$licenseItem[$this->license] ?? ''; } - public function getTshirtTextAttribute() + public function getTshirtSizeTextAttribute() { - return $this->tshirtSizeItem[$this->tshirt_size] ?? ''; + return self::$tshirtSizeItem[$this->tshirt_size] ?? ''; } public function getMealPreferenceTextAttribute() { - return $this->mealPreferenceItem[$this->meal_preference] ?? ''; + return self::$mealPreferenceItem[$this->meal_preference] ?? ''; + } + + public function getSpeakerStatusTextAttribute() + { + return self::$speakerStatusItem[$this->speaker_status] ?? ''; + } + + public function getSpeakerTypeTextAttribute() + { + return self::$speakerTypeItem[$this->speaker_type] ?? ''; } } diff --git a/database/factories/SpeakerFactory.php b/database/factories/SpeakerFactory.php new file mode 100644 index 00000000..b727e080 --- /dev/null +++ b/database/factories/SpeakerFactory.php @@ -0,0 +1,39 @@ +define(Speaker::class, function (Faker\Generator $faker) { + $zhFaker = Faker\Factory::create('zh_TW'); + + $tags = $faker->randomElements(array_keys(Speaker::$tagItem), $faker->numberBetween(0, count(Speaker::$tagItem) -1)); + sort($tags); + + return [ + 'name' => $zhFaker->name, + 'name_e' => $faker->name, + 'company' => $zhFaker->company, + 'job_title' => $zhFaker->jobTitle, + 'bio' => $zhFaker->text(120), + 'bio_e' => $faker->text(240), + 'photo' => 'https://picsum.photos/200', + 'link_fb' => $faker->url, + 'link_github' => $faker->url, + 'link_twitter' => $faker->url, + 'link_other' => $faker->url, + 'topic' => $zhFaker->text(16), + 'topic_e' => $faker->text(32), + 'summary' => $zhFaker->text(240), + 'summary_e' => $faker->text(480), + 'tag' => $tags, + 'level' => rand(0, count(Speaker::$levelItem) -1), + 'license' => rand(0, count(Speaker::$licenseItem) -1), + 'promotion' => rand(0, 1), + 'tshirt_size' => rand(0, count(Speaker::$tshirtSizeItem) -1), + 'need_parking_space' => rand(0, 1), + 'has_dinner' => rand(0, 1), + 'meal_preference' => rand(0, count(Speaker::$mealPreferenceItem) -1), + 'has_companion' => rand(0, 10), + 'speaker_status' => rand(0, count(Speaker::$speakerStatusItem) -1), + 'speaker_type' => rand(0, count(Speaker::$speakerTypeItem) -1), + ]; +}); diff --git a/database/migrations/2019_06_21_115922_add_and_modify_fields_to_speakers_table.php b/database/migrations/2019_06_21_115922_add_and_modify_fields_to_speakers_table.php new file mode 100644 index 00000000..bbf4714c --- /dev/null +++ b/database/migrations/2019_06_21_115922_add_and_modify_fields_to_speakers_table.php @@ -0,0 +1,38 @@ +tinyInteger('speaker_status')->nullable()->after('has_companion'); + $table->tinyInteger('speaker_type')->nullable()->after('speaker_status'); + $table->text('note')->nullable()->after('speaker_type'); + $table->string('last_edited_by')->default('')->after('note'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('speakers', function (Blueprint $table) { + $table->dropColumn('speaker_status'); + $table->dropColumn('speaker_type'); + $table->dropColumn('note'); + $table->dropColumn('last_edited_by'); + }); + } +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index 57339986..55a67390 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -17,5 +17,6 @@ public function run() $this->call(StaffTableSeeder::class); $this->call(SystemLogTableSeeder::class); $this->call(SystemLogTypeTableSeeder::class); + $this->call(SpeakersTableSeeder::class); } } diff --git a/database/seeds/SpeakersTableSeeder.php b/database/seeds/SpeakersTableSeeder.php new file mode 100644 index 00000000..31aa4289 --- /dev/null +++ b/database/seeds/SpeakersTableSeeder.php @@ -0,0 +1,16 @@ +create(); + } +} diff --git a/routes/web.php b/routes/web.php index 323614ae..4917b3aa 100644 --- a/routes/web.php +++ b/routes/web.php @@ -23,6 +23,8 @@ Route::post('/login', 'AuthController@postLogin'); Route::get('/logout', 'AuthController@logout'); Route::post('/telegram/web/hook/' . env('BOT_WEB_HOOK_HASH'), 'TelegramHookController@handle'); +Route::get('/speaker/{accessKey}', 'SpeakerController@externalShow'); +Route::post('/speaker/{accessKey}', 'SpeakerController@externalUpdate'); Route::group(['prefix' => 'api', 'middleware' => 'auth'], function () { Route::get('/whoami', function () { @@ -54,4 +56,5 @@ ->where(['model' => '[a-z]+']); Route::apiResource('system-log', 'SystemLogController', ['only' => ['index']]); Route::apiResource('system-log-type', 'SystemLogTypeController', ['only' => ['index']]); + Route::apiResource('speaker', 'SpeakerController'); }); From 719ce4ee0fdcbc7205c59eef24a4e4b7e790944c Mon Sep 17 00:00:00 2001 From: Xavier Wang Date: Mon, 24 Jun 2019 15:53:21 +0800 Subject: [PATCH 2/4] add endpoint to get options --- app/Http/Controllers/SpeakerController.php | 18 ++++++++++++++++++ routes/web.php | 1 + 2 files changed, 19 insertions(+) diff --git a/app/Http/Controllers/SpeakerController.php b/app/Http/Controllers/SpeakerController.php index b24665c5..7d280b11 100644 --- a/app/Http/Controllers/SpeakerController.php +++ b/app/Http/Controllers/SpeakerController.php @@ -95,6 +95,24 @@ public function destroy(Speaker $speaker) return $this->returnSuccess('destroy success.'); } + /** + * @return \Illuminate\Http\JsonResponse + */ + public function getOptions() + { + $options = [ + 'tagItem' => Speaker::$tagItem, + 'levelItem' => Speaker::$levelItem, + 'licenseItem' => Speaker::$licenseItem, + 'tshirtSizeItem' => Speaker::$tshirtSizeItem, + 'mealPreferenceItem' => Speaker::$mealPreferenceItem, + 'speakerStatusItem' => Speaker::$speakerStatusItem, + 'speakerTypeItem' => Speaker::$speakerTypeItem, + ]; + + return $this->returnSuccess('Success.', $options); + } + /** * @param $accessKey * @return \Illuminate\Http\JsonResponse diff --git a/routes/web.php b/routes/web.php index 4917b3aa..01ec16a5 100644 --- a/routes/web.php +++ b/routes/web.php @@ -23,6 +23,7 @@ Route::post('/login', 'AuthController@postLogin'); Route::get('/logout', 'AuthController@logout'); Route::post('/telegram/web/hook/' . env('BOT_WEB_HOOK_HASH'), 'TelegramHookController@handle'); +Route::get('/speaker/get-options', 'SpeakerController@getOptions'); Route::get('/speaker/{accessKey}', 'SpeakerController@externalShow'); Route::post('/speaker/{accessKey}', 'SpeakerController@externalUpdate'); From c6d0014f50b3174275d1db4953e69139d66f471d Mon Sep 17 00:00:00 2001 From: Xavier Wang Date: Mon, 24 Jun 2019 22:03:05 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E6=B8=AC=E8=A9=A6?= =?UTF-8?q?=E7=94=A8=E7=9A=84=E7=A8=8B=E5=BC=8F=E4=B8=A6=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=20coding=20style?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/SpeakerController.php | 14 -------------- app/Http/Middleware/VerifyCsrfToken.php | 2 -- database/factories/SpeakerFactory.php | 12 ++++++------ 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/app/Http/Controllers/SpeakerController.php b/app/Http/Controllers/SpeakerController.php index 7d280b11..1137fc66 100644 --- a/app/Http/Controllers/SpeakerController.php +++ b/app/Http/Controllers/SpeakerController.php @@ -2,13 +2,11 @@ namespace App\Http\Controllers; -use App\User; use App\Speaker; use App\Http\Requests\SpeakerRequest; use Illuminate\Http\Request; use Illuminate\Http\UploadedFile; use Illuminate\Support\Str; -use Illuminate\Support\Facades\Storage; class SpeakerController extends Controller { @@ -162,16 +160,4 @@ public function savePhoto(UploadedFile $image, Speaker $speaker) return $speaker; } - - public function savePhotoTest(Request $request, $accessKey) - { - // print_r($request->file('file')); - // die(); - $speaker = Speaker::where('access_key', '=', $accessKey)->first(); - if ($speaker) { - $speaker = $this->savePhoto($request->file('file'), $speaker); - } - - return $this->returnSuccess('Update success.', $speaker); - } } diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 4824c0e0..6946bfcb 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -20,8 +20,6 @@ class VerifyCsrfToken extends Middleware */ protected $except = [ '/telegram/web/hook/*', - '/api/speaker', - '/api/speaker/*', '/speaker/*', ]; } diff --git a/database/factories/SpeakerFactory.php b/database/factories/SpeakerFactory.php index b727e080..62a3181a 100644 --- a/database/factories/SpeakerFactory.php +++ b/database/factories/SpeakerFactory.php @@ -5,7 +5,7 @@ $factory->define(Speaker::class, function (Faker\Generator $faker) { $zhFaker = Faker\Factory::create('zh_TW'); - $tags = $faker->randomElements(array_keys(Speaker::$tagItem), $faker->numberBetween(0, count(Speaker::$tagItem) -1)); + $tags = $faker->randomElements(array_keys(Speaker::$tagItem), $faker->numberBetween(0, count(Speaker::$tagItem) - 1)); sort($tags); return [ @@ -25,15 +25,15 @@ 'summary' => $zhFaker->text(240), 'summary_e' => $faker->text(480), 'tag' => $tags, - 'level' => rand(0, count(Speaker::$levelItem) -1), - 'license' => rand(0, count(Speaker::$licenseItem) -1), + 'level' => rand(0, count(Speaker::$levelItem) - 1), + 'license' => rand(0, count(Speaker::$licenseItem) - 1), 'promotion' => rand(0, 1), - 'tshirt_size' => rand(0, count(Speaker::$tshirtSizeItem) -1), + 'tshirt_size' => rand(0, count(Speaker::$tshirtSizeItem) - 1), 'need_parking_space' => rand(0, 1), 'has_dinner' => rand(0, 1), - 'meal_preference' => rand(0, count(Speaker::$mealPreferenceItem) -1), + 'meal_preference' => rand(0, count(Speaker::$mealPreferenceItem) - 1), 'has_companion' => rand(0, 10), 'speaker_status' => rand(0, count(Speaker::$speakerStatusItem) -1), - 'speaker_type' => rand(0, count(Speaker::$speakerTypeItem) -1), + 'speaker_type' => rand(0, count(Speaker::$speakerTypeItem) - 1), ]; }); From 0e7cf837bcd1aeee4787dee32a94612be1a78980 Mon Sep 17 00:00:00 2001 From: Xavier Wang Date: Mon, 24 Jun 2019 22:36:13 +0800 Subject: [PATCH 4/4] fix CS issue --- database/factories/SpeakerFactory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/factories/SpeakerFactory.php b/database/factories/SpeakerFactory.php index 62a3181a..cb34f8da 100644 --- a/database/factories/SpeakerFactory.php +++ b/database/factories/SpeakerFactory.php @@ -33,7 +33,7 @@ 'has_dinner' => rand(0, 1), 'meal_preference' => rand(0, count(Speaker::$mealPreferenceItem) - 1), 'has_companion' => rand(0, 10), - 'speaker_status' => rand(0, count(Speaker::$speakerStatusItem) -1), + 'speaker_status' => rand(0, count(Speaker::$speakerStatusItem) - 1), 'speaker_type' => rand(0, count(Speaker::$speakerTypeItem) - 1), ]; });