remove usernames
This commit is contained in:
@@ -23,7 +23,7 @@ class UserConductor extends Conductor
|
||||
{
|
||||
$user = auth()->user();
|
||||
if ($user === null || $user->hasPermission('admin/users') === false) {
|
||||
return ['id', 'username'];
|
||||
return ['id', 'display_name'];
|
||||
}
|
||||
|
||||
return parent::fields($model);
|
||||
@@ -41,7 +41,7 @@ class UserConductor extends Conductor
|
||||
$data = $model->toArray();
|
||||
|
||||
if ($user === null || ($user->hasPermission('admin/users') === false && strcasecmp($user->id, $model->id) !== 0)) {
|
||||
$fields = ['id', 'username', 'display_name'];
|
||||
$fields = ['id', 'display_name'];
|
||||
$data = arrayLimitKeys($data, $fields);
|
||||
} else {
|
||||
$data['permissions'] = $user->permissions;
|
||||
|
||||
@@ -47,18 +47,18 @@ class AuthController extends ApiController
|
||||
*/
|
||||
public function login(AuthLoginRequest $request)
|
||||
{
|
||||
$user = User::where('username', '=', $request->input('username'))->first();
|
||||
$user = User::where('email', '=', $request->input('email'))->first();
|
||||
|
||||
if ($user !== null && Hash::check($request->input('password'), $user->password) === true) {
|
||||
if ($user->email_verified_at === null) {
|
||||
return $this->respondWithErrors([
|
||||
'username' => 'Email address has not been verified.'
|
||||
'email' => 'Email address has not been verified.'
|
||||
]);
|
||||
}
|
||||
|
||||
if ($user->disabled === true) {
|
||||
return $this->respondWithErrors([
|
||||
'username' => 'Account has been disabled.'
|
||||
'email' => 'Account has been disabled.'
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -78,8 +78,8 @@ class AuthController extends ApiController
|
||||
}//end if
|
||||
|
||||
return $this->respondWithErrors([
|
||||
'username' => 'Invalid username or password',
|
||||
'password' => 'Invalid username or password',
|
||||
'email' => 'Invalid email or password',
|
||||
'password' => 'Invalid email or password',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace App\Http\Controllers\Api;
|
||||
use App\Enum\HttpResponseCodes;
|
||||
use App\Http\Requests\UserRequest;
|
||||
use App\Http\Requests\UserForgotPasswordRequest;
|
||||
use App\Http\Requests\UserForgotUsernameRequest;
|
||||
use App\Http\Requests\UserRegisterRequest;
|
||||
use App\Http\Requests\UserResendVerifyEmailRequest;
|
||||
use App\Http\Requests\UserResetPasswordRequest;
|
||||
@@ -14,7 +13,6 @@ use App\Jobs\SendEmailJob;
|
||||
use App\Mail\ChangedEmail;
|
||||
use App\Mail\ChangedPassword;
|
||||
use App\Mail\ChangeEmailVerify;
|
||||
use App\Mail\ForgotUsername;
|
||||
use App\Mail\ForgotPassword;
|
||||
use App\Mail\EmailVerify;
|
||||
use App\Models\User;
|
||||
@@ -37,7 +35,6 @@ class UserController extends ApiController
|
||||
'register',
|
||||
'exists',
|
||||
'forgotPassword',
|
||||
'forgotUsername',
|
||||
'resetPassword',
|
||||
'verifyEmail',
|
||||
'resendVerifyEmailCode'
|
||||
@@ -105,7 +102,7 @@ class UserController extends ApiController
|
||||
{
|
||||
if (UserConductor::updatable($user) === true) {
|
||||
$input = [];
|
||||
$updatable = ['username', 'first_name', 'last_name', 'email', 'phone', 'password', 'display_name'];
|
||||
$updatable = ['first_name', 'last_name', 'email', 'phone', 'password', 'display_name'];
|
||||
|
||||
if ($request->user()->hasPermission('admin/user') === true) {
|
||||
$updatable = array_merge($updatable, ['email_verified_at']);
|
||||
@@ -149,15 +146,28 @@ class UserController extends ApiController
|
||||
public function register(UserRegisterRequest $request)
|
||||
{
|
||||
try {
|
||||
$user = User::create([
|
||||
'first_name' => $request->input('first_name'),
|
||||
'last_name' => $request->input('last_name'),
|
||||
'username' => $request->input('username'),
|
||||
'email' => $request->input('email'),
|
||||
'phone' => $request->input('phone', ''),
|
||||
'password' => Hash::make($request->input('password')),
|
||||
'display_name' => $request->input('display_name', $request->input('username')),
|
||||
]);
|
||||
$user = User::where('email', $request->input('email'))
|
||||
->whereNull('password')
|
||||
->first();
|
||||
|
||||
if ($user === null) {
|
||||
$user = User::create([
|
||||
'first_name' => $request->input('first_name'),
|
||||
'last_name' => $request->input('last_name'),
|
||||
'email' => $request->input('email'),
|
||||
'phone' => $request->input('phone', ''),
|
||||
'password' => Hash::make($request->input('password')),
|
||||
'display_name' => $request->input('display_name'),
|
||||
]);
|
||||
} else {
|
||||
$user->update([
|
||||
'first_name' => $request->input('first_name'),
|
||||
'last_name' => $request->input('last_name'),
|
||||
'phone' => $request->input('phone', ''),
|
||||
'password' => Hash::make($request->input('password')),
|
||||
'display_name' => $request->input('display_name'),
|
||||
]);
|
||||
}//end if
|
||||
|
||||
$code = $user->codes()->create([
|
||||
'action' => 'verify-email',
|
||||
@@ -175,26 +185,6 @@ class UserController extends ApiController
|
||||
}//end try
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an email with all the usernames registered at that address
|
||||
*
|
||||
* @param \App\Http\Requests\UserForgotUsernameRequest $request The forgot username request.
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function forgotUsername(UserForgotUsernameRequest $request)
|
||||
{
|
||||
$users = User::where('email', $request->input('email'))->whereNotNull('email_verified_at')->get();
|
||||
if ($users->count() > 0) {
|
||||
dispatch((new SendEmailJob(
|
||||
$users->first()->email,
|
||||
new ForgotUsername($users->pluck('username')->toArray())
|
||||
)))->onQueue('mail');
|
||||
return $this->respondNoContent();
|
||||
}
|
||||
|
||||
return $this->respondJson(['message' => 'Username send to the email address if registered']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new reset password code
|
||||
*
|
||||
@@ -203,7 +193,7 @@ class UserController extends ApiController
|
||||
*/
|
||||
public function forgotPassword(UserForgotPasswordRequest $request)
|
||||
{
|
||||
$user = User::where('username', $request->input('username'))->first();
|
||||
$user = User::where('email', $request->input('email'))->first();
|
||||
if ($user !== null) {
|
||||
$user->codes()->where('action', 'reset-password')->delete();
|
||||
$code = $user->codes()->create([
|
||||
@@ -299,7 +289,7 @@ class UserController extends ApiController
|
||||
{
|
||||
UserCode::clearExpired();
|
||||
|
||||
$user = User::where('username', $request->input('username'))->first();
|
||||
$user = User::where('email', $request->input('email'))->first();
|
||||
if ($user !== null) {
|
||||
$code = $user->codes()->where('action', 'verify-email')->first();
|
||||
$code->regenerate();
|
||||
@@ -324,7 +314,7 @@ class UserController extends ApiController
|
||||
*/
|
||||
public function resendVerifyEmailCode(UserResendVerifyEmailRequest $request)
|
||||
{
|
||||
$user = User::where('username', $request->input('username'))->first();
|
||||
$user = User::where('email', $request->input('email'))->first();
|
||||
if ($user !== null) {
|
||||
$user->codes()->where('action', 'verify-email')->delete();
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ class AuthLoginRequest extends FormRequest
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'username' => 'required|string|min:6|max:255',
|
||||
'email' => 'required|string|min:6|max:255',
|
||||
'password' => 'required|string|min:6',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class UserForgotPasswordRequest extends FormRequest
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'username' => 'required|exists:users,username',
|
||||
'email' => 'required|exists:users,email',
|
||||
// 'captcha_token' => [new Recaptcha()],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Rules\Recaptcha;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UserForgotUsernameRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'email' => 'required|email|max:255',
|
||||
// 'captcha_token' => [new Recaptcha()],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,8 @@ class UserRegisterRequest extends FormRequest
|
||||
return [
|
||||
'first_name' => 'required|string|max:255',
|
||||
'last_name' => 'required|string|max:255',
|
||||
'display_name' => 'required|string|max:255',
|
||||
'email' => 'required|string|email|max:255',
|
||||
'username' => 'required|string|min:4|max:255|unique:users',
|
||||
'display_name' => 'required|string|max:255|uniqueish:users',
|
||||
'email' => 'required|string|email|max:255|unique:users',
|
||||
'password' => 'required|string|min:8',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -3,9 +3,18 @@
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
use App\Rules\Uniqueish;
|
||||
|
||||
class UserRequest extends BaseRequest
|
||||
{
|
||||
/**
|
||||
* Fields that are required unless all are null.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $required_with_all = ['first_name','last_name','display_name','phone'];
|
||||
|
||||
|
||||
/**
|
||||
* Apply the additional POST base rules to this request
|
||||
*
|
||||
@@ -14,11 +23,10 @@ class UserRequest extends BaseRequest
|
||||
public function postRules()
|
||||
{
|
||||
return [
|
||||
'username' => 'required|string|max:255|min:4|unique:users',
|
||||
'first_name' => 'required|string|max:255|min:2',
|
||||
'last_name' => 'required|string|max:255|min:2',
|
||||
'display_name' => 'required|string|max:255',
|
||||
'email' => 'required|string|email|max:255',
|
||||
'display_name' => 'required|string|max:255|uniqueish:users',
|
||||
'email' => 'required|string|email|max:255|unique:users',
|
||||
'phone' => ['string', 'regex:/^(\+|00)?[0-9][0-9 \-\(\)\.]{7,32}$/'],
|
||||
'email_verified_at' => 'date'
|
||||
];
|
||||
@@ -33,24 +41,32 @@ class UserRequest extends BaseRequest
|
||||
{
|
||||
$user = $this->route('user');
|
||||
|
||||
$required_with_all = count($this->required_with_all) > 0 ? 'required_with_all:' . implode(',', $this->required_with_all) : '';
|
||||
|
||||
return [
|
||||
'username' => [
|
||||
'first_name' => "nullable|string|required_if_any:users,last_name,display_name,phone,password|between:2,255",
|
||||
'last_name' => "nullable|required_if_any:users,first_name,display_name,phone,password|string|max:255|min:2",
|
||||
'display_name' => [
|
||||
'nullable',
|
||||
'required_if_any:users,first_name,last_name,phone,password',
|
||||
'string',
|
||||
'max:255',
|
||||
'min:4',
|
||||
'min:2',
|
||||
(new Uniqueish('users', 'display_name'))->ignore($user->id),
|
||||
],
|
||||
'email' => [
|
||||
'string',
|
||||
'email',
|
||||
'max:255',
|
||||
Rule::unique('users')->ignore($user->id)->when(
|
||||
$this->username !== $user->username,
|
||||
$this->email !== $user->email,
|
||||
function ($query) {
|
||||
return $query->where('username', $this->username);
|
||||
return $query->where('email', $this->email);
|
||||
}
|
||||
),
|
||||
],
|
||||
'first_name' => 'string|max:255|min:2',
|
||||
'last_name' => 'string|max:255|min:2',
|
||||
'display_name' => 'string|max:255|min:2',
|
||||
'email' => 'string|email|max:255',
|
||||
'phone' => ['nullable','regex:/^(\+|00)?[0-9][0-9 \-\(\)\.]{7,32}$/'],
|
||||
'password' => 'string|min:8'
|
||||
'phone' => ['nullable', 'regex:/^(\+|00)?[0-9][0-9 \-\(\)\.]{7,32}$/'],
|
||||
'password' => "nullable|{$required_with_all}|string|min:8"
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class UserResendVerifyEmailRequest extends FormRequest
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'username' => 'required|exists:users,username',
|
||||
'email' => 'required|exists:users,email',
|
||||
// 'captcha_token' => [new Recaptcha()],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ForgotUsername extends Mailable
|
||||
{
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* The list of usernames
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public $usernames;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @param array $usernames The usernames.
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(array $usernames)
|
||||
{
|
||||
$this->usernames = $usernames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Envelope
|
||||
*/
|
||||
public function envelope()
|
||||
{
|
||||
return new Envelope(
|
||||
subject: '🤦 Forgot your username?',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*
|
||||
* @return \Illuminate\Mail\Mailables\Content
|
||||
*/
|
||||
public function content()
|
||||
{
|
||||
return new Content(
|
||||
view: 'emails.user.forgot_username',
|
||||
text: 'emails.user.forgot_username_plain',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ class User extends Authenticatable implements Auditable
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'username',
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
|
||||
@@ -20,14 +20,12 @@ class UserFactory extends Factory
|
||||
$faker = \Faker\Factory::create();
|
||||
$faker->addProvider(new \Faker\Provider\CustomInternetProvider($faker));
|
||||
|
||||
$username = $faker->unique()->userNameWithMinLength(6);
|
||||
$first_name = $faker->firstName();
|
||||
$last_name = $faker->lastName();
|
||||
|
||||
$display_name = $faker->randomElement([$username, $first_name . ' ' . $last_name]);
|
||||
$display_name = $first_name . ' ' . $last_name;
|
||||
|
||||
return [
|
||||
'username' => $username,
|
||||
'first_name' => $first_name,
|
||||
'last_name' => $last_name,
|
||||
'email' => $faker->safeEmail(),
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('username');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('username')->unique();
|
||||
});
|
||||
|
||||
DB::table('users')->update(['username' => DB::raw('display_name')]);
|
||||
}
|
||||
};
|
||||
@@ -20,7 +20,7 @@ class DatabaseSeeder extends Seeder
|
||||
\App\Models\User::factory(40)->create();
|
||||
|
||||
\App\Models\User::factory()->create([
|
||||
'username' => 'nomadjimbob',
|
||||
'display_name' => 'James Collins',
|
||||
'first_name' => 'James',
|
||||
'last_name' => 'Collins',
|
||||
'email' => 'james@stemmechanics.com.au',
|
||||
|
||||
@@ -288,6 +288,16 @@ export const routes = [
|
||||
component: () =>
|
||||
import("@/views/dashboard/UserList.vue"),
|
||||
},
|
||||
{
|
||||
path: "create",
|
||||
name: "dashboard-user-create",
|
||||
meta: {
|
||||
title: "Create User",
|
||||
middleware: "authenticated",
|
||||
},
|
||||
component: () =>
|
||||
import("@/views/dashboard/UserEdit.vue"),
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
name: "dashboard-user-edit",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</h1>
|
||||
<SMToolbar>
|
||||
<div>
|
||||
<div class="author">By {{ article.user.username }}</div>
|
||||
<div class="author">By {{ article.user.display_name }}</div>
|
||||
<div class="date">{{ formattedDate(article.publish_at) }}</div>
|
||||
</div>
|
||||
<SMButton
|
||||
@@ -47,7 +47,7 @@ const applicationStore = useApplicationStore();
|
||||
*/
|
||||
let article: Ref<Article> = ref({
|
||||
title: "",
|
||||
user: { username: "" },
|
||||
user: { display_name: "" },
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,11 +5,10 @@
|
||||
<template v-if="!formDone">
|
||||
<h1>Forgot Password</h1>
|
||||
<p>
|
||||
Enter your username below to receive a password reset
|
||||
link to your email address.
|
||||
Enter your email below to receive a password reset link.
|
||||
</p>
|
||||
<SMForm v-model="form" @submit="handleSubmit">
|
||||
<SMInput control="username" />
|
||||
<SMInput control="email" />
|
||||
<SMButtonRow>
|
||||
<template #left>
|
||||
<div class="small">
|
||||
@@ -31,9 +30,9 @@
|
||||
<template v-else>
|
||||
<h1>Email Sent!</h1>
|
||||
<p class="text-center">
|
||||
If that username has been registered, you will receive
|
||||
an email with a reset password link in the next few
|
||||
minutes.
|
||||
If that email address has been registered, you will
|
||||
receive an email with a reset password link in the next
|
||||
few minutes.
|
||||
</p>
|
||||
<SMRow class="pb-2">
|
||||
<SMColumn class="justify-content-center">
|
||||
@@ -56,13 +55,13 @@ import SMButtonRow from "../components/SMButtonRow.vue";
|
||||
import SMInput from "../components/SMInput.vue";
|
||||
import { api } from "../helpers/api";
|
||||
import { Form, FormControl } from "../helpers/form";
|
||||
import { And, Min, Required } from "../helpers/validate";
|
||||
import { And, Email, Required } from "../helpers/validate";
|
||||
|
||||
// const { executeRecaptcha, recaptchaLoaded } = useReCaptcha();
|
||||
const formDone = ref(false);
|
||||
let form = reactive(
|
||||
Form({
|
||||
username: FormControl("", And([Required(), Min(4)])),
|
||||
email: FormControl("", And([Required(), Email()])),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -76,7 +75,7 @@ const handleSubmit = async () => {
|
||||
await api.post({
|
||||
url: "/users/forgotPassword",
|
||||
body: {
|
||||
username: form.controls.username.value,
|
||||
email: form.controls.email.value,
|
||||
// captcha_token: captcha,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
<template>
|
||||
<SMPage>
|
||||
<SMRow>
|
||||
<SMFormCard class="mt-5">
|
||||
<template v-if="!formDone">
|
||||
<h1>Forgot Username</h1>
|
||||
<p>
|
||||
Enter your email address, and if an account exists, we
|
||||
will email you your username.
|
||||
</p>
|
||||
<SMForm v-model="form" @submit="handleSubmit">
|
||||
<SMInput control="email" />
|
||||
<SMButtonRow>
|
||||
<template #left>
|
||||
<div class="small">
|
||||
<span class="pr-1">Remember?</span
|
||||
><router-link :to="{ name: 'login' }"
|
||||
>Log in</router-link
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<template #right>
|
||||
<SMButton
|
||||
type="submit"
|
||||
label="Send"
|
||||
icon="arrow-forward-outline" />
|
||||
</template>
|
||||
</SMButtonRow>
|
||||
</SMForm>
|
||||
</template>
|
||||
<template v-else>
|
||||
<h1>Email Sent!</h1>
|
||||
<p class="text-center">
|
||||
If that email has a registered account, you should
|
||||
receive it shortly.
|
||||
</p>
|
||||
<SMRow class="pb-2">
|
||||
<SMColumn class="justify-content-center">
|
||||
<SMButton :to="{ name: 'home' }" label="Home" />
|
||||
</SMColumn>
|
||||
</SMRow>
|
||||
</template>
|
||||
</SMFormCard>
|
||||
</SMRow>
|
||||
</SMPage>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from "vue";
|
||||
// import { useReCaptcha } from "vue-recaptcha-v3";
|
||||
import SMButton from "../components/SMButton.vue";
|
||||
import SMFormCard from "../components/SMFormCard.vue";
|
||||
import SMForm from "../components/SMForm.vue";
|
||||
import SMButtonRow from "../components/SMButtonRow.vue";
|
||||
import SMInput from "../components/SMInput.vue";
|
||||
import { api } from "../helpers/api";
|
||||
import { Form, FormControl } from "../helpers/form";
|
||||
import { And, Email, Required } from "../helpers/validate";
|
||||
|
||||
// const { executeRecaptcha, recaptchaLoaded } = useReCaptcha();
|
||||
const formDone = ref(false);
|
||||
let form = reactive(
|
||||
Form({
|
||||
email: FormControl("", And([Required(), Email()])),
|
||||
})
|
||||
);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
form.loading(true);
|
||||
|
||||
try {
|
||||
// await recaptchaLoaded();
|
||||
// const captcha = await executeRecaptcha("submit");
|
||||
|
||||
await api.post({
|
||||
url: "/users/forgotUsername",
|
||||
body: {
|
||||
email: form.controls.email.value,
|
||||
// captcha_token: captcha,
|
||||
},
|
||||
});
|
||||
|
||||
formDone.value = true;
|
||||
} catch (error) {
|
||||
form.apiErrors(error);
|
||||
}
|
||||
|
||||
form.loading(false);
|
||||
};
|
||||
</script>
|
||||
@@ -9,11 +9,7 @@
|
||||
</p>
|
||||
</template>
|
||||
<template #body>
|
||||
<SMInput control="username" autofocus>
|
||||
<router-link to="/forgot-username"
|
||||
>Forgot username?</router-link
|
||||
>
|
||||
</SMInput>
|
||||
<SMInput control="email" autofocus> </SMInput>
|
||||
<SMInput control="password" type="password">
|
||||
<router-link to="/forgot-password"
|
||||
>Forgot password?</router-link
|
||||
@@ -54,7 +50,7 @@ import SMInput from "../components/SMInput.vue";
|
||||
import { api } from "../helpers/api";
|
||||
import { LoginResponse } from "../helpers/api.types";
|
||||
import { Form, FormControl } from "../helpers/form";
|
||||
import { And, Min, Required } from "../helpers/validate";
|
||||
import { And, Email, Required } from "../helpers/validate";
|
||||
import { useUserStore } from "../store/UserStore";
|
||||
import SMButtonRow from "../components/SMButtonRow.vue";
|
||||
|
||||
@@ -63,7 +59,7 @@ const userStore = useUserStore();
|
||||
const router = useRouter();
|
||||
let form = reactive(
|
||||
Form({
|
||||
username: FormControl("", And([Required(), Min(4)])),
|
||||
email: FormControl("", And([Required(), Email()])),
|
||||
password: FormControl("", Required()),
|
||||
})
|
||||
);
|
||||
@@ -81,7 +77,7 @@ const handleSubmit = async () => {
|
||||
let result = await api.post({
|
||||
url: "/login",
|
||||
body: {
|
||||
username: form.controls.username.value,
|
||||
email: form.controls.email.value,
|
||||
password: form.controls.password.value,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<SMContainer :center="true">
|
||||
<SMForm v-if="!userRegistered" v-model="form" @submit="handleSubmit">
|
||||
<SMFormCard full>
|
||||
<SMFormCard>
|
||||
<template #header>
|
||||
<h2>Register</h2>
|
||||
<p>
|
||||
@@ -10,36 +10,9 @@
|
||||
</p>
|
||||
</template>
|
||||
<template #body>
|
||||
<SMRow>
|
||||
<SMColumn>
|
||||
<SMInput control="username" autofocus />
|
||||
</SMColumn>
|
||||
<SMColumn>
|
||||
<SMInput
|
||||
control="password"
|
||||
type="password"></SMInput>
|
||||
</SMColumn>
|
||||
</SMRow>
|
||||
<SMRow>
|
||||
<SMColumn>
|
||||
<SMInput control="first_name" />
|
||||
</SMColumn>
|
||||
<SMColumn>
|
||||
<SMInput control="last_name" />
|
||||
</SMColumn>
|
||||
</SMRow>
|
||||
<SMRow>
|
||||
<SMColumn>
|
||||
<SMInput control="email" />
|
||||
</SMColumn>
|
||||
<SMColumn>
|
||||
<SMInput control="phone"
|
||||
><template #help
|
||||
>This field is optional</template
|
||||
>
|
||||
</SMInput>
|
||||
</SMColumn>
|
||||
</SMRow>
|
||||
<SMInput control="email" autofocus />
|
||||
<SMInput control="password" type="password" />
|
||||
<SMInput control="display_name" label="Display Name" />
|
||||
</template>
|
||||
<template #footer-space-between>
|
||||
<div class="small">
|
||||
@@ -125,7 +98,6 @@ let form = reactive(
|
||||
first_name: FormControl("", Required()),
|
||||
last_name: FormControl("", Required()),
|
||||
email: FormControl("", And([Required(), Email()])),
|
||||
phone: FormControl("", Phone()),
|
||||
username: FormControl("", And([Min(4), Custom(checkUsername)])),
|
||||
password: FormControl("", And([Required(), Password()])),
|
||||
})
|
||||
@@ -135,20 +107,14 @@ const handleSubmit = async () => {
|
||||
form.loading(true);
|
||||
|
||||
try {
|
||||
// await recaptchaLoaded();
|
||||
// const captcha = await executeRecaptcha("submit");
|
||||
|
||||
await api.post({
|
||||
url: "/register",
|
||||
body: {
|
||||
first_name: form.controls.first_name.value,
|
||||
last_name: form.controls.last_name.value,
|
||||
email: form.controls.email.value,
|
||||
phone: form.controls.phone.value,
|
||||
username: form.controls.username.value,
|
||||
password: form.controls.password.value,
|
||||
display_name: form.controls.username.value,
|
||||
// captcha_token: captcha,
|
||||
display_name: form.controls.display_name.value,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<template v-if="!formDone">
|
||||
<h1>Resend Verify Email</h1>
|
||||
<SMForm v-model="form" @submit="handleSubmit">
|
||||
<SMInput control="username" />
|
||||
<SMInput control="email" />
|
||||
<SMButtonRow>
|
||||
<template #left>
|
||||
<div class="small">
|
||||
@@ -27,9 +27,9 @@
|
||||
<template v-else>
|
||||
<h1>Email Sent!</h1>
|
||||
<p class="text-center">
|
||||
If that username has been registered, and you still need
|
||||
to verify your email, you will receive an email with a
|
||||
new verify code.
|
||||
If that email address has been registered, and you still
|
||||
need to verify your email, you will receive an email
|
||||
with a new verify code.
|
||||
</p>
|
||||
<SMButtonRow>
|
||||
<template #right>
|
||||
@@ -52,13 +52,13 @@ import SMButtonRow from "../components/SMButtonRow.vue";
|
||||
import SMInput from "../components/SMInput.vue";
|
||||
import { api } from "../helpers/api";
|
||||
import { Form, FormControl } from "../helpers/form";
|
||||
import { Required } from "../helpers/validate";
|
||||
import { And, Email, Required } from "../helpers/validate";
|
||||
|
||||
// const { executeRecaptcha, recaptchaLoaded } = useReCaptcha();
|
||||
const formDone = ref(false);
|
||||
let form = reactive(
|
||||
Form({
|
||||
username: FormControl("", Required()),
|
||||
email: FormControl("", And([Required(), Email()])),
|
||||
})
|
||||
);
|
||||
|
||||
@@ -72,7 +72,7 @@ const handleSubmit = async () => {
|
||||
await api.post({
|
||||
url: "/users/resendVerifyEmailCode",
|
||||
body: {
|
||||
username: form.controls.username.value,
|
||||
email: form.controls.email.value,
|
||||
// captcha_token: captcha,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -301,7 +301,7 @@ const loadOptionsAuthors = async () => {
|
||||
api.get({
|
||||
url: "/users",
|
||||
params: {
|
||||
fields: "id,username,first_name,last_name",
|
||||
fields: "id,display_name",
|
||||
limit: 100,
|
||||
},
|
||||
})
|
||||
@@ -312,7 +312,7 @@ const loadOptionsAuthors = async () => {
|
||||
authors.value = {};
|
||||
|
||||
data.users.forEach((item) => {
|
||||
authors.value[item.id] = `${item.username}`;
|
||||
authors.value[item.id] = `${item.display_name}`;
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2,15 +2,18 @@
|
||||
<SMMastHead
|
||||
:title="pageHeading"
|
||||
:back-link="
|
||||
route.params.id
|
||||
route.params.id || isCreatingUser
|
||||
? { name: 'dashboard-user-list' }
|
||||
: { name: 'dashboard' }
|
||||
"
|
||||
:back-title="route.params.id ? 'Back to Users' : 'Back to Dashboard'" />
|
||||
:back-title="
|
||||
route.params.id || isCreatingUser
|
||||
? 'Back to Users'
|
||||
: 'Back to Dashboard'
|
||||
" />
|
||||
<SMContainer>
|
||||
<SMForm :model-value="form" @submit="handleSubmit">
|
||||
<SMRow>
|
||||
<SMColumn><SMInput control="username" disabled /></SMColumn>
|
||||
<SMColumn><SMInput control="display_name" /></SMColumn>
|
||||
</SMRow>
|
||||
<SMRow>
|
||||
@@ -90,9 +93,10 @@ const route = useRoute();
|
||||
const router = useRouter();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const isCreatingUser = route.path.endsWith("/create");
|
||||
|
||||
let form = reactive(
|
||||
Form({
|
||||
username: FormControl("", And([Required()])),
|
||||
display_name: FormControl("", And([Required()])),
|
||||
first_name: FormControl("", And([Required()])),
|
||||
last_name: FormControl("", And([Required()])),
|
||||
@@ -122,7 +126,6 @@ const loadData = async () => {
|
||||
const data = result.data as UserResponse;
|
||||
|
||||
if (data && data.user) {
|
||||
form.controls.username.value = data.user.username;
|
||||
form.controls.first_name.value = data.user.first_name;
|
||||
form.controls.last_name.value = data.user.last_name;
|
||||
form.controls.display_name.value = data.user.display_name;
|
||||
@@ -134,8 +137,7 @@ const loadData = async () => {
|
||||
} finally {
|
||||
form.loading(false);
|
||||
}
|
||||
} else {
|
||||
form.controls.username.value = userStore.username;
|
||||
} else if (isCreatingUser == false) {
|
||||
form.controls.first_name.value = userStore.firstName;
|
||||
form.controls.last_name.value = userStore.lastName;
|
||||
form.controls.display_name.value = userStore.displayName;
|
||||
@@ -150,34 +152,56 @@ const loadData = async () => {
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
form.loading(true);
|
||||
const result = await api.put({
|
||||
url: "/users/{id}",
|
||||
params: {
|
||||
id: userStore.id,
|
||||
},
|
||||
body: {
|
||||
first_name: form.controls.first_name.value,
|
||||
last_name: form.controls.last_name.value,
|
||||
display_name: form.controls.display_name.value,
|
||||
email: form.controls.email.value,
|
||||
phone: form.controls.phone.value,
|
||||
},
|
||||
});
|
||||
const id = route.params.id ? route.params.id : userStore.id;
|
||||
|
||||
const data = result.data as UserResponse;
|
||||
if (isCreatingUser == false) {
|
||||
const result = await api.put({
|
||||
url: "/users/{id}",
|
||||
params: {
|
||||
id: id,
|
||||
},
|
||||
body: {
|
||||
first_name: form.controls.first_name.value,
|
||||
last_name: form.controls.last_name.value,
|
||||
display_name: form.controls.display_name.value,
|
||||
email: form.controls.email.value,
|
||||
phone: form.controls.phone.value,
|
||||
},
|
||||
});
|
||||
|
||||
if (data && data.user) {
|
||||
userStore.setUserDetails(data.user);
|
||||
const data = result.data as UserResponse;
|
||||
|
||||
if (route.params.id && data && data.user) {
|
||||
userStore.setUserDetails(data.user);
|
||||
}
|
||||
|
||||
useToastStore().addToast({
|
||||
title: "Details Updated",
|
||||
content: "The user has been updated.",
|
||||
type: "success",
|
||||
});
|
||||
} else {
|
||||
await api.post({
|
||||
url: "/users",
|
||||
params: {
|
||||
id: id,
|
||||
},
|
||||
body: {
|
||||
first_name: form.controls.first_name.value,
|
||||
last_name: form.controls.last_name.value,
|
||||
display_name: form.controls.display_name.value,
|
||||
email: form.controls.email.value,
|
||||
phone: form.controls.phone.value,
|
||||
},
|
||||
});
|
||||
|
||||
useToastStore().addToast({
|
||||
title: "User Created",
|
||||
content: "The user has been created.",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
|
||||
useToastStore().addToast({
|
||||
title: route.params.id ? "Details Updated" : "User Created",
|
||||
content: route.params.id
|
||||
? "The user has been updated."
|
||||
: "The user has been created.",
|
||||
type: "success",
|
||||
});
|
||||
|
||||
router.push({ name: "dashboard" });
|
||||
} catch (err) {
|
||||
form.apiErrors(err);
|
||||
|
||||
@@ -1,91 +1,154 @@
|
||||
<template>
|
||||
<SMPage permission="admin/users" :page-error="pageError">
|
||||
<SMPage permission="admin/users">
|
||||
<SMMastHead
|
||||
title="Users"
|
||||
:back-link="{ name: 'dashboard' }"
|
||||
back-title="Return to Dashboard" />
|
||||
<SMContainer>
|
||||
<SMTable
|
||||
:headers="headers"
|
||||
:items="items"
|
||||
@row-click="handleRowClick">
|
||||
<template #item-actions="item">
|
||||
<SMButton
|
||||
label="Edit"
|
||||
:dropdown="{
|
||||
download: 'Download',
|
||||
delete: 'Delete',
|
||||
}"
|
||||
size="medium" />
|
||||
</template>
|
||||
</SMTable>
|
||||
<SMContainer class="flex-grow-1">
|
||||
<SMToolbar>
|
||||
<SMButton
|
||||
:to="{ name: 'dashboard-user-create' }"
|
||||
type="primary"
|
||||
label="Create User" />
|
||||
<SMInput
|
||||
v-model="itemSearch"
|
||||
label="Search"
|
||||
class="toolbar-search"
|
||||
@keyup.enter="handleSearch">
|
||||
<template #append>
|
||||
<SMButton
|
||||
type="primary"
|
||||
label="Search"
|
||||
icon="search-outline"
|
||||
@click="handleSearch" />
|
||||
</template>
|
||||
</SMInput>
|
||||
</SMToolbar>
|
||||
<SMLoading large v-if="itemsLoading" />
|
||||
<template v-else>
|
||||
<SMPagination
|
||||
v-if="items.length < itemsTotal"
|
||||
v-model="itemsPage"
|
||||
:total="itemsTotal"
|
||||
:per-page="itemsPerPage" />
|
||||
<SMNoItems v-if="items.length == 0" text="No Media Found" />
|
||||
<SMTable
|
||||
:headers="headers"
|
||||
:items="items"
|
||||
@row-click="handleEdit">
|
||||
<template #item-actions="item">
|
||||
<SMButton
|
||||
label="Edit"
|
||||
:dropdown="{
|
||||
delete: 'Delete',
|
||||
}"
|
||||
size="medium"
|
||||
@click="handleActionButton(item, $event)" />
|
||||
</template>
|
||||
</SMTable>
|
||||
</template>
|
||||
</SMContainer>
|
||||
</SMPage>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, watch } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { ref, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { openDialog } from "../../components/SMDialog";
|
||||
import DialogConfirm from "../../components/dialogs/SMDialogConfirm.vue";
|
||||
import { api } from "../../helpers/api";
|
||||
import { api, getApiResultData } from "../../helpers/api";
|
||||
import { SMDate } from "../../helpers/datetime";
|
||||
import SMTable from "../../components/SMTable.vue";
|
||||
import SMMastHead from "../../components/SMMastHead.vue";
|
||||
import { useToastStore } from "../../store/ToastStore";
|
||||
import SMNoItems from "../../components/SMNoItems.vue";
|
||||
import SMButton from "../../components/SMButton.vue";
|
||||
import SMInput from "../../components/SMInput.vue";
|
||||
import SMToolbar from "../../components/SMToolbar.vue";
|
||||
import { updateRouterParams } from "../../helpers/url";
|
||||
import { User, UserCollection } from "../../helpers/api.types";
|
||||
import SMLoading from "../../components/SMLoading.vue";
|
||||
import SMPagination from "../../components/SMPagination.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const searchValue = ref("");
|
||||
const pageError = ref(0);
|
||||
|
||||
const items = ref([]);
|
||||
const itemsLoading = ref(false);
|
||||
const itemSearch = ref((route.query.search as string) || "");
|
||||
const itemsTotal = ref(0);
|
||||
const itemsPerPage = 25;
|
||||
const itemsPage = ref(parseInt((route.query.page as string) || "1"));
|
||||
|
||||
const headers = [
|
||||
{ text: "Username", value: "username", sortable: true },
|
||||
{ text: "Display name", value: "display_name", sortable: true },
|
||||
{ text: "First name", value: "first_name", sortable: true },
|
||||
{ text: "Last name", value: "last_name", sortable: true },
|
||||
{ text: "Email", value: "email", sortable: true },
|
||||
{ text: "Phone", value: "phone", sortable: true },
|
||||
{ text: "Joined", value: "created_at", sortable: true },
|
||||
// { text: "Last logged in", value: "lastAttended", width: 200},
|
||||
{ text: "Actions", value: "actions" },
|
||||
];
|
||||
|
||||
const items = ref([]);
|
||||
const formLoading = ref(false);
|
||||
const serverItemsLength = ref(0);
|
||||
const serverOptions = ref({
|
||||
page: 1,
|
||||
rowsPerPage: 25,
|
||||
sortBy: null,
|
||||
sortType: null,
|
||||
/**
|
||||
* Watch if page number changes.
|
||||
*/
|
||||
watch(itemsPage, () => {
|
||||
handleLoad();
|
||||
});
|
||||
|
||||
const handleRowClick = (item) => {
|
||||
router.push({ name: "dashboard-user-edit", params: { id: item.id } });
|
||||
/**
|
||||
* Handle searching for item.
|
||||
*/
|
||||
const handleSearch = () => {
|
||||
itemsPage.value = 1;
|
||||
handleLoad();
|
||||
};
|
||||
|
||||
const loadFromServer = async () => {
|
||||
formLoading.value = true;
|
||||
/**
|
||||
* Handle user selecting option in action button.
|
||||
*
|
||||
* @param {Event} item The event item.
|
||||
* @param option
|
||||
*/
|
||||
const handleActionButton = (item: Event, option: string): void => {
|
||||
if (option.length == 0) {
|
||||
handleEdit(item);
|
||||
} else if (option.toLowerCase() == "delete") {
|
||||
handleDelete(item);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle loading the page and list
|
||||
*/
|
||||
const handleLoad = async () => {
|
||||
itemsLoading.value = true;
|
||||
items.value = [];
|
||||
itemsTotal.value = 0;
|
||||
|
||||
updateRouterParams(router, {
|
||||
search: itemSearch.value,
|
||||
page: itemsPage.value == 1 ? "" : itemsPage.value.toString(),
|
||||
});
|
||||
|
||||
try {
|
||||
let params = {};
|
||||
if (serverOptions.value.sortBy) {
|
||||
params["sort"] = serverOptions.value.sortBy;
|
||||
if (
|
||||
serverOptions.value.sortType &&
|
||||
serverOptions.value.sortType === "desc"
|
||||
) {
|
||||
params["sort"] = "-" + params["sort"];
|
||||
}
|
||||
let params = {
|
||||
page: itemsPage.value,
|
||||
limit: itemsPerPage,
|
||||
};
|
||||
|
||||
if (itemSearch.value.length > 0) {
|
||||
params[
|
||||
"filter"
|
||||
] = `title:${itemSearch.value},OR,content:${itemSearch.value}`;
|
||||
}
|
||||
|
||||
params["page"] = serverOptions.value.page;
|
||||
params["limit"] = serverOptions.value.rowsPerPage;
|
||||
|
||||
let res = await api.get({
|
||||
let result = await api.get({
|
||||
url: "/users",
|
||||
params: params,
|
||||
});
|
||||
items.value = res.data.users;
|
||||
|
||||
const userCollection = getApiResultData<UserCollection>(result);
|
||||
items.value = userCollection.users;
|
||||
|
||||
items.value.forEach((row) => {
|
||||
if (row.created_at !== "undefined") {
|
||||
@@ -96,44 +159,22 @@ const loadFromServer = async () => {
|
||||
}
|
||||
});
|
||||
|
||||
serverItemsLength.value = res.data.total;
|
||||
itemsTotal.value = userCollection.total;
|
||||
} catch (err) {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
formLoading.value = false;
|
||||
itemsLoading.value = false;
|
||||
};
|
||||
|
||||
loadFromServer();
|
||||
|
||||
watch(
|
||||
serverOptions,
|
||||
() => {
|
||||
loadFromServer();
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
const headerItemClassNameFunction = (header) => {
|
||||
if (["position", "actions"].includes(header.value))
|
||||
return "easy-data-table-cell-center";
|
||||
return "";
|
||||
};
|
||||
|
||||
const bodyItemClassNameFunction = (column) => {
|
||||
if (["position", "actions"].includes(column))
|
||||
return "easy-data-table-cell-center";
|
||||
return "";
|
||||
};
|
||||
|
||||
const handleEdit = (user) => {
|
||||
const handleEdit = (user: User) => {
|
||||
router.push({ name: "dashboard-user-edit", params: { id: user.id } });
|
||||
};
|
||||
|
||||
const handleDelete = async (user) => {
|
||||
const handleDelete = async (user: User) => {
|
||||
let result = await openDialog(DialogConfirm, {
|
||||
title: "Delete User?",
|
||||
text: `Are you sure you want to delete the user <strong>${user.username}</strong>?`,
|
||||
text: `Are you sure you want to delete the user <strong>${user.display_name}</strong>?`,
|
||||
cancel: {
|
||||
type: "secondary",
|
||||
label: "Cancel",
|
||||
@@ -147,7 +188,7 @@ const handleDelete = async (user) => {
|
||||
if (result == true) {
|
||||
try {
|
||||
await api.delete(`users${user.id}`);
|
||||
loadFromServer();
|
||||
handleLoad();
|
||||
|
||||
useToastStore().addToast({
|
||||
title: "User Deleted",
|
||||
@@ -163,6 +204,33 @@ const handleDelete = async (user) => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleLoad();
|
||||
</script>
|
||||
|
||||
<style lang="scss"></style>
|
||||
<style lang="scss">
|
||||
.page-dashboard-user-list {
|
||||
.toolbar-search {
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
// .table tr {
|
||||
// td:first-of-type,
|
||||
// td:nth-of-type(2) {
|
||||
// word-break: break-all;
|
||||
// }
|
||||
|
||||
// td:not(:first-of-type) {
|
||||
// white-space: nowrap;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.page-dashboard-user-list {
|
||||
.toolbar-search {
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><h2>Hey {{ $user?->username }},</h2></td>
|
||||
<td><h2>Hey {{ $user?->display_name }},</h2></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Hey {{ $user?->username }},
|
||||
Hey {{ $user?->display_name }},
|
||||
|
||||
We just need to confirm that this is your new email address.
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><h2>Yo {{ $user?->username }}</h2></td>
|
||||
<td><h2>Yo {{ $user?->display_name }}</h2></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding-bottom: 2rem">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Yo {{ $user?->username }}
|
||||
Yo {{ $user?->display_name }}
|
||||
|
||||
Just a quick word that your email has been changed to {{ $new_email }}.
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><h2>Yo {{ $user?->username }}</h2></td>
|
||||
<td><h2>Yo {{ $user?->display_name }}</h2></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding-bottom: 2rem">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Yo {{ $user?->username }}
|
||||
Yo {{ $user?->display_name }}
|
||||
|
||||
Just a quick word that your password has been changed.
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><h2>Welcome {{ $user?->username }},</h2></td>
|
||||
<td><h2>Welcome {{ $user?->display_name }},</h2></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Welcome {{ $user?->username }},
|
||||
Welcome {{ $user?->display_name }},
|
||||
|
||||
We've heard you would like to try out our workshops and courses!
|
||||
Before we can let you loose on our website, we need to make sure you are a real person and not a pesky robot or cat.
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><h2>Yo {{ $user?->username }}</h2></td>
|
||||
<td><h2>Yo {{ $user?->display_name }}</h2></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Yo {{ $user?->username }}
|
||||
Yo {{ $user?->display_name }}
|
||||
|
||||
We all forget things sometimes! But you can reset your password typing the following into your browser https://www.stemmechanics.com.au/reset-password and entering the following code:
|
||||
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html
|
||||
lang="{{ str_replace('_', '-', app()->getLocale()) }}"
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:v="urn:schemas-microsoft-com:vml"
|
||||
xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||
>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>STEMMechanics - Forgot Password</title>
|
||||
<link
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<!--[if gte mso 9]>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG />
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
<style>
|
||||
@import url("https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800&display=swap");
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<table
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
border="0"
|
||||
role="presentation"
|
||||
style="
|
||||
width: 100%;
|
||||
padding: 2rem;
|
||||
font-size: 1.1rem;
|
||||
color: #000000;
|
||||
font-family: Nunito, Arial, Helvetica, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
"
|
||||
>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://www.stemmechanics.com.au/">
|
||||
<img
|
||||
alt="STEMMechanics Logo"
|
||||
src="{{ $message->embed(public_path('assets').'/logo.webp') }}"
|
||||
width="400"
|
||||
height="62"
|
||||
/>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
@if (count($usernames) > 2)
|
||||
<h2>Yo {{ $usernames[0] }}, {{ $usernames[1] }}, or is it {{ $usernames[count($usernames)-1] }}?</h2>
|
||||
@elseif (count($usernames) > 1)
|
||||
<h2>Yo {{ $usernames[0] }}, or is it {{ $usernames[1] }}?</h2>
|
||||
@else
|
||||
<h2>Yo {{ $usernames[0] }},</h2>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding-bottom: 2rem;">
|
||||
@if (count($usernames) == 1)
|
||||
Guess what, your username is <strong>{{ $usernames[0] }}</strong>.
|
||||
@else
|
||||
We have the following usernames registered to this email address:
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding-bottom: 2rem;">
|
||||
<ul>
|
||||
@foreach($usernames as $username)
|
||||
<li>{{ $username }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
align="center"
|
||||
style="
|
||||
font-size: 90%;
|
||||
text-align: center;
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 2rem;
|
||||
border-top: 1px solid #ddd;
|
||||
"
|
||||
>
|
||||
Need help or got feedback?
|
||||
<a href="https://www.stemmechanics.com.au/contact"
|
||||
>Contact us</a
|
||||
>
|
||||
or touch base at
|
||||
<a href="https://twitter.com/stemmechanics"
|
||||
>@stemmechanics</a
|
||||
>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
align="center"
|
||||
style="
|
||||
font-size: 80%;
|
||||
text-align: center;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 2rem;
|
||||
"
|
||||
>
|
||||
Sent by STEMMechanics ·
|
||||
<a href="https://www.stemmechanics.com.au/"
|
||||
>Visit our Website</a
|
||||
>
|
||||
·
|
||||
<a href="https://twitter.com/stemmechanics"
|
||||
>@stemmechanics</a
|
||||
><br />PO Box 36, Edmonton, QLD 4869, Australia
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,24 +0,0 @@
|
||||
@if (count($usernames) > 2)
|
||||
Yo {{ $usernames[0] }}, {{ $usernames[1] }}, or is it {{ $usernames[count($usernames)-1] }}?
|
||||
@elseif (count($usernames) > 1)
|
||||
Yo {{ $usernames[0] }} or is it {{ $usernames[1] }}?
|
||||
@else
|
||||
Yo {{ $usernames[0] }},
|
||||
@endif
|
||||
|
||||
@if (count($usernames) == 1)
|
||||
Guess what, your username is {{ $usernames[0] }}.
|
||||
@else
|
||||
We have the following usernames registered to this email address:
|
||||
|
||||
@foreach($usernames as $username)
|
||||
- {{ $username }}
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
Need help or got feedback? Contact us at https://www.stemmechanics.com.au/contact or touch base on twitter at @stemmechanics
|
||||
|
||||
--
|
||||
Sent by STEMMechanics
|
||||
https://www.stemmechanics.com.au/
|
||||
PO Box 36, Edmonton, QLD 4869, Australia
|
||||
@@ -1,116 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html
|
||||
lang="{{ str_replace('_', '-', app()->getLocale()) }}"
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:v="urn:schemas-microsoft-com:vml"
|
||||
xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||
>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>STEMMechanics - Forgot Password</title>
|
||||
<link
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<!--[if gte mso 9]>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG />
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
<style>
|
||||
@import url("https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800&display=swap");
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<table
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
border="0"
|
||||
role="presentation"
|
||||
style="
|
||||
width: 100%;
|
||||
padding: 2rem;
|
||||
font-size: 1.1rem;
|
||||
color: #000000;
|
||||
font-family: Nunito, Arial, Helvetica, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
"
|
||||
>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://www.stemmechanics.com.au/">
|
||||
<img
|
||||
alt="STEMMechanics Logo"
|
||||
src="{{ $message->embed(public_path('assets').'/logo.webp') }}"
|
||||
width="400"
|
||||
height="62"
|
||||
/>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<p></p>
|
||||
<td><h2>Howdy there,</h2></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding-bottom: 2rem">
|
||||
At your request, you are now subscribed to our newsletter giving you tips, tricks and letting you know when new workshops are scheduled.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding-bottom: 2rem">
|
||||
If this wasn't you, you can unsubscribe by visiting <a href="https://www.stemmechanics.com.au/unsubscribe?email={{ $email }}">stemmechanics.com.au/unsubscribe</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
align="center"
|
||||
style="
|
||||
font-size: 90%;
|
||||
text-align: center;
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 2rem;
|
||||
border-top: 1px solid #ddd;
|
||||
"
|
||||
>
|
||||
Need help or got feedback?
|
||||
<a href="https://www.stemmechanics.com.au/contact"
|
||||
>Contact us</a
|
||||
>
|
||||
or touch base at
|
||||
<a href="https://twitter.com/stemmechanics"
|
||||
>@stemmechanics</a
|
||||
>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
align="center"
|
||||
style="
|
||||
font-size: 80%;
|
||||
text-align: center;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 2rem;
|
||||
"
|
||||
>
|
||||
Sent by STEMMechanics ·
|
||||
<a href="https://www.stemmechanics.com.au/"
|
||||
>Visit our Website</a
|
||||
>
|
||||
·
|
||||
<a href="https://twitter.com/stemmechanics"
|
||||
>@stemmechanics</a
|
||||
><br />PO Box 36, Edmonton, QLD 4869, Australia
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,14 +0,0 @@
|
||||
Howdy there,
|
||||
|
||||
At your request, you are now subscribed to our newsletter giving you tips, tricks and letting you know when new workshops are scheduled.
|
||||
|
||||
If this wasn't you, you can unsubscribe by visiting the following URL in your browser:
|
||||
|
||||
https://www.stemmechanics.com.au/unsubscribe
|
||||
|
||||
Need help or got feedback? Contact us at https://www.stemmechanics.com.au/contact or touch base on twitter at @stemmechanics
|
||||
|
||||
--
|
||||
Sent by STEMMechanics
|
||||
https://www.stemmechanics.com.au/
|
||||
PO Box 36, Edmonton, QLD 4869, Australia
|
||||
@@ -1,111 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html
|
||||
lang="{{ str_replace('_', '-', app()->getLocale()) }}"
|
||||
xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:v="urn:schemas-microsoft-com:vml"
|
||||
xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||
>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>STEMMechanics - Forgot Password</title>
|
||||
<link
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<!--[if gte mso 9]>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG />
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
<style>
|
||||
@import url("https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800&display=swap");
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<table
|
||||
cellspacing="0"
|
||||
cellpadding="0"
|
||||
border="0"
|
||||
role="presentation"
|
||||
style="
|
||||
width: 100%;
|
||||
padding: 2rem;
|
||||
font-size: 1.1rem;
|
||||
color: #000000;
|
||||
font-family: Nunito, Arial, Helvetica, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
"
|
||||
>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="https://www.stemmechanics.com.au/">
|
||||
<img
|
||||
alt="STEMMechanics Logo"
|
||||
src="{{ $message->embed(public_path('assets').'/logo.webp') }}"
|
||||
width="400"
|
||||
height="62"
|
||||
/>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<p></p>
|
||||
<td><h2>Howdy there,</h2></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding-bottom: 2rem">
|
||||
At your request, you are now unsubscribed from our newsletter.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
align="center"
|
||||
style="
|
||||
font-size: 90%;
|
||||
text-align: center;
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 2rem;
|
||||
border-top: 1px solid #ddd;
|
||||
"
|
||||
>
|
||||
Need help or got feedback?
|
||||
<a href="https://www.stemmechanics.com.au/contact"
|
||||
>Contact us</a
|
||||
>
|
||||
or touch base at
|
||||
<a href="https://twitter.com/stemmechanics"
|
||||
>@stemmechanics</a
|
||||
>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
align="center"
|
||||
style="
|
||||
font-size: 80%;
|
||||
text-align: center;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 2rem;
|
||||
"
|
||||
>
|
||||
Sent by STEMMechanics ·
|
||||
<a href="https://www.stemmechanics.com.au/"
|
||||
>Visit our Website</a
|
||||
>
|
||||
·
|
||||
<a href="https://twitter.com/stemmechanics"
|
||||
>@stemmechanics</a
|
||||
><br />PO Box 36, Edmonton, QLD 4869, Australia
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,10 +0,0 @@
|
||||
Howdy there,
|
||||
|
||||
At your request, you have been unsubscribed from our newsletter.
|
||||
|
||||
Need help or got feedback? Contact us at https://www.stemmechanics.com.au/contact or touch base on twitter at @stemmechanics
|
||||
|
||||
--
|
||||
Sent by STEMMechanics
|
||||
https://www.stemmechanics.com.au/
|
||||
PO Box 36, Edmonton, QLD 4869, Australia
|
||||
@@ -30,7 +30,6 @@ Route::get('/analytics', [AnalyticsController::class, 'index']);
|
||||
Route::post('/analytics', [AnalyticsController::class, 'store']);
|
||||
|
||||
Route::apiResource('users', UserController::class);
|
||||
Route::post('/users/forgotUsername', [UserController::class, 'forgotUsername']);
|
||||
Route::post('/users/forgotPassword', [UserController::class, 'forgotPassword']);
|
||||
Route::post('/users/resetPassword', [UserController::class, 'resetPassword']);
|
||||
Route::post('/users/resendVerifyEmailCode', [UserController::class, 'resendVerifyEmailCode']);
|
||||
@@ -45,9 +44,6 @@ Route::apiAttachmentResource('articles', ArticleController::class);
|
||||
Route::apiResource('events', EventController::class);
|
||||
Route::apiAttachmentResource('events', EventController::class);
|
||||
|
||||
Route::apiResource('subscriptions', SubscriptionController::class);
|
||||
Route::delete('subscriptions', [SubscriptionController::class, 'destroyByEmail']);
|
||||
|
||||
Route::post('/contact', [ContactController::class, 'send']);
|
||||
|
||||
Route::get('/logs/{name}', [LogController::class, 'show']);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
use App\Models\User;
|
||||
@@ -7,15 +8,16 @@ class AuthApiTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
|
||||
public function testLogin()
|
||||
{
|
||||
$user = User::factory()->create([
|
||||
'password' => bcrypt('password'),
|
||||
]);
|
||||
|
||||
|
||||
// Test successful login
|
||||
$response = $this->postJson('/api/login', [
|
||||
'username' => $user->username,
|
||||
'email' => $user->email,
|
||||
'password' => 'password',
|
||||
]);
|
||||
$response->assertStatus(200);
|
||||
@@ -23,7 +25,7 @@ class AuthApiTest extends TestCase
|
||||
'token',
|
||||
]);
|
||||
$token = $response->json('token');
|
||||
|
||||
|
||||
// Test getting authenticated user
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => "Bearer $token",
|
||||
@@ -32,19 +34,19 @@ class AuthApiTest extends TestCase
|
||||
$response->assertJson([
|
||||
'user' => [
|
||||
'id' => $user->id,
|
||||
'username' => $user->username,
|
||||
'email' => $user->email,
|
||||
]
|
||||
]);
|
||||
|
||||
|
||||
// Test logout
|
||||
$response = $this->withHeaders([
|
||||
'Authorization' => "Bearer $token",
|
||||
])->postJson('/api/logout');
|
||||
$response->assertStatus(204);
|
||||
|
||||
|
||||
// Test failed login
|
||||
$response = $this->postJson('/api/login', [
|
||||
'username' => $user->username,
|
||||
'email' => $user->email,
|
||||
'password' => 'wrongpassword',
|
||||
]);
|
||||
$response->assertStatus(422);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Tests\TestCase;
|
||||
@@ -8,6 +9,7 @@ class UsersApiTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
|
||||
public function testNonAdminUsersCanOnlyViewBasicUserInfo()
|
||||
{
|
||||
// create a non-admin user
|
||||
@@ -25,7 +27,7 @@ class UsersApiTest extends TestCase
|
||||
'users' => [
|
||||
'*' => [
|
||||
'id',
|
||||
'username'
|
||||
'display_name'
|
||||
]
|
||||
],
|
||||
'total'
|
||||
@@ -41,7 +43,7 @@ class UsersApiTest extends TestCase
|
||||
]);
|
||||
$response->assertJsonFragment([
|
||||
'id' => $nonAdminUser->id,
|
||||
'username' => $nonAdminUser->username
|
||||
'email' => $nonAdminUser->email
|
||||
]);
|
||||
|
||||
// ensure the admin user can access the endpoint and see additional user info
|
||||
@@ -51,7 +53,6 @@ class UsersApiTest extends TestCase
|
||||
'users' => [
|
||||
'*' => [
|
||||
'id',
|
||||
'username',
|
||||
'email'
|
||||
]
|
||||
],
|
||||
@@ -66,14 +67,13 @@ class UsersApiTest extends TestCase
|
||||
]);
|
||||
$response->assertJsonFragment([
|
||||
'id' => $nonAdminUser->id,
|
||||
'username' => $nonAdminUser->username
|
||||
'email' => $nonAdminUser->email
|
||||
]);
|
||||
}
|
||||
|
||||
public function testGuestCannotCreateUser()
|
||||
{
|
||||
$userData = [
|
||||
'username' => 'johndoe',
|
||||
'email' => 'johndoe@example.com',
|
||||
'password' => 'password',
|
||||
];
|
||||
@@ -81,7 +81,6 @@ class UsersApiTest extends TestCase
|
||||
$response = $this->postJson('/api/users', $userData);
|
||||
$response->assertStatus(401);
|
||||
$this->assertDatabaseMissing('users', [
|
||||
'username' => $userData['username'],
|
||||
'email' => $userData['email'],
|
||||
]);
|
||||
}
|
||||
@@ -91,7 +90,6 @@ class UsersApiTest extends TestCase
|
||||
$userData = [
|
||||
'first_name' => 'John',
|
||||
'last_name' => 'Doe',
|
||||
'username' => 'johndoe',
|
||||
'display_name' => 'jackdoe',
|
||||
'email' => 'johndoe@example.com',
|
||||
'password' => 'password',
|
||||
@@ -100,18 +98,16 @@ class UsersApiTest extends TestCase
|
||||
$response = $this->postJson('/api/register', $userData);
|
||||
$response->assertStatus(200);
|
||||
$this->assertDatabaseHas('users', [
|
||||
'username' => $userData['username'],
|
||||
'email' => $userData['email'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function testCannotCreateDuplicateUsername()
|
||||
public function testCannotCreateDuplicateEmailOrDisplayName()
|
||||
{
|
||||
$userData = [
|
||||
'display_name' => 'JackDoe',
|
||||
'first_name' => 'Jack',
|
||||
'last_name' => 'Doe',
|
||||
'username' => 'jackdoe',
|
||||
'display_name' => 'jackdoe',
|
||||
'email' => 'jackdoe@example.com',
|
||||
'password' => 'password',
|
||||
];
|
||||
@@ -120,14 +116,13 @@ class UsersApiTest extends TestCase
|
||||
$response = $this->postJson('/api/register', $userData);
|
||||
$response->assertStatus(200);
|
||||
$this->assertDatabaseHas('users', [
|
||||
'username' => 'jackdoe',
|
||||
'email' => 'jackdoe@example.com',
|
||||
]);
|
||||
|
||||
// Test creating duplicate user
|
||||
$response = $this->postJson('/api/register', $userData);
|
||||
$response->assertStatus(422);
|
||||
$response->assertJsonValidationErrors('username');
|
||||
$response->assertJsonValidationErrors(['display_name', 'email']);
|
||||
}
|
||||
|
||||
public function testUserCanOnlyUpdateOwnUser()
|
||||
@@ -135,7 +130,6 @@ class UsersApiTest extends TestCase
|
||||
$user = User::factory()->create();
|
||||
|
||||
$userData = [
|
||||
'username' => 'raffi',
|
||||
'email' => 'raffi@example.com',
|
||||
'password' => 'password',
|
||||
];
|
||||
@@ -145,14 +139,12 @@ class UsersApiTest extends TestCase
|
||||
$response->assertStatus(200);
|
||||
$this->assertDatabaseHas('users', [
|
||||
'id' => $user->id,
|
||||
'username' => 'raffi',
|
||||
'email' => 'raffi@example.com',
|
||||
]);
|
||||
|
||||
// Test updating another user
|
||||
$otherUser = User::factory()->create();
|
||||
$otherUserData = [
|
||||
'username' => 'otherraffi',
|
||||
'email' => 'otherraffi@example.com',
|
||||
'password' => 'password',
|
||||
];
|
||||
@@ -185,7 +177,6 @@ class UsersApiTest extends TestCase
|
||||
$user = User::factory()->create();
|
||||
|
||||
$userData = [
|
||||
'username' => 'Todd Doe',
|
||||
'email' => 'todddoe@example.com',
|
||||
'password' => 'password',
|
||||
];
|
||||
@@ -195,14 +186,12 @@ class UsersApiTest extends TestCase
|
||||
$response->assertStatus(200);
|
||||
$this->assertDatabaseHas('users', [
|
||||
'id' => $user->id,
|
||||
'username' => 'Todd Doe',
|
||||
'email' => 'todddoe@example.com'
|
||||
]);
|
||||
|
||||
// Test updating another user
|
||||
$otherUser = User::factory()->create();
|
||||
$otherUserData = [
|
||||
'username' => 'Kim Doe',
|
||||
'email' => 'kimdoe@example.com',
|
||||
'password' => 'password',
|
||||
];
|
||||
@@ -211,7 +200,6 @@ class UsersApiTest extends TestCase
|
||||
$response->assertStatus(200);
|
||||
$this->assertDatabaseHas('users', [
|
||||
'id' => $otherUser->id,
|
||||
'username' => 'Kim Doe',
|
||||
'email' => 'kimdoe@example.com',
|
||||
]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user