Relationship between three tables - additional information - eloquent

I have a problem in Laravel.
I have 3 models:
Thread - fuser_id
Post - thread_id, fuser_id
User - id
I want to get the last post that was created for a given thread and information about the user who wrote it.
Model Thread:
public function lastpost()
{
return $this->hasOneThrough(Post::class, User::class, 'id', 'fuser_id', 'fuser_id', 'id);
}
Controller:
$threads = Thread::where('category_id', $categories->id)->with('lastPosts')->get();
Receives user ID information, but needs more information, which is in the database.

Related

Return additional relationship data in Laravel Pivot table (Laravel 9, Inertia, Vue)

I'm in a Laravel 9 project trying to access extra relationship data in a pivot table - I'm sure this will have been answered before but the answers are confusing me more!
I have a set of courses, a set of cohorts and a set of sessions. Each course can be run multiple times (each one of these I've called an instance), with multiple sessions, and one cohort.
I've created an instances table in my database which has:
course_id
cohort_id
I have a sessions table which has data on each individual session, for example a name, a review date and review status.
I then have a pivot table called instance_sessions which looks like this:
instance_id
session_id
date
trainer_id
zoom_room_id
cohort_id
I'm not sure I need the cohort id but thats by the by :-)
My question is how can I get the relationships from this pivot table to the trainer and the zoom room?
In my instance model my relationships are setup like this:
public function sessions(){
return $this->belongsToMany(Session::class)->withPivot(['date'])
->using(InstanceSession::class);
}
public function course(){
return $this->belongsTo(Course::class);
}
public function cohort(){
return $this->hasOne(Cohort::class, 'id', 'cohort_id');
}
Session model:
function instances(){
return $this->belongsToMany(Instance::class);
}
Instance Session Model:
class InstanceSession extends Pivot
{
use HasFactory;
protected $table = 'instance_session';
protected $fillable = ['instance_id', 'session_id', 'date', 'trainer_id', 'zoom_room_id', 'cohort_id' ];
public function instance(){
return $this->belongsTo(Instance::class);
}
public function session(){
return $this->belongsToMany(Session::class);
}
public function zoomRoom(){
return $this->belongsTo(ZoomRoom::class);
}
public function trainer(){
return $this->hasOne(Trainer::class);
}
}
And my Instance Controller:
$instances = Instance::with(['course', 'cohort', 'sessions'])->get();
which returns the data to an Inertia/Vue view. I've attached an image of the data being retrieved.
Any help would be gratefully received as I'm clearly not understanding something :-)
EDIT: End goal being that I want to show on the front end all of the instances with all the sessions that belong to that instance, the date those sessions take place on, the trainer name (from trainers table) taking the session and the zoom room link (from zoom room table) for the session.

Eloquent destroy() isn't deleting row in db - Laravel 5.6

I am trying to make a feature on my site to delete a row in the database for news articles and it doesn't seem to be working. Here is my Route and my Controller:
Route:
Route::get('/admin/news/delete/{id}', 'NewsController#destroy');
Controller:
public function destroy(News $id)
{
News::destroy($id);
return redirect('/admin/news')->with('status', 'News successfully deleted.');
}
It returns with the success message but the row never gets deleted in the database. What am i missing?
Since you are using route model binding, $id already contains a News instance (not an id).
You should probably rename it:
Route::get('/admin/news/delete/{news}', 'NewsController#destroy');
public function destroy(News $news)
{
$news->delete();
return redirect('/admin/news')->with('status', 'News successfully deleted.');
}
Also, it's recommended to use POST requests when you delete data.

Yii2: Populating model field with value from many-to-many relation

We are trying to set-up our REST API in a way that a user can only retrieve data related to his account. Uptill now, We've only had a public API and it all worked like a charm.
The public API will still get used, so we started from scratch.
We have a User model, extending ActiveRecord and implementing IdentityInterface. A User will have a relation to a Customer model (Many:Many, but at least one). A User will have only one MASTER Customer.
The above is saved in the database using 3 tables app_customer_users,app_customers and link_customer_users_customers. The join table contains a boolean field master.
In the User model:
public function getCustomers() {
return $this->hasMany(Customer::className(), ['id' => 'customer_id'])
->viaTable('link_customer_users_customers', ['user_id' => 'id']);
}
In the Customer model:
public function getUsers() {
return $this->hasMany(User::className(), ['id' => 'user_id'])
->viaTable('link_customer_users_customers', ['customer_id' => 'id']);
}
This works great if we would request all customers, they'll have 'users' populated (if we add it in extraFields etc, etc...)
The new API uses Basic user:pass in authentication, and we can get the current user object/identity by calling Yii::$app->user->getIdentity().
NOW THE PROBLEM
We would like to be able to include the Customer ID when a user connects in a way that we can get it by calling Yii::$app->user->getIdentity()->customer_id OR Yii::$app->user->getIdentity()->getCustomerId() The Customer ID should be the ID where master in the join table == true.
We've tried adding it to fields, as we did before with hasOne relations, but in this case it does not seem to work:
$fields['customer_id'] = function($model) {
return $model->getCustomers()->where('link_customer_users_customers.master', true)->one()->customer_id;
}; // probably the where part is our porblem?
And we've tried creating a custom getter like this:
public function getCustomerId() {
return $this->getCustomers()->andFilterWhere(['link_customer_users_customers.master' => true])->one()->customer_id;
}
This last attempt resulted in an error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'link_customer_users_customers.master' in 'where clause'\nThe SQL being executed was: SELECT * FROM `app_customers` WHERE (`link_customer_users_customers`.`master`=TRUE) AND (`id`='93')
We've been searching in the docs and on Google, but did not find an example on how to do this.
SOLUTION
Based on accepted answer we added some code.
In User model:
// Include customer always with user
public static function findIdentity($id) {
return static::findOne($id)->with('customer');
}
// Next to `getCustomers()`, added this `hasOne` relation to get only the customer from
// which the current user is the master user
public function getCustomer() {
return $this->hasOne(Customer::className(), ['id' => 'customer_id'])
->viaTable('link_customer_users_customers', ['user_id' => 'id'], function ($query) {
$query->andWhere(['master' => 1]);
});
}
// Getter, just because
public function getCustomerId() {
return $this->customer->id;
}
And now we can get the ID from the customer by calling Yii::$app->user->getIdentity()->customer->id OR Yii::$app->user->identity->customer->id etc, etc.. in the project..
You should add a relation like below and use an anonymous function as the third parameter to the viaTable to add another condition to check master field as true.
public function getMasterCustomerId() {
return $this->hasOne(Customer::className(), ['id' => 'customer_id'])
->viaTable('link_customer_users_customers', ['user_id' => 'id'],function($query){
$query->andWhere(['master' => 1]);
});
}
and then you can use it for the logged in user as below.
Yii::$app->user->identity->masterCustomerId->id;

How to use custom Roles using Windows Authentication in MVC Application

I am developing an internal MVC Application using Windows Authentication (WA). Authenticating users using WA is straight forward, however; with respect to user Roles, I have the following requirements:
We will use custom Roles ignoring the AD Roles. For example, a user
may have a 'Manager' role in the AD but his app role is set to
'Supervisor'. After the User is authenticated, the system will fetch
the user roles and set the CurrentPrincipal accordingly.
For the above, I plan to have 3 tables including User, Role
and UserRole. The Role table has the custom roles while the
User table consists of company users. The UserRole table will define
the mapping between User and their Role(s). The issue I see with this approach
is to pre-populate all 3 tables. The User table must have the list of all
company employees and is maintained for new/inactive employees. The UserRole
table should be set with each user and his role(s) before he logs in.
In the application, User are assigned to different tasks (for example John is
supervising Vehicles) plus we need to maintain user activity logs. Assuming
the above two points are valid, is it OK to use the ID field in the User
table for this purpose?
There is also a chance that later, we may deploy the application
over the public domain. In such a case, how can we use the existing
User/Role infrastructure for this purpose.
Thanks in advance.
You are in exactly the same boat as me, my friend! I managed to do this through a Custom Authorization Attribute. Here are a couple of points that I have stumbled on through this process.
I did not create my own user table. You can, but you can query AD for users depending on the amount of users on your domain and link it to your Roles / Activities tables using the Guid to search. If you do create a users table that mirrors AD, use the Guid for the user. This way, if the login/name/anything else changes, the Guid stays the same.
Custom authorization attribute:
namespace YourSite.Attributes
{
[AttributeUsage(AttributeTargets.Method)]
public class AuthRoleAttribute : ActionFilterAttribute
{
public string RoleName { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!ViewIsAuthorizedActivity(RoleName)) // create a bool function to see if your user is in this role... check your db or whatever
{
string requestMethod = filterContext.HttpContext.Request.HttpMethod;
if (requestMethod == "GET")// I chose two different means of forbidding a user... this just hides the part of the page based on the #if(ViewBag.Authorization = "FORBIDDEN") where you render a partial, else show the view
{
filterContext.Controller.ViewBag.Authorization = "FORBIDDEN";
}
else if (requestMethod == "POST") // This prevents over posting by redirecting them completely away from that controller... also prevents them from posting if they have the page loaded and you remove permission
{ // Hiding a part of the page doesn't matter for the POST if the page is already loaded
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "controller", "Home" },
{ "action", "Forbidden" },
{ "area", ""}
});
}
base.OnActionExecuting(filterContext);
}
}
}
}
How GETs are handled in the view:
#if (ViewBag.Authorization == "FORBIDDEN")
{
ViewBag.Title = "Forbidden!";
#Html.Partial("~/Views/Forbidden.cshtml");
}
else
<!-- User is not forbidden and have the view here -->
Note that for the POSTs the user is redirected away from the controller to the Forbidden controller.
Attribute on controller:
[AuthRole(RoleName = "Admin")]
public ActionResult YourController()
I also made a extension to the User so things may be hidden in the view if they don't have permission:
public static bool IsAuthorized(this IPrincipal user, string roleName)
{
return Attributes.AuthActivityAttribute.ViewIsAuthorizedByRole(roleName); // function determining if the user is in that role, therefore show what you want to show in the view or don't show it if false
}
Which is called by:
#if (User.IsAuthorized("Admin"))
{
<!-- show something, a link, etc. -->
}
Hopefully this gives you a better head start than I had. Let me know if you have questions.

