Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion backend/app/Http/Controllers/Api/AuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
use Illuminate\Http\Request;
use App\Models\User;
use Illuminate\Validation\ValidationException;
use App\Http\Requests\StoreUserRequest;
use App\Http\Requests\{StoreUserRequest, GoogleRequest};
use Illuminate\Support\Facades\Hash;
use Laravel\Socialite\Socialite;


class AuthController extends Controller
{
Expand Down Expand Up @@ -54,4 +56,48 @@ public function logout(Request $request){
$user->tokens()->delete();
return response()->json(["message" => "Logged out successfully " . $user->name]);
}





public function google(GoogleRequest $request)
{
$token = $request->validated()['token'];

try {
// userFromToken() works with access tokens
// take access token from frontend and verify it
$googleUser = Socialite::driver('google')
->stateless()
->userFromToken($token);

} catch (\Exception $e) {
return response()->json([
'message' => 'Invalid Google token',
], 401);
}

$user = User::firstOrCreate(
['email' => $googleUser->getEmail()],
[
'name' => $googleUser->getName(),
'google_id' => $googleUser->getId(),
'password' => null,
'email_verified_at' => now(),
]
);

if (!$user->google_id) {
$user->update([
'google_id' => $googleUser->getId(),
]);
// google_id here in case the user changed their email but not their entire account
}

$token = $user->createToken('api-token')->plainTextToken;

return response()->json(['token' => $token, 'user' => $user]);
}

}
75 changes: 75 additions & 0 deletions backend/app/Http/Controllers/Api/ChannelController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

namespace App\Http\Controllers\Api;
use App\Models\Channel;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Resources\ChannelResource;
use App\Http\Requests\{StoreChannelRequest, UpdateChannelRequest};
class ChannelController extends Controller
{
public function __construct()
{
$this->authorizeResource(Channel::class, 'channel');
}




Copy link
Copy Markdown
Collaborator

@IbrahimTEslim IbrahimTEslim Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add pagination
add filters general use filters

public function index(Request $request)
{
$type = $request->input('type'); // owned | joined

$query = match ($type) {
'owned' => Channel::owned(auth()->user()),
'joined' => Channel::joined(auth()->user()),
default => Channel::query(),
};

$channels = $query
->withCount('members')
->latest()
->paginate($this->paginate);

return ChannelResource::collection($channels);
}

/**
* Store a newly created resource in storage.
*/
public function store(StoreChannelRequest $request)
{
$channel = auth()->user()->channels()->create($request->validated());
return new ChannelResource($channel);
}

/**
* Display the specified resource.
*/
public function show(Channel $channel)
{
$channel->load(['members.user', 'invitations'])
->loadCount('members');

return new ChannelResource($channel);
}

/**
* Update the specified resource in storage.
*/
public function update(UpdateChannelRequest $request, Channel $channel)
{
$validated = $request->validated();
$channel->update($validated);
return new ChannelResource($channel);
}

/**
* Remove the specified resource from storage.
*/
public function destroy(Channel $channel)
{
$channel->delete();
return new ChannelResource($channel);
}
}
54 changes: 54 additions & 0 deletions backend/app/Http/Controllers/Api/CommentController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace App\Http\Controllers\Api;

use App\Http\Resources\CommentResource;
use App\Http\Controllers\Controller;
use App\Http\Requests\{StoreCommentRequest, UpdateCommentRequest};
use App\Models\{Channel, Comment, SharedDay};

class CommentController extends Controller
{
public function index(SharedDay $sharedDay)
{
$this->authorize('viewAny', [Comment::class, $sharedDay->channel]);

return CommentResource::collection(
$sharedDay->comments()
->with(['author', 'sharedDay.channel'])
->latest()
->paginate($this->paginate)
);
}

public function store(StoreCommentRequest $request, SharedDay $sharedDay)
{
$this->authorize('create', [Comment::class, $sharedDay->channel]);

$comment = $sharedDay->comments()->create([
...$request->validated(),
'user_id' => auth()->id()
]);

return new CommentResource($comment->load(['author', 'sharedDay.channel']));
}

public function update(UpdateCommentRequest $request, SharedDay $sharedDay, Comment $comment)
{
$this->authorize('update', $comment);

$comment->update($request->validated());

return new CommentResource($comment->load(['author', 'sharedDay.channel']));
}

public function destroy(SharedDay $sharedDay, Comment $comment)
{
$this->authorize('delete', [$comment, $sharedDay->channel]);

$comment->delete();

return new CommentResource($comment);
}

}
52 changes: 52 additions & 0 deletions backend/app/Http/Controllers/Api/InvitationController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Models\{Channel, Invitation, User};
use App\Http\Controllers\Controller;
use App\Http\Requests\{StoreInvitationRequest, UpdateInvitationRequest};
use App\Http\Resources\InvitationResource;
use App\Services\{InvitationStatusService, InviteService};

