From fc5cef0efddbb1141b0a371108a23fc7f6e8d860 Mon Sep 17 00:00:00 2001 From: Camil Staps Date: Mon, 5 Sep 2016 22:30:47 +0200 Subject: Vote on verb suggestions --- app/Http/Controllers/VerbController.php | 86 ++++++++++++++++++++++ app/Http/Middleware/Login.php | 34 +++++++++ app/Http/routes.php | 30 ++++++-- app/User.php | 9 +++ app/Verb.php | 42 ++++++++--- app/VerbAction.php | 41 +++++++++++ bootstrap/app.php | 1 + ...2016_09_04_081754_create_verb_actions_table.php | 38 ++++++++++ .../2016_09_04_081924_add_active_to_verbs.php | 31 ++++++++ public/css/hebrewparsetrainer.css | 17 +++++ public/js/moderators.js | 57 ++++++++++++++ resources/views/contribute.blade.php | 35 +++++++++ resources/views/layouts/master.blade.php | 33 ++++++++- resources/views/stats.blade.php | 51 +++---------- resources/views/suggest.blade.php | 76 +++++++++++++++++++ resources/views/suggestions.blade.php | 32 ++++++++ 16 files changed, 555 insertions(+), 58 deletions(-) create mode 100644 app/Http/Controllers/VerbController.php create mode 100644 app/Http/Middleware/Login.php create mode 100644 app/VerbAction.php create mode 100644 database/migrations/2016_09_04_081754_create_verb_actions_table.php create mode 100644 database/migrations/2016_09_04_081924_add_active_to_verbs.php create mode 100644 public/js/moderators.js create mode 100644 resources/views/contribute.blade.php create mode 100644 resources/views/suggest.blade.php create mode 100644 resources/views/suggestions.blade.php diff --git a/app/Http/Controllers/VerbController.php b/app/Http/Controllers/VerbController.php new file mode 100644 index 0000000..98b0579 --- /dev/null +++ b/app/Http/Controllers/VerbController.php @@ -0,0 +1,86 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +namespace App\Http\Controllers; + +use HebrewParseTrainer\Verb; +use HebrewParseTrainer\VerbAction; +use HebrewParseTrainer\RandomLog; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Input; +use Illuminate\Support\Facades\Auth; +use Laravel\Lumen\Routing\Controller as BaseController; + +class VerbController extends BaseController { + + public function random() { + $verbs = Verb::where('active', 1)->get(); + foreach (Input::get() as $col => $val) { + $val = explode(',', $val); + $verbs = $verbs->filter(function(Verb $item) use ($col, $val) { + return in_array($item->getAttribute($col), $val); + }); + } + $verb = $verbs->random(); + + $log = new RandomLog(); + $log->request = json_encode(Input::get()); + $log->response = $verb->id; + $log->ip = $_SERVER['REMOTE_ADDR']; + $log->save(); + + $obj = ['verb' => $verb, 'answers' => $verb->otherParsings()]; + return response()->json($obj); + } + + public function vote($choice, $verb_id) { + $verb = Verb::findOrFail($verb_id); + $user = Auth::user(); + + if ($verb->active) + return ['success' => false, 'message' => 'This verb has been accepted already.']; + + foreach ($verb->actions()->where('kind', VerbAction::KIND_VOTE)->get() as $vote) { + if ($vote->user->id == $user->id) { + $vote->delete(); + } + } + + $vote = new VerbAction; + $vote->user_id = $user->id; + $vote->verb_id = $verb_id; + $vote->kind = VerbAction::KIND_VOTE; + $vote->vote_weight = ($choice == 1 ? 1 : -1) * $user->voteWeight(); + $vote->save(); + + $message = 'You have voted.'; + + if ($verb->voteCount() >= Verb::ACCEPTED_VOTE_COUNT) { + $verb->active = 1; + $verb->save(); + } + + return [ + 'success' => true, + 'vote_weight' => $user->voteWeight(), + 'accepted' => (bool) $verb->active, + 'new_vote_count' => $verb->voteCount() + ]; + } + +} diff --git a/app/Http/Middleware/Login.php b/app/Http/Middleware/Login.php new file mode 100644 index 0000000..8a71104 --- /dev/null +++ b/app/Http/Middleware/Login.php @@ -0,0 +1,34 @@ +has('login') && !Auth::check()) { + return response('Unauthorized.', 401) + ->header('WWW-Authenticate', 'Basic realm="Please enter your email and password"'); + } + + return $next($request); + } +} diff --git a/app/Http/routes.php b/app/Http/routes.php index 9cf12b4..8313029 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -29,7 +29,10 @@ */ $app->group( - ['prefix' => parse_url(env('APP_URL'), PHP_URL_PATH)], + [ + 'prefix' => parse_url(env('APP_URL'), PHP_URL_PATH), + 'middleware' => 'login' + ], function ($app) { $app->get('/', function () use ($app) { @@ -44,15 +47,24 @@ $app->group( return \HebrewParseTrainer\Tense::all(); }); + $app->get('/verb/random', + 'App\Http\Controllers\VerbController@random'); + $app->get('/logout', function () use ($app) { - return response('Unauthorized.', 401) - ->header('WWW-Authenticate', 'Basic realm="Please click OK, then Cancel to logout."'); + return response('You have been logged out.', 401) + ->header( + 'WWW-Authenticate', + 'Basic realm="Please click OK, then Cancel to logout."'); }); - $app->get('/verb/random', 'App\Http\Controllers\RandomVerbController@show'); + $app->get('/contribute', function () use ($app) { + return view('contribute'); + }); - $app->get('/user/create', 'App\Http\Controllers\UserController@createForm'); - $app->post('/user/create', 'App\Http\Controllers\UserController@createForm'); + $app->get('/user/create', + 'App\Http\Controllers\UserController@createForm'); + $app->post('/user/create', + 'App\Http\Controllers\UserController@createForm'); $app->group( ['middleware' => 'auth:basic-http'], @@ -62,6 +74,12 @@ $app->group( return view('stats'); }); + $app->get('/verb/{id}/vote/{choice}', + 'App\Http\Controllers\VerbController@vote'); + + $app->post('/verb/suggest', + 'App\Http\Controllers\VerbController@suggest'); + }); }); diff --git a/app/User.php b/app/User.php index 3c1799d..dda14ca 100644 --- a/app/User.php +++ b/app/User.php @@ -28,6 +28,8 @@ class User extends Model implements Authenticatable { public $timestamps = false; protected $fillable = ['email', 'name']; + const VOTE_WEIGHT_BASE = 5; + public function changePoints($kind, $change, $verb = null) { $change = new PointChange; $change->user = $this->id; @@ -40,6 +42,13 @@ class User extends Model implements Authenticatable { $this->save(); } + public function voteWeight() { + if ($this->points <= 0) + return 0; + + return floor(log($this->points, self::VOTE_WEIGHT_BASE)); + } + public function setPasswordAttribute($pass) { $this->attributes['password'] = Hash::make($pass); } diff --git a/app/Verb.php b/app/Verb.php index 28e22ea..aa0db26 100644 --- a/app/Verb.php +++ b/app/Verb.php @@ -22,14 +22,38 @@ use Illuminate\Database\Eloquent\Model; class Verb extends Model { - protected $table = 'verbs'; - public $timestamps = false; - protected $fillable = ['verb', 'root', 'stem', 'tense', 'person', 'gender', 'number']; + protected $table = 'verbs'; + public $timestamps = false; + protected $fillable = ['verb', 'root', 'stem', 'tense', 'person', 'gender', 'number']; - public function otherParsings() - { - return self::where('verb', $this->verb)->get() - ->filter(function($v){return $v->verb === $this->verb;}); - } + const ACCEPTED_VOTE_COUNT = 1; -} \ No newline at end of file + public function actions() { + return $this->hasMany('HebrewParseTrainer\VerbAction'); + } + + public function otherParsings() { + return self::where('verb', $this->verb)->get() + ->filter(function($v){return $v->verb === $this->verb;}); + } + + public function voteCount() { + $votes = $this->actions()->where('kind', VerbAction::KIND_VOTE)->get(); + $total = 0; + foreach ($votes as $vote) + $total += $vote->vote_weight; + return $total; + } + + public function userVote(User $user) { + $votes = $this->actions() + ->where('kind', VerbAction::KIND_VOTE) + ->where('user_id', $user->id) + ->get(); + foreach ($votes as $vote) { + return $vote->vote_weight; + } + return 0; + } + +} diff --git a/app/VerbAction.php b/app/VerbAction.php new file mode 100644 index 0000000..79c8cd0 --- /dev/null +++ b/app/VerbAction.php @@ -0,0 +1,41 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +namespace HebrewParseTrainer; + +use Illuminate\Database\Eloquent\Model; + +class VerbAction extends Model { + + protected $table = 'verb_actions'; + public $timestamps = false; + protected $dates = ['date']; + protected $fillable = ['user_id', 'verb_id', 'kind', 'vote_weight', 'comment_text']; + + const KIND_SUGGEST = 1; + const KIND_VOTE = 2; + + public function verb() { + return $this->belongsTo('HebrewParseTrainer\Verb'); + } + + public function user() { + return $this->belongsTo('HebrewParseTrainer\User'); + } + +} diff --git a/bootstrap/app.php b/bootstrap/app.php index 04f448e..c72ab23 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -68,6 +68,7 @@ $app->singleton( $app->routeMiddleware([ 'auth' => App\Http\Middleware\Authenticate::class, + 'login' => App\Http\Middleware\Login::class, ]); /* diff --git a/database/migrations/2016_09_04_081754_create_verb_actions_table.php b/database/migrations/2016_09_04_081754_create_verb_actions_table.php new file mode 100644 index 0000000..d7b0f29 --- /dev/null +++ b/database/migrations/2016_09_04_081754_create_verb_actions_table.php @@ -0,0 +1,38 @@ +increments('id'); + $table->integer('user_id')->unsigned(); + $table->integer('verb_id')->unsigned(); + $table->tinyInteger('kind'); + $table->tinyInteger('vote_weight')->nullable(); + $table->string('comment_text')->nullable(); + $table->timestamp('date')->default(DB::raw('CURRENT_TIMESTAMP')); + + $table->foreign('user_id')->references('id')->on('users'); + $table->foreign('verb_id')->references('id')->on('verbs'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('verb_actions'); + } +} diff --git a/database/migrations/2016_09_04_081924_add_active_to_verbs.php b/database/migrations/2016_09_04_081924_add_active_to_verbs.php new file mode 100644 index 0000000..79c5993 --- /dev/null +++ b/database/migrations/2016_09_04_081924_add_active_to_verbs.php @@ -0,0 +1,31 @@ +boolean('active')->default(true); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('verbs', function (Blueprint $table) { + $table->dropColumn('active'); + }); + } +} diff --git a/public/css/hebrewparsetrainer.css b/public/css/hebrewparsetrainer.css index 54f0c2d..30ef258 100644 --- a/public/css/hebrewparsetrainer.css +++ b/public/css/hebrewparsetrainer.css @@ -20,6 +20,23 @@ src: url('fonts/EzraSIL.ttf'); } +body { + padding-top: 20px; +} + +.large { + font-size: 150%; +} + +.header { + border-bottom: 1px solid #e5e5e5; + margin-bottom: 30px; +} + +.suggestions td { + vertical-align: middle !important; +} + #trainer-404 { display: none; padding: 15px; diff --git a/public/js/moderators.js b/public/js/moderators.js new file mode 100644 index 0000000..2b192f8 --- /dev/null +++ b/public/js/moderators.js @@ -0,0 +1,57 @@ +/** + * HebrewParseTrainer - practice Hebrew verbs + * Copyright (C) 2015 Camil Staps + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +$(document).ready(function(){ + $('.vote').click(function(){ + var vote = parseInt($(this).data('vote')); + var verbId = $(this).data('verb'); + + var container = $(this).parent(); + + $.ajax({ + url: app_url + 'verb/' + verbId + '/vote/' + vote, + success: function(data) { + if (!data.success) { + fail(data.message); + return; + } + + var btns = container.find('.vote'); + if (vote) { + $(btns[0]).removeClass('btn-danger'); + $(btns[0]).addClass('btn-default'); + $(btns[1]).addClass('btn-success'); + $(btns[1]).removeClass('btn-default'); + } else { + $(btns[0]).addClass('btn-danger'); + $(btns[0]).removeClass('btn-default'); + $(btns[1]).removeClass('btn-success'); + $(btns[1]).addClass('btn-default'); + } + + container.find('.vote-count').text(data.new_vote_count); + + if (data.accepted) { + alert('This verb has now been accepted!'); + container.parent().remove(); + } + } + }); + + return true; + }); +}); diff --git a/resources/views/contribute.blade.php b/resources/views/contribute.blade.php new file mode 100644 index 0000000..4c3b6de --- /dev/null +++ b/resources/views/contribute.blade.php @@ -0,0 +1,35 @@ +@extends('layouts.master') + +@section('master-content') +

+ Thank you for wanting to help out! To expand our database, we are looking for volunteers to enter more verbs. +

+ +@if(!Auth::check()) + Login + Sign up +@endif + +

Here's how it works:

+ +
    +
  • Any user can suggest new verbs.
  • +
  • These have to be peer-reviewed by other contributors.
  • +
  • It has to get five votes to be accepted.
  • +
  • Contributors earn points for all accepted verbs they suggested.
  • +
  • The vote weight is dependent on the number of points a user has.
  • +
+ +@if(Auth::check()) +
+
+
+ @include('suggestions') +
+
+ @include('suggest') +
+
+@endif + +@endsection diff --git a/resources/views/layouts/master.blade.php b/resources/views/layouts/master.blade.php index 3c111da..199db2d 100644 --- a/resources/views/layouts/master.blade.php +++ b/resources/views/layouts/master.blade.php @@ -16,17 +16,43 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . --> + ['/', ''], + 'Contribute' => ['contribute', 'contribute'], +]; + +if (Auth::check()) { + $menu['Statistics'] = ['stats', 'stats']; + $menu['Logout'] = ['logout', 'logout']; +} +?> ParseTrainer + +
-