tsitsi98's avatar

Create one time click urls

Hello, i have two buttons in an email template for either accepting or rejecting a request, i want when the user presses one button, both of them to get marked as 'used' and don't let the user perform the reject or accept action again. So far i use time temporary urls:

		public function sendRequest()
    {
        $acceptUrl = URL::temporarySignedRoute('accept.request', now()->addMinutes(30), [
            'user' => $this->user->id,
            'accountant' => Auth::user()->id
        ]);
        $rejectUrl = URL::temporarySignedRoute('reject.request', now()->addMinutes(30), [
            'user' => $this->user->id,
            'accountant' => Auth::user()->id
        ]);

        $data = [
            'email' => Auth::user()->email,
            'name' => Auth::user()->name,
            'userName' => $this->userName,
            'acceptUrl' => $acceptUrl,
            'rejectUrl' => $rejectUrl
        ];
        Mail::to($this->user->email)->send(new Email($data, 'accountant-request'));
        $this->successMessage = 'send successfull!';
    }

and these are the routes:

Route::get('/accept-request/{user}/{accountant}', [RequestController::class, 'acceptRequest'])
    ->name('accept.request')
    ->middleware('signed');
Route::get('/reject-request/{user}/{accountant}', [RequestController::class, 'rejectRequest'])
    ->name('reject.request')
    ->middleware('signed');

and here is a part of my controller where the accept action is implemented:

public function acceptRequest($userId, $accountantId)
    {
        $accountant = User::findOrFail($accountantId);
        $user = User::findOrFail($userId);

        $accountant->users()->attach($userId);
        $data = [
            'email' => '[email protected]',
            'name' => 'test name',
            'accountantName' => $accountant->name,
            'userName' => $user->name,
        ];
        Mail::to($accountant->email)->send(new Email($data, 'accept-accountant-request'));
        return redirect('/')->with('success_message', 'Success');
    }
0 likes
8 replies
s4muel's avatar

Here is an AI generated answer, so you can get inspired, seems pretty ok to me:

To achieve this, you can introduce a new table (let's call it request_actions) in your database to keep track of whether a request has been acted upon or not. This table can have fields like user_id, accountant_id and status.

When a request is made, an entry is created in this table with the status as 'pending'. When either the 'accept' or 'reject' action is performed, the status is updated to 'used'.

Before performing any action, you can check the status of the request. If it's 'used', you can prevent the action and return a message to the user.

Here is how you can modify your acceptRequest method:

public function acceptRequest($userId, $accountantId)
{
    $requestAction = RequestAction::where('user_id', $userId)
                                  ->where('accountant_id', $accountantId)
                                  ->first();

    if ($requestAction && $requestAction->status == 'used') {
        return redirect('/')->with('error_message', 'This request has already been acted upon.');
    }

    $accountant = User::findOrFail($accountantId);
    $user = User::findOrFail($userId);

    $accountant->users()->attach($userId);

    // Update the request action status to 'used'
    if ($requestAction) {
        $requestAction->status = 'used';
        $requestAction->save();
    }

    $data = [
        'email' => '[email protected]',
        'name' => 'test name',
        'accountantName' => $accountant->name,
        'userName' => $user->name,
    ];

    Mail::to($accountant->email)->send(new Email($data, 'accept-accountant-request'));
    return redirect('/')->with('success_message', 'Success');
}

You would need to do a similar check in your rejectRequest method as well.

Please note that you would need to create a new model RequestAction and a migration for the request_actions table. Also, don't forget to create an entry in the request_actions table when a new request is made.

martinbean's avatar

@s4muel I’m sure OP could have thrown their question into ChatGPT, or waited for Lary to wake up, if they wanted an A.I. generated answer…

s4muel's avatar

@martinbean Lary wasnt here and he got a pretty decent answer (curated, not just random one). your assumption of him trying chatgpt might as well be false as true. if he didnt, i provided one for him and made it clear its from AI and that i went through it. 🤷‍♂️

martinbean's avatar

if he didnt [try ChatGPT], i provided one for him and made it clear its from AI and that i went through it.

@s4muel Which is my point. You’re not providing any value copy-and-pasting answers from something like ChatGPT.

If you’re going to try and answer a question, then actually try to answer it yourself.

s4muel's avatar

@martinbean why not? i respect your presence on the forum, but i dont agree with your point. are you trying to help the guy or teach me a lesson or to just to be a self-proclaimed guardian of the forum? it does provide a value and might help him, i went through it (i start to repeat myself) and it should really help him to find a solution.

anyway, what else do you disapprove next? using google? ("let me chatgpt that for you" as an analogy https://letmegooglethat.com/ ) its a tool to solve the issue, isn't it? dont get triggered so easy, man ✌️.

sorry for the off-topic.

Snapey's avatar

I would have a single link with a parameter of accept or reject. I don't see any point in using distinct endpoints.

Whatever they are accepting or rejecting just needs marking with their response, only if it is not already set.

Use signed URLs if you want. Use temporary (time limited) links if you want. Neither play any part in noting which link they clicked.

1 like
martinbean's avatar

@tsitsi98 I have no idea what it is that is being “accepted” or “rejected” as your route and controller naming is a mess. Try to follow resourceful resource naming when creating your routes.

In an answer to the actual question, if you’re “accepting” or “rejecting” something then all you need to do is check if whatever model has already been accepted or rejected. If it has, return an error response. If not, accept/reject it.

class FooController extends Controller
{
    public function accept(Foo $foo)
    {
        abort_if($foo->hasBeenAccepted() || $foo->hasBeenRejected(), 400);

        $foo->accept();

        return redirect()->to('/somewhere');
    }

    public function reject(Foo $foo)
    {
        abort_if($foo->hasBeenAccepted() || $foo->hasBeenRejected(), 400);

        $foo->reject();

        return redirect()->to('/somewhere');
    }
}

So above, it’s clear to see that it’s a Foo model that a user can accept or reject. The corresponding URLs would look something like /foos/{foo}/accept and /foos/{foo}/reject (with the signature appended of course).

1 like
tsitsi98's avatar

@martinbean thank you for your response! I will try this and i will be more resourceful as you said! I'm a beginner and still learning! :)

Please or to participate in this conversation.