class InvitationController extends Controller
{
public function index(Channel $channel)
{
$this->authorize('viewAny', [Invitation::class, $channel]);
$invitations = Invitation::query()
->forChannel($channel)
->pending()
->with(['invitedUser', 'channel'])
->latest()
->paginate($this->paginate);


return InvitationResource::collection($invitations);
}



public function store(StoreInvitationRequest $request, Channel $channel, InviteService $service )
{
$validated = $request->validated();
$identifier = $validated['identifier'];
$invitation = $service->invite($identifier, $channel);
return new InvitationResource($invitation);
}

public function update(UpdateInvitationRequest $request, Channel $channel, Invitation $invitation, InvitationStatusService $service)
{
$validated = $request->validated();
$status = $validated['status'];

match ($status) {
'accepted' => $this->authorize('accept', $invitation),
'declined' => $this->authorize('decline', $invitation),
'cancelled' => $this->authorize('cancel', $invitation),
};

$service->changeStatus($invitation, $status, auth()->user());
return new InvitationResource($invitation);
}

}
43 changes: 43 additions & 0 deletions backend/app/Http/Controllers/Api/MeController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Invitation;
use App\Http\Resources\{InvitationResource, SharedDayResource};

class MeController extends Controller
{
public function invitations(Request $request)
{
$invitations = Invitation::query()
->received($request->user())
->pending()
->with(['invitedBy', 'channel'])
->latest()
->paginate($this->paginate);

return InvitationResource::collection($invitations);
}

Copy link
Copy Markdown
Collaborator

@IbrahimTEslim IbrahimTEslim Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add pagination + well done avoiding N+1 query problem

public function sharedDays()
{
$sharedDays = auth()->user()
->sharedDays()
->with('channel')
->get()
->groupBy('date');

return $sharedDays->map(function ($days, $date) {
return [
'date' => $date,
'channels' => $days->map(fn($day) => [
'id' => $day->channel->id,
'name' => $day->channel->name,
'shared_day_id' => $day->id,
])
];
})->values();
}
}
20 changes: 20 additions & 0 deletions backend/app/Http/Controllers/Api/ReportController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Requests\ReportRequest;
use App\Services\ReportService;
class ReportController extends Controller
{
public function index(ReportRequest $request, ReportService $service)
{
return $service->generate(auth()->id(),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

read $from + $to from the validated form request :)

$request->validated()['from'],
$request->validated()['to'],
$request->boolean('refresh'));
}


}
68 changes: 68 additions & 0 deletions backend/app/Http/Controllers/Api/SharedDayController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

namespace App\Http\Controllers\Api;

use App\Models\{Channel, SharedDay};
use App\Http\Controllers\Controller;
use App\Http\Requests\StoreSharedDayRequest;
use Illuminate\Support\Facades\DB;
use App\Http\Resources\SharedDayResource;

class SharedDayController extends Controller
{
// GET /channels/{channel}/shared-days
public function index(Channel $channel)
{
$this->authorize('viewAny', $channel);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pagination. in case where we have millions of records it will fails with 500 not handled error

return SharedDayResource::collection(
$channel->sharedDays()->paginate($this->paginate)
);
}

// GET /channels/{channel}/shared-days/{shared_day}
public function show(Channel $channel, SharedDay $sharedDay)
{
$this->authorize('view', $channel);

return new SharedDayResource($sharedDay->load(['entries.timeEntry', 'channel']));
}

// POST /shared-days
public function store(StoreSharedDayRequest $request)
{
$sharedDays = collect();

DB::transaction(function () use ($request, &$sharedDays) {
foreach ($request->channel_ids as $channelId) {
$channel = Channel::findOrFail($channelId);
$this->authorize('create', [SharedDay::class, $channel]);

$sharedDay = SharedDay::firstOrCreate([
'channel_id' => $channelId,
'user_id' => auth()->id(),
'date' => $request->date,
]);

foreach ($request->entry_ids as $entryId) {
$sharedDay->entries()->firstOrCreate([
'time_entry_id' => $entryId
]);
}

$sharedDay->load(['entries.timeEntry', 'channel']);
$sharedDays->push($sharedDay);
}
});

return SharedDayResource::collection($sharedDays);
}

public function destroy(Channel $channel, SharedDay $sharedDay)
{
$this->authorize('delete', $sharedDay);

$sharedDay->delete();
return new SharedDayResource($sharedDay);
}
}
Loading