Redux getter if store initial state is empty

I was watching a talk about Reactjs/Flux during which they showed a code inside Facebook app that is used to preview user profile picture.
Link to the Youtube video.
Snippet:
class ProfilePicture extends React.component{
render(){
let info = usersStore.getUser(this.props.id);
return (<img src={info.uri} />);
}
}
This little snippet had me thinking about how do they implement getUser inside the user store?
I understand that Flux has multiple stores while Flux keeps a single source. Yet such snippet had me thinking that may be.. if I'm fetching a comment for the server the returned value is something like:
/posts
[
{
id:1,
title: 'This is post title',
userId:1,
userName: 'User name',
picture: 'http://user.picture',
}
]
Tet the above ProfilePicture component snippet doesn't read user info from the Post object, it reads it from users Store object, so that made lot sense to me since in my app a user typically have access to <200 other users. So maybe I should stop returning user info with every post. And instead I'd use a user store that store all users info, cache them inlocal storage, retrieve them from server, if needed.
So I'm considering changing my API to respond for posts to:
/post : select id,title from posts;
[
{id:1,title:'Post title'}
]
And have another endpoint that return users info to be loaded lazily by Redux.
So in my Redux I need to change initial state from:
InitialState = {
posts:[] //where all posts and the user who posted this post is stored
}
Into:
InitialState = {
posts:[] //posts and userId only
users:[],//users data <---- here i need a getter function
}
Now one way I can populate the users store is during app initialization. But is this the right timing to do it while app starts up or should I wait until a post component is loaded first, and needs to access data of specific user/s then I'd load the users?
Is it possible for Redux to have getters setters?
class ProfilePicture extends React.component{
render(){
let info = usersStore.getUser(this.props.id);
return (<img src={info.uri} />);
}
}
** Why are they using usersStore.getUser instead of just passing the user by id to the mapStateToProps in the connect function of Redux ?