Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
dist
.github
i18n
.*
26 changes: 26 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
"unused-imports"
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],
rules: {
"no-case-declarations": "off",
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}
]
},
};
31 changes: 31 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Build and Publish
# Triggers the workflow on push or pull request events
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Setup Node.js environment
uses: actions/setup-node@v2.1.2
with:
node-version: '22.11.0'

- name: Install dependencies
run: npm i

- name: Install Rocket.Chat Apps cli
run: npm i -g @rocket.chat/apps-cli

- name: ESLint check
run: npm run lint

- name: Prettier check
run: npm run prettier-check

- name: Typescript check
run: npm run typecheck

- name: Bundle App
run: rc-apps package
70 changes: 70 additions & 0 deletions .github/workflows/package.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# This workflow will be responsible to pack the app (compiled and not compiled version) to be added as asset on the release
name: Package App
on:
workflow_dispatch:
release:
types: [published]

permissions:
contents: write
# This is the main job that will be running in a virtual machine (ubuntu)
jobs:
package:
runs-on: ubuntu-latest

steps:
# L21-L25 This is the checkout step wich is responsible authenticate the workflow
- name: Checkout
uses: actions/checkout@v2
with:
persist-credentials: false
ssh-key: '${{ secrets.COMMIT_KEY }}'
# L26-L31 This will set all properties from the app.json to github action env variables
- name: JSON to variables
uses: antifree/json-to-variables@v1.0.1
with:
filename: 'app.json'
prefix: app
# L33-L36 Setup the nodejs version
- name: Setup Node.js environment
uses: actions/setup-node@v2.1.2
with:
node-version: '14.21.3'
# L40-L41 Install the dependencies
- name: Install dependencies
run: npm i
# L43-L44 Install the apps cli
- name: Install Rocket.Chat Apps cli
run: npm i -g @rocket.chat/apps-cli
# L46-L47 Pack the app (compiled)
- name: Bundle App Compiled
run: rc-apps package
# L49-L50 Renames the pack (compiled)
- name: Renaming Package Name
run: cd dist && mv ${{ env.app_nameSlug }}_${{ env.app_version }}.zip ${{ env.app_nameSlug }}_${{ env.app_version }}-compiled.zip
# L52-L53 Pack the app (not compiled)
- name: Bundle App Not Compiled
run: rc-apps package --no-compile
# L55-L56 Renames the pack (not compiled)
- name: Renaming Not Compiled Package Name
run: cd dist && mv ${{ env.app_nameSlug }}_${{ env.app_version }}.zip ${{ env.app_nameSlug }}_${{ env.app_version }}-not-compiled.zip
# L58-L76 Uploads both compiled and not compiled files inside the release
- name: Upload TimeOff Compiled
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: dist/${{ env.app_nameSlug}}_${{ env.app_version }}-compiled.zip
asset_name: ${{ env.app_nameSlug}}_${{ env.app_version }}-compiled.zip
tag: ${{ env.app_version }}
overwrite: true
body: ${{ github.event.release.body }}

- name: Upload TimeOff Not Compiled
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: dist/${{ env.app_nameSlug }}_${{ env.app_version }}-not-compiled.zip
asset_name: ${{ env.app_nameSlug }}_${{ env.app_version }}-not-compiled.zip
tag: ${{ env.app_version }}
overwrite: true
body: ${{ github.event.release.body }}
9 changes: 9 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

# This loads nvm.sh and sets the correct PATH before running hook
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

npm run lint
npm run prettier-check
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
dist
.*
app.json
8 changes: 8 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"semi": true,
"trailingComma": "all",
"singleQuote": true,
"printWidth": 120,
"useTabs": true,
"tabWidth": 4
}
77 changes: 44 additions & 33 deletions TimeOffApp.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {
IAppAccessors,
IConfigurationExtend,
IConfigurationModify,
IEnvironmentRead,
IHttp,
ILogger,
IModify,
IPersistence,
IRead,
IAppAccessors,
IConfigurationExtend,
IConfigurationModify,
IEnvironmentRead,
IHttp,
ILogger,
IModify,
IPersistence,
IRead,
} from '@rocket.chat/apps-engine/definition/accessors';
import { App } from '@rocket.chat/apps-engine/definition/App';
import { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata';
Expand All @@ -23,35 +23,46 @@ import { TimeOffCache } from './TimeOffCache';
import { UserService } from './services/UserService';

export class TimeOffApp extends App implements IPostMessageSent {
constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) {
super(info, logger, accessors);
}
constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) {
super(info, logger, accessors);
}

public async extendConfiguration(configuration: IConfigurationExtend, environmentRead: IEnvironmentRead): Promise<void> {
configuration.slashCommands.provideSlashCommand(new TimeOffCommand(this));
}
public async extendConfiguration(
configuration: IConfigurationExtend,
_environmentRead: IEnvironmentRead,
): Promise<void> {
configuration.slashCommands.provideSlashCommand(new TimeOffCommand(this));
}

