From 40b1ef6f0c347f963083a0edb24818fa6b6a65d6 Mon Sep 17 00:00:00 2001 From: Pijus Kamandulis Date: Sat, 12 Oct 2024 17:20:16 +0300 Subject: [PATCH] Allow user to set a username; Show list of users on estimation screen --- appwrite.json | 31 ++++ functions/EstimationSessionInvite/src/main.ts | 32 ++++- functions/UsernameChangeHandler/.gitignore | 2 + functions/UsernameChangeHandler/README.md | 10 ++ functions/UsernameChangeHandler/go.mod | 7 + functions/UsernameChangeHandler/go.sum | 4 + functions/UsernameChangeHandler/main.go | 134 ++++++++++++++++++ src/components/Header.tsx | 11 +- src/lib/context/estimation.tsx | 7 +- src/lib/context/estimationsList.tsx | 71 ++++++---- src/lib/context/user.tsx | 25 ++-- src/lib/mappers/estimationSession.ts | 29 ++++ src/lib/types/databaseModels.ts | 2 + src/lib/types/entityModels.ts | 9 ++ src/pages/Estimation/Estimation.tsx | 4 + .../Estimation/components/PlayerList.tsx | 39 +++++ src/pages/Estimation/components/VoteList.tsx | 2 +- src/pages/Profile.tsx | 49 ++++++- 18 files changed, 423 insertions(+), 45 deletions(-) create mode 100644 functions/UsernameChangeHandler/.gitignore create mode 100644 functions/UsernameChangeHandler/README.md create mode 100644 functions/UsernameChangeHandler/go.mod create mode 100644 functions/UsernameChangeHandler/go.sum create mode 100644 functions/UsernameChangeHandler/main.go create mode 100644 src/pages/Estimation/components/PlayerList.tsx diff --git a/appwrite.json b/appwrite.json index 8dfb5a6..ee6d9b3 100644 --- a/appwrite.json +++ b/appwrite.json @@ -52,6 +52,21 @@ "entrypoint": "src/main.ts", "commands": "bun install", "path": "functions/EstimationSessionInvite" + }, + { + "$id": "670a4b770001c5a71194", + "execute": [], + "name": "UsernameChangeHandler", + "enabled": true, + "logging": true, + "runtime": "go-1.23", + "scopes": ["users.read", "documents.read", "documents.write"], + "events": ["users.*.update.name"], + "schedule": "", + "timeout": 15, + "entrypoint": "main.go", + "commands": "", + "path": "functions/UsernameChangeHandler" } ], "databases": [ @@ -101,6 +116,22 @@ "array": false, "size": 1000, "default": null + }, + { + "key": "players", + "type": "string", + "required": false, + "array": true, + "size": 100, + "default": null + }, + { + "key": "playerIds", + "type": "string", + "required": false, + "array": true, + "size": 100, + "default": null } ], "indexes": [] diff --git a/functions/EstimationSessionInvite/src/main.ts b/functions/EstimationSessionInvite/src/main.ts index 99d3013..14f2df5 100644 --- a/functions/EstimationSessionInvite/src/main.ts +++ b/functions/EstimationSessionInvite/src/main.ts @@ -1,4 +1,4 @@ -import { Client, Databases } from 'node-appwrite'; +import { Client, Databases, Models, Users } from 'node-appwrite'; import { AppwriteRuntimeContext, AppwriteSendReturn } from './definitions.mjs'; const joinSession = async ({ @@ -13,6 +13,7 @@ const joinSession = async ({ ctx: AppwriteRuntimeContext; }): Promise => { const databases = new Databases(client); + const users = new Users(client); let estimation; try { @@ -31,16 +32,43 @@ const joinSession = async ({ ); } + let user: Models.User; + try { + user = await users.get(userId); + } catch (e) { + error({ e }); + return res.json( + { + message: 'Failed to get user', + }, + 500, + ); + } + try { const permissions: string[] = estimation['$permissions']; permissions.push(`read("user:${userId}")`); permissions.push(`update("user:${userId}")`); + const playerIds: string[] = estimation['playerIds']; + playerIds.push(userId); + + const players: string[] = estimation['players']; + players.push( + JSON.stringify({ + userId, + name: user.name.length > 0 ? user.name : `Guest - ${userId}`, + }), + ); + await databases.updateDocument( Bun.env.APPWRITE_DATABASE_ID, Bun.env.APPWRITE_ESTIMATION_SESSION_COLLECTION_ID, estimationId, - {}, + { + playerIds, + players, + }, permissions, ); diff --git a/functions/UsernameChangeHandler/.gitignore b/functions/UsernameChangeHandler/.gitignore new file mode 100644 index 0000000..72b7d67 --- /dev/null +++ b/functions/UsernameChangeHandler/.gitignore @@ -0,0 +1,2 @@ +# Directory used by Appwrite CLI for local development +.appwrite \ No newline at end of file diff --git a/functions/UsernameChangeHandler/README.md b/functions/UsernameChangeHandler/README.md new file mode 100644 index 0000000..2e77962 --- /dev/null +++ b/functions/UsernameChangeHandler/README.md @@ -0,0 +1,10 @@ +# Username Change Handler + +This function is called when a user changes his username. + +Currently this funcion updates users name accross all of his estimation sessions. + +## Environment Variables + +- APPWRITE_DATABASE_ID - Database Id +- APPWRITE_ESTIMATION_SESSION_COLLECTION_ID - Sessions collection Id diff --git a/functions/UsernameChangeHandler/go.mod b/functions/UsernameChangeHandler/go.mod new file mode 100644 index 0000000..690187d --- /dev/null +++ b/functions/UsernameChangeHandler/go.mod @@ -0,0 +1,7 @@ +module openruntimes/handler + +go 1.23.0 + +require github.com/open-runtimes/types-for-go/v4 v4.0.6 + +require github.com/appwrite/sdk-for-go v0.0.1-rc.2 diff --git a/functions/UsernameChangeHandler/go.sum b/functions/UsernameChangeHandler/go.sum new file mode 100644 index 0000000..1774028 --- /dev/null +++ b/functions/UsernameChangeHandler/go.sum @@ -0,0 +1,4 @@ +github.com/appwrite/sdk-for-go v0.0.1-rc.2 h1:kh8p6OmSgA4d7aT1KXE9Z3W99ioDKdhhY1OrKsTLu1I= +github.com/appwrite/sdk-for-go v0.0.1-rc.2/go.mod h1:aFiOAbfOzGS3811eMCt3T9WDBvjvPVAfOjw10Vghi4E= +github.com/open-runtimes/types-for-go/v4 v4.0.6 h1:0Xf58LMy/vwWkiRN6BvvpWt1mWzcWUWQ5wsWSezG2TU= +github.com/open-runtimes/types-for-go/v4 v4.0.6/go.mod h1:ab4mDSfgeG4kN8wWpaBSv0Ao3m9P6oEfN5gsXtx+iaI= diff --git a/functions/UsernameChangeHandler/main.go b/functions/UsernameChangeHandler/main.go new file mode 100644 index 0000000..c895a54 --- /dev/null +++ b/functions/UsernameChangeHandler/main.go @@ -0,0 +1,134 @@ +package handler + +import ( + "encoding/json" + "os" + + "github.com/appwrite/sdk-for-go/appwrite" + "github.com/appwrite/sdk-for-go/models" + "github.com/appwrite/sdk-for-go/query" + "github.com/open-runtimes/types-for-go/v4/openruntimes" +) + +type Response struct { + Message string `json:"message"` +} + +type ErrorResponse struct { + Error string `json:"error"` +} + +type EstimationSession struct { + Id string `json:"$id"` + UserID string `json:"userId"` + Name string `json:"name"` + Tickets []string `json:"tickets"` + SessionState string `json:"sessionState"` + Players []string `json:"players"` + PlayerIDs []string `json:"playerIds"` +} + +type EstimationSessionList struct { + Total int `json:"total"` + Documents []EstimationSession `json:"documents"` +} + +type Player struct { + UserID string `json:"userId"` + Name string `json:"name"` +} + +func Main(Context openruntimes.Context) openruntimes.Response { + databaseId := os.Getenv("APPWRITE_DATABASE_ID") + collectionId := os.Getenv("APPWRITE_ESTIMATION_SESSION_COLLECTION_ID") + if databaseId == "" || collectionId == "" { + Context.Error("Environment variables not provided") + return Context.Res.Json(ErrorResponse{ + Error: "Environment variables not provided", + }, Context.Res.WithStatusCode(500)) + } + + var userData models.User + Context.Req.BodyJson(&userData) + if userData.Id == "" { + Context.Log("Request body did not contain id") + return Context.Res.Json(ErrorResponse{ + Error: "User id was not provided", + }, Context.Res.WithStatusCode(400)) + } + + client := appwrite.NewClient( + appwrite.WithEndpoint(os.Getenv("APPWRITE_FUNCTION_API_ENDPOINT")), + appwrite.WithProject(os.Getenv("APPWRITE_FUNCTION_PROJECT_ID")), + appwrite.WithKey(Context.Req.Headers["x-appwrite-key"]), + ) + databases := appwrite.NewDatabases(client) + + queries := []string{query.Contains("playerIds", userData.Id)} + userEstimationSessions, err := databases.ListDocuments(databaseId, collectionId, databases.WithListDocumentsQueries(queries)) + if err != nil { + Context.Error(err.Error()) + return Context.Res.Json(ErrorResponse{ + Error: err.Error(), + }, Context.Res.WithStatusCode(500)) + } + + var documents EstimationSessionList + err = userEstimationSessions.Decode(&documents) + if err != nil { + Context.Error(err.Error()) + return Context.Res.Json(ErrorResponse{ + Error: err.Error(), + }, Context.Res.WithStatusCode(500)) + } + + newUsername := userData.Name + if newUsername == "" { + newUsername = "Guest - " + userData.Id + } + + for _, estimationSession := range documents.Documents { + for i, jsonString := range estimationSession.Players { + var player Player + + err := json.Unmarshal([]byte(jsonString), &player) + if err != nil { + Context.Error(err.Error()) + return Context.Res.Json(ErrorResponse{ + Error: err.Error(), + }, Context.Res.WithStatusCode(500)) + } + + if player.UserID == userData.Id { + player.Name = newUsername + + updatedPlayer, err := json.Marshal(player) + if err != nil { + Context.Error(err.Error()) + return Context.Res.Json(ErrorResponse{ + Error: err.Error(), + }, Context.Res.WithStatusCode(500)) + + } + + estimationSession.Players[i] = string(updatedPlayer) + break + } + } + + patch := map[string]any{"players": estimationSession.Players} + _, err := databases.UpdateDocument(databaseId, collectionId, estimationSession.Id, databases.WithUpdateDocumentData(patch)) + if err != nil { + Context.Error(err.Error()) + return Context.Res.Json(ErrorResponse{ + Error: err.Error(), + }, Context.Res.WithStatusCode(500)) + } + + Context.Log("Updated estimation: ", estimationSession.Id) + } + + return Context.Res.Json(Response{ + Message: "Updated player name", + }) +} diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 3a1c68f..9a6f6e7 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -3,13 +3,20 @@ import { useState } from 'react'; import { useUser } from '../lib/context/user'; const Header = () => { - const { logout } = useUser(); + const { current, isLoading, logout } = useUser(); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const toggleDropdown = () => { setIsDropdownOpen(!isDropdownOpen); }; + if (isLoading || !current) { + return null; + } + + const userName = + current.name.length > 0 ? current.name : `Guest - ${current.$id}`; + return (