Implement session invites
This commit is contained in:
parent
9b45d372fa
commit
1463c05731
1
.env
1
.env
|
@ -2,3 +2,4 @@ VITE_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
|
||||||
VITE_APPWRITE_PROJECT_ID=scrummie-poker
|
VITE_APPWRITE_PROJECT_ID=scrummie-poker
|
||||||
VITE_APPWRITE_DATABASE_ID=670402eb000f5aff721f
|
VITE_APPWRITE_DATABASE_ID=670402eb000f5aff721f
|
||||||
VITE_APPWRITE_ESTIMATION_SESSION_COLLECTION_ID=670402f60023cb78d441
|
VITE_APPWRITE_ESTIMATION_SESSION_COLLECTION_ID=670402f60023cb78d441
|
||||||
|
VITE_SESSION_INVITE_FUNCTION_ID=6708356a001290606744
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
{
|
||||||
|
"projectId": "scrummie-poker",
|
||||||
|
"projectName": "ScrummiePoker",
|
||||||
|
"settings": {
|
||||||
|
"services": {
|
||||||
|
"account": true,
|
||||||
|
"avatars": true,
|
||||||
|
"databases": true,
|
||||||
|
"locale": true,
|
||||||
|
"health": true,
|
||||||
|
"storage": true,
|
||||||
|
"teams": true,
|
||||||
|
"users": true,
|
||||||
|
"functions": true,
|
||||||
|
"graphql": true,
|
||||||
|
"messaging": true
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"methods": {
|
||||||
|
"jwt": true,
|
||||||
|
"phone": true,
|
||||||
|
"invites": true,
|
||||||
|
"anonymous": true,
|
||||||
|
"email-otp": true,
|
||||||
|
"magic-url": true,
|
||||||
|
"email-password": true
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"duration": 31536000,
|
||||||
|
"limit": 0,
|
||||||
|
"sessionsLimit": 10,
|
||||||
|
"passwordHistory": 0,
|
||||||
|
"passwordDictionary": false,
|
||||||
|
"personalDataCheck": false,
|
||||||
|
"sessionAlerts": false,
|
||||||
|
"mockNumbers": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"functions": [
|
||||||
|
{
|
||||||
|
"$id": "6708356a001290606744",
|
||||||
|
"execute": ["users"],
|
||||||
|
"name": "EstimationSessionInvite",
|
||||||
|
"enabled": true,
|
||||||
|
"logging": true,
|
||||||
|
"runtime": "bun-1.0",
|
||||||
|
"scopes": ["users.read", "documents.read", "documents.write"],
|
||||||
|
"events": [],
|
||||||
|
"schedule": "",
|
||||||
|
"timeout": 15,
|
||||||
|
"entrypoint": "src/main.ts",
|
||||||
|
"commands": "bun install",
|
||||||
|
"path": "functions/EstimationSessionInvite"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"databases": [
|
||||||
|
{
|
||||||
|
"$id": "670402eb000f5aff721f",
|
||||||
|
"name": "estimations",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"collections": [
|
||||||
|
{
|
||||||
|
"$id": "670402f60023cb78d441",
|
||||||
|
"$permissions": ["create(\"users\")"],
|
||||||
|
"databaseId": "670402eb000f5aff721f",
|
||||||
|
"name": "sessions",
|
||||||
|
"enabled": true,
|
||||||
|
"documentSecurity": true,
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"key": "userId",
|
||||||
|
"type": "string",
|
||||||
|
"required": true,
|
||||||
|
"array": false,
|
||||||
|
"size": 50,
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "name",
|
||||||
|
"type": "string",
|
||||||
|
"required": true,
|
||||||
|
"array": false,
|
||||||
|
"size": 200,
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "tickets",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"array": true,
|
||||||
|
"size": 100,
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "sessionState",
|
||||||
|
"type": "string",
|
||||||
|
"required": false,
|
||||||
|
"array": false,
|
||||||
|
"size": 1000,
|
||||||
|
"default": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
### Node ###
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
### Node Patch ###
|
||||||
|
# Serverless Webpack directories
|
||||||
|
.webpack/
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
|
||||||
|
# SvelteKit build / generate output
|
||||||
|
.svelte-kit
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/node
|
||||||
|
|
||||||
|
# OS
|
||||||
|
## Mac
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Directory used by Appwrite CLI for local development
|
||||||
|
.appwrite
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Estimation Session Invite Function
|
||||||
|
|
||||||
|
This function allows other users to join an existing estimation session by providing it's Id.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### GET /?action=get-info&estimationId=[session-id]
|
||||||
|
|
||||||
|
- Gets session's information by id
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
Sample `200` Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "session-id",
|
||||||
|
"name": "session name"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### GET /?action=join&estimationId=[session-id]
|
||||||
|
|
||||||
|
- Joins session by id
|
||||||
|
|
||||||
|
**Response**
|
||||||
|
|
||||||
|
Sample `200` Response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Estimation session joined"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
- APPWRITE_DATABASE_ID - Database Id
|
||||||
|
- APPWRITE_ESTIMATION_SESSION_COLLECTION_ID - Sessions collection Id
|
Binary file not shown.
|
@ -0,0 +1,10 @@
|
||||||
|
declare module 'bun' {
|
||||||
|
interface Env {
|
||||||
|
APPWRITE_FUNCTION_API_ENDPOINT: string;
|
||||||
|
APPWRITE_FUNCTION_PROJECT_ID: string;
|
||||||
|
APPWRITE_DATABASE_ID: string;
|
||||||
|
APPWRITE_ESTIMATION_SESSION_COLLECTION_ID: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
|
@ -0,0 +1,88 @@
|
||||||
|
{
|
||||||
|
"name": "estimation-session-invite",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "estimation-session-invite",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"node-appwrite": "^14.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "^1.1.11",
|
||||||
|
"prettier": "^3.3.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/bun": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-0N7D/H/8sbf9JMkaG5F3+I/cB4TlhKTkO9EskEWP8XDr8aVcDe4EywSnU4cnyZy6tar1dq70NeFNkqMEUigthw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"bun-types": "1.1.30"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "20.12.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.14.tgz",
|
||||||
|
"integrity": "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~5.26.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/ws": {
|
||||||
|
"version": "8.5.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz",
|
||||||
|
"integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/bun-types": {
|
||||||
|
"version": "1.1.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.1.30.tgz",
|
||||||
|
"integrity": "sha512-mGh7NLisOXskBU62DxLS+/nwmLlCYHYAkCzdo4DZ9+fzrpP41hAdOqaN4DO6tQfenHb4pYb0/shw29k4/6I2yQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "~20.12.8",
|
||||||
|
"@types/ws": "~8.5.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-appwrite": {
|
||||||
|
"version": "14.1.0",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch-native-with-agent": "1.7.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-fetch-native-with-agent": {
|
||||||
|
"version": "1.7.2",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/prettier": {
|
||||||
|
"version": "3.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
|
||||||
|
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"prettier": "bin/prettier.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "5.26.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||||
|
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"name": "estimation-session-invite",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "src/main.ts",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"format": "prettier --write ."
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"node-appwrite": "^14.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "^1.1.11",
|
||||||
|
"bun-types": "^1.1.30",
|
||||||
|
"prettier": "^3.3.3"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
declare type AppwriteRequest = {
|
||||||
|
bodyRaw: string;
|
||||||
|
body: Object;
|
||||||
|
headers: Object;
|
||||||
|
scheme: string;
|
||||||
|
method: string;
|
||||||
|
url: string;
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
path: string;
|
||||||
|
queryString: string;
|
||||||
|
query: Object;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare type AppwriteSendReturn = {
|
||||||
|
body: any;
|
||||||
|
statusCode: number;
|
||||||
|
headers: Object;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare type AppwriteResponse = {
|
||||||
|
empty: () => AppwriteSendReturn;
|
||||||
|
json: (obj: any, statusCode?: number, headers?: Object) => AppwriteSendReturn;
|
||||||
|
text: (text: string) => AppwriteSendReturn;
|
||||||
|
redirect: (
|
||||||
|
url: string,
|
||||||
|
statusCode?: number,
|
||||||
|
headers?: Object,
|
||||||
|
) => AppwriteSendReturn;
|
||||||
|
send: (
|
||||||
|
body: any,
|
||||||
|
statusCode?: number,
|
||||||
|
headers?: Object,
|
||||||
|
) => AppwriteSendReturn;
|
||||||
|
};
|
||||||
|
|
||||||
|
declare type AppwriteRuntimeContext = {
|
||||||
|
req: AppwriteRequest;
|
||||||
|
res: AppwriteResponse;
|
||||||
|
log: (message: any) => void;
|
||||||
|
error: (message: any) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
AppwriteRequest,
|
||||||
|
AppwriteSendReturn,
|
||||||
|
AppwriteResponse,
|
||||||
|
AppwriteRuntimeContext,
|
||||||
|
};
|
|
@ -0,0 +1,148 @@
|
||||||
|
import { Client, Databases } from 'node-appwrite';
|
||||||
|
import { AppwriteRuntimeContext, AppwriteSendReturn } from './definitions.mjs';
|
||||||
|
|
||||||
|
const joinSession = async ({
|
||||||
|
client,
|
||||||
|
estimationId,
|
||||||
|
userId,
|
||||||
|
ctx: { error, res },
|
||||||
|
}: {
|
||||||
|
client: Client;
|
||||||
|
estimationId: string;
|
||||||
|
userId: string;
|
||||||
|
ctx: AppwriteRuntimeContext;
|
||||||
|
}): Promise<AppwriteSendReturn> => {
|
||||||
|
const databases = new Databases(client);
|
||||||
|
|
||||||
|
let estimation;
|
||||||
|
try {
|
||||||
|
estimation = await databases.getDocument(
|
||||||
|
Bun.env.APPWRITE_DATABASE_ID,
|
||||||
|
Bun.env.APPWRITE_ESTIMATION_SESSION_COLLECTION_ID,
|
||||||
|
estimationId,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
error({ e });
|
||||||
|
return res.json(
|
||||||
|
{
|
||||||
|
message: 'Estimation with this id does not exist',
|
||||||
|
},
|
||||||
|
400,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const permissions: string[] = estimation['$permissions'];
|
||||||
|
permissions.push(`read("user:${userId}")`);
|
||||||
|
permissions.push(`update("user:${userId}")`);
|
||||||
|
|
||||||
|
await databases.updateDocument(
|
||||||
|
Bun.env.APPWRITE_DATABASE_ID,
|
||||||
|
Bun.env.APPWRITE_ESTIMATION_SESSION_COLLECTION_ID,
|
||||||
|
estimationId,
|
||||||
|
{},
|
||||||
|
permissions,
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.json({
|
||||||
|
message: 'Estimation session joined',
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
error({ e });
|
||||||
|
return res.json(
|
||||||
|
{
|
||||||
|
message: 'Failed to join estimation session',
|
||||||
|
},
|
||||||
|
500,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSessionInfo = async ({
|
||||||
|
client,
|
||||||
|
estimationId,
|
||||||
|
ctx: { log, res },
|
||||||
|
}: {
|
||||||
|
client: Client;
|
||||||
|
estimationId: string;
|
||||||
|
ctx: AppwriteRuntimeContext;
|
||||||
|
}): Promise<AppwriteSendReturn> => {
|
||||||
|
const databases = new Databases(client);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const estimation = await databases.getDocument(
|
||||||
|
Bun.env.APPWRITE_DATABASE_ID,
|
||||||
|
Bun.env.APPWRITE_ESTIMATION_SESSION_COLLECTION_ID,
|
||||||
|
estimationId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.json({
|
||||||
|
result: {
|
||||||
|
id: estimation.$id,
|
||||||
|
name: estimation.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log({ e });
|
||||||
|
log({ e });
|
||||||
|
return res.json(
|
||||||
|
{
|
||||||
|
message: 'Estimation with this id does not exist',
|
||||||
|
},
|
||||||
|
400,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async (ctx: AppwriteRuntimeContext) => {
|
||||||
|
const { req, res } = ctx;
|
||||||
|
const userId = req.headers['x-appwrite-user-id'];
|
||||||
|
if (!userId) {
|
||||||
|
return res.json(
|
||||||
|
{
|
||||||
|
message: 'Unauthorized',
|
||||||
|
},
|
||||||
|
401,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const action = req.query['action'];
|
||||||
|
const estimationId = req.query['estimationId'];
|
||||||
|
if (!action || !estimationId) {
|
||||||
|
return res.json(
|
||||||
|
{
|
||||||
|
message: 'Bad request',
|
||||||
|
},
|
||||||
|
400,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = new Client()
|
||||||
|
.setEndpoint(Bun.env.APPWRITE_FUNCTION_API_ENDPOINT)
|
||||||
|
.setProject(Bun.env.APPWRITE_FUNCTION_PROJECT_ID)
|
||||||
|
.setKey(req.headers['x-appwrite-key'] ?? '');
|
||||||
|
|
||||||
|
if (action === 'get-info') {
|
||||||
|
return await getSessionInfo({
|
||||||
|
client,
|
||||||
|
ctx,
|
||||||
|
estimationId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === 'join') {
|
||||||
|
return await joinSession({
|
||||||
|
client,
|
||||||
|
estimationId,
|
||||||
|
userId,
|
||||||
|
ctx,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json(
|
||||||
|
{
|
||||||
|
message: 'Not found',
|
||||||
|
},
|
||||||
|
404,
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["bun-types"]
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { Client, Account, Databases } from 'appwrite';
|
import { Client, Account, Databases, Functions } from 'appwrite';
|
||||||
|
|
||||||
export const client = new Client();
|
export const client = new Client();
|
||||||
|
|
||||||
|
@ -10,7 +10,10 @@ export { ID } from 'appwrite';
|
||||||
|
|
||||||
export const account = new Account(client);
|
export const account = new Account(client);
|
||||||
export const databases = new Databases(client);
|
export const databases = new Databases(client);
|
||||||
|
export const functions = new Functions(client);
|
||||||
|
|
||||||
export const DATABASE_ID = import.meta.env.VITE_APPWRITE_DATABASE_ID;
|
export const DATABASE_ID = import.meta.env.VITE_APPWRITE_DATABASE_ID;
|
||||||
export const ESTIMATION_SESSION_COLLECTION_ID = import.meta.env
|
export const ESTIMATION_SESSION_COLLECTION_ID = import.meta.env
|
||||||
.VITE_APPWRITE_ESTIMATION_SESSION_COLLECTION_ID;
|
.VITE_APPWRITE_ESTIMATION_SESSION_COLLECTION_ID;
|
||||||
|
export const SESSION_INVITE_FUNCTION_ID = import.meta.env
|
||||||
|
.VITE_SESSION_INVITE_FUNCTION_ID;
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { ExecutionMethod } from 'appwrite';
|
||||||
|
import { functions, SESSION_INVITE_FUNCTION_ID } from '../appwrite';
|
||||||
|
|
||||||
|
type SessionInviteInfo =
|
||||||
|
| {
|
||||||
|
success: true;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
success: false;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type JoinSessionResponse =
|
||||||
|
| {
|
||||||
|
success: true;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
success: false;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInviteInfo = async (sessionId: string): Promise<SessionInviteInfo> => {
|
||||||
|
const result = await functions.createExecution(
|
||||||
|
SESSION_INVITE_FUNCTION_ID,
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
`/?action=get-info&estimationId=${sessionId}`,
|
||||||
|
ExecutionMethod.GET,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.status === 'failed') {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: result.errors ?? 'Failed to get estimation session info',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseBody = JSON.parse(result.responseBody);
|
||||||
|
if (responseBody.message) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: responseBody.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id, name } = responseBody.result;
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const joinSession = async (sessionId: string): Promise<JoinSessionResponse> => {
|
||||||
|
const result = await functions.createExecution(
|
||||||
|
SESSION_INVITE_FUNCTION_ID,
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
`/?action=join&estimationId=${sessionId}`,
|
||||||
|
ExecutionMethod.GET,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.status === 'failed') {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: result.errors ?? 'Failed to join session',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseBody = JSON.parse(result.responseBody);
|
||||||
|
if (responseBody.message) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: responseBody.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getInviteInfo, joinSession };
|
||||||
|
export type { SessionInviteInfo };
|
|
@ -17,6 +17,7 @@ import { EstimationContextProvider } from './lib/context/estimation';
|
||||||
import Estimation from './pages/Estimation/Estimation';
|
import Estimation from './pages/Estimation/Estimation';
|
||||||
import Header from './components/Header';
|
import Header from './components/Header';
|
||||||
import Profile from './pages/Profile';
|
import Profile from './pages/Profile';
|
||||||
|
import Join from './pages/Join';
|
||||||
|
|
||||||
interface RouterContext {
|
interface RouterContext {
|
||||||
userContext: UserContextType;
|
userContext: UserContextType;
|
||||||
|
@ -68,6 +69,12 @@ const profileRoute = createRoute({
|
||||||
getParentRoute: () => authenticatedRoute,
|
getParentRoute: () => authenticatedRoute,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const joinRoute = createRoute({
|
||||||
|
path: 'join/$sessionId',
|
||||||
|
component: Join,
|
||||||
|
getParentRoute: () => authenticatedRoute,
|
||||||
|
});
|
||||||
|
|
||||||
const estimationSessionRoute = createRoute({
|
const estimationSessionRoute = createRoute({
|
||||||
path: 'estimate/session/$sessionId',
|
path: 'estimate/session/$sessionId',
|
||||||
component: Estimation,
|
component: Estimation,
|
||||||
|
@ -77,6 +84,7 @@ const estimationSessionRoute = createRoute({
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
routeTree: rootRoute.addChildren([
|
routeTree: rootRoute.addChildren([
|
||||||
authenticatedRoute.addChildren([
|
authenticatedRoute.addChildren([
|
||||||
|
joinRoute,
|
||||||
indexRoute,
|
indexRoute,
|
||||||
profileRoute,
|
profileRoute,
|
||||||
estimationSessionRoute,
|
estimationSessionRoute,
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
getInviteInfo,
|
||||||
|
joinSession,
|
||||||
|
SessionInviteInfo,
|
||||||
|
} from '../lib/functions/estimationSessionInvite';
|
||||||
|
import { getRouteApi } from '@tanstack/react-router';
|
||||||
|
|
||||||
|
const route = getRouteApi('/_authenticated/join/$sessionId');
|
||||||
|
|
||||||
|
const Join = () => {
|
||||||
|
const navigate = route.useNavigate();
|
||||||
|
const { sessionId } = route.useParams();
|
||||||
|
const [sessionInfo, setSessionInfo] = useState<SessionInviteInfo>();
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getInviteInfo(sessionId).then((sessionInfo) => {
|
||||||
|
setSessionInfo(sessionInfo);
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
}, [sessionId]);
|
||||||
|
|
||||||
|
const handleAccept = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
await joinSession(sessionId);
|
||||||
|
|
||||||
|
navigate({
|
||||||
|
to: '/estimate/session/$sessionId',
|
||||||
|
params: {
|
||||||
|
sessionId: sessionId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReturnHome = () => {
|
||||||
|
navigate({
|
||||||
|
to: '/',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!sessionInfo || isLoading) {
|
||||||
|
// TODO: add loader
|
||||||
|
return <p>Loading...</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sessionInfo.success) {
|
||||||
|
return <p>{sessionInfo.message}</p>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen flex-col items-center justify-center bg-gray-50 transition-colors dark:bg-nero-900">
|
||||||
|
<div className="max-w-lg rounded-lg bg-white p-8 text-center shadow-lg dark:bg-nero-800">
|
||||||
|
<h1 className="mb-4 text-2xl font-semibold text-gray-900 dark:text-gray-100">
|
||||||
|
You have been invited to join a new estimation session!
|
||||||
|
</h1>
|
||||||
|
<p className="mb-6 text-lg text-gray-700 dark:text-gray-300">
|
||||||
|
Session Name: <strong>{sessionInfo.name}</strong>
|
||||||
|
</p>
|
||||||
|
<div className="flex justify-center gap-4">
|
||||||
|
<button
|
||||||
|
onClick={handleAccept}
|
||||||
|
className="rounded-md bg-indigo-600 px-6 py-2 text-sm font-semibold text-white shadow-sm transition-colors hover:bg-indigo-500"
|
||||||
|
>
|
||||||
|
Accept
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleReturnHome}
|
||||||
|
className="rounded-md bg-gray-300 px-6 py-2 text-sm font-semibold text-gray-900 shadow-sm transition-colors hover:bg-gray-200 dark:bg-nero-700 dark:text-gray-100 dark:hover:bg-gray-600"
|
||||||
|
>
|
||||||
|
Return to Home
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Join;
|
|
@ -5,6 +5,7 @@ interface ImportMetaEnv {
|
||||||
readonly VITE_APPWRITE_PROJECT_ID: string;
|
readonly VITE_APPWRITE_PROJECT_ID: string;
|
||||||
readonly VITE_APPWRITE_DATABASE_ID: string;
|
readonly VITE_APPWRITE_DATABASE_ID: string;
|
||||||
readonly VITE_APPWRITE_ESTIMATION_SESSION_COLLECTION_ID: string;
|
readonly VITE_APPWRITE_ESTIMATION_SESSION_COLLECTION_ID: string;
|
||||||
|
readonly VITE_SESSION_INVITE_FUNCTION_ID: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
|
|
Loading…
Reference in New Issue