public async onEnable(environmentRead: IEnvironmentRead, configurationModify: IConfigurationModify): Promise<boolean> {
TimeOffCache.getInstance().invalidateCache();
return Promise.resolve(true);
}
public async onEnable(
_environmentRead: IEnvironmentRead,
_configurationModify: IConfigurationModify,
): Promise<boolean> {
TimeOffCache.getInstance().invalidateCache();
return Promise.resolve(true);
}

public async checkPostMessageSent?(message: IMessage, read: IRead, http: IHttp): Promise<boolean> {
// We only want to notify the user if the message was sent in a direct message
return Promise.resolve(message.room.type === RoomType.DIRECT_MESSAGE);
}
public async checkPostMessageSent?(message: IMessage, _read: IRead, _http: IHttp): Promise<boolean> {
// We only want to notify the user if the message was sent in a direct message
return Promise.resolve(message.room.type === RoomType.DIRECT_MESSAGE);
}

public async executePostMessageSent(message: IMessage, read: IRead, http: IHttp, persistence: IPersistence, modify: IModify): Promise<void> {
const userRepository = new UserRepository(read);
const userService = new UserService(userRepository);
public async executePostMessageSent(
message: IMessage,
read: IRead,
_http: IHttp,
persistence: IPersistence,
_modify: IModify,
): Promise<void> {
const userRepository = new UserRepository(read);
const userService = new UserService(userRepository);

const timeOffRepository = new TimeOffRepository(this, read, persistence);
const timeOffService = new TimeOffService(timeOffRepository);
const timeOffRepository = new TimeOffRepository(this, read, persistence);
const timeOffService = new TimeOffService(timeOffRepository);

const notifier = new AppNotifier(this, read);

const handler = new PostMessageSentHandler(this, userService, timeOffService, notifier);
await handler.handle(message);
}
const notifier = new AppNotifier(this, read);

const handler = new PostMessageSentHandler(this, userService, timeOffService, notifier);
await handler.handle(message);
}
}
80 changes: 40 additions & 40 deletions TimeOffCache.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,43 @@
import { ITimeOff } from "./interfaces/ITimeOff";
import { ITimeOff } from './interfaces/ITimeOff';

export class TimeOffCache {
private static instance: TimeOffCache;
private cache: Map<string, { data: ITimeOff; expiresAt: number }>;
private TTL = 60 * 60 * 1000; // 1 hour cache

private constructor() {
this.cache = new Map();
}

public static getInstance(): TimeOffCache {
if (!TimeOffCache.instance) {
TimeOffCache.instance = new TimeOffCache();
}
return TimeOffCache.instance;
}

public set(userId: string, timeOffData: ITimeOff): void {
const expiresAt = Date.now() + this.TTL;
this.cache.set(userId, { data: timeOffData, expiresAt });
}

public get(userId: string): ITimeOff | undefined {
const cached = this.cache.get(userId);
if (!cached) return undefined;

if (Date.now() > cached.expiresAt) {
this.cache.delete(userId);
return undefined;
}

return cached.data;
}

public invalidateUser(userId: string): void {
this.cache.delete(userId);
}

public invalidateCache(): void {
this.cache.clear();
}
private static instance: TimeOffCache;
private cache: Map<string, { data: ITimeOff; expiresAt: number }>;
private TTL = 60 * 60 * 1000; // 1 hour cache

private constructor() {
this.cache = new Map();
}

public static getInstance(): TimeOffCache {
if (!TimeOffCache.instance) {
TimeOffCache.instance = new TimeOffCache();
}
return TimeOffCache.instance;
}

public set(userId: string, timeOffData: ITimeOff): void {
const expiresAt = Date.now() + this.TTL;
this.cache.set(userId, { data: timeOffData, expiresAt });
}

public get(userId: string): ITimeOff | undefined {
const cached = this.cache.get(userId);
if (!cached) return undefined;

if (Date.now() > cached.expiresAt) {
this.cache.delete(userId);
return undefined;
}

return cached.data;
}

public invalidateUser(userId: string): void {
this.cache.delete(userId);
}

public invalidateCache(): void {
this.cache.clear();
}
}
4 changes: 2 additions & 2 deletions app.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"id": "29025e21-7154-47bc-969f-97d4c530fdba",
"id": "29025e21-7154-47bc-969f-97d4c530fdbd",
"version": "1.0.0",
"requiredApiVersion": "^1.44.0",
"iconFile": "icon.png",
Expand All @@ -15,4 +15,4 @@
"implements": [
"IPostMessageSent"
]
}
}
Loading