Skip to content

Second task#2

Open
maneer17 wants to merge 7 commits into
mainfrom
second-task
Open

Second task#2
maneer17 wants to merge 7 commits into
mainfrom
second-task

Conversation

@maneer17
Copy link
Copy Markdown
Owner

No description provided.

@maneer17
Copy link
Copy Markdown
Owner Author

maneer17 commented Mar 27, 2026

Breakdown of how I handled Task 2 :
I've divided this task to small subtasks as follows :
Subtask 1 : Designing the Database (Migrations,Models):
Migrations created:

  • channels — id, user_id, name, description
  • members — id, channel_id, user_id
  • invitations — id, channel_id, user_id, invited_by_id, status
  • invitation_logs — id, invitation_id, status, changed_by
  • shared_days — id, channel_id, user_id, date + unique constraint [channel_id, user_id, date]
  • shared_day_entries — id, shared_day_id, time_entry_id + unique constraint [shared_day_id, time_entry_id]
  • comments — id, shared_day_id, user_id, body
    Models created:
  • Channel — owner(), members(), invitations(), sharedDays(), scopeOwned(), scopeJoined(), isOwner(), isMember(), isOwnerOrMember(), membersCount appended
  • Member — channel(), user()
  • Invitation — channel(), invitedUser(), invitedBy(), logs(), scopePending(), scopeReceived()
  • InvitationLog — invitation()
  • SharedDay — channel(), sharedBy(), entries(), comments(), totalTime attribute, entriesCount attribute
  • SharedDayEntry — sharedDay(), timeEntry()
  • Comment — sharedDay(), author()
  • TimeEntry — extended with sharedDayEntries(), scopeDate(), scopeHistory()
  • User — extended with channels(), memberships(), invitations(), sharedDays(), comments()

From Here I started dividing every feature into back and front
In Back I tackled it in this order :

  • Routes(Define the endpoints first)
  • Request (Validation)
  • Policies for Authorization
  • Service (if needed) Complex business logic that doesn't belong in the controller
  • Resource(for the output json)
  • controller(for putting everything together) .

Then I would try things in postman and get familiar with different responses .
Finally I will handle the front part by creating js file for the endpoints and move on with building components and putting them together in a view .


Subtask 2 : Channels
Allow users to create, view, update and delete channels. The owner has full control, members can only view.
Backend:

  • Routes — apiResource('channels') gives all crud routes.
  • StoreChannelRequest — validates name (required, max 255) and description (optional).
  • ChannelPolicy — viewAny: any authenticated user. view: owner or member. create: any authenticated user. update/delete: owner only.
  • ChannelResource — shows all attributes.
  • ChannelController — index returns {owned, joined} using scopes. show loads members and invitations. update/destroy check policy.

Frontend:

  • channelService.js — getChannels, getChannel, createChannel, updateChannel, deleteChannel.
  • ChannelsListView.vue — two tabs (My Channels / Member Of) using owned and joined from the index response.
  • ChannelCard.vue — displays name, description, members_count. Clicking navigates to channel dashboard.
  • CreateChannelView.vue — form with name and description. Redirects to channels list on success.
  • ChannelDashboard.vue — the main channel page with 4 tabs (overview, members, shared days, settings). Uses provide/inject to share channel and isOwner down to all child components.
  • ChannelOverview.vue — injects channel. Shows name, description, members count, created date.
  • ChannelSettings.vue — injects channel and isOwner. Shows delete button with confirm flow. Only visible to owner via visibleTabs computed in dashboard

