Skip to content

Commit

Permalink
feat: chat and notifications :3
Browse files Browse the repository at this point in the history
  • Loading branch information
Manuel-Antunes committed Jan 2, 2022
1 parent 3f3cae6 commit 82333fa
Show file tree
Hide file tree
Showing 17 changed files with 221 additions and 66 deletions.
5 changes: 4 additions & 1 deletion app/Controllers/Http/BoardChatsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import Board from 'App/Models/Board'
import Message from 'App/Schemas/Message'

export default class BoardChatsController {
public async index({ params, response, view }: HttpContextContract) {
public async index({ params, response, view, bouncer }: HttpContextContract) {
const board = await Board.find(params.board_id)

if (!board) {
return response.notFound('Board not found!')
}

await bouncer.with('BoardChatPolicy').authorize('view', board)

const messages = await Message.find({ boardId: board.id })

return view.render('boards/chat', { board, messages })
Expand Down
9 changes: 9 additions & 0 deletions app/Controllers/Http/NotificationsController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Notification from 'App/Schemas/Notification'

export default class NotificationsController {
public async index({ auth }: HttpContextContract) {
const notifications = await Notification.find({ ownerId: auth.user!.id })
return notifications
}
}
28 changes: 7 additions & 21 deletions app/Models/Board.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,6 @@ export default class Board extends BaseModel {
@column()
public masterId: number

@computed()
public get playersAtTheBoard(): number {
return this.players.length
}

@beforeFetch()
@beforeFind()
public static preloadPlayers(q: ModelQueryBuilderContract<typeof Board>) {
q.preload('players', (qb) => {
qb.pivotColumns(['character_name', 'session_who_entered'])
})
}

@hasOne(() => User, {
foreignKey: 'master_id',
})
Expand Down Expand Up @@ -75,7 +62,6 @@ export default class Board extends BaseModel {
relatedKey: 'id',
pivotRelatedForeignKey: 'player_id',
pivotTable: 'board_players',
pivotColumns: ['character_name', 'session_who_entered'],
})
public players: ManyToMany<typeof Users>

Expand All @@ -102,13 +88,13 @@ export default class Board extends BaseModel {

@computed()
public get avaluation(): number {
return (
this.starRating
.map((rating) => rating.number)
.reduce((count, el) => {
return count + el
}) / this.rateNumber
)
return this.starRating.length > 0
? this.starRating
.map((rating) => rating.number)
.reduce((count, el) => {
return count + el
}) / this.rateNumber
: 0
}

public async getRatingByUser(user: User) {
Expand Down
14 changes: 7 additions & 7 deletions app/Models/Event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,13 @@ export default class Event extends BaseModel {

@computed()
public get avaluation(): number {
return (
this.starRating
.map((rating) => rating.number)
.reduce((count, el) => {
return count + el
}) / this.rateNumber
)
return this.starRating.length > 0
? this.starRating
.map((rating) => rating.number)
.reduce((count, el) => {
return count + el
}) / this.rateNumber
: 0
}

@column()
Expand Down
14 changes: 7 additions & 7 deletions app/Models/User.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,13 @@ export default class User extends BaseModel {

@computed()
public get avaluation(): number {
return (
this.starRating
.map((rating) => rating.number)
.reduce((count, el) => {
return count + el
}) / this.rateNumber
)
return this.starRating.length > 0
? this.starRating
.map((rating) => rating.number)
.reduce((count, el) => {
return count + el
}) / this.rateNumber
: 0
}

@column()
Expand Down
16 changes: 16 additions & 0 deletions app/Policies/BoardChatPolicy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { BasePolicy } from '@ioc:Adonis/Addons/Bouncer'
import User from 'App/Models/User'
import Board from 'App/Models/Board'

export default class BoardChatPolicy extends BasePolicy {
public async view(user: User, board: Board) {
const exists = await board
.related('players')
.query()
.where('board_players.player_id', user.id)
.first()
if (!!exists || board.masterId === user.id) {
return true
}
}
}
35 changes: 35 additions & 0 deletions app/Schemas/Notification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Mongo from '@ioc:CuC/AdonisGoose'
import INotification from 'Contracts/interfaces/INotification'

const NotificationSchema = new Mongo.Schema<INotification>(
{
content: {
type: String,
required: true,
},
ownerId: {
type: Number,
required: true,
},
sender: {
type: Object,
required: true,
},
subject: {
type: String,
required: true,
},
photoUrl: {
type: String,
},
read: {
type: Boolean,
default: false,
},
},
{
timestamps: true,
}
)

export default Mongo.model<INotification>('Notification', NotificationSchema)
12 changes: 12 additions & 0 deletions contracts/interfaces/INotification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Board from 'App/Models/Board'
import { Document } from 'mongoose'

// eslint-disable-next-line @typescript-eslint/naming-convention
export default interface INotification extends Document {
ownerId: number
sender: Board
subject: string
content: string
photoUrl?: string
read: boolean
}
1 change: 1 addition & 0 deletions resources/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ window.Alpine = Alpine
window.IMask = IMask
Alpine.start()
window.axios = axios.create({ baseURL: '/' })
window.io = io
10 changes: 6 additions & 4 deletions resources/js/chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ export default () => ({
this.initEvents()
},
sendMessage(text) {
this.text = ''
const message = { content: text, boardId: this.boardId, sender: this.user }
this.socket.emit('message', message)
this.addMessageToChat(message)
if (text !== '') {
this.text = ''
const message = { content: text, boardId: this.boardId, sender: this.user }
this.socket.emit('message', message)
this.addMessageToChat(message)
}
},
addMessageToChat(message) {
this.messages.push(message)
Expand Down
26 changes: 23 additions & 3 deletions resources/js/partials/navbar.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
export default () => ({
notifications: [],
initNavbar(userId) {
this.socket = io('/notification', { query: { id: userId } })
this.initEvents()
this.getNotifications()
},
getNotifications() {
fetch('/notifications').then((data) => {
data.json().then((notifications) => {
this.notifications = notifications
})
})
},
toggle() {
this.open = !this.open
},
toggleNotifications() {
this.showNotifications = !this.showNotifications
},
open: false,
mobileShow: false,
newNotifications: false,
showNotifications: false,
close() {
this.open = false
},
handleProfileManagement() {
console.log('ook')
},
handleMobileArea() {
this.mobileShow = !this.mobileShow
},
initEvents() {
this.socket.on('notification', (data) => {
this.notifications.push(data)
})
},
})
15 changes: 12 additions & 3 deletions resources/views/boards/chat.edge
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,23 @@
</div> --}}
</div>
<div id="messages" class="flex flex-col space-y-4 p-3 overflow-y-auto h-96 scrollbar-thumb-blue scrollbar-thumb-rounded scrollbar-track-blue-lighter scrollbar-w-2 scrolling-touch" x-ref="message_field">
<template x-for='message in messages'>
<template x-for='(message,index) in messages'>
<div class="chat-message">
<div :class="{'flex items-end justify-end': user.id == message.sender.id, 'flex items-end': user.id != message.sender.id }">
<div :class="{'flex flex-col space-y-2 text-xs max-w-xs mx-2 order-2 items-start': user.id != message.sender.id, 'flex flex-col space-y-2 text-xs max-w-xs mx-2 order-2 items-end': user.id == message.sender.id }">
<div>
<span :class="{'px-4 py-2 rounded-lg inline-block rounded-br-none bg-blue-600 text-white ': user.id == message.sender.id, 'px-4 py-2 rounded-lg inline-block bg-gray-300 text-gray-600': user.id != message.sender.id }" x-text='message.content'></span>
<template x-if="messages[index+1]?messages[index+1].sender.id != message.sender.id?true:false:true">
<div :class="{'flex items-end': user.id == message.sender.id, 'flex items-end flex-row-reverse': user.id != message.sender.id }">
<span :class="{'px-4 py-2 rounded-lg inline-block rounded-br-none bg-blue-600 text-white ': user.id == message.sender.id, 'px-4 py-2 rounded-lg inline-block bg-gray-300 rounded-bl-none text-gray-600': user.id != message.sender.id }" x-text='message.content'></span>
<img x-bind:src="message.sender.photoUrl" alt="My profile" class="w-6 h-6 rounded-full order-1 mx-3 mt-2 align-bottom">
</div>
</template>
<template x-if="messages[index+1]?messages[index+1].sender.id != message.sender.id?false:true:false">
<div class="mx-12">
<span :class="{'px-4 py-2 rounded-lg inline-block bg-blue-600 text-white ': user.id == message.sender.id, 'px-4 py-2 rounded-lg inline-block bg-gray-300 text-gray-600': user.id != message.sender.id }" x-text='message.content'></span>
</div>
</template>
</div>
<img x-bind:src="message.sender.photoUrl" alt="My profile" class="w-6 h-6 rounded-full order-1">
</div>
</div>
</div>
Expand Down
12 changes: 5 additions & 7 deletions resources/views/partials/calendar.edge
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
<div class="antialiased sans-serif bg-gray-100">
@each(event in events)
<script>
if(!window.events){
window.events = []
}
<script>
window.events = []
@each(event in events)
window.events.push({
name:'{{event.name}}',
description: '{{event.description}}',
Expand All @@ -12,8 +10,8 @@
theme: '{{event.theme}}',
id: '{{event.id}}',
})
</script>
@endeach
@endeach
</script>
<div x-data="calendar" x-init="[initDate({{month}}), getNoOfDays(), setEvents(window.events)]" x-cloak>
<div class="container mx-auto md:px-4 py-2 md:py-5 mb-10">

Expand Down
35 changes: 29 additions & 6 deletions resources/views/partials/navbar.edge
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<!-- This example requires Tailwind CSS v2.0+ -->
<nav class="bg-gray-800 z-10" x-data="navbar">
<nav
class="bg-gray-800 z-10"
x-data="navbar"
@if(auth.user)
x-init="initNavbar({{auth.user.id}})
@endif
">
<div class="max-w-7xl mx-auto px-2 sm:px-6 lg:px-8">
<div class="relative flex items-center justify-between h-16">
<div class="absolute inset-y-0 left-0 flex items-center sm:hidden">
Expand Down Expand Up @@ -28,13 +34,30 @@
</div>
</div>
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
<button type="button" class="bg-gray-800 p-1 rounded-full text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white">
<div class="relative">
<button @click="toggleNotifications" type="button" class="bg-gray-800 p-1 rounded-full text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white">
<span class="sr-only">View notifications</span>
<!-- Heroicon name: outline/bell -->
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
</svg>
</button>
<div x-show="showNotifications" class="absolute text-gray-400 right-0 mt-2 bg-white rounded-md shadow-lg overflow-hidden z-20" style="width:20rem;">
<div class="py-2">
<template x-for="notification in notifications">
<a href="#" class="flex items-center px-4 py-3 border-b hover:bg-gray-100 -mx-2">
<img class="h-8 w-8 rounded-full object-cover mx-1" :src="notification.photoUrl" alt="avatar">
<p class="text-gray-600 text-sm mx-2" x-html="notification.subject">
</p>
</a>
</template>
<template x-if="notifications.length === 0">
<p class="text-center">Nenhuma notificação</p>
</template>
</div>
<a href="#" class="block bg-gray-800 text-white text-center font-bold py-2">Ver todas as notificações</a>
</div>
</div>
<div class="ml-3 relative">
@if(auth.isLoggedIn)
<div>
Expand All @@ -51,10 +74,10 @@
@else
<div>
<a href="/login">
<button @click="handleProfileManagement" type="button" class="bg-gray-800 flex text-sm rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white" id="user-menu-button" aria-expanded="false" aria-haspopup="true">
<span class="sr-only">Open user menu</span>
<img class="h-8 w-8 rounded-full" src="https://upload.wikimedia.org/wikipedia/commons/8/89/Portrait_Placeholder.png" alt="Profile Photo">
</button>
<button @click="handleProfileManagement" type="button" class="bg-gray-800 flex text-sm rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white" id="user-menu-button" aria-expanded="false" aria-haspopup="true">
<span class="sr-only">Open user menu</span>
<img class="h-8 w-8 rounded-full" src="https://upload.wikimedia.org/wikipedia/commons/8/89/Portrait_Placeholder.png" alt="Profile Photo">
</button>
</a>
</div>
@endif
Expand Down
1 change: 1 addition & 0 deletions start/bouncer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,5 @@ export const { policies } = Bouncer.registerPolicies({
BoardPolicy: () => import('App/Policies/BoardPolicy'),
CommentPolicy: () => import('App/Policies/CommentPolicy'),
StarRatingPolicy: () => import('App/Policies/StarRatingPolicy'),
BoardChatPolicy: () => import('App/Policies/BoardChatPolicy'),
})
1 change: 1 addition & 0 deletions start/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ Route.delete('/ratings/:id', 'StarRatingsController.destroy')
Route.post('/boards/:board_id/players', 'BoardPlayersController.store')

Route.get('/boards/:board_id/chat', 'BoardChatsController.index').middleware(['auth:web'])
Route.get('/notifications', 'NotificationsController.index').middleware(['auth:web'])
Loading

0 comments on commit 82333fa

Please sign in to comment.