Add in Discord OAuth and show player cards to authenticated users
This commit is contained in:
parent
4fb2d7b4c8
commit
cddc982e8b
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<nav class="navbar navbar-expand-sm" style="margin-bottom: 1rem" id="navbar">
|
<nav class="navbar navbar-expand-sm" style="margin-bottom: 1rem" id="navbar">
|
||||||
<RouterLink class="navbar-brand nav-link" to="/">SBa Season {{ seasonNumber }}</RouterLink>
|
<RouterLink class="navbar-brand nav-link" to="/">SBa Season {{ seasonNumber() }}</RouterLink>
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#top-navbar-collapse"
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#top-navbar-collapse"
|
||||||
aria-controls="top-navbar-collapse" aria-expanded="false" aria-label="Toggle navigation">
|
aria-controls="top-navbar-collapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
@ -13,7 +13,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<RouterLink class="nav-link"
|
<RouterLink class="nav-link"
|
||||||
:to="{ name: 'team', params: { seasonNumber: seasonNumber, teamAbbreviation: 'FA' } }">Free Agents
|
:to="{ name: 'team', params: { seasonNumber: seasonNumber(), teamAbbreviation: 'FA' } }">Free Agents
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
@ -23,11 +23,16 @@
|
|||||||
<a class="nav-link" target="_blank" href="https://sbanews.manticorum.com/">News</a>
|
<a class="nav-link" target="_blank" href="https://sbanews.manticorum.com/">News</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a id="login" class="nav-link">Login with Discord</a>
|
<button v-if="!isAuthenticated" id="login" class="nav-link" @click="authenticate">Login with Discord</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li v-if="false" class="nav-item">
|
||||||
<!-- TODO RouterLink to team page with team icon -->
|
<!-- make the above true if you want to clear cookies for testing -->
|
||||||
<a id="team-login-link" href="/teams?abbrev="></a>
|
<button class="nav-link" @click="clearCookie">Clear Cookie</button>
|
||||||
|
</li>
|
||||||
|
<li v-if="isAuthenticated && userTeam" class="nav-item">
|
||||||
|
<RouterLink :to="{ name: 'team', params: { seasonNumber: seasonNumber(), teamAbbreviation: userTeam.abbrev } }">
|
||||||
|
<img id="thumbnail" style="max-height: 35px; float:right; vertical-align:middle" :src=userTeam?.thumbnail>
|
||||||
|
</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
@ -68,17 +73,23 @@ import { RouterLink } from 'vue-router'
|
|||||||
import type { MenuOption } from 'naive-ui'
|
import type { MenuOption } from 'naive-ui'
|
||||||
import { fetchPlayers, type Player } from '@/services/playersService'
|
import { fetchPlayers, type Player } from '@/services/playersService'
|
||||||
import { CURRENT_SEASON } from '@/services/utilities'
|
import { CURRENT_SEASON } from '@/services/utilities'
|
||||||
|
import type { Team } from '@/services/apiResponseTypes'
|
||||||
|
import { fetchActiveTeamByOwnerId } from '@/services/teamsService'
|
||||||
|
import { authenticate, clearCookie, completeAuthentication, getOwnerId, isDiscordAuthenticated, parseCookie } from '@/services/authenticationService'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'NavBar',
|
name: 'NavBar',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
userTeam: undefined as Team | undefined,
|
||||||
players: [] as Player[],
|
players: [] as Player[],
|
||||||
searchPlayerName: undefined
|
searchPlayerName: undefined
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.populatePlayerNames()
|
this.populatePlayerNames()
|
||||||
|
this.completeAuthentication()
|
||||||
|
this.fetchUserTeam()
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
menuOptions(): MenuOption[] {
|
menuOptions(): MenuOption[] {
|
||||||
@ -267,23 +278,43 @@ export default {
|
|||||||
{ label: 'News', key: 'News' }
|
{ label: 'News', key: 'News' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
seasonNumber(): number {
|
|
||||||
// TODO pull this from DB?
|
|
||||||
return CURRENT_SEASON
|
|
||||||
},
|
|
||||||
sortedPlayerNames(): string[] {
|
sortedPlayerNames(): string[] {
|
||||||
return this.players.sort((p1, p2) => p2.wara - p1.wara).map(p => p.name)
|
return this.players.sort((p1, p2) => p2.wara - p1.wara).map(p => p.name)
|
||||||
|
},
|
||||||
|
isAuthenticated(): boolean {
|
||||||
|
return isDiscordAuthenticated()
|
||||||
|
},
|
||||||
|
ownerId(): string | undefined {
|
||||||
|
return getOwnerId()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async getPlayers(): Promise<Player[]> {
|
async getPlayers(): Promise<Player[]> {
|
||||||
return await fetchPlayers(this.seasonNumber)
|
return await fetchPlayers(this.seasonNumber())
|
||||||
},
|
},
|
||||||
async populatePlayerNames(): Promise<void> {
|
async populatePlayerNames(): Promise<void> {
|
||||||
this.players = await this.getPlayers()
|
this.players = await this.getPlayers()
|
||||||
},
|
},
|
||||||
searchPlayers(): void {
|
searchPlayers(): void {
|
||||||
this.$router.push({ path: `/players/${this.seasonNumber}/${this.searchPlayerName}` })
|
this.$router.push({ path: `/players/${this.seasonNumber()}/${this.searchPlayerName}` })
|
||||||
|
},
|
||||||
|
authenticate(): void {
|
||||||
|
authenticate()
|
||||||
|
},
|
||||||
|
async completeAuthentication(): Promise<void> {
|
||||||
|
if (this.ownerId) return
|
||||||
|
|
||||||
|
completeAuthentication()
|
||||||
|
},
|
||||||
|
async fetchUserTeam(): Promise<void> {
|
||||||
|
if (!this.ownerId) return
|
||||||
|
this.userTeam = await fetchActiveTeamByOwnerId(this.ownerId)
|
||||||
|
},
|
||||||
|
seasonNumber(): number {
|
||||||
|
return CURRENT_SEASON
|
||||||
|
},
|
||||||
|
clearCookie(): void {
|
||||||
|
clearCookie()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
67
src/services/authenticationService.ts
Normal file
67
src/services/authenticationService.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { SITE_URL } from './utilities'
|
||||||
|
|
||||||
|
export function authenticate(): void {
|
||||||
|
// MAJOR DOMO - 712002920950005870
|
||||||
|
const clientID = '712002920950005870'
|
||||||
|
// TODO: dynamically redirect to current view by getting current query params and adding them back on after?
|
||||||
|
const redirectURI = encodeURIComponent(SITE_URL)
|
||||||
|
const scope = 'identify' // Adjust the scope as needed
|
||||||
|
window.location.href = `https://discord.com/oauth2/authorize?client_id=${clientID}&redirect_uri=${redirectURI}&response_type=token&scope=${scope}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isDiscordAuthenticated(): boolean {
|
||||||
|
return !!getOwnerId()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOwnerId(): string | undefined {
|
||||||
|
return parseCookie(document.cookie)?.discord
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function completeAuthentication(): Promise<void> {
|
||||||
|
if (!window.location.hash) {
|
||||||
|
console.warn('No token hash found in return URL')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the access token from the URL params
|
||||||
|
const urlParams = new URLSearchParams(window.location.hash.slice(1))
|
||||||
|
const accessToken = urlParams.get('access_token')
|
||||||
|
const tokenType = urlParams.get('token_type') //should be 'Bearer'
|
||||||
|
const userResponse = await fetch('https://discord.com/api/users/@me', {
|
||||||
|
headers: {
|
||||||
|
authorization: `${tokenType} ${accessToken}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!userResponse.ok) {
|
||||||
|
console.warn('userResponse was not OK', userResponse)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// right now we only care about the id property but we could make a full discord account object if desired
|
||||||
|
const user: { id: string } = await userResponse.json()
|
||||||
|
if (!user?.id) {
|
||||||
|
console.warn('No user or id found while authenticating')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href = SITE_URL
|
||||||
|
saveOwnerIdCookie(user.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on https://www.30secondsofcode.org/js/s/parse-cookie/
|
||||||
|
export function parseCookie(cookie: string | undefined): { [key: string]: string } {
|
||||||
|
if (!cookie) return {}
|
||||||
|
return cookie.split(';')
|
||||||
|
.map(v => v.split('='))
|
||||||
|
.reduce((acc: { [key: string]: string }, v) => {
|
||||||
|
acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(v[1].trim())
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveOwnerIdCookie(ownerId: string): void {
|
||||||
|
document.cookie = `discord=${ownerId}; expires=Fri, 1 Jan 2100 12:00:00 UTC; path=/`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearCookie(): void {
|
||||||
|
document.cookie = "discord=;-1;path=/"
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import type { Team } from './apiResponseTypes'
|
import type { Team } from './apiResponseTypes'
|
||||||
import { SITE_URL } from './utilities'
|
import { CURRENT_SEASON, SITE_URL } from './utilities'
|
||||||
|
|
||||||
export async function fetchTeam(seasonNumber: number, teamAbbreviation: string): Promise<Team> {
|
export async function fetchTeam(seasonNumber: number, teamAbbreviation: string): Promise<Team> {
|
||||||
const response = await fetch(`${SITE_URL}/api/v3/teams?season=${seasonNumber}&team_abbrev=${teamAbbreviation}`)
|
const response = await fetch(`${SITE_URL}/api/v3/teams?season=${seasonNumber}&team_abbrev=${teamAbbreviation}`)
|
||||||
@ -15,3 +15,19 @@ export async function fetchTeam(seasonNumber: number, teamAbbreviation: string):
|
|||||||
|
|
||||||
return teamResponse.teams[0]
|
return teamResponse.teams[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchActiveTeamByOwnerId(ownerId: string): Promise<Team | undefined> {
|
||||||
|
const response = await fetch(`${SITE_URL}/api/v3/teams?season=${CURRENT_SEASON}&active_only=True&owner_id=${ownerId}`)
|
||||||
|
|
||||||
|
const teamResponse: {
|
||||||
|
count: number
|
||||||
|
teams: Team[]
|
||||||
|
} = await response.json()
|
||||||
|
|
||||||
|
if (teamResponse.count === 0) {
|
||||||
|
console.warn('teamServices.fetchActiveTeamByOwnerId - Received 0 active teams for owner id, are they active?')
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return teamResponse.teams[0]
|
||||||
|
}
|
||||||
|
|||||||
@ -59,6 +59,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { isDiscordAuthenticated } from '@/services/authenticationService'
|
||||||
import { fetchLast2DecisionsByPlayerId, type Decision } from '@/services/decisionsService'
|
import { fetchLast2DecisionsByPlayerId, type Decision } from '@/services/decisionsService'
|
||||||
import { type Player, fetchPlayerByName } from '@/services/playersService'
|
import { type Player, fetchPlayerByName } from '@/services/playersService'
|
||||||
|
|
||||||
@ -75,9 +76,8 @@ export default {
|
|||||||
playerName: { type: String, required: true }
|
playerName: { type: String, required: true }
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isAuthorized(): boolean {
|
isAuthenticated(): boolean {
|
||||||
//TODO check discord oauth/cookie/token
|
return isDiscordAuthenticated()
|
||||||
return false
|
|
||||||
},
|
},
|
||||||
teamAbbreviation(): string | undefined {
|
teamAbbreviation(): string | undefined {
|
||||||
return this.player?.team?.abbrev
|
return this.player?.team?.abbrev
|
||||||
@ -94,11 +94,11 @@ export default {
|
|||||||
return this.player?.vanity_card ?? this.teamThumbnail
|
return this.player?.vanity_card ?? this.teamThumbnail
|
||||||
},
|
},
|
||||||
playerCardImage1Url(): string | undefined {
|
playerCardImage1Url(): string | undefined {
|
||||||
if (!this.isAuthorized) return undefined
|
if (!this.isAuthenticated) return undefined
|
||||||
return this.player?.image
|
return this.player?.image
|
||||||
},
|
},
|
||||||
playerCardImage2Url(): string | undefined {
|
playerCardImage2Url(): string | undefined {
|
||||||
if (!this.isAuthorized) return undefined
|
if (!this.isAuthenticated) return undefined
|
||||||
return this.player?.image2
|
return this.player?.image2
|
||||||
},
|
},
|
||||||
injuryReturnDate(): string | undefined {
|
injuryReturnDate(): string | undefined {
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
<div class="team-view centerDiv">
|
<div class="team-view centerDiv">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<h1 id="team-record">{{ teamName }}</h1>
|
<h1 id="team-record">{{ teamName }} {{ teamRecord }}</h1>
|
||||||
<h2 id="standings"></h2>
|
<h2 id="standings"></h2>
|
||||||
<h2 id="streak">Last 8: {{ lastEight }} / Streak: {{ streak }}</h2>
|
<h2 id="streak">Last 8: {{ lastEight }} / Streak: {{ streak }}</h2>
|
||||||
</div>
|
</div>
|
||||||
@ -199,6 +199,10 @@ export default {
|
|||||||
teamName(): string | undefined {
|
teamName(): string | undefined {
|
||||||
return this.team?.lname
|
return this.team?.lname
|
||||||
},
|
},
|
||||||
|
teamRecord(): string | undefined {
|
||||||
|
if (!this.teamStanding) return undefined
|
||||||
|
return `(${this.teamStanding?.wins}-${this.teamStanding.losses})`
|
||||||
|
},
|
||||||
minorsTeamName(): string | undefined {
|
minorsTeamName(): string | undefined {
|
||||||
return this.teamMinors?.sname
|
return this.teamMinors?.sname
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user