Skip to content

Commit

Permalink
Version 0.1.0 (#7)
Browse files Browse the repository at this point in the history
* Clean up script

* chat look and feel update

* Bicep tweaks
  • Loading branch information
benc-uk authored Nov 11, 2021
1 parent 2358668 commit 64a43d6
Show file tree
Hide file tree
Showing 15 changed files with 136 additions and 53 deletions.
4 changes: 0 additions & 4 deletions .vscode/settings.json

This file was deleted.

16 changes: 8 additions & 8 deletions api/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions api/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "chatr-serverless-api",
"version": "0.0.9",
"version": "0.1.0",
"description": "Serverless API and event handler for Chatr",
"scripts": {
"start": "swa start ../client --api . --swa-config-location ../client",
"start": "swa start ../client --api-location . --swa-config-location ../client",
"lint": "eslint . && prettier --check *.js",
"lint-fix": "eslint . --fix && prettier --write *.js"
},
Expand Down
13 changes: 8 additions & 5 deletions api/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ const chatsTable = 'chats'
const usersTable = 'users'
const partitionKey = 'chatr'

if (!account || !accountKey) {
console.log('### 💥 Fatal! STORAGE_ACCOUNT_NAME and/or STORAGE_ACCOUNT_KEY is not set')
}

const credential = new AzureNamedKeyCredential(account, accountKey)
const serviceClient = new TableServiceClient(`https://${account}.table.core.windows.net`, credential)
const userTableClient = new TableClient(`https://${account}.table.core.windows.net`, usersTable, credential)
const chatTableClient = new TableClient(`https://${account}.table.core.windows.net`, chatsTable, credential)

if (!account || !accountKey) {
console.log('### 💥 Fatal! STORAGE_ACCOUNT_NAME and/or STORAGE_ACCOUNT_KEY is not set')
}

// ==============================================================
// Create tables and absorb errors if they already exist
// ==============================================================
Expand Down Expand Up @@ -79,7 +79,10 @@ async function listChats() {
let chatList = chatTableClient.listEntities()

for await (const chat of chatList) {
chatsResp[chat.rowKey] = JSON.parse(chat.data)
let chatObj = JSON.parse(chat.data)
// Timestamp only used by cleanup script
chatObj.timestamp = chat.timestamp
chatsResp[chat.rowKey] = chatObj
}
return chatsResp
}
Expand Down
23 changes: 23 additions & 0 deletions client/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,29 @@ footer {
color: #c0bdba !important;
}

.chatMsgRow {
width: 100%;
display: flex;
}

.chatMsg {
max-width: 70%;
}

.chatMsgTitle {
font-size: 0.6rem;
font-weight: bold;
color: hsl(204, 86%, 53%);
}

.chatMsgBody {
font-size: 0.9rem;
}

.chatRight {
justify-content: flex-end;
}

.loaderOverlay {
position: absolute;
top: 0;
Expand Down
11 changes: 6 additions & 5 deletions client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Chatr App</title>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected].2/css/bulma.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected].3/css/bulma.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" />

Expand All @@ -25,20 +25,21 @@
<!-- Main app -->
<section class="section" id="app">
<!-- display spinner if we're not online yet -->
<div v-if="!online || !user || !ws" class="loaderOverlay">
<div v-if="(!online || !user || !ws) && !error" class="loaderOverlay">
<div class="loader">Loading...</div>
</div>

<div class="notification is-danger" v-if="error">{{ error }}</div>
<div class="columns" v-if="ws">

<div class="columns" v-if="ws && !error">
<!-- box for group chats -->
<div class="column">
<div class="title is-4">
Group Chats
<button @click="newChat()" class="button is-info is-pulled-right"><i class="fas fa-comment-dots"></i>&nbsp; New Chat</button>
</div>

<aside class="menu chatMenu box">
<aside class="menu chatMenu box" ref="chatList">
<ul class="menu-list">
<li v-for="(chat, chatId) in allChats" >
<a class="animate__animated animate__bounceIn" @click.stop="joinChat(chatId, chat.name)">
Expand Down Expand Up @@ -134,6 +135,6 @@

</section>