Subtask 3 : Invitations
Channel owners can invite users by email or username. Invited users can accept or decline. Owners can cancel pending invitations. All changes are logged.
Backend:

  • Routes — apiResource('channels.invitations').scoped() for channel-scoped invitations. GET /me/invitations in MeController for received invitations.GET /users
  • UserController::index — searches users by name or email using searchByNameOrEmail scope to find users to invite.
  • UserResource — shapes the user response for the search
  • StoreInvitationRequest — validates identifier field which can be email or username.
  • UpdateInvitationRequest — validates status must be one of: accepted, declined, cancelled.
  • InvitationPolicy — viewAny/create: owner only. accept/decline: invited user only AND invitation must be pending. cancel: inviter only AND invitation must be pending.
  • InviteService — finds user by email or username, validates they're not owner/member, handles existing invitation states (pending/declined/cancelled)
  • InvitationStatusService — handles accept (+ creates Member), decline, cancel. Validates invitation is still pending.
  • InvitationObserver — automatically logs every invitation change without the services needing to know about it:
  • created → logs 'sent' with invited_by_id as user
  • updated → only fires if status changed, only for accepted/declined/cancelled. Logs accepted/declined with invited_id, logs cancelled with invited_by_id
  • InvitationResource — includes channel, invitedUser, invitedBy, status, created_at.
  • InvitationController — index returns channel's pending invitations (owner only). store uses InviteService. update handles accept (also creates Member record), decline, cancel. destroy for owner to delete.
  • MeController::invitations() — returns authenticated user's received pending invitations with channel info.

Frontend:

  • invitationService.js — getChanelInvitations, createInvitation, acceptInvitation, declineInvitation, cancelInvitation, getMyInvitations, getUsers (for search).
  • ChannelMembers.vue — injects channel and isOwner. Shows active members list. If owner: also shows pending invitations section and invite button.
  • InviteMemberModal.vue — search input that watches for changes and fetches matching users. Clicking a user fills the identifier. Submit sends invitation.
  • PendingInvitationsList.vue — loops through invitations and renders InvitationCard in 'sent' mode.
  • InvitationCard.vue — two modes: 'sent' (shows cancel button) and 'received' (shows accept + decline buttons). Uses useApi for each action.
  • InvitationsView.vue — route /my-invitations. Loads received pending invitations. Each card in 'received' mode. Refreshes list after accept/decline.





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


class CommentController extends Controller
{
public function index(Channel $channel, SharedDay $sharedDay)
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.

not an error but the channel can be fetched from the shared_day


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 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 :)

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

->paginate(15);

return response()->json($dates);
return response()->json([
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.

ok, but the meta attributes of pagination are returned by default.

];
}

public function withValidator($validator): void
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.

bad validation. enhance it. 2 queries while it can be done in one

Comment thread backend/app/Models/Channel.php Outdated
use HasFactory;

protected $fillable = ['user_id', 'name', 'description'];
protected $appends = ['members_count'];
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.

good but im a little concerned about adding the 'members_count' in the $append attribute. cuz it will execute a DB query on each fetched object which may cause N+1 problem

in general, try to avoid $append attributes that may make DB queries

Comment thread backend/app/Services/ReportService.php Outdated
->inRange($from, $to)
->selectRaw('count(*) as total_entries,
SUM(duration_minutes) as total_minutes,
COUNT(DISTINCT DATE(created_at)) as total_days
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.

COUNT(DISTINCT DATE(created_at)) as total_days bad performance

also read a little more about databases and query planners.

here the Date function break using the index and enforce full table scan. also the DISTINCT alone is heavy process.

Comment thread backend/app/Services/ReportService.php Outdated
->orderByDesc('total_minutes')
->get();

return $rows->map(fn($row) => [
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.

this require the data be processed inside php after fetched from database. which is not the best. try to return the data ready from the database directly as possible

if it costs a lot. then maybe writing a sql function and calling it is easier

])->toArray();
}

private function avgLabelsPerDay(int $userId, ?string $from, ?string $to): float
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.

some performance issues here
1- using Date() function in select
2- using Date() function in grouping
3- processing the data in php

always try to imagine your query will run by hundreds of users on millions of rows

Comment thread backend/app/Services/ReportService.php Outdated
->orderByDesc('avg_minutes')
->get();

return $rows->map(fn($row) => [
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.

adding the duartion_minutes column to simplify DB queries very good.

processing the data in php not ideal practice

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants