Files
opnform-host-nginx/api/app/Models/User.php
Chirag Chhatrala 2366f9515d Readonly User (#637)
* Readonly User

* Refactor FormPolicy and TemplatePolicy to centralize write operation logic

- Introduced a private method `canPerformWriteOperation` in both FormPolicy and TemplatePolicy to encapsulate the logic for determining if a user can perform write operations on the respective models.
- Updated the `update`, `delete`, `restore`, and `forceDelete` methods in FormPolicy to use the new method for improved readability and maintainability.
- Simplified the `update` and `delete` methods in TemplatePolicy to leverage the centralized write operation logic.

This refactoring enhances code clarity and reduces duplication across policy classes.

* Refactor user and workspace permissions handling

- Updated FormController to authorize form creation based on workspace context.
- Removed the `is_readonly` attribute from UserResource and integrated it into WorkspaceResource for better encapsulation.
- Refactored User model to eliminate the `getIsReadonlyAttribute` method, shifting readonly logic to the Workspace model.
- Adjusted FormPolicy and TemplatePolicy to utilize workspace readonly checks for user permissions.
- Updated various frontend components to reference workspace readonly status instead of user readonly status, enhancing clarity and consistency in permission handling.

These changes improve the management of user permissions in relation to workspaces, ensuring a more robust and maintainable authorization system.

* Fix isReadonlyUser

* fix pint

---------

Co-authored-by: Julien Nahum <julien@nahum.net>
2024-12-30 14:35:23 +01:00

272 lines
6.4 KiB
PHP

<?php
namespace App\Models;
use App\Models\Forms\Form;
use App\Notifications\ResetPassword;
use App\Notifications\VerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Cashier\Billable;
use Laravel\Sanctum\HasApiTokens;
use Tymon\JWTAuth\Contracts\JWTSubject;
class User extends Authenticatable implements JWTSubject
{
use Billable;
use HasFactory;
use Notifiable;
use HasApiTokens;
public const ROLE_ADMIN = 'admin';
public const ROLE_USER = 'user';
public const ROLE_READONLY = 'readonly';
public const ROLES = [
self::ROLE_ADMIN,
self::ROLE_USER,
self::ROLE_READONLY,
];
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
'hear_about_us',
'utm_data',
'meta'
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
'hear_about_us',
'meta'
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected function casts()
{
return [
'email_verified_at' => 'datetime',
'utm_data' => 'array',
'meta' => 'array',
];
}
/**
* The accessors to append to the model's array form.
*
* @var array
*/
protected $appends = [
'photo_url',
];
public function ownsForm(Form $form)
{
return $this->workspaces()->where('workspaces.id', $form->workspace_id)->exists();
}
public function ownsWorkspace(Workspace $workspace)
{
return $this->workspaces()->where('workspaces.id', $workspace->id)->exists();
}
/**
* Get the profile photo URL attribute.
*
* @return string
*/
public function getPhotoUrlAttribute()
{
return vsprintf('https://www.gravatar.com/avatar/%s.jpg?s=200&d=%s', [
md5(strtolower($this->email)),
$this->name ? urlencode("https://ui-avatars.com/api/$this->name.jpg") : 'mp',
]);
}
public function getHasFormsAttribute()
{
return $this->workspaces()->whereHas('forms')->exists();
}
public function getIsSubscribedAttribute()
{
return $this->subscribed()
|| in_array($this->email, config('opnform.extra_pro_users_emails'))
|| !is_null($this->activeLicense());
}
public function getHasCustomerIdAttribute()
{
return !is_null($this->stripe_id);
}
public function getAdminAttribute()
{
return in_array($this->email, config('opnform.admin_emails'));
}
public function getModeratorAttribute()
{
return in_array($this->email, config('opnform.moderator_emails')) || $this->admin;
}
public function getTemplateEditorAttribute()
{
return $this->admin || in_array($this->email, config('opnform.template_editor_emails'));
}
/**
* =================================
* Helper Related
* =================================
*/
/**
* Send the password reset notification.
*
* @param string $token
* @return void
*/
public function sendPasswordResetNotification($token)
{
$this->notify(new ResetPassword($token));
}
/**
* Send the email verification notification.
*
* @return void
*/
public function sendEmailVerificationNotification()
{
$this->notify(new VerifyEmail());
}
/**
* =================================
* Relationship
* =================================
*/
public function workspaces()
{
return $this->belongsToMany(Workspace::class);
}
public function forms()
{
return $this->hasMany(Form::class, 'creator_id');
}
public function formTemplates()
{
return $this->hasMany(Template::class, 'creator_id');
}
public function licenses()
{
return $this->hasMany(License::class);
}
public function activeLicense(): ?License
{
return $this->licenses()->active()->first();
}
/**
* =================================
* Oauth Related
* =================================
*/
/**
* Get the oauth providers.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function oauthProviders()
{
return $this->hasMany(OAuthProvider::class);
}
/**
* @return int
*/
public function getJWTIdentifier()
{
return $this->getKey();
}
/**
* @return array
*/
public function getJWTCustomClaims()
{
return [
'ip' => \Hash::make(request()->ip()),
'ua' => \Hash::make(request()->userAgent()),
];
}
public function getIsRiskyAttribute()
{
return $this->created_at->isAfter(now()->subDays(3)) || // created in last 3 days
$this->subscriptions()->where(function ($q) {
$q->where('stripe_status', 'trialing')
->orWhere('stripe_status', 'active');
})->first()?->onTrial();
}
public function flushCache()
{
$this->workspaces()->with('forms')->get()->each(function (Workspace $workspace) {
$workspace->flush();
$workspace->forms->each(function (Form $form) {
$form->flush();
});
});
}
public static function boot()
{
parent::boot();
static::deleting(function (User $user) {
// Remove user's workspace if he's the only one with this workspace
foreach ($user->workspaces as $workspace) {
if ($workspace->users()->count() == 1) {
$workspace->delete();
} else {
$workspace->users()->detach($user->id);
}
}
});
}
public function scopeWithActiveSubscription($query)
{
return $query->whereHas('subscriptions', function ($query) {
$query->where(function ($q) {
$q->where('stripe_status', 'trialing')
->orWhere('stripe_status', 'active');
});
});
}
}