<footer>Chatr v0.0.9, Ben Coleman 2021 <a href="https://github.com/benc-uk/chatr" target="_blank">[GitHub Project]</a></footer>
<footer>Chatr v0.1.0, Ben Coleman 2021 <a href="https://github.com/benc-uk/chatr" target="_blank">[GitHub Project]</a></footer>
</body>
</html>
14 changes: 14 additions & 0 deletions client/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ new Vue({

// Now connect to Azure Web PubSub using the URL we got
this.ws = new WebSocket(token.url, 'json.webpubsub.azure.v1')

// Both of these handle error situations
this.ws.onerror = (evt) => {
this.error = `WebSocket error ${evt.message}`
}
this.ws.onclose = (evt) => {
this.error = `WebSocket closed, code: ${evt.code}`
}

// Custom notification event, rather that relying on the system connected event
this.ws.onopen = () => {
this.ws.send(
Expand Down Expand Up @@ -123,6 +132,11 @@ new Vue({
if (msg.from === 'server' && msg.data.chatEvent === 'chatCreated') {
let chat = JSON.parse(msg.data.data)
this.$set(this.allChats, chat.id, chat)

this.$nextTick(() => {
const chatList = this.$refs.chatList
chatList.scrollTop = chatList.scrollHeight
})
}

if (msg.from === 'server' && msg.data.chatEvent === 'chatDeleted') {
Expand Down
24 changes: 17 additions & 7 deletions client/js/components/chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import Vue from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.esm.browser.js
export default Vue.component('chat', {
data() {
return {
chatText: '',
message: '',
connected: false,
chats: [],
}
},

Expand All @@ -15,8 +15,7 @@ export default Vue.component('chat', {
id: String,
active: Boolean,
user: Object,
// This is shared with the parent app component
ws: WebSocket,
ws: WebSocket, // This is shared with the parent app component
},

async mounted() {
Expand All @@ -31,7 +30,7 @@ export default Vue.component('chat', {

// User sent messages, i.e. from sendMessage() below
if (msg.data.message && msg.data.fromUserName) {
this.appendChat(`<b>${msg.data.fromUserName}:</b> ${msg.data.message}`)
this.appendChat(msg.data.message, msg.data.fromUserName)
break
}

Expand All @@ -50,8 +49,12 @@ export default Vue.component('chat', {
},

methods: {
appendChat(text) {
this.chatText += `${text}<br/>`
appendChat(text, from = null) {
this.chats.push({
text,
from,
time: new Date(),
})

// eslint-disable-next-line no-undef
Vue.nextTick(() => {
Expand Down Expand Up @@ -91,6 +94,13 @@ export default Vue.component('chat', {
<button class="button is-warning" @click="$emit('leave', id)"><i class="far fa-times-circle"></i><span class="is-hidden-mobile">&nbsp; Leave</span></button>
</div>
<div class="chatBox" contentEditable="false" readonly v-html="chatText" ref="chatBox"></div>
<div class="chatBox" contentEditable="false" readonly ref="chatBox">
<div v-for="chat of chats" class="chatMsgRow" :class="{chatRight: user.userDetails == chat.from}">
<div class="card m-3 p-2 chatMsg">
<div class="chatMsgTitle text-info" v-if="chat.from">{{ chat.from }}</div>
<div class="chatMsgBody" v-html="chat.text"></div>
</div>
</div>
</div>
</div>`,
})
4 changes: 2 additions & 2 deletions client/js/utils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { toast } from 'https://cdn.jsdelivr.net/npm/bulma-toast@2.3.0/dist/bulma-toast.esm.js'
import { toast } from 'https://cdn.jsdelivr.net/npm/bulma-toast@2.4.1/dist/bulma-toast.esm.js'

export default {
uuidv4() {
Expand All @@ -14,7 +14,7 @@ export default {
message: msg,
type: `is-${type}`,
duration: 1500,
position: 'top-center',
position: 'bottom-center',
animate: { in: 'fadeIn', out: 'fadeOut' },
})
},
Expand Down
2 changes: 1 addition & 1 deletion client/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ <h4 class="title is-4">Azure Web PubSub Demo</h4>
</div>
</section>

<footer>Chatr v0.0.9, Ben Coleman 2021 <a href="https://github.com/benc-uk/chatr" target="_blank">[GitHub Project]</a></footer>
<footer>Chatr v0.1.0, Ben Coleman 2021 <a href="https://github.com/benc-uk/chatr" target="_blank">[GitHub Project]</a></footer>

</body>
</html>
2 changes: 1 addition & 1 deletion deploy/modules/pubsub.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ param name string = 'chatr'
param sku string = 'Free_F1'
param eventHandlerUrl string

resource pubsub 'Microsoft.SignalRService/WebPubSub@2021-04-01-preview' = {
resource pubsub 'Microsoft.SignalRService/webPubSub@2021-10-01' = {
name: name
location: location

Expand Down
2 changes: 1 addition & 1 deletion deploy/modules/static-webapp.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ param repoUrl string
@secure()
param repoToken string

resource staticApp 'Microsoft.Web/staticSites@2020-12-01' = {
resource staticApp 'Microsoft.Web/staticSites@2021-02-01' = {
name: name
location: location

Expand Down
1 change: 0 additions & 1 deletion deploy/modules/storage.bicep
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ resource storageAcct 'Microsoft.Storage/storageAccounts@2021-02-01' = {

sku: {
name: 'Standard_LRS'
tier: 'Standard'
}
}

Expand Down
32 changes: 16 additions & 16 deletions makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,41 @@ GITHUB_REPO ?= $(shell git remote get-url origin)
GITHUB_TOKEN ?=

# Don't change
SRC_DIR := api
API_DIR := api
CLIENT_DIR := client

.PHONY: help run deploy lint lint-fix
.DEFAULT_GOAL := help
.EXPORT_ALL_VARIABLES:

help: ## 💬 This help message
help: ## 💬 This help message
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'

lint: $(SRC_DIR)/node_modules ## 🔎 Lint & format, will not fix but sets exit code on error
cd $(SRC_DIR); npm run lint
lint: $(API_DIR)/node_modules ## 🔎 Lint & format, will not fix but sets exit code on error
cd $(API_DIR); npm run lint
eslint $(CLIENT_DIR)

lint-fix: $(SRC_DIR)/node_modules ## 📜 Lint & format, will try to fix errors and modify code
cd $(SRC_DIR); npm run lint-fix
lint-fix: $(API_DIR)/node_modules ## 📜 Lint & format, will try to fix errors and modify code
cd $(API_DIR); npm run lint-fix

run: $(SRC_DIR)/node_modules ## 🏃 Run server locally using node
run: $(API_DIR)/node_modules ## 🏃 Run server locally using node
@which swa > /dev/null || { echo "👋 Must install the SWA CLI https://aka.ms/swa-cli"; exit 1; }
swa start ./client --api ./api --swa-config-location ./client
swa start ./client --api-location ./api --swa-config-location ./client

clean: ## 🧹 Clean up project
rm -rf $(SRC_DIR)/node_modules
clean: ## 🧹 Clean up project
rm -rf $(API_DIR)/node_modules

deploy: ## 🚀 Deploy everything to Azure using Bicep
deploy: ## 🚀 Deploy everything to Azure using Bicep
@./deploy/deploy.sh

tunnel: ## 🚇 Start loophole tunnel to expose localhost
tunnel: ## 🚇 Start loophole tunnel to expose localhost
loophole http 7071 --hostname chatr

# ============================================================================

$(SRC_DIR)/node_modules: $(SRC_DIR)/package.json
cd $(SRC_DIR); npm install --silent
touch -m $(SRC_DIR)/node_modules
$(API_DIR)/node_modules: $(API_DIR)/package.json
cd $(API_DIR); npm install --silent
touch -m $(API_DIR)/node_modules

$(SRC_DIR)/package.json:
$(API_DIR)/package.json:
@echo "package.json was modified"
37 changes: 37 additions & 0 deletions scripts/cleanup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#!node
const state = require('../api/state')

const DELETE_AGE = process.argv[2] !== undefined ? parseInt(process.argv[2]) : 24
console.log(`### Deleting data older than ${DELETE_AGE} hours`)

console.log('### Cleaning up old users')
state.listUsers().then((users) => {
for (let userId in users) {
const user = users[userId]

const timestamp = new Date(user.timestamp)
const now = new Date()
const ageInHours = (now.getTime() - timestamp.getTime()) / (1000 * 60 * 60)

if (ageInHours > DELETE_AGE) {
console.log(`### Deleting user ${user.userName} with age of ${ageInHours} hours`)
state.removeUser(userId)
}
}
})

console.log('### Cleaning up old chats')
state.listChats().then((chats) => {
for (let chatId in chats) {
const chat = chats[chatId]

const timestamp = new Date(chat.timestamp)
const now = new Date()
const ageInHours = (now.getTime() - timestamp.getTime()) / (1000 * 60 * 60)

if (ageInHours > DELETE_AGE) {
console.log(`### Deleting chat ${chat.name} with age of ${ageInHours} hours`)
state.removeChat(chatId)
}
}
})

0 comments on commit 64a43d6

Please sign in to comment.