From a395d9a16de61861ba7fd1ac62a7bfe282b57d0b Mon Sep 17 00:00:00 2001 From: Eviee Py Date: Tue, 25 Jun 2024 11:07:49 +1000 Subject: [PATCH 01/69] Add debug flags to backend --- backend/app/main.go | 7 +++++++ backend/app/server/oauth.go | 33 ++++++++++++++++++++++++++++++--- backend/app/server/server.go | 1 + 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/backend/app/main.go b/backend/app/main.go index fbc6eaa..5dc2db1 100644 --- a/backend/app/main.go +++ b/backend/app/main.go @@ -1,14 +1,20 @@ package main import ( + "codejam.io/config" "codejam.io/database" + //"codejam.io/logging" + "flag" + "codejam.io/server" "github.com/gin-gonic/gin" ) func main() { + debugArg := flag.Bool("debug", false, "Enable debug mode.") + flag.Parse() // TODO setup logger // Disables the debug logging... Comment this out to enable debug logging for GIN @@ -30,6 +36,7 @@ func main() { server := server.Server{ Config: *config, + Debug: *debugArg, } server.StartServer() } diff --git a/backend/app/server/oauth.go b/backend/app/server/oauth.go index ef88ba7..f9fd1ec 100644 --- a/backend/app/server/oauth.go +++ b/backend/app/server/oauth.go @@ -1,6 +1,10 @@ package server import ( + "net/http" + "os" + "strings" + "codejam.io/database" "codejam.io/integrations" "github.com/emicklei/pgtalk/convert" @@ -8,13 +12,15 @@ import ( "github.com/gin-gonic/gin" "golang.org/x/oauth2" githubOAuth "golang.org/x/oauth2/github" - "net/http" - "os" - "strings" ) // SetupOAuth initializes the OAuth provider specified in the application config. func (server *Server) SetupOAuth() { + if server.Debug { + logger.Warn("Debug mode is set. No OAuth Providers are set!") + return + } + var endpoint oauth2.Endpoint switch strings.ToLower(server.Config.OAuth.Provider) { @@ -40,10 +46,30 @@ func (server *Server) SetupOAuth() { } func (server *Server) GetOAuthRedirect(ctx *gin.Context) { + if server.Debug { + ctx.Redirect(http.StatusFound, "/oauth/debug-login") + return + } + url := server.OAuth.AuthCodeURL(ctx.Request.Header.Get("Referer")) ctx.Redirect(http.StatusFound, url) } +func (server *Server) GetDebugSession(ctx *gin.Context) { + redir := ctx.Query("state") + + session := sessions.Default(ctx) + session.Set("userId", "424384163454517268") + session.Set("displayName", "estha") + err := session.Save() + + if err != nil { + logger.Error("Error saving debug session: %v", err) + } + + ctx.Redirect(http.StatusFound, redir) +} + func (server *Server) GetOAuthCallback(ctx *gin.Context) { authCode := ctx.Query("code") redir := ctx.Query("state") @@ -80,5 +106,6 @@ func (server *Server) SetupOAuthRoutes() { { group.GET("/redirect", server.GetOAuthRedirect) group.GET("/callback", server.GetOAuthCallback) + group.GET("/debug-login", server.GetDebugSession) } } diff --git a/backend/app/server/server.go b/backend/app/server/server.go index a9d082d..1107a7e 100644 --- a/backend/app/server/server.go +++ b/backend/app/server/server.go @@ -18,6 +18,7 @@ type Server struct { Config config.Config OAuth *oauth2.Config Gin *gin.Engine + Debug bool } func (server *Server) SetupSessionStore() { From 1ffffd00c3aa5357a8cdd88804b6ec5c8279ff95 Mon Sep 17 00:00:00 2001 From: Eviee Py Date: Tue, 25 Jun 2024 11:33:33 +1000 Subject: [PATCH 02/69] Add Debug user --- backend/app/server/oauth.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/app/server/oauth.go b/backend/app/server/oauth.go index f9fd1ec..8c6cfde 100644 --- a/backend/app/server/oauth.go +++ b/backend/app/server/oauth.go @@ -58,9 +58,11 @@ func (server *Server) GetOAuthRedirect(ctx *gin.Context) { func (server *Server) GetDebugSession(ctx *gin.Context) { redir := ctx.Query("state") + dbUser := database.CreateUser("discord", "0", "DebugCow") session := sessions.Default(ctx) - session.Set("userId", "424384163454517268") - session.Set("displayName", "estha") + session.Set("userId", convert.UUIDToString(dbUser.Id)) + session.Set("displayName", dbUser.DisplayName) + err := session.Save() if err != nil { From 0829122dc7a7b84174370b0c1bf04190c2efdf8c Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Mon, 24 Jun 2024 18:53:13 -0700 Subject: [PATCH 03/69] route for browsing teams --- backend/app/server/oauth.go | 2 + backend/app/server/teams.go | 4 +- html/src/lib/components/CreateTeamForm.svelte | 1 - html/src/lib/components/Nav.svelte | 2 +- html/src/lib/pages/EventList.svelte | 6 +- html/src/lib/pages/Invite.svelte | 2 +- html/src/lib/pages/MyTeam.svelte | 8 +- html/src/lib/pages/TeamsBrowse.svelte | 100 +++++++++++++----- html/src/lib/pages/UserTeams.svelte | 56 +++++----- html/src/lib/services/services.ts | 3 + html/src/routes.ts | 2 + 11 files changed, 123 insertions(+), 63 deletions(-) diff --git a/backend/app/server/oauth.go b/backend/app/server/oauth.go index 8c6cfde..37b7c09 100644 --- a/backend/app/server/oauth.go +++ b/backend/app/server/oauth.go @@ -60,6 +60,8 @@ func (server *Server) GetDebugSession(ctx *gin.Context) { dbUser := database.CreateUser("discord", "0", "DebugCow") session := sessions.Default(ctx) + session.Set("userId", "16a2adf0-de4c-4f2a-ae3e-c4cff316194e") + session.Set("displayName", "estha") session.Set("userId", convert.UUIDToString(dbUser.Id)) session.Set("displayName", dbUser.DisplayName) diff --git a/backend/app/server/teams.go b/backend/app/server/teams.go index 869b32c..3dd2db0 100644 --- a/backend/app/server/teams.go +++ b/backend/app/server/teams.go @@ -50,6 +50,7 @@ func (server *Server) GetAllTeams(ctx *gin.Context) { ctx.JSON(http.StatusOK, teams) } else { ctx.Status(http.StatusInternalServerError) + return } } @@ -255,7 +256,7 @@ func (server *Server) SetupTeamRoutes() { group := server.Gin.Group("/team") { group.POST("/", server.CreateTeam) - group.GET("/", server.GetAllTeams) + //group.GET("/", server.GetAllTeams) group.GET("/:id", server.GetTeamInfo) group.GET("/invite/:invitecode", server.GetTeamInfoByInviteCode) // group.PUT("/:id", server.UpdateTeam) @@ -263,4 +264,5 @@ func (server *Server) SetupTeamRoutes() { } server.Gin.GET("/teams", server.GetUserTeams) // I think this works rofl + server.Gin.GET("/teams/browse", server.GetAllTeams) } diff --git a/html/src/lib/components/CreateTeamForm.svelte b/html/src/lib/components/CreateTeamForm.svelte index c1ab41e..4e07535 100644 --- a/html/src/lib/components/CreateTeamForm.svelte +++ b/html/src/lib/components/CreateTeamForm.svelte @@ -63,7 +63,6 @@ function saveForm() { // const teamId = pathSegments[pathSegments.length - 1]; response.json() .then((data) => { - console.log(data.id) // Stepp 1: GET team info // this uses routes.ts --> MyTeam.svelte page diff --git a/html/src/lib/components/Nav.svelte b/html/src/lib/components/Nav.svelte index a710f38..18c9edf 100644 --- a/html/src/lib/components/Nav.svelte +++ b/html/src/lib/components/Nav.svelte @@ -10,7 +10,7 @@ function setActiveContent(content: string) { activeContent.set(content); console.log(activeContent); - if (loggedIn) { + if ($loggedInStore) { document.getElementById(content)?.classList.add('card-module'); } } diff --git a/html/src/lib/pages/EventList.svelte b/html/src/lib/pages/EventList.svelte index 8b7ab60..0832f48 100644 --- a/html/src/lib/pages/EventList.svelte +++ b/html/src/lib/pages/EventList.svelte @@ -11,7 +11,7 @@ import {eventStatusStore} from "../stores/stores"; let events : Array = new Array(); -let statuses = null +let statuses: any = null // Implicit any type? function editEvent(eventId: string) { window.location.href = '/#/admin/event/' + eventId; @@ -25,7 +25,7 @@ function getEventStatus(statusId: number): string { if (event !== null) { return event.Title; } - return ''; + return ''; // Should this be unindented? (In the outter scope) - Mysty {See line 20 : string} } } @@ -50,7 +50,7 @@ onMount(() => { -

My Events

+

Events

diff --git a/html/src/lib/pages/Invite.svelte b/html/src/lib/pages/Invite.svelte index 3e6a725..6f7f0f8 100644 --- a/html/src/lib/pages/Invite.svelte +++ b/html/src/lib/pages/Invite.svelte @@ -12,7 +12,7 @@ import DiscordIcon from '../components/DiscordIcon.svelte'; export let params: any; // set by svelte-spa-router - console.log(params) // returns Object { invitecode: "d1869a59b4fdf3" } + //console.log(params) // returns Object { invitecode: "d1869a59b4fdf3" } console.log(params.invitecode) let teamData: CodeJamTeam | null = null; diff --git a/html/src/lib/pages/MyTeam.svelte b/html/src/lib/pages/MyTeam.svelte index 8fed596..33582c9 100644 --- a/html/src/lib/pages/MyTeam.svelte +++ b/html/src/lib/pages/MyTeam.svelte @@ -9,7 +9,10 @@ import CodeJamEvent from '../models/event'; import { loggedInStore, userStore } from '../stores/stores'; - export let params: any; // set by svelte-spa-router + interface Params { // THis is what the params is because you pass an id with type of string. + id: string + } + export let params: Params; let teamData: CodeJamTeam | null = null; let teamMembers: TeamMember[] = []; @@ -26,14 +29,13 @@ teamEvent = data.Event; } catch (err) { error = `Failed to load team data: ${err}`; - console.error(err); } finally { loading = false; } } $: if (params) { - loadData(params.id); + loadData(params.id); // See this doesn't error cause it expects an id: You can run to see I guess } diff --git a/html/src/lib/pages/TeamsBrowse.svelte b/html/src/lib/pages/TeamsBrowse.svelte index dd87054..8746f1c 100644 --- a/html/src/lib/pages/TeamsBrowse.svelte +++ b/html/src/lib/pages/TeamsBrowse.svelte @@ -1,32 +1,82 @@ + } catch (err) { + error = `Failed to load team data: ${err}`; + console.error(error) + } finally { + loading = false; + } +} + +loadData(); + + - + + +

Browse All Teams

- -

Current Teams

-
-
- - -

Team 1

-

Join us, we're the best!

-

- - -
-
+ {#if loading} +
Loading...
+ {:else if error} +
{error}
-
+ {:else if allTeams.length == 0} +
+ Looks like you don't have any teams! Go to browse teams to join one! +
+ {:else if allTeams == null} +
+ Error, please contact admin. +
+ {:else} + + {#each allTeams as Team} + +
+

Team {Team.Name}

+
+ + Visibility: {Team.Visibility} + + + Technologies: {Team.Technologies} + + + Availability: {Team.Availability} + + + Description: {Team.Description} + + + +
+ + + {/each} + + + {/if} +
+ +
\ No newline at end of file diff --git a/html/src/lib/pages/UserTeams.svelte b/html/src/lib/pages/UserTeams.svelte index f7db226..826c11d 100644 --- a/html/src/lib/pages/UserTeams.svelte +++ b/html/src/lib/pages/UserTeams.svelte @@ -1,37 +1,37 @@ diff --git a/html/src/lib/services/services.ts b/html/src/lib/services/services.ts index b2eb48c..9f3a1dc 100644 --- a/html/src/lib/services/services.ts +++ b/html/src/lib/services/services.ts @@ -103,6 +103,9 @@ export async function postTeam(team: CodeJamTeam) { }); } +export async function getTeams() { + return await fetch(baseApiUrl + "/teams/browse") +} export async function getUserTeams(){ return fetch(baseApiUrl + "/teams"); } diff --git a/html/src/routes.ts b/html/src/routes.ts index e8211a6..4a36a2d 100644 --- a/html/src/routes.ts +++ b/html/src/routes.ts @@ -9,6 +9,7 @@ import Invite from "./lib/pages/Invite.svelte"; import UserTeams from "./lib/pages/UserTeams.svelte"; + export default { '/': HomePage, '/home': HomePage, @@ -20,4 +21,5 @@ export default { '/teams': UserTeams, // displays all the user's teams (private) '/teams/browse': TeamsBrowse, '/teams/create': TeamsCreate, + } \ No newline at end of file From ad857f92a23cdca3a2211d1d926ddf58d09f0532 Mon Sep 17 00:00:00 2001 From: Eviee Py Date: Tue, 25 Jun 2024 12:04:47 +1000 Subject: [PATCH 04/69] Fix teams/browse endpoint --- html/src/lib/pages/TeamsBrowse.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/html/src/lib/pages/TeamsBrowse.svelte b/html/src/lib/pages/TeamsBrowse.svelte index 8746f1c..038bf6c 100644 --- a/html/src/lib/pages/TeamsBrowse.svelte +++ b/html/src/lib/pages/TeamsBrowse.svelte @@ -40,11 +40,11 @@ loadData(); {:else if error}
{error}
- {:else if allTeams.length == 0} + {:else if allTeams === null}
Looks like you don't have any teams! Go to browse teams to join one!
- {:else if allTeams == null} + {:else if allTeams.length === 0}
Error, please contact admin.
From f6ae698e1b1b1f252228f2dc33bec11822e673f9 Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Mon, 24 Jun 2024 19:26:59 -0700 Subject: [PATCH 05/69] fix debugcow, userId check, and order of operations in team views --- backend/app/server/oauth.go | 2 -- backend/app/server/teams.go | 9 ++----- html/src/lib/pages/TeamsBrowse.svelte | 8 ++++--- html/src/lib/pages/UserTeams.svelte | 34 ++++----------------------- 4 files changed, 12 insertions(+), 41 deletions(-) diff --git a/backend/app/server/oauth.go b/backend/app/server/oauth.go index 37b7c09..8c6cfde 100644 --- a/backend/app/server/oauth.go +++ b/backend/app/server/oauth.go @@ -60,8 +60,6 @@ func (server *Server) GetDebugSession(ctx *gin.Context) { dbUser := database.CreateUser("discord", "0", "DebugCow") session := sessions.Default(ctx) - session.Set("userId", "16a2adf0-de4c-4f2a-ae3e-c4cff316194e") - session.Set("displayName", "estha") session.Set("userId", convert.UUIDToString(dbUser.Id)) session.Set("displayName", dbUser.DisplayName) diff --git a/backend/app/server/teams.go b/backend/app/server/teams.go index 3dd2db0..f38900c 100644 --- a/backend/app/server/teams.go +++ b/backend/app/server/teams.go @@ -166,18 +166,12 @@ func (server *Server) CreateTeam(ctx *gin.Context) { // Step 4: Post Team Data API (TWO PARTS 1) create team 2) add team members) session := sessions.Default(ctx) userId := session.Get("userId") - if userId != nil { - ctx.Status(http.StatusUnauthorized) - return - } + strUserId := userId.(string) var team database.DBTeam var teamReq CreateTeamRequest - // var tempMember CreateTeamMember - // shouldbindJSON binds the POST-req-JSON-info to the provided structure in () - // err should be (ctx feature) err := ctx.ShouldBindJSON(&teamReq) if err != nil { logger.Error("CreateTeam Request ShouldBindJSON error: %v", err) @@ -203,6 +197,7 @@ func (server *Server) CreateTeam(ctx *gin.Context) { team.InviteCode = md5code fmt.Printf("%+v", team) + // INSERTS TEAM into DB // PART 1/2 DONE teamUUID, err := database.CreateTeam(team) diff --git a/html/src/lib/pages/TeamsBrowse.svelte b/html/src/lib/pages/TeamsBrowse.svelte index 038bf6c..0edbd3f 100644 --- a/html/src/lib/pages/TeamsBrowse.svelte +++ b/html/src/lib/pages/TeamsBrowse.svelte @@ -42,12 +42,14 @@ loadData(); {:else if allTeams === null}
- Looks like you don't have any teams! Go to browse teams to join one! + Error, please contact admin.
- {:else if allTeams.length === 0} + + {:else if allTeams.length === 0}
- Error, please contact admin. + Looks like you don't have any teams! Go to browse teams to join one!
+ {:else} {#each allTeams as Team} diff --git a/html/src/lib/pages/UserTeams.svelte b/html/src/lib/pages/UserTeams.svelte index 826c11d..2af5ece 100644 --- a/html/src/lib/pages/UserTeams.svelte +++ b/html/src/lib/pages/UserTeams.svelte @@ -49,19 +49,15 @@ If not team owner, cannot view-->
Loading...
{:else if error}
{error}
- - {:else if userTeams.length == 0} + {:else if userTeams === null}
- Looks like you don't have any teams! Go to browse teams to join one! + Error, please contact admin.
- {:else if userTeams == null} + {:else if userTeams.length === 0}
- Error, please contact admin. + Looks like you don't have any teams! Go to browse teams to join one!
{:else} - hi - - {userTeams} {#each userTeams as userTeam}
@@ -80,27 +76,7 @@ If not team owner, cannot view--> Description: {userTeam.Description} - + From 1863983a81006af67b33008db1e27c1aeff00e3b Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Wed, 26 Jun 2024 20:32:20 -0700 Subject: [PATCH 06/69] add avatar_url string to debugcow args in oauth --- backend/app/server/oauth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app/server/oauth.go b/backend/app/server/oauth.go index 6a8fd28..b295c80 100644 --- a/backend/app/server/oauth.go +++ b/backend/app/server/oauth.go @@ -58,7 +58,7 @@ func (server *Server) GetOAuthRedirect(ctx *gin.Context) { func (server *Server) GetDebugSession(ctx *gin.Context) { redir := ctx.Query("state") - dbUser := database.CreateUser("discord", "0", "DebugCow") + dbUser := database.CreateUser("discord", "0", "DebugCow", "") session := sessions.Default(ctx) session.Set("userId", convert.UUIDToString(dbUser.Id)) session.Set("displayName", dbUser.DisplayName) From 29d6eb6a8de539f89a061251b2d6a5af1ed75b9e Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Thu, 1 Aug 2024 00:25:43 -0700 Subject: [PATCH 07/69] separate sendTeamInfo route from getTeamInfo --- backend/app/server/teams.go | 85 ++++++++++++++++++++++++++++++- html/src/lib/services/services.ts | 23 +++++++-- 2 files changed, 102 insertions(+), 6 deletions(-) diff --git a/backend/app/server/teams.go b/backend/app/server/teams.go index 9baf617..5d5250b 100644 --- a/backend/app/server/teams.go +++ b/backend/app/server/teams.go @@ -35,6 +35,19 @@ type GetTeamResponse struct { Members *[]database.DBTeamMemberInfo // array(slice) of a struct } +type InvitePayload struct { + TeamId string `json:"teamId"` + InviteCode string `json:"inviteCode"` +} + +type JoinPayload struct { + TeamId string `json:"teamId"` +} + +type Response struct { + teamData GetTeamResponse +} + func MD5HashCode(teamName string) (string, error) { randNum, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) if err != nil { @@ -94,6 +107,15 @@ func (server *Server) GetUserTeams(ctx *gin.Context) { // assign each team to teamResponse type.. } +func (server *Server) sendTeamInfo(teamResponse GetTeamResponse) { + router := gin.Default() + router.POST("/teams/browse", func(ctx *gin.Context) { + response := Response { + teamData: teamResponse, + } + ctx.JSON(http.StatusOK, response) + }) +} // stepp 4: GET team info // purpose is to construct the DBTeamMemberInfo @@ -131,9 +153,12 @@ func (server *Server) GetTeamInfo(ctx *gin.Context) { teamResponse.Event = &event teamResponse.Members = members - ctx.JSON(http.StatusOK, teamResponse) + server.sendTeamInfo(teamResponse) + //ctx.JSON(http.StatusOK, teamResponse) } + + func (server *Server) GetTeamInfoByInviteCode(ctx *gin.Context) { inviteCode := ctx.Param("invitecode") fmt.Println("\n===server getteam by invite code: ", inviteCode) @@ -225,6 +250,7 @@ func (server *Server) CreateTeam(ctx *gin.Context) { ctx.Status(http.StatusBadRequest) return } + fmt.Println(convert.StringToUUID(strUserId), teamUUID,) // PART 2/2 DONE // construct TeamMember @@ -246,9 +272,10 @@ func (server *Server) CreateTeam(ctx *gin.Context) { func (server *Server) UpdateTeam(ctx *gin.Context) { session := sessions.Default(ctx) userId := session.Get("userId") + fmt.Println(ctx.Request.Body) if userId != nil { var team database.DBTeam - err := ctx.ShouldBindJSON(&team) + err := ctx.ShouldBindJSON(&team) //"message incoming data to this struct" if err != nil { logger.Error("UpdateEvent Request ShouldBindJSON error: %v", err) ctx.Status(http.StatusBadRequest) @@ -266,10 +293,64 @@ func (server *Server) UpdateTeam(ctx *gin.Context) { } } +func (server *Server) UpdateTeamMembers(ctx *gin.Context) { + session := sessions.Default(ctx) + userId := session.Get("userId") + if userId != nil { + ctx.Status(http.StatusBadRequest) + return + } + + //database.AddTeamMember(convert.StringToUUID(userId.(string), )) +} + +func (server *Server) MemberJoin(ctx *gin.Context) { + + session := sessions.Default(ctx) + userId := session.Get("userId") + + fmt.Println(userId) + var teamId JoinPayload + + if err := ctx.ShouldBindJSON(&teamId); err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + } + fmt.Println(teamId) // this prints {cc457e3e-210a-4d5d-83d8-0899426dfc93} + + // call getteaminfo + //verify team is public + //var team + //var isPublic bool = team.ispublic + +} + +func (server *Server) MemberInvite(ctx *gin.Context) { + session := sessions.Default(ctx) + userId:= session.Get("userId") + + var payload InvitePayload + // bind the message context to the structure, and do an error check + if err := ctx.ShouldBindJSON(&payload); err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + inviteCode := payload.InviteCode + teamId := payload.TeamId + + fmt.Println("===316", inviteCode, teamId, userId) + + //var team database.DBTeamMemberInfo + // to add a member, i need to add a user to team_member table +} + + func (server *Server) SetupTeamRoutes() { group := server.Gin.Group("/team") { group.POST("/", server.CreateTeam) + group.POST("/edit/:teamid", server.UpdateTeamMembers) // for admin to remove people + group.POST("/join", server.MemberJoin) + group.POST("/:invitecode", server.MemberInvite) //group.GET("/", server.GetAllTeams) group.GET("/:id", server.GetTeamInfo) group.GET("/invite/:invitecode", server.GetTeamInfoByInviteCode) diff --git a/html/src/lib/services/services.ts b/html/src/lib/services/services.ts index c4c0068..4dfe208 100644 --- a/html/src/lib/services/services.ts +++ b/html/src/lib/services/services.ts @@ -137,14 +137,29 @@ export async function getTeamByInvite(inviteCode: string) { } -// any one who has the link joinTeam connects to can join the team. +// any one who has a link joinTeam() works on, can join the team. // make sure invite_code matches -export async function joinTeam(team: CodeJamTeam, userId: string, invite_code: string) { + +export async function joinTeam(teamId: string, inviteCode: string) { // making a post to team_members - return await fetch(baseApiUrl + "/team/" + invite_code, + return await fetch(baseApiUrl + "/team/" + inviteCode, + { + method: "POST", + body: JSON.stringify({ teamId, inviteCode }) + } + ) +} + + + +// write a joinPublicTeam function that accepts team Id. +// server: check if team is public. + +export async function joinPublicTeam(teamId: string) { + return await fetch(baseApiUrl + "/team/join", { method: "POST", - body: JSON.stringify({ team, userId, invite_code }) + body: JSON.stringify({ teamId }) } ) } From 4244584f13f848cc5759f8debf79c4abc119de9c Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Wed, 14 Aug 2024 23:54:14 -0700 Subject: [PATCH 08/69] added: login modal Co-authored-by: Mysty --- html/src/lib/pages/TeamsBrowse.svelte | 54 ++++++++++++++------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/html/src/lib/pages/TeamsBrowse.svelte b/html/src/lib/pages/TeamsBrowse.svelte index 0edbd3f..b868ef9 100644 --- a/html/src/lib/pages/TeamsBrowse.svelte +++ b/html/src/lib/pages/TeamsBrowse.svelte @@ -1,27 +1,28 @@

Browse All Teams

- + + {#if loading}
Loading...
{:else if error}
{error}
- - {:else if allTeams === null} -
- Error, please contact admin. -
- - {:else if allTeams.length === 0} -
- Looks like you don't have any teams! Go to browse teams to join one! -
- {:else} + {#each publicTeams as Team} + - {#each allTeams as Team} -

Team {Team.Name}

-
+
Visibility: {Team.Visibility} @@ -70,12 +63,21 @@ loadData(); Description: {Team.Description} - + // only shows if user isnt a member + {#if $loggedInStore} + + {:else} + + +

Login with Discord to join a team!

+
+ {/if}
- - - {/each} - + {:else} +
+ Looks like you don't have any teams! Go to browse teams to join one! +
+ {/each} {/if} From b96c96539292b0b4d0707434fa5f12438dd1bef4 Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Wed, 14 Aug 2024 23:57:30 -0700 Subject: [PATCH 09/69] added: getTeamInfo separeated from sendTeamInfo for MemberJoin --- backend/app/server/teams.go | 90 ++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/backend/app/server/teams.go b/backend/app/server/teams.go index 5d5250b..b35d637 100644 --- a/backend/app/server/teams.go +++ b/backend/app/server/teams.go @@ -44,10 +44,6 @@ type JoinPayload struct { TeamId string `json:"teamId"` } -type Response struct { - teamData GetTeamResponse -} - func MD5HashCode(teamName string) (string, error) { randNum, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) if err != nil { @@ -72,6 +68,7 @@ func (server *Server) signupsAllowed(eventId string) bool { } func (server *Server) GetAllTeams(ctx *gin.Context) { + // what if there's no session => no user id teams, err := database.GetTeams() if err == nil { ctx.JSON(http.StatusOK, teams) @@ -84,43 +81,23 @@ func (server *Server) GetAllTeams(ctx *gin.Context) { func (server *Server) GetUserTeams(ctx *gin.Context) { session := sessions.Default(ctx) userId := session.Get("userId") - strUserId := userId.(string) - if userId == nil { ctx.Status(http.StatusNotFound) return } - // var teamResponse GetTeamResponse - // var teamsResponse []GetTeamResponse + strUserId := userId.(string) teams, err := database.GetUserTeams(convert.StringToUUID(strUserId)) if err != nil { ctx.Status(http.StatusInternalServerError) return } - - //1. join databse to return members ctx.JSON(http.StatusOK, teams) - - // add all the GetTeamResponse to []GetTeamResponse - // loop through teams, get team id - // assign each team to teamResponse type.. - -} -func (server *Server) sendTeamInfo(teamResponse GetTeamResponse) { - router := gin.Default() - router.POST("/teams/browse", func(ctx *gin.Context) { - response := Response { - teamData: teamResponse, - } - ctx.JSON(http.StatusOK, response) - }) } // stepp 4: GET team info // purpose is to construct the DBTeamMemberInfo -func (server *Server) GetTeamInfo(ctx *gin.Context) { - id := convert.StringToUUID(ctx.Param("id")) +func (server *Server) GetTeamInfo(id pgtype.UUID) (*GetTeamResponse, error) { var teamResponse GetTeamResponse var team database.DBTeam @@ -130,34 +107,39 @@ func (server *Server) GetTeamInfo(ctx *gin.Context) { team, err := database.GetTeam(id) if err != nil { logger.Error("failed to get team: %v", err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to get team: %v", err)}) - return + return nil, err } event, err = database.GetEvent(team.EventId) if err != nil { logger.Error("failed to get event: %v", err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to get event: %v", err)}) - return + return nil, err } members, err = database.GetMembersByTeamId(team.Id) if err != nil { logger.Error("failed to get event: %v", err) - ctx.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to get members: %v", err)}) - return + return nil, err } // attach all 3 structures to GetTeamResponse --> nested structs turn into nested JSON (with ctx.JSON) teamResponse.Team = &team teamResponse.Event = &event teamResponse.Members = members - - server.sendTeamInfo(teamResponse) - //ctx.JSON(http.StatusOK, teamResponse) + + return &teamResponse, nil } +func (server *Server) sendTeamInfo(ctx *gin.Context) { + id := convert.StringToUUID(ctx.Param("id")) + + teamResponse, err := server.GetTeamInfo(id) + if err != nil { + ctx.Status(http.StatusBadRequest) + } + ctx.JSON(http.StatusOK, teamResponse) +} func (server *Server) GetTeamInfoByInviteCode(ctx *gin.Context) { inviteCode := ctx.Param("invitecode") @@ -205,7 +187,10 @@ func (server *Server) CreateTeam(ctx *gin.Context) { // Step 4: Post Team Data API (TWO PARTS 1) create team 2) add team members) session := sessions.Default(ctx) userId := session.Get("userId") - + if userId == nil { + ctx.Status(http.StatusBadRequest) + return + } strUserId := userId.(string) var team database.DBTeam @@ -300,28 +285,43 @@ func (server *Server) UpdateTeamMembers(ctx *gin.Context) { ctx.Status(http.StatusBadRequest) return } - //database.AddTeamMember(convert.StringToUUID(userId.(string), )) } func (server *Server) MemberJoin(ctx *gin.Context) { - session := sessions.Default(ctx) userId := session.Get("userId") - fmt.Println(userId) + + if userId == nil { + ctx.Status(http.StatusNotFound) + return + } + strUserId := userId.(string) + uuidUserId := convert.StringToUUID(strUserId) + + var teamId JoinPayload if err := ctx.ShouldBindJSON(&teamId); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return } fmt.Println(teamId) // this prints {cc457e3e-210a-4d5d-83d8-0899426dfc93} + uuidTeamId := convert.StringToUUID(teamId.TeamId) + teamInfo, err := server.GetTeamInfo(uuidTeamId) + if err != nil { + ctx.Status(http.StatusUnauthorized) + return + } - // call getteaminfo - //verify team is public - //var team - //var isPublic bool = team.ispublic - + // verifies team is public + var isPublic string = teamInfo.Team.Visibility + if isPublic == "private" { + ctx.Status(http.StatusForbidden) + return + } + database.AddTeamMember(uuidUserId, uuidTeamId, "member") } func (server *Server) MemberInvite(ctx *gin.Context) { @@ -352,7 +352,7 @@ func (server *Server) SetupTeamRoutes() { group.POST("/join", server.MemberJoin) group.POST("/:invitecode", server.MemberInvite) //group.GET("/", server.GetAllTeams) - group.GET("/:id", server.GetTeamInfo) + group.GET("/:id", server.sendTeamInfo) group.GET("/invite/:invitecode", server.GetTeamInfoByInviteCode) // group.PUT("/:id", server.UpdateTeam) // Step 3: Post Team Data API From edfaaa7cd435f92edbbf44c737be7b49348d6c0b Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Thu, 15 Aug 2024 00:07:11 -0700 Subject: [PATCH 10/69] cleaned two files --- html/src/lib/pages/Invite.svelte | 40 +++++++++++++++++------------ html/src/lib/pages/UserTeams.svelte | 10 +------- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/html/src/lib/pages/Invite.svelte b/html/src/lib/pages/Invite.svelte index 6f7f0f8..88ae2a0 100644 --- a/html/src/lib/pages/Invite.svelte +++ b/html/src/lib/pages/Invite.svelte @@ -1,6 +1,6 @@ @@ -62,7 +72,7 @@ Click below to join {teamMembers[0]?.DisplayName}'s team: - + {:else}
Must be logged in to join a team. @@ -70,10 +80,6 @@ - - {/if} - - diff --git a/html/src/lib/pages/UserTeams.svelte b/html/src/lib/pages/UserTeams.svelte index 2af5ece..ab4d17b 100644 --- a/html/src/lib/pages/UserTeams.svelte +++ b/html/src/lib/pages/UserTeams.svelte @@ -20,8 +20,6 @@ async function loadData() { try { const response = await getUserTeams(); userTeams = await response.json(); // Array of teams... - console.log(userTeams) - } catch (err) { error = `Failed to load team data: ${err}`; console.error(err); @@ -75,14 +73,8 @@ If not team owner, cannot view--> Description: {userTeam.Description} - - - - - {/each} - - + {/each} {/if} From 4172f43396f25701a443175cd86b41693ab05a2e Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Thu, 15 Aug 2024 00:10:10 -0700 Subject: [PATCH 11/69] cleaned up strange formats --- html/src/lib/stores/stores.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/html/src/lib/stores/stores.ts b/html/src/lib/stores/stores.ts index 19fe274..e13b65c 100644 --- a/html/src/lib/stores/stores.ts +++ b/html/src/lib/stores/stores.ts @@ -8,11 +8,13 @@ export const activeContent = writable(''); /** * Deprecated - use activeUserStore.user instead */ + export const userStore = writable(null); /** * Deprecated - use activeUserStore.loggedIn instead */ + export const loggedInStore = derived(userStore, (userData) => userData != null); /** From 5210c25e88f75c762c8d69d3bed26f8442712312 Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Fri, 16 Aug 2024 10:34:14 -0700 Subject: [PATCH 12/69] changed StatusBadRequest to StatusUnauthorized --- backend/app/server/teams.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/app/server/teams.go b/backend/app/server/teams.go index b35d637..9fe4f28 100644 --- a/backend/app/server/teams.go +++ b/backend/app/server/teams.go @@ -188,7 +188,7 @@ func (server *Server) CreateTeam(ctx *gin.Context) { session := sessions.Default(ctx) userId := session.Get("userId") if userId == nil { - ctx.Status(http.StatusBadRequest) + ctx.Status(http.StatusUnauthorized) return } strUserId := userId.(string) @@ -282,7 +282,7 @@ func (server *Server) UpdateTeamMembers(ctx *gin.Context) { session := sessions.Default(ctx) userId := session.Get("userId") if userId != nil { - ctx.Status(http.StatusBadRequest) + ctx.Status(http.StatusUnauthorized) return } //database.AddTeamMember(convert.StringToUUID(userId.(string), )) @@ -294,7 +294,7 @@ func (server *Server) MemberJoin(ctx *gin.Context) { fmt.Println(userId) if userId == nil { - ctx.Status(http.StatusNotFound) + ctx.Status(http.StatusUnauthorized) return } strUserId := userId.(string) From 68539a96aaecd8ca27c7ebd8fde1f198edf08051 Mon Sep 17 00:00:00 2001 From: Eviee Py Date: Sat, 17 Aug 2024 09:00:02 +1000 Subject: [PATCH 13/69] Add redirect after login --- backend/app/server/oauth.go | 60 +++++++++++++++++++++++++-- backend/app/server/server.go | 5 ++- backend/app/server/utils.go | 17 ++++++++ html/src/lib/components/Header.svelte | 2 +- html/src/lib/pages/Invite.svelte | 3 +- html/src/lib/pages/TeamsBrowse.svelte | 4 +- 6 files changed, 83 insertions(+), 8 deletions(-) create mode 100644 backend/app/server/utils.go diff --git a/backend/app/server/oauth.go b/backend/app/server/oauth.go index b295c80..935d2bd 100644 --- a/backend/app/server/oauth.go +++ b/backend/app/server/oauth.go @@ -1,6 +1,7 @@ package server import ( + "fmt" "net/http" "os" "strings" @@ -14,13 +15,18 @@ import ( githubOAuth "golang.org/x/oauth2/github" ) +type StateData struct { + Token string + Redirect string +} + // SetupOAuth initializes the OAuth provider specified in the application config. func (server *Server) SetupOAuth() { if server.Debug { logger.Warn("Debug mode is set. No OAuth Providers are set!") return } - + var endpoint oauth2.Endpoint switch strings.ToLower(server.Config.OAuth.Provider) { @@ -46,12 +52,38 @@ func (server *Server) SetupOAuth() { } func (server *Server) GetOAuthRedirect(ctx *gin.Context) { + session := sessions.Default(ctx) + token, err := GenerateToken(16) + + if err != nil { + ctx.String(500, "Internal Server Error") + } + + redirect := ctx.Query("redirect") + if redirect == "" { + redirect = "/" + } + + if !strings.HasPrefix(redirect, "/") { + redirect = "/" + } else { + redirect = fmt.Sprintf("/#%s", redirect) + } + + state := StateData{Token: token, Redirect: redirect} + session.Set("state", state) + err = session.Save() + + if err != nil { + logger.Error("Error saving session: %v", err) + } + if server.Debug { ctx.Redirect(http.StatusFound, "/oauth/debug-login") return } - url := server.OAuth.AuthCodeURL(ctx.Request.Header.Get("Referer")) + url := server.OAuth.AuthCodeURL(token) ctx.Redirect(http.StatusFound, url) } @@ -74,7 +106,28 @@ func (server *Server) GetDebugSession(ctx *gin.Context) { func (server *Server) GetOAuthCallback(ctx *gin.Context) { authCode := ctx.Query("code") - redir := ctx.Query("state") + stateCode := ctx.Query("state") + + if len(stateCode) == 0 { + ctx.String(400, "Bad Request: Missing State Value.") + return + } + + session := sessions.Default(ctx) + stateI := session.Get("state") + + session.Clear() + session.Save() + + var stateData *StateData + stateData, ok := stateI.(*StateData) + if !ok { + ctx.String(400, "Bad Request: Invalid State Data.") + return + } + + redir := stateData.Redirect + token, err := server.OAuth.Exchange(oauth2.NoContext, authCode) if err != nil { // todo - can any of these be handled? @@ -86,7 +139,6 @@ func (server *Server) GetOAuthCallback(ctx *gin.Context) { providerUser := integrations.GetUser(integrationName, token.AccessToken) if providerUser != nil { dbUser := database.CreateUser(integrationName, providerUser.UserId, providerUser.DisplayName, providerUser.AvatarUrl) - session := sessions.Default(ctx) session.Set("userId", convert.UUIDToString(dbUser.Id)) session.Set("displayName", dbUser.DisplayName) err = session.Save() diff --git a/backend/app/server/server.go b/backend/app/server/server.go index 1107a7e..48e5877 100644 --- a/backend/app/server/server.go +++ b/backend/app/server/server.go @@ -3,6 +3,7 @@ package server import ( "codejam.io/config" "codejam.io/logging" + "encoding/gob" "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/redis" "github.com/gin-gonic/gin" @@ -18,7 +19,7 @@ type Server struct { Config config.Config OAuth *oauth2.Config Gin *gin.Engine - Debug bool + Debug bool } func (server *Server) SetupSessionStore() { @@ -47,6 +48,8 @@ func (server *Server) SetupSessionStore() { func (server *Server) StartServer() { logger.Info("Starting server...") + gob.Register(&StateData{}) + server.Gin = gin.Default() server.SetupSessionStore() diff --git a/backend/app/server/utils.go b/backend/app/server/utils.go new file mode 100644 index 0000000..147ed60 --- /dev/null +++ b/backend/app/server/utils.go @@ -0,0 +1,17 @@ +package server + +import ( + "crypto/rand" + "encoding/base64" +) + +func GenerateToken(length uint8) (string, error) { + token := make([]byte, length) + + _, err := rand.Read(token) + if err != nil { + return "", err + } + + return base64.URLEncoding.EncodeToString(token), nil +} diff --git a/html/src/lib/components/Header.svelte b/html/src/lib/components/Header.svelte index c7146d7..1b063db 100644 --- a/html/src/lib/components/Header.svelte +++ b/html/src/lib/components/Header.svelte @@ -63,7 +63,7 @@ $: activeUrl = '/#' + $location; {/if} {:else} - Login with Discord + Login with Discord {/if} diff --git a/html/src/lib/pages/Invite.svelte b/html/src/lib/pages/Invite.svelte index 88ae2a0..6bfba9a 100644 --- a/html/src/lib/pages/Invite.svelte +++ b/html/src/lib/pages/Invite.svelte @@ -10,6 +10,7 @@ import CodeJamEvent from '../models/event'; import { loggedInStore, userStore } from '../stores/stores'; import DiscordIcon from '../components/DiscordIcon.svelte'; + import {location} from "svelte-spa-router"; export let params: any; // set by svelte-spa-router //console.log(params) // returns Object { invitecode: "d1869a59b4fdf3" } @@ -78,7 +79,7 @@ Must be logged in to join a team.
{/if} diff --git a/html/src/lib/pages/TeamsBrowse.svelte b/html/src/lib/pages/TeamsBrowse.svelte index b868ef9..d48f57d 100644 --- a/html/src/lib/pages/TeamsBrowse.svelte +++ b/html/src/lib/pages/TeamsBrowse.svelte @@ -7,8 +7,10 @@ import { loggedInStore, userStore } from '../stores/stores'; import CodeJamTeam from '../models/team'; import type TeamMember from '../models/TeamMember'; import DiscordIcon from '../components/DiscordIcon.svelte'; +import {location} from "svelte-spa-router"; export const params: Record = {}; + let teamData: CodeJamTeam | null = null; let teamMembers: TeamMember[] = []; let loading: boolean = true; @@ -69,7 +71,7 @@ $: publicTeams = allTeams.filter(t => t.Visibility == "public" ) {:else} -

Login with Discord to join a team!

+

Login with Discord to join a team!

{/if} From bbaf8a411f2477368b4ed6d1cbf8f3e93e732edb Mon Sep 17 00:00:00 2001 From: Eviee Py Date: Sat, 17 Aug 2024 09:18:48 +1000 Subject: [PATCH 14/69] Fix potential abuse vector in redirect. --- backend/app/server/oauth.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/app/server/oauth.go b/backend/app/server/oauth.go index 935d2bd..85718ae 100644 --- a/backend/app/server/oauth.go +++ b/backend/app/server/oauth.go @@ -66,6 +66,8 @@ func (server *Server) GetOAuthRedirect(ctx *gin.Context) { if !strings.HasPrefix(redirect, "/") { redirect = "/" + } else if !strings.HasPrefix(redirect, "/oauth") { + redirect = "/" } else { redirect = fmt.Sprintf("/#%s", redirect) } From a5bf8b1d6d1c7f2c51c0d4ed3a078110ace83142 Mon Sep 17 00:00:00 2001 From: Eviee Py Date: Sat, 17 Aug 2024 09:20:40 +1000 Subject: [PATCH 15/69] Fix ! check on hasPrefix --- backend/app/server/oauth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app/server/oauth.go b/backend/app/server/oauth.go index 85718ae..4510161 100644 --- a/backend/app/server/oauth.go +++ b/backend/app/server/oauth.go @@ -66,7 +66,7 @@ func (server *Server) GetOAuthRedirect(ctx *gin.Context) { if !strings.HasPrefix(redirect, "/") { redirect = "/" - } else if !strings.HasPrefix(redirect, "/oauth") { + } else if strings.HasPrefix(redirect, "/oauth") { redirect = "/" } else { redirect = fmt.Sprintf("/#%s", redirect) From cabb3b34bbce22238921f3dc491a0af9e0c55509 Mon Sep 17 00:00:00 2001 From: Eviee Py Date: Sat, 17 Aug 2024 09:24:43 +1000 Subject: [PATCH 16/69] Add missing stateCode token check --- backend/app/server/oauth.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/app/server/oauth.go b/backend/app/server/oauth.go index 4510161..6522cbf 100644 --- a/backend/app/server/oauth.go +++ b/backend/app/server/oauth.go @@ -128,6 +128,11 @@ func (server *Server) GetOAuthCallback(ctx *gin.Context) { return } + if stateData.Token != stateCode { + ctx.String(400, "Bad Request: Invalid State Code Provided.") + return + } + redir := stateData.Redirect token, err := server.OAuth.Exchange(oauth2.NoContext, authCode) From c13e7987a1aa2cb394897d73db098d4db43f4757 Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Fri, 30 Aug 2024 02:12:34 -0700 Subject: [PATCH 17/69] change team_members schema to prevent user_id duplicates in teams --- .../migrations/00000004_team_members.up.sql | 13 ++++---- backend/app/database/teams.go | 9 ++++-- backend/app/server/teams.go | 31 ++++++++++++++----- html/src/lib/pages/TeamsBrowse.svelte | 6 +++- 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/backend/app/database/migrations/00000004_team_members.up.sql b/backend/app/database/migrations/00000004_team_members.up.sql index 30b2431..5429dbd 100644 --- a/backend/app/database/migrations/00000004_team_members.up.sql +++ b/backend/app/database/migrations/00000004_team_members.up.sql @@ -1,9 +1,10 @@ CREATE TABLE IF NOT EXISTS team_members ( - id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, - team_id UUID references teams(id) ON DELETE CASCADE, - user_id UUID references users(id) ON DELETE CASCADE, - role TEXT NOT NULL, - created_on TIMESTAMP WITH TIME ZONE DEFAULT (now() AT TIME ZONE('utc')) + team_id UUID NOT NULL references teams(id) ON DELETE CASCADE, + user_id UUID NOT NULL references users(id) ON DELETE CASCADE, + team_role TEXT NOT NULL, + created_on TIMESTAMP WITH TIME ZONE DEFAULT (now() AT TIME ZONE('utc')), + PRIMARY KEY (team_id, user_id) ); -ALTER TABLE teams DROP COLUMN IF EXISTS owner_user_id; \ No newline at end of file +ALTER TABLE teams DROP COLUMN IF EXISTS owner_user_id; + diff --git a/backend/app/database/teams.go b/backend/app/database/teams.go index 022db63..09e0e08 100644 --- a/backend/app/database/teams.go +++ b/backend/app/database/teams.go @@ -149,15 +149,20 @@ func UpdateTeam(team DBTeam) (DBTeam, error) { } // fields: userid, teamid, role -// called at server/teams.go createTeam & when someone clicks "join team" +// called at server/teams.go createTeam & when someone clicks "join team" // DONT MESS WITH BELOW. IT WORKS. func AddTeamMember(userId pgtype.UUID, teamUUID pgtype.UUID, role string) (userID pgtype.UUID, err error) { - fmt.Println("=== line 100 userId", userId) + // userId prints something like: {[22 162 173 240 222 76 79 42 174 62 196 207 243 22 25 78] true} + teamMember, err := GetRow[CreateTeamMember]( `INSERT INTO team_members (user_id, team_id, team_role) VALUES ($1, $2, $3) RETURNING user_id, team_id, team_role`, userId, teamUUID, role) + if err != nil { + fmt.Println(err) + return userId, err + } return teamMember.UserId, err } diff --git a/backend/app/server/teams.go b/backend/app/server/teams.go index 9fe4f28..d933c17 100644 --- a/backend/app/server/teams.go +++ b/backend/app/server/teams.go @@ -103,7 +103,7 @@ func (server *Server) GetTeamInfo(id pgtype.UUID) (*GetTeamResponse, error) { var team database.DBTeam var event database.DBEvent var members *[]database.DBTeamMemberInfo //user info based on teamId - + // fmt.Println("========id: ", id) prints: {[204 69 126 62 33 10 77 93 131 216 8 153 66 109 252 147] true} team, err := database.GetTeam(id) if err != nil { logger.Error("failed to get team: %v", err) @@ -291,7 +291,7 @@ func (server *Server) UpdateTeamMembers(ctx *gin.Context) { func (server *Server) MemberJoin(ctx *gin.Context) { session := sessions.Default(ctx) userId := session.Get("userId") - fmt.Println(userId) + fmt.Println("userID = ", userId) if userId == nil { ctx.Status(http.StatusUnauthorized) @@ -300,28 +300,45 @@ func (server *Server) MemberJoin(ctx *gin.Context) { strUserId := userId.(string) uuidUserId := convert.StringToUUID(strUserId) - var teamId JoinPayload if err := ctx.ShouldBindJSON(&teamId); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - fmt.Println(teamId) // this prints {cc457e3e-210a-4d5d-83d8-0899426dfc93} + // teamId prints: {cc457e3e-210a-4d5d-83d8-0899426dfc93} uuidTeamId := convert.StringToUUID(teamId.TeamId) teamInfo, err := server.GetTeamInfo(uuidTeamId) if err != nil { - ctx.Status(http.StatusUnauthorized) - return + ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return } + // TODO: check if the user already exists in the team before adding the member. + // use teamInfo: + // type GetTeamResponse struct { + // Team *database.DBTeam + // Event *database.DBEvent + // Members *[]database.DBTeamMemberInfo // array(slice) of a struct + // } + + fmt.Println("print (*teamInfo.Members)[0].DBUser.Id = ", convert.UUIDToString((*teamInfo.Members)[0].DBUser.Id)) + // ^ dereferences pointer to get to actual slice of database.DBTeamMemberInfo, + // then accesses the first element in dereferenced slice + // verifies team is public var isPublic string = teamInfo.Team.Visibility if isPublic == "private" { ctx.Status(http.StatusForbidden) + return + } + _, err = database.AddTeamMember(uuidUserId, uuidTeamId, "member") + if err != nil { + ctx.JSON(http.StatusConflict, err) return } - database.AddTeamMember(uuidUserId, uuidTeamId, "member") + strTeamId := convert.UUIDToString(teamInfo.Team.Id) + ctx.JSON(http.StatusOK, strTeamId) } func (server *Server) MemberInvite(ctx *gin.Context) { diff --git a/html/src/lib/pages/TeamsBrowse.svelte b/html/src/lib/pages/TeamsBrowse.svelte index d48f57d..8da2367 100644 --- a/html/src/lib/pages/TeamsBrowse.svelte +++ b/html/src/lib/pages/TeamsBrowse.svelte @@ -67,7 +67,11 @@ $: publicTeams = allTeams.filter(t => t.Visibility == "public" ) // only shows if user isnt a member {#if $loggedInStore} - + {:else} From 0db23eda57fc0eb209a3dadbaa815035fdfb5a29 Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Wed, 4 Sep 2024 23:50:43 -0700 Subject: [PATCH 18/69] added: Toaster to create team and join team --- html/src/App.svelte | 6 +-- html/src/lib/components/CreateTeamForm.svelte | 43 ++++++++--------- html/src/lib/pages/MyTeam.svelte | 14 +++--- html/src/lib/pages/TeamsBrowse.svelte | 46 ++++++++++++++----- 4 files changed, 65 insertions(+), 44 deletions(-) diff --git a/html/src/App.svelte b/html/src/App.svelte index 308f017..88cef65 100644 --- a/html/src/App.svelte +++ b/html/src/App.svelte @@ -1,12 +1,10 @@ - - -
+
\ No newline at end of file diff --git a/html/src/lib/components/CreateTeamForm.svelte b/html/src/lib/components/CreateTeamForm.svelte index 4e07535..99ce54a 100644 --- a/html/src/lib/components/CreateTeamForm.svelte +++ b/html/src/lib/components/CreateTeamForm.svelte @@ -1,22 +1,19 @@ @@ -94,7 +95,7 @@ function saveForm() {
Public Team - (If you want your team to be searchable) + (If you want your team to be searchable.)
Private Team diff --git a/html/src/lib/pages/MyTeam.svelte b/html/src/lib/pages/MyTeam.svelte index 33582c9..040a4a0 100644 --- a/html/src/lib/pages/MyTeam.svelte +++ b/html/src/lib/pages/MyTeam.svelte @@ -1,15 +1,12 @@

Browse All Teams

- {#if loading}
Loading...
{:else if error} @@ -68,10 +87,14 @@ $: publicTeams = allTeams.filter(t => t.Visibility == "public" ) // only shows if user isnt a member {#if $loggedInStore} + .then((resTeamId) => { + if (isValidTeamId(resTeamId)) { + toast.success("Successfully joined team") + window.location.href = '/#/team/' + resTeamId; // redirect to team page + } else { + toast.error("Error:" + resTeamId.Message) + } + })}>Join {:else} @@ -87,6 +110,5 @@ $: publicTeams = allTeams.filter(t => t.Visibility == "public" ) {/if}
-
\ No newline at end of file From 48b40ff4f830f749b384b5891a1c08a730f16f6a Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Wed, 4 Sep 2024 23:58:35 -0700 Subject: [PATCH 19/69] cleaned up comments, added package-lock.json --- .gitignore | 2 + backend/app/server/oauth.go | 1 + backend/app/server/teams.go | 5 +- html/src/lib/pages/UserTeams.svelte | 2 +- html/src/lib/services/services.ts | 1 - package-lock.json | 250 ++++++++++++++++++++++++++++ package.json | 5 + 7 files changed, 261 insertions(+), 5 deletions(-) create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.gitignore b/.gitignore index 68da839..2e564db 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ .idea/ *.iml +node_modules + # VSCODE .vscode/ diff --git a/backend/app/server/oauth.go b/backend/app/server/oauth.go index 6522cbf..9545169 100644 --- a/backend/app/server/oauth.go +++ b/backend/app/server/oauth.go @@ -122,6 +122,7 @@ func (server *Server) GetOAuthCallback(ctx *gin.Context) { session.Save() var stateData *StateData + // give the stateI a defined type/struct of stateData instead of interface{}/any: stateData, ok := stateI.(*StateData) if !ok { ctx.String(400, "Bad Request: Invalid State Data.") diff --git a/backend/app/server/teams.go b/backend/app/server/teams.go index d933c17..27e35a5 100644 --- a/backend/app/server/teams.go +++ b/backend/app/server/teams.go @@ -242,7 +242,6 @@ func (server *Server) CreateTeam(ctx *gin.Context) { _, err = database.AddTeamMember(convert.StringToUUID(strUserId), teamUUID, "owner") if err == nil { - fmt.Println("Successfully added team member") ctx.JSON(http.StatusCreated, map[string]pgtype.UUID{ "id": teamUUID, }) @@ -291,7 +290,7 @@ func (server *Server) UpdateTeamMembers(ctx *gin.Context) { func (server *Server) MemberJoin(ctx *gin.Context) { session := sessions.Default(ctx) userId := session.Get("userId") - fmt.Println("userID = ", userId) + //fmt.Println("userID = ", userId) if userId == nil { ctx.Status(http.StatusUnauthorized) @@ -321,7 +320,7 @@ func (server *Server) MemberJoin(ctx *gin.Context) { // Members *[]database.DBTeamMemberInfo // array(slice) of a struct // } - fmt.Println("print (*teamInfo.Members)[0].DBUser.Id = ", convert.UUIDToString((*teamInfo.Members)[0].DBUser.Id)) + // fmt.Println("print (*teamInfo.Members)[0].DBUser.Id = ", convert.UUIDToString((*teamInfo.Members)[0].DBUser.Id)) // ^ dereferences pointer to get to actual slice of database.DBTeamMemberInfo, // then accesses the first element in dereferenced slice diff --git a/html/src/lib/pages/UserTeams.svelte b/html/src/lib/pages/UserTeams.svelte index ab4d17b..a3433ba 100644 --- a/html/src/lib/pages/UserTeams.svelte +++ b/html/src/lib/pages/UserTeams.svelte @@ -53,7 +53,7 @@ If not team owner, cannot view-->
{:else if userTeams.length === 0}
- Looks like you don't have any teams! Go to browse teams to join one! + Looks like you don't have any teams. Go to browse teams to join one or create your own team!
{:else} {#each userTeams as userTeam} diff --git a/html/src/lib/services/services.ts b/html/src/lib/services/services.ts index 4dfe208..5ec084a 100644 --- a/html/src/lib/services/services.ts +++ b/html/src/lib/services/services.ts @@ -151,7 +151,6 @@ export async function joinTeam(teamId: string, inviteCode: string) { } - // write a joinPublicTeam function that accepts team Id. // server: check if team is public. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a4d9579 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,250 @@ +{ + "name": "Codejam2024", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "svelte-french-toast": "^1.2.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "peer": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "peer": true + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "peer": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/code-red": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz", + "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==", + "peer": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.1", + "acorn": "^8.10.0", + "estree-walker": "^3.0.3", + "periscopic": "^3.1.0" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "peer": true, + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/is-reference": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", + "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", + "peer": true, + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "peer": true + }, + "node_modules/magic-string": { + "version": "0.30.11", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", + "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==", + "peer": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "peer": true + }, + "node_modules/periscopic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", + "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==", + "peer": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^3.0.0", + "is-reference": "^3.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/svelte": { + "version": "4.2.19", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.19.tgz", + "integrity": "sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/estree": "^1.0.1", + "acorn": "^8.9.0", + "aria-query": "^5.3.0", + "axobject-query": "^4.0.0", + "code-red": "^1.0.3", + "css-tree": "^2.3.1", + "estree-walker": "^3.0.3", + "is-reference": "^3.0.1", + "locate-character": "^3.0.0", + "magic-string": "^0.30.4", + "periscopic": "^3.1.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/svelte-french-toast": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/svelte-french-toast/-/svelte-french-toast-1.2.0.tgz", + "integrity": "sha512-5PW+6RFX3xQPbR44CngYAP1Sd9oCq9P2FOox4FZffzJuZI2mHOB7q5gJBVnOiLF5y3moVGZ7u2bYt7+yPAgcEQ==", + "dependencies": { + "svelte-writable-derived": "^3.1.0" + }, + "peerDependencies": { + "svelte": "^3.57.0 || ^4.0.0" + } + }, + "node_modules/svelte-writable-derived": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/svelte-writable-derived/-/svelte-writable-derived-3.1.1.tgz", + "integrity": "sha512-w4LR6/bYZEuCs7SGr+M54oipk/UQKtiMadyOhW0PTwAtJ/Ai12QS77sLngEcfBx2q4H8ZBQucc9ktSA5sUGZWw==", + "funding": { + "url": "https://ko-fi.com/pixievoltno1" + }, + "peerDependencies": { + "svelte": "^3.2.1 || ^4.0.0-next.1 || ^5.0.0-next.94" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6b62c9f --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "svelte-french-toast": "^1.2.0" + } +} From 7afef69a5c5a984375204e81af44773f4bb62ae3 Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Thu, 5 Sep 2024 00:00:27 -0700 Subject: [PATCH 20/69] joinPublicTeam now returns teamId an error --- html/src/lib/services/services.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/html/src/lib/services/services.ts b/html/src/lib/services/services.ts index 5ec084a..938f13c 100644 --- a/html/src/lib/services/services.ts +++ b/html/src/lib/services/services.ts @@ -153,14 +153,14 @@ export async function joinTeam(teamId: string, inviteCode: string) { // write a joinPublicTeam function that accepts team Id. // server: check if team is public. - export async function joinPublicTeam(teamId: string) { - return await fetch(baseApiUrl + "/team/join", + const response = await fetch(baseApiUrl + "/team/join", { method: "POST", body: JSON.stringify({ teamId }) } ) + return response.json() } // Always call at startup to get the initial states From 10a9793c998191c1ae72b56334d15e681d57bd79 Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Thu, 5 Sep 2024 00:18:44 -0700 Subject: [PATCH 21/69] team visibility set to public by default via CodeJamTeam --- html/src/lib/components/CreateTeamForm.svelte | 1 - html/src/lib/models/team.ts | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/html/src/lib/components/CreateTeamForm.svelte b/html/src/lib/components/CreateTeamForm.svelte index 99ce54a..2f22a58 100644 --- a/html/src/lib/components/CreateTeamForm.svelte +++ b/html/src/lib/components/CreateTeamForm.svelte @@ -39,7 +39,6 @@ let textareaprops = { }; let formData: CodeJamTeam | null = new CodeJamTeam(); -formData.Visibility = 'public'; let isSaving: boolean = false; let teamCreated: boolean = false; diff --git a/html/src/lib/models/team.ts b/html/src/lib/models/team.ts index 59fbbc6..e4eff88 100644 --- a/html/src/lib/models/team.ts +++ b/html/src/lib/models/team.ts @@ -10,16 +10,18 @@ class CodeJamTeam { Availability: string; Description: string; InviteCode: string; + TeamMembers: TeamMember; constructor() { this.Id = ''; this.EventId = ''; this.Name = ''; - this.Visibility = ''; + this.Visibility = 'public'; this.Technologies = ''; this.Availability = ''; this.Description = ''; this.InviteCode = ''; + this.TeamMembers = new TeamMember(); } } From 0c3296c08757a1a6b8c6e1839a8b5fd27e31c38f Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Wed, 25 Sep 2024 22:24:34 -0700 Subject: [PATCH 22/69] add: avatars to browse teams view --- .../migrations/00000011_avatars.up.sql | 2 + backend/app/database/teams.go | 165 +++++++++++++++++- html/src/lib/models/TeamMember.ts | 6 +- html/src/lib/pages/TeamsBrowse.svelte | 26 ++- html/src/lib/services/services.ts | 2 + 5 files changed, 189 insertions(+), 12 deletions(-) create mode 100644 backend/app/database/migrations/00000011_avatars.up.sql diff --git a/backend/app/database/migrations/00000011_avatars.up.sql b/backend/app/database/migrations/00000011_avatars.up.sql new file mode 100644 index 0000000..35f3b8a --- /dev/null +++ b/backend/app/database/migrations/00000011_avatars.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE users +RENAME COLUMN avatar_url TO avatar_id; \ No newline at end of file diff --git a/backend/app/database/teams.go b/backend/app/database/teams.go index 09e0e08..a921ac0 100644 --- a/backend/app/database/teams.go +++ b/backend/app/database/teams.go @@ -14,7 +14,7 @@ type DBTeam struct { Technologies string `db:"technologies"` Availability string `db:"availability"` Description string `db:"description"` - CreatedOn pgtype.Timestamp `db:"created_on" json:"createdOn-hidden"` + CreatedOn pgtype.Timestamp `db:"created_on"` InviteCode string `db:"invite_code"` } @@ -32,12 +32,27 @@ type DBTeamMemberInfo struct { // For team_member table. type DBTeamMember struct { - Id pgtype.UUID `db:"id"` TeamId pgtype.UUID `db:"team_id"` UserId pgtype.UUID `db:"user_id"` TeamRole string `db:"team_role"` - CreatedOn pgtype.Timestamp `db:"created_on" json:"createdOn-hidden"` + CreatedOn pgtype.Timestamp `db:"created_on"` } +//json:"createdOn-hidden" + +// type DBTeamAndMember struct { +// DBTeam // table teams +// DBTeamMember DBTeamMember // table team_members +// DisplayName string `db:"display_name"` // table_user +// } + +// type TeamAndMember struct { +// DBTeam +// TeamMembers []TeamMember +// } +// type TeamMember struct { +// DBTeamMember +// DisplayName string +// } type DBUserTeams struct { DBTeam @@ -60,7 +75,7 @@ func CreateTeam(team DBTeam) (pgtype.UUID, error) { return team.Id, err } -// stepp 5: used to construct the GetTeamResponse struct +// stepp 5: used to construct the GetTeamResponse struct in server/teams.go func GetTeam(teamId pgtype.UUID) (DBTeam, error) { team, err := GetRow[DBTeam]( `SELECT @@ -109,11 +124,147 @@ func GetTeamByInvite(inviteCode string) (DBTeam, error) { return team, nil } -func GetTeams() ([]DBTeam, error) { - result, err := GetRows[DBTeam](`SELECT * FROM teams`) - return result, err +type DBTeamAndMember struct { + Id pgtype.UUID `db:"id"` + EventId pgtype.UUID `db:"event_id"` + Name string `db:"name"` + Visibility string `db:"visibility"` + Timezone string `db:"timezone"` + Technologies string `db:"technologies"` + Availability string `db:"availability"` + Description string `db:"description"` + InviteCode string `db:"invite_code"` + TeamId pgtype.UUID `db:"team_id"` + UserId pgtype.UUID `db:"user_id"` + TeamRole string `db:"team_role"` + DisplayName string `db:"display_name"` + AvatarId string `db:"avatar_id"` + ServiceUserId string `db:"service_user_id` +} + +type UITeam struct { + Id pgtype.UUID `db:"id"` + EventId pgtype.UUID `db:"event_id"` + Name string `db:"name"` + Visibility string `db:"visibility"` + Timezone string `db:"timezone"` + Technologies string `db:"technologies"` + Availability string `db:"availability"` + Description string `db:"description"` + InviteCode string `db:"invite_code"` +} + +type UITeamMember struct { + TeamId pgtype.UUID `db:"team_id"` + UserId pgtype.UUID `db:"user_id"` + TeamRole string `db:"team_role"` +} + +type TeamMember struct { + UITeamMember + DisplayName string `db:"display_name"` + AvatarId string `db:"avatar_id"` + ServiceUserId string `db:"service_user_id"` } +type TeamAndMember struct { + UITeam + TeamMembers []TeamMember +} + +func MapToTeamAndMember(data []DBTeamAndMember) []TeamAndMember{ + // instantiates array to store output, mapped by team id (uuid) for key + teamMap := make(map[pgtype.UUID]*TeamAndMember) + for _, item := range data { + // Check if team already exists in the map. This map loopkup returns: + // 1) value associated with the key if it exsits + // 2) boolean indicating whether key was found in the map + team, ok := teamMap[item.TeamId] + if !ok { + // Create a new team + team = &TeamAndMember{ + UITeam: UITeam { + Id: item.TeamId, + EventId: item.EventId, + Name: item.Name, + Visibility: item.Visibility, + Timezone: item.Timezone, + Technologies: item.Technologies, + Availability: item.Availability, + Description: item.Description, + InviteCode: item.InviteCode, + }, + TeamMembers: []TeamMember{}, + } + teamMap[item.TeamId] = team + } + // Add team member to TeamMembers slice + member := TeamMember{ + UITeamMember: UITeamMember{ + TeamId: item.TeamId, + UserId: item.UserId, + TeamRole: item.TeamRole, + }, + DisplayName: item.DisplayName, + AvatarId: item.AvatarId, + ServiceUserId: item.ServiceUserId, + } + team.TeamMembers = append(team.TeamMembers, member) + } + // Convert map back to slice + var result []TeamAndMember + for _, team := range teamMap { + result = append(result, *team) + } + fmt.Println(result) + return result +} + +func GetTeams() (*[]TeamAndMember, error){ + // TODO: MAKE THE FLAT STRUCTURE HIERARCHICAL + teamAndMember, err := GetRows[DBTeamAndMember]( // returns { team 1: { userA: {display_name: "momo"}}, team 1...} + `SELECT + t.id, + t.event_id, + t.name, + t.visibility, + t.timezone, + t.technologies, + t.availability, + t.description, + t.invite_code, + u.display_name, + u.avatar_id, + u.service_user_id, + tm.team_id, + tm.user_id, + tm.team_role + FROM teams t + INNER JOIN team_members tm ON (tm.team_id = t.id) + INNER JOIN users u ON (u.id = tm.user_id) + ORDER BY t.id + `, + ) + if err != nil { + return nil, err + } + for _, t := range teamAndMember { + fmt.Printf("%v\n", t) + } + UITeamAndMember := MapToTeamAndMember(teamAndMember) + + //fmt.Printf("%T\n", t) + // var TeamMember TeamMember + // var DisplayName string + // var TeamAndMember + // var TeamAndMembers []TeamAndMember + + // // loop through teamAndMembers, if + + return &UITeamAndMember, err +} + + func GetUserTeams(userId pgtype.UUID) ([]DBUserTeams, error) { result, err := GetRows[DBUserTeams]( `SELECT diff --git a/html/src/lib/models/TeamMember.ts b/html/src/lib/models/TeamMember.ts index 41afbb9..1a61108 100644 --- a/html/src/lib/models/TeamMember.ts +++ b/html/src/lib/models/TeamMember.ts @@ -3,15 +3,19 @@ class TeamMember { UserId: string; TeamRole: string; DisplayName: string; + AvatarId: string; + ServiceUserID: string; //TODO add Array constructor() { + this.TeamId = ''; this.UserId = ''; this.TeamRole = ''; this.DisplayName = ''; - + this.AvatarId = ''; + this.ServiceUserID = ''; } } diff --git a/html/src/lib/pages/TeamsBrowse.svelte b/html/src/lib/pages/TeamsBrowse.svelte index 2a2f9c1..490db9b 100644 --- a/html/src/lib/pages/TeamsBrowse.svelte +++ b/html/src/lib/pages/TeamsBrowse.svelte @@ -1,18 +1,20 @@ diff --git a/html/src/lib/pages/TeamsBrowse.svelte b/html/src/lib/pages/TeamsBrowse.svelte index 38e346c..1744378 100644 --- a/html/src/lib/pages/TeamsBrowse.svelte +++ b/html/src/lib/pages/TeamsBrowse.svelte @@ -74,14 +74,13 @@ onMount(() => { loadAvatarUrls(); }); - $: allTeams, loadAvatarUrls(); $: publicTeams = allTeams.filter(t => t.Visibility == "public" ) function isValidTeamId(resTeamId: string | ErrorResponse): resTeamId is string { // Check if resTeamId is an object with a 'Severity' property indicating an error if (typeof resTeamId === 'object' && 'Severity' in resTeamId && resTeamId.Severity === 'ERROR') { - return false; // Not a valid Team ID + return false; // Not a valid Team ID } return true; // Valid Team ID } @@ -91,16 +90,16 @@ function isValidTeamId(resTeamId: string | ErrorResponse): resTeamId is string {

Browse All Teams

- {#if loading} -
Loading...
- {:else if error} -
{error}
+ {#if loading} +
Loading...
+ {:else if error} +
{error}
{:else} {#each publicTeams as Team} -
-

Team {Team.Name}

+
+

Team {Team.Name}

Members: @@ -114,19 +113,19 @@ function isValidTeamId(resTeamId: string | ErrorResponse): resTeamId is string { - + + + Visibility: {Team.Visibility} + + + Technologies: {Team.Technologies} + - Visibility: {Team.Visibility} - - - Technologies: {Team.Technologies} - - - Availability: {Team.Availability} - - - Description: {Team.Description} - + Availability: {Team.Availability} + + + Description: {Team.Description} + // only shows if user isnt a member @@ -146,14 +145,14 @@ function isValidTeamId(resTeamId: string | ErrorResponse): resTeamId is string {

Login with Discord to join a team!

{/if} - + {:else}
Looks like you don't have any teams! Go to browse teams to join one!
{/each} - - {/if} - + + {/if} + - \ No newline at end of file + \ No newline at end of file From 2ea3becd2f6d7edd16ffdd87098b253f2231edb3 Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Sun, 29 Sep 2024 00:26:28 -0700 Subject: [PATCH 25/69] fix: navbar pfp works now, both gif and png --- html/src/lib/components/UserAvatar.svelte | 10 +++++++--- html/src/lib/models/user.ts | 7 +++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/html/src/lib/components/UserAvatar.svelte b/html/src/lib/components/UserAvatar.svelte index 543b06b..b9d90dd 100644 --- a/html/src/lib/components/UserAvatar.svelte +++ b/html/src/lib/components/UserAvatar.svelte @@ -8,8 +8,12 @@ -{#if user && user.AvatarUrl} - +{#if user && user.AvatarId} + {#if user.AvatarId && user.AvatarId.startsWith("a_")} + + {:else} + + {/if} {:else} - + {/if} \ No newline at end of file diff --git a/html/src/lib/models/user.ts b/html/src/lib/models/user.ts index c9235ed..9a3cbe1 100644 --- a/html/src/lib/models/user.ts +++ b/html/src/lib/models/user.ts @@ -1,10 +1,13 @@ export interface User { - UserId: string; + Id: string; DisplayName: string; Role: string; ServiceName : string; ServiceUserId: string; - AvatarUrl: string; + ServiceUserName: string; + AvatarId: string; + AccountStatus: string; + LockDisplayName: boolean; } export interface ActiveUser { From 67ceb6c7ea5f12e1c7dd424e9d647edaef47cc46 Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Sun, 29 Sep 2024 00:49:42 -0700 Subject: [PATCH 26/69] fix: getUserTeams query includes avatar_id --- backend/app/database/teams.go | 1 + backend/app/server/teams.go | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/app/database/teams.go b/backend/app/database/teams.go index 209084b..e42778e 100644 --- a/backend/app/database/teams.go +++ b/backend/app/database/teams.go @@ -269,6 +269,7 @@ func GetUserTeams(userId pgtype.UUID) ([]DBUserTeams, error) { result, err := GetRows[DBUserTeams]( `SELECT u.display_name, + u.avatar_id, t.*, tm.team_role FROM users u diff --git a/backend/app/server/teams.go b/backend/app/server/teams.go index 9832860..38fdd0d 100644 --- a/backend/app/server/teams.go +++ b/backend/app/server/teams.go @@ -84,11 +84,12 @@ func (server *Server) GetAllTeams(ctx *gin.Context) { func (server *Server) GetUserTeams(ctx *gin.Context) { session := sessions.Default(ctx) userId := session.Get("userId") + if userId == nil { ctx.Status(http.StatusNotFound) return } - strUserId := userId.(string) + strUserId := userId.(string) teams, err := database.GetUserTeams(convert.StringToUUID(strUserId)) if err != nil { From 975495c17736af920c4356af584610919f417e60 Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Wed, 2 Oct 2024 19:14:26 -0700 Subject: [PATCH 27/69] fix: browseTeams disables join button if user already joined team --- html/src/lib/models/user.ts | 3 +- html/src/lib/pages/TeamsBrowse.svelte | 64 +++++++++++++++++---------- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/html/src/lib/models/user.ts b/html/src/lib/models/user.ts index 9a3cbe1..3af3c5b 100644 --- a/html/src/lib/models/user.ts +++ b/html/src/lib/models/user.ts @@ -12,5 +12,6 @@ export interface User { export interface ActiveUser { user : User | null; - loggedIn : boolean; + loggedIn : boolean; + userId : User["Id"] | null; } \ No newline at end of file diff --git a/html/src/lib/pages/TeamsBrowse.svelte b/html/src/lib/pages/TeamsBrowse.svelte index 1744378..7118872 100644 --- a/html/src/lib/pages/TeamsBrowse.svelte +++ b/html/src/lib/pages/TeamsBrowse.svelte @@ -8,8 +8,9 @@ import Page from "../components/Page.svelte"; import CodeJamTeam from '../models/team'; import TeamMember from '../models/TeamMember'; import { getTeams, joinPublicTeam } from "../services/services"; -import { activeUserStore, loggedInStore } from '../stores/stores'; +import { activeUserStore, loggedInStore, userStore } from '../stores/stores'; import { onMount } from "svelte"; + import { labelClass } from "flowbite-svelte/Radio.svelte"; export const params: Record = {}; @@ -69,9 +70,21 @@ async function loadAvatarUrls() { await Promise.all(promises); } +let currUserId: string | undefined= $userStore?.Id + +function isUserInTeam(teamMembers: TeamMember[]): boolean { + for (let teamMember of teamMembers) { + if (teamMember.UserId == currUserId) { + return true + } + } + return false +} + onMount(() => { loadData(); loadAvatarUrls(); + }); $: allTeams, loadAvatarUrls(); @@ -101,18 +114,15 @@ function isValidTeamId(resTeamId: string | ErrorResponse): resTeamId is string {

Team {Team.Name}

+ Members: - -
+
{#each Team.TeamMembers as Member} {/each}
- - - Visibility: {Team.Visibility} @@ -126,24 +136,30 @@ function isValidTeamId(resTeamId: string | ErrorResponse): resTeamId is string { Description: {Team.Description} - - // only shows if user isnt a member - - {#if $loggedInStore} - - {:else} - - -

Login with Discord to join a team!

-
+ + + {#if !$loggedInStore} + NOT logged in, Log Into Discord button + + +

Login with Discord to join a team!

+
+ {:else if $loggedInStore} + {#if !isUserInTeam(Team.TeamMembers)} + Show join button option + + {:else if isUserInTeam(Team.TeamMembers)} + + {/if} {/if} {:else} From 82bf7238beb59555d6b23516f6b3b41a596c2e34 Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Fri, 4 Oct 2024 15:56:11 -0700 Subject: [PATCH 28/69] add: browse/teams shows owner --- backend/app/database/teams.go | 44 ++++++++++++++++----------- backend/app/server/teams.go | 1 - html/src/lib/pages/TeamsBrowse.svelte | 23 +++++++++----- 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/backend/app/database/teams.go b/backend/app/database/teams.go index e42778e..2e24539 100644 --- a/backend/app/database/teams.go +++ b/backend/app/database/teams.go @@ -252,37 +252,47 @@ func GetTeams() (*[]TeamAndMember, error){ fmt.Printf("%v\n", t) } UITeamAndMember := MapToTeamAndMember(teamAndMember) - - //fmt.Printf("%T\n", t) - // var TeamMember TeamMember - // var DisplayName string - // var TeamAndMember - // var TeamAndMembers []TeamAndMember - - // // loop through teamAndMembers, if return &UITeamAndMember, err } +type UserTeamAndMember struct { + +} -func GetUserTeams(userId pgtype.UUID) ([]DBUserTeams, error) { - result, err := GetRows[DBUserTeams]( +func GetUserTeams(userId pgtype.UUID) (*[]TeamAndMember, error) { + result, err := GetRows[DBTeamAndMember]( `SELECT + t.id, + t.event_id, + t.name, + t.visibility, + t.timezone, + t.technologies, + t.availability, + t.description, + t.invite_code, u.display_name, u.avatar_id, - t.*, - tm.team_role - FROM users u - INNER JOIN team_members tm ON u.id = tm.user_id - INNER JOIN teams t ON tm.team_id = t.id - WHERE u.id = $1`, + u.service_user_id, + tm.team_id, + tm.user_id, + tm.team_role, + FROM teams t + INNER JOIN team_members tm ON (tm.team_id = t.id) + INNER JOIN users u ON (u.id = tm.user_id) + WHERE u.id = $1`, userId) if err != nil { fmt.Println("that didn't work: database.GetUserTeams") return nil, err } + for _, t := range result { + fmt.Printf("%v\n", t) + } + UITeamAndMember := MapToTeamAndMember(result) - return result, err // Try look at the table + return &UITeamAndMember, err // Try look at the table } func UpdateTeam(team DBTeam) (DBTeam, error) { diff --git a/backend/app/server/teams.go b/backend/app/server/teams.go index 38fdd0d..18c4a01 100644 --- a/backend/app/server/teams.go +++ b/backend/app/server/teams.go @@ -71,7 +71,6 @@ func (server *Server) signupsAllowed(eventId string) bool { func (server *Server) GetAllTeams(ctx *gin.Context) { // what if there's no session => no user id teams, err := database.GetTeams() - fmt.Println(teams) if err == nil { ctx.JSON(http.StatusOK, teams) } else { diff --git a/html/src/lib/pages/TeamsBrowse.svelte b/html/src/lib/pages/TeamsBrowse.svelte index 7118872..c0a9d58 100644 --- a/html/src/lib/pages/TeamsBrowse.svelte +++ b/html/src/lib/pages/TeamsBrowse.svelte @@ -10,12 +10,9 @@ import TeamMember from '../models/TeamMember'; import { getTeams, joinPublicTeam } from "../services/services"; import { activeUserStore, loggedInStore, userStore } from '../stores/stores'; import { onMount } from "svelte"; - import { labelClass } from "flowbite-svelte/Radio.svelte"; export const params: Record = {}; -//activeUserStore.set({user: responseData.Data, loggedIn: true}); - let teamData: CodeJamTeam | null = null; let loading: boolean = true; let error: string | null = null; @@ -26,9 +23,9 @@ let avatarUrls: Record = {}; interface ErrorResponse { Severity: string; + Detail?: string; Code: string; Message: string; - Detail?: string; Hint?: string; Position?: number; InternalPosition?: number; @@ -40,7 +37,7 @@ interface ErrorResponse { async function loadData() { try { const response = await getTeams(); - console.log("response type: ", typeof response) + allTeams = await response.json(); // Array of teams... console.log("allTeams: ", allTeams) } catch (err) { @@ -81,10 +78,18 @@ function isUserInTeam(teamMembers: TeamMember[]): boolean { return false } +function getTeamOwner(teamMembers: TeamMember[]): string { + let owner = teamMembers.find(member => member.TeamRole === "owner"); + if (owner) { + return owner.DisplayName + } else { + return "No owner found." + } +} + onMount(() => { loadData(); loadAvatarUrls(); - }); $: allTeams, loadAvatarUrls(); @@ -114,7 +119,9 @@ function isValidTeamId(resTeamId: string | ErrorResponse): resTeamId is string {

Team {Team.Name}

- + + Owner: {getTeamOwner(Team.TeamMembers)} + Members:
@@ -134,7 +141,7 @@ function isValidTeamId(resTeamId: string | ErrorResponse): resTeamId is string { Availability: {Team.Availability} - Description: {Team.Description} + Description: {Team.Description} From 0f3a3c2f84135846f3b4340be66612f31c02b216 Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Fri, 4 Oct 2024 15:56:33 -0700 Subject: [PATCH 29/69] add: users teams show avatar --- html/src/lib/pages/UserTeams.svelte | 60 ++++++++++++++++++++++++----- 1 file changed, 51 insertions(+), 9 deletions(-) diff --git a/html/src/lib/pages/UserTeams.svelte b/html/src/lib/pages/UserTeams.svelte index a3433ba..be313cb 100644 --- a/html/src/lib/pages/UserTeams.svelte +++ b/html/src/lib/pages/UserTeams.svelte @@ -1,25 +1,31 @@ - @@ -61,6 +93,16 @@ If not team owner, cannot view-->

Team {userTeam.Name}

+ + + Members: +
+ {#each userTeam.TeamMembers as Member} + + {/each} +
+
+ Visibility: {userTeam.Visibility} From d273eb548872a1f503483a5023bad36ce4d281f5 Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Fri, 4 Oct 2024 16:03:45 -0700 Subject: [PATCH 30/69] fix: getUserTeams query bug --- backend/app/database/teams.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app/database/teams.go b/backend/app/database/teams.go index 2e24539..07592be 100644 --- a/backend/app/database/teams.go +++ b/backend/app/database/teams.go @@ -277,7 +277,7 @@ func GetUserTeams(userId pgtype.UUID) (*[]TeamAndMember, error) { u.service_user_id, tm.team_id, tm.user_id, - tm.team_role, + tm.team_role FROM teams t INNER JOIN team_members tm ON (tm.team_id = t.id) INNER JOIN users u ON (u.id = tm.user_id) From 63445750b29e1801973519ad0591495c04fa1834 Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Fri, 4 Oct 2024 16:16:48 -0700 Subject: [PATCH 31/69] add: userTeams edit team button if owner --- html/src/lib/pages/UserTeams.svelte | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/html/src/lib/pages/UserTeams.svelte b/html/src/lib/pages/UserTeams.svelte index be313cb..66991f8 100644 --- a/html/src/lib/pages/UserTeams.svelte +++ b/html/src/lib/pages/UserTeams.svelte @@ -1,12 +1,13 @@ + + + + Home + Team Options + Edit Team + + +
+ Edit team form here: + {#if loading} + {console.log(formData, "line 107")} +
Loading...
+ {:else if error} +
{error}
+ {:else if formData !== null} + +
+
+ + + +
+ Public Team + (If you want your team to be searchable.) +
+
+ Private Team + (Your team will be invite only) +
+ + + + + + + + + + + + + + + +
{/if} diff --git a/html/src/lib/services/services.ts b/html/src/lib/services/services.ts index 207dce7..fe3ddda 100644 --- a/html/src/lib/services/services.ts +++ b/html/src/lib/services/services.ts @@ -6,7 +6,7 @@ import { } from "../stores/stores"; import CodeJamEvent from "../models/event"; import CodeJamTeam from "../models/team"; -import type {ActiveUser, User} from "../models/user"; +import type { ActiveUser, User } from "../models/user"; // This shouldn't ever need to be set since dev and prod environments will just use relative endpoints export let baseApiUrl: string = ""; @@ -32,12 +32,12 @@ export async function getUser() { .then((response) => { if (response.status === 401) { userStore.set(null); - activeUserStore.set({user: null, loggedIn: false}); + activeUserStore.set({ user: null, loggedIn: false }); } else { response.json() .then((data) => { userStore.set(data); - activeUserStore.set({user: data as User, loggedIn: true}) + activeUserStore.set({ user: data as User, loggedIn: true }) }) .catch((err) => { console.error("error deserializing user", err); @@ -58,7 +58,7 @@ export async function logout() { return fetch(baseApiUrl + "/user/logout") .then(() => { userStore.set(null); - activeUserStore.set({user: null, loggedIn: false}); + activeUserStore.set({ user: null, loggedIn: false }); }) .catch((err) => { console.error("Logout error", err); @@ -125,7 +125,7 @@ export async function getTeams() { } -export async function getUserTeams(){ +export async function getUserTeams() { return fetch(baseApiUrl + "/teams"); } @@ -179,5 +179,4 @@ export async function removeMemberFromTeam(teamId: string, memberId: string) { async function initialLoad() { await Promise.all([getUser(), getActiveEvent(), getEventStatuses()]); } - initialLoad(); \ No newline at end of file From fbd65cb98f1fb201ae834709a495b847e7e23254 Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Mon, 28 Oct 2024 15:16:48 -0700 Subject: [PATCH 57/69] add: owner info to myteam page --- html/src/lib/pages/MyTeam.svelte | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/html/src/lib/pages/MyTeam.svelte b/html/src/lib/pages/MyTeam.svelte index 80e6b86..fcf0a91 100644 --- a/html/src/lib/pages/MyTeam.svelte +++ b/html/src/lib/pages/MyTeam.svelte @@ -25,6 +25,16 @@ let error: string | null = null; let teamCreated: boolean = false; // Reactivate variable to track if team was just created + // to display the owner the team + function getTeamOwner(teamMembers: TeamMember[]): string { + let owner = teamMembers.find((member) => member.TeamRole === 'owner'); + if (owner) { + return owner.DisplayName; + } else { + return 'No owner found.'; + } + } + async function loadData(id: string) { try { const response = await getTeamById(id); @@ -97,6 +107,9 @@

Team {teamData.Name}

(edit)
+ + Owner: {getTeamOwner(teamMembers)} + Team Members: {#each teamMembers as member} From 0d04cd26c6e99b2ecfa55d62413006e2078c7b2c Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Mon, 28 Oct 2024 15:24:18 -0700 Subject: [PATCH 58/69] add: only show edit btn when loggedinuser is owner --- html/src/lib/pages/MyTeam.svelte | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/html/src/lib/pages/MyTeam.svelte b/html/src/lib/pages/MyTeam.svelte index fcf0a91..8d7bb43 100644 --- a/html/src/lib/pages/MyTeam.svelte +++ b/html/src/lib/pages/MyTeam.svelte @@ -7,7 +7,7 @@ import CodeJamTeam from '../models/team'; import { getTeamById } from '../services/services'; import toast from 'svelte-french-toast'; - + import {userStore} from "./../stores/stores"; interface Params { // This is what the params is because you pass an id with type of string. id: string; @@ -105,7 +105,10 @@

Team {teamData.Name}

+ {#if $userStore?.DisplayName == getTeamOwner(teamMembers)} + (edit) + {/if}
Owner: {getTeamOwner(teamMembers)} From c5adabc9a4dcc40a689941fa77b94e983aad3d39 Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Mon, 28 Oct 2024 15:32:12 -0700 Subject: [PATCH 59/69] fix: url of invite link --- html/src/lib/pages/MyTeam.svelte | 2 +- html/src/lib/pages/TeamEdit.svelte | 32 ++++++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/html/src/lib/pages/MyTeam.svelte b/html/src/lib/pages/MyTeam.svelte index 8d7bb43..0ac30ba 100644 --- a/html/src/lib/pages/MyTeam.svelte +++ b/html/src/lib/pages/MyTeam.svelte @@ -67,7 +67,7 @@ let url: string = ''; $: if (teamData?.Id) { - url = `localhost:8080/#/team/edit/${teamData.Id}`; + url = `localhost:8080/#/team/invite/${teamData.InviteCode}`; } function copyToClipboard(): void { diff --git a/html/src/lib/pages/TeamEdit.svelte b/html/src/lib/pages/TeamEdit.svelte index 7b842d1..e7fe43b 100644 --- a/html/src/lib/pages/TeamEdit.svelte +++ b/html/src/lib/pages/TeamEdit.svelte @@ -121,7 +121,6 @@ await Promise.all(promises); } - async function loadData(id: string) { try { @@ -150,7 +149,22 @@ $: if (params) { loadData(params.id); } + let url: string = ''; + $: if (teamData?.Id) { + url = `localhost:8080/#/team/invite/${teamData.InviteCode}`; + } + + function copyToClipboard(): void { + navigator.clipboard + .writeText(url) + .then(() => { + toast.success('Copied to clipboard'); + }) + .catch((err: Error) => { + toast.error(`Failed to copy: ${err}`); + }); + } @@ -204,10 +218,20 @@ {/if} - - - Invite Link: https://localhost:8080/#/team/invite/{teamInviteCode} + + Invite Link: +
+ + +

Team Members

From 06d48db46353ce9f50ab7bd2e2a6a7543b07126f Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Tue, 29 Oct 2024 15:52:15 -0700 Subject: [PATCH 60/69] fix: owners can remove team members appropriately --- backend/app/database/teams.go | 13 +++- backend/app/server/teams.go | 62 +++++++++--------- html/src/lib/pages/TeamEdit.svelte | 91 ++++++++++++++------------- html/src/lib/pages/TeamsBrowse.svelte | 4 +- html/src/lib/pages/UserTeams.svelte | 1 - html/src/lib/services/services.ts | 23 ++++++- 6 files changed, 117 insertions(+), 77 deletions(-) diff --git a/backend/app/database/teams.go b/backend/app/database/teams.go index 0dc7918..0d1edca 100644 --- a/backend/app/database/teams.go +++ b/backend/app/database/teams.go @@ -288,8 +288,17 @@ func AddTeamMember(userId pgtype.UUID, teamUUID pgtype.UUID, role string) (userI return teamMember.UserId, err } -func RemoveTeamMember(teamId pgtype.UUID, userId pgtype.UUID) { - +func RemoveTeamMember(teamId pgtype.UUID, userId pgtype.UUID) (DBTeamMember, error) { + deletedMember, err := GetRow[DBTeamMember](` + DELETE FROM team_members + WHERE team_id = $1 AND user_id = $2 + RETURNING team_id, user_id, team_role, created_on AS user_created_on`, + teamId, userId) + if err != nil { + logger.Error("Failed to remove team member: %v", err) + return DBTeamMember{}, err + } + return deletedMember, nil } func GetMembersByTeamId(teamId pgtype.UUID) (*[]DBTeamMemberInfo, error) { diff --git a/backend/app/server/teams.go b/backend/app/server/teams.go index 0c05b27..6e95d23 100644 --- a/backend/app/server/teams.go +++ b/backend/app/server/teams.go @@ -30,18 +30,23 @@ type CreateTeamRequest struct { } type GetTeamResponse struct { - Team *database.DBTeam - Event *database.DBEvent + Team *database.DBTeam + Event *database.DBEvent TeamMembers *[]database.DBTeamMemberInfo // array(slice) of a struct } type InvitePayload struct { - TeamId string `json:"teamId"` - InviteCode string `json:"inviteCode"` + TeamId string `json:"teamId"` + InviteCode string `json:"inviteCode"` } type JoinPayload struct { - TeamId string `json:"teamId"` + TeamId string `json:"teamId"` +} + +type MemberPayload struct { + TeamId string `json:"teamId"` + MemberId string `json:"memberId"` } func MD5HashCode(teamName string) (string, error) { @@ -88,7 +93,7 @@ func (server *Server) GetUserTeams(ctx *gin.Context) { ctx.Status(http.StatusNotFound) return } - strUserId := userId.(string) + strUserId := userId.(string) teams, err := database.GetUserTeams(convert.StringToUUID(strUserId)) if err != nil { @@ -99,7 +104,6 @@ func (server *Server) GetUserTeams(ctx *gin.Context) { ctx.JSON(http.StatusOK, teams) } - // purpose is to construct the DBTeamMemberInfo func (server *Server) GetTeamInfo(id pgtype.UUID) (*GetTeamResponse, error) { @@ -130,7 +134,7 @@ func (server *Server) GetTeamInfo(id pgtype.UUID) (*GetTeamResponse, error) { teamResponse.Team = &team teamResponse.Event = &event teamResponse.TeamMembers = teamMembers - + return &teamResponse, nil } @@ -191,7 +195,7 @@ func (server *Server) CreateTeam(ctx *gin.Context) { // Step 4: Post Team Data API (TWO PARTS 1) create team 2) add team members) session := sessions.Default(ctx) userId := session.Get("userId") - if userId == nil { + if userId == nil { ctx.Status(http.StatusUnauthorized) return } @@ -230,7 +234,7 @@ func (server *Server) CreateTeam(ctx *gin.Context) { team.InviteCode = md5code fmt.Printf("%+v", team) - + // INSERTS TEAM into DB // PART 1/2 DONE teamUUID, err := database.CreateTeam(team) @@ -239,7 +243,7 @@ func (server *Server) CreateTeam(ctx *gin.Context) { ctx.Status(http.StatusBadRequest) return } - fmt.Println(convert.StringToUUID(strUserId), teamUUID,) + fmt.Println(convert.StringToUUID(strUserId), teamUUID) // PART 2/2 DONE // construct TeamMember @@ -263,7 +267,7 @@ func (server *Server) UpdateTeam(ctx *gin.Context) { fmt.Println(ctx.Request.Body) if userId != nil { var team database.DBTeam - err := ctx.ShouldBindJSON(&team) //"message incoming data to this struct" + err := ctx.ShouldBindJSON(&team) //"message incoming data to this struct" if err != nil { logger.Error("UpdateEvent Request ShouldBindJSON error: %v", err) ctx.Status(http.StatusBadRequest) @@ -288,22 +292,25 @@ func (server *Server) RemoveTeamMember(ctx *gin.Context) { ctx.Status(http.StatusUnauthorized) return } - strUserId := userId.(string) - uuidUserId := convert.StringToUUID(strUserId) - var teamId JoinPayload + var MemberPayload MemberPayload - if err := ctx.ShouldBindJSON(&teamId); err != nil { + if err := ctx.ShouldBindJSON(&MemberPayload); err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - // teamId prints: {cc457e3e-210a-4d5d-83d8-0899426dfc93} - uuidTeamId := convert.StringToUUID(teamId.TeamId) - fmt.Println("user id: ", uuidUserId, "==== teamd id:", uuidTeamId) - //_, err := database.RemoveTeamMember(teamId, userId) - ctx.JSON(http.StatusOK, "pass") + uuidTeamId := convert.StringToUUID(MemberPayload.TeamId) + uuidMemberId := convert.StringToUUID(MemberPayload.MemberId) + + removedMember, err := database.RemoveTeamMember(uuidTeamId, uuidMemberId) + if err != nil { + fmt.Println("Error removing team member: ", err) + } + + strTeamId := convert.UUIDToString(removedMember.TeamId) + ctx.JSON(http.StatusOK, strTeamId) } func (server *Server) MemberJoin(ctx *gin.Context) { @@ -328,17 +335,17 @@ func (server *Server) MemberJoin(ctx *gin.Context) { // teamId prints: {cc457e3e-210a-4d5d-83d8-0899426dfc93} uuidTeamId := convert.StringToUUID(teamId.TeamId) teamInfo, err := server.GetTeamInfo(uuidTeamId) - + if err != nil { ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return + return } // verifies team is public var isPublic string = teamInfo.Team.Visibility if isPublic == "private" { ctx.Status(http.StatusForbidden) - return + return } _, err = database.AddTeamMember(uuidUserId, uuidTeamId, "member") if err != nil { @@ -351,7 +358,7 @@ func (server *Server) MemberJoin(ctx *gin.Context) { func (server *Server) MemberInvite(ctx *gin.Context) { session := sessions.Default(ctx) - userId:= session.Get("userId") + userId := session.Get("userId") var payload InvitePayload // bind the message context to the structure, and do an error check @@ -369,7 +376,7 @@ func (server *Server) MemberInvite(ctx *gin.Context) { dbInviteCode, err := database.GetTeamInviteCode(uuidTeamId) if err != nil { ctx.JSON(http.StatusBadRequest, inviteCode) - } + } fmt.Println("server dbInviteCode: ", dbInviteCode) if inviteCode != dbInviteCode.InviteCode { fmt.Println("invalid request") @@ -382,7 +389,6 @@ func (server *Server) MemberInvite(ctx *gin.Context) { } } - func (server *Server) SetupTeamRoutes() { group := server.Gin.Group("/team") { @@ -394,7 +400,7 @@ func (server *Server) SetupTeamRoutes() { group.GET("/invite/:invitecode", server.GetTeamInfoByInviteCode) group.PUT("/edit/:teamid", server.UpdateTeam) // for admin to remove people - group.PUT("/team/:teamid/member/:memberid", server.RemoveTeamMember) + group.DELETE("/:teamid/member/:memberid", server.RemoveTeamMember) } server.Gin.GET("/teams", server.GetUserTeams) // I think this works rofl server.Gin.GET("/teams/browse", server.GetAllTeams) diff --git a/html/src/lib/pages/TeamEdit.svelte b/html/src/lib/pages/TeamEdit.svelte index e7fe43b..981be7d 100644 --- a/html/src/lib/pages/TeamEdit.svelte +++ b/html/src/lib/pages/TeamEdit.svelte @@ -38,7 +38,7 @@ let teamAvailability: string = ''; let teamTechnologies: string = ''; let teamDescription: string = ''; - let teamInviteCode: string = ''; + let teamInviteCode: string = ''; let loading: boolean = true; let formData: CodeJamTeam | null = null; @@ -80,47 +80,43 @@ } } - function removeMember(teamId: string, memberUserId: string) { + async function removeMember(teamId: string, memberUserId: string): Promise { if (confirm('Are you sure you want to remove this team member?')) { - // Call API to remove member - removeMemberFromTeam(teamId, memberUserId) - .then(() => { - // Update formData to reflect changes - teamMembers = teamMembers.filter((member) => member.UserId !== memberUserId); - toast.success('Member removed successfully'); - }) - .catch((err) => { - toast.error('Failed to remove member'); - console.error(err); - }); + try { + // Call API to remove member + await removeMemberFromTeam(teamId, memberUserId); + return teamId; + } catch (err) { + toast.error('Failed to remove member'); + console.error(err); + return ''; + } } + return ''; } async function getAvatarUrl(member: TeamMember): Promise { - console.log("member: ", member) let ext = member.AvatarId.startsWith('a_') ? '.gif' : '.png'; - console.log("meember.AvatarUrl: ", member.AvatarId) return `https://cdn.discordapp.com/avatars/${member.ServiceUserId}/${member.AvatarId}${ext}`; } - async function loadAvatarUrls() { + async function loadAvatarUrls() { let members: TeamMember[] = []; - members.push(...teamMembers); + members.push(...teamMembers); console.log(`MEMBERS: ${members}`); const promises = members.map(async (member) => { const url = await getAvatarUrl(member); - // for this page, we use Id instead of UserId because it queries the User table, which uses 'Id' - // may need to make a new class/model if we want to fix the error: Property 'Id' does not exist on type 'TeamMember'. + // for this page, we use Id instead of UserId because it queries the User table, which uses 'Id' + // may need to make a new class/model if we want to fix the error: Property 'Id' does not exist on type 'TeamMember'. avatarUrls[member.Id] = url; }); await Promise.all(promises); - - } + } async function loadData(id: string) { try { @@ -128,7 +124,7 @@ response.json().then((data) => { formData = data as CodeJamTeam; teamData = data.Team; - teamInviteCode = data.Team.InviteCode; + teamInviteCode = data.Team.InviteCode; teamMembers = data.TeamMembers; teamEvent = data.Event; teamName = data.Team.Name; @@ -136,8 +132,8 @@ teamAvailability = data.Team.Availability; teamTechnologies = data.Team.Technologies; teamDescription = data.Team.Description; - loadAvatarUrls(); - }); + loadAvatarUrls(); + }); }); } catch (err) { error = `Failed to load team data: ${err}`; @@ -147,7 +143,7 @@ } $: if (params) { - loadData(params.id); + loadData(params.id); } let url: string = ''; @@ -218,22 +214,22 @@ {/if} - - Invite Link: - -
- - -
+ + Invite Link: + +
+ + +
-

Team Members

+

Team Members

Avatar @@ -247,15 +243,24 @@ {#each formData.TeamMembers as Member} - - + + {Member.DisplayName} {Member.TeamRole} {#if Member.TeamRole !== 'owner'} - {:else} - - {/if} - - - - \ No newline at end of file + +

Edit Profile

+ {#if formData !== null} +
+
+ + + + +
+ + + {:else} + + {/if} +
+ diff --git a/html/src/lib/pages/TeamEdit.svelte b/html/src/lib/pages/TeamEdit.svelte index 2d298f6..c8f9e93 100644 --- a/html/src/lib/pages/TeamEdit.svelte +++ b/html/src/lib/pages/TeamEdit.svelte @@ -22,7 +22,6 @@ import FormField from '../components/FormField.svelte'; import Page from '../components/Page.svelte'; import type CodeJamEvent from '../models/event'; - import CodeJamTeam from '../models/team'; import CodeJamTeamExtended from '../models/teamExtended'; import type TeamMember from '../models/TeamMember'; import { getTeamById, putTeam, removeMemberFromTeam } from '../services/services'; @@ -39,7 +38,7 @@ let avatarUrls: Record = {}; let teamName: string = ''; - let teamId: string = ''; + let teamId: string = ''; let teamVisibility: string = ''; let teamAvailability: string = ''; let teamTechnologies: string = ''; @@ -132,7 +131,7 @@ formData = data as CodeJamTeamExtended; teamData = data.Team; - teamId = data.Team.Id; + teamId = data.Team.Id; teamName = data.Team.Name; teamInviteCode = data.Team.InviteCode; teamMembers = data.TeamMembers; @@ -159,7 +158,7 @@ let url: string = ''; $: if (teamId) { - console.log("does thissssssss...") + console.log('does thissssssss...'); url = `localhost:8080/#/team/invite/${teamInviteCode}`; } From e3ae671a69934e4bf8009c5a410d274a1958ea11 Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Mon, 11 Nov 2024 13:18:12 -0800 Subject: [PATCH 67/69] fix: small formatting --- backend/app/integrations/generic.go | 12 +++++------- html/src/routes.ts | 1 - 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/backend/app/integrations/generic.go b/backend/app/integrations/generic.go index e60f201..a651a15 100644 --- a/backend/app/integrations/generic.go +++ b/backend/app/integrations/generic.go @@ -14,7 +14,7 @@ type IntegrationUser struct { IntegrationName string UserId string ServiceUserName string - AvatarId string + AvatarId string } func getGitHubUser(accessToken string) *IntegrationUser { @@ -32,14 +32,13 @@ func getGitHubUser(accessToken string) *IntegrationUser { func getDiscordUser(accessToken string) *IntegrationUser { user := discord.GetUser(accessToken) avatar, ok := user["avatar"].(string) - if !ok { - avatar = "" // Or set a default avatar URL if preferred - } + if !ok { + avatar = "" // Or set a default avatar URL if preferred + } if user == nil { logger.Error("User not found for token: %s", accessToken) return nil } else { - var avatar = "" if user["avatar"] != nil { avatar = user["avatar"].(string) } @@ -47,7 +46,7 @@ func getDiscordUser(accessToken string) *IntegrationUser { IntegrationName: "discord", UserId: user["id"].(string), ServiceUserName: user["global_name"].(string), - AvatarId: avatar, + AvatarId: avatar, } } } @@ -61,5 +60,4 @@ func GetUser(integrationName string, accessToken string) *IntegrationUser { default: return nil } - } diff --git a/html/src/routes.ts b/html/src/routes.ts index f1cf515..5090528 100644 --- a/html/src/routes.ts +++ b/html/src/routes.ts @@ -14,7 +14,6 @@ import TeamEdit from "./lib/pages/TeamEdit.svelte"; export default { '/': HomePage, '/home': HomePage, - '/team': TeamOptions, '/team/:id': MyTeam, // link to one of your teams (sharable) We get an id here in this route... '/team/invite/:invitecode': Invite, // sharable From 122ac57ba01b7c569cb0807f70950ca36e70a3d4 Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Mon, 11 Nov 2024 13:49:27 -0800 Subject: [PATCH 68/69] fix: when userstore displayname loads --- backend/app/database/migrations/00000011_avatars.up.sql | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 backend/app/database/migrations/00000011_avatars.up.sql diff --git a/backend/app/database/migrations/00000011_avatars.up.sql b/backend/app/database/migrations/00000011_avatars.up.sql deleted file mode 100644 index 35f3b8a..0000000 --- a/backend/app/database/migrations/00000011_avatars.up.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE users -RENAME COLUMN avatar_url TO avatar_id; \ No newline at end of file From d664d848d655a89b8d9e77df02c93124eaff47bc Mon Sep 17 00:00:00 2001 From: timeenjoyed Date: Mon, 11 Nov 2024 13:49:53 -0800 Subject: [PATCH 69/69] fix: when userstore display name shows(when the page loads?) --- html/src/lib/pages/UserTeams.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/html/src/lib/pages/UserTeams.svelte b/html/src/lib/pages/UserTeams.svelte index 24b7580..567bdf3 100644 --- a/html/src/lib/pages/UserTeams.svelte +++ b/html/src/lib/pages/UserTeams.svelte @@ -65,11 +65,11 @@ return 'No owner found.'; } } - + let userDisplayName = $userStore?.DisplayName onMount(() => { loadData(); }); - + $: userDisplayName; // Currently, I'm querying every team where the loggedin user is the owner, and separating all the members. // I'm also checking client side whether the loggined user is the owner or not. // -- If it's an owner, it shows an edit button.