-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Frontend backend connection #8
Changes from all commits
b1782aa
654127b
13d6eab
c668101
8ea2606
9ca7a74
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,3 +9,5 @@ | |
# Generated by Nix devshell | ||
.devenv/ | ||
.direnv/ | ||
|
||
/backend/cmd/.env |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,161 @@ | ||||||
package handler | ||||||
|
||||||
import ( | ||||||
"context" | ||||||
"crypto/rand" | ||||||
"crypto/sha256" | ||||||
"encoding/base64" | ||||||
"encoding/hex" | ||||||
"fmt" | ||||||
"log" | ||||||
"math/big" | ||||||
"net/http" | ||||||
"strings" | ||||||
|
||||||
"github.com/valyala/fasthttp" | ||||||
"github.com/valyala/fasthttp/fasthttpadaptor" | ||||||
spotifyauth "github.com/zmb3/spotify/v2/auth" | ||||||
|
||||||
"platnm/internal/config" | ||||||
|
||||||
"github.com/gofiber/fiber/v2" | ||||||
"github.com/sethvargo/go-envconfig" | ||||||
"github.com/zmb3/spotify/v2" | ||||||
"golang.org/x/oauth2" | ||||||
"golang.org/x/oauth2/clientcredentials" | ||||||
) | ||||||
|
||||||
var ( | ||||||
redirectURI = "http://localhost:8080/spotify/callback" | ||||||
auth = spotifyauth.New(spotifyauth.WithRedirectURL(redirectURI), spotifyauth.WithScopes(spotifyauth.ScopeUserReadPrivate)) | ||||||
ch = make(chan *spotify.Client) | ||||||
state = "abc123" | ||||||
codeVerifier, err = generateRandomString(128) | ||||||
) | ||||||
|
||||||
func generateRandomString(length int) (string, error) { | ||||||
const possible = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" | ||||||
result := make([]byte, length) | ||||||
fmt.Printf("length: %v", length) | ||||||
|
||||||
for i := range length { | ||||||
fmt.Printf("i: %v", i) | ||||||
index, err := rand.Int(rand.Reader, big.NewInt(int64(len(possible)))) | ||||||
if err != nil { | ||||||
return "", err | ||||||
} | ||||||
result[i] = possible[index.Int64()] | ||||||
} | ||||||
|
||||||
return string(result), nil | ||||||
} | ||||||
|
||||||
func computeSHA256(plain string) (string, error) { | ||||||
hasher := sha256.New() | ||||||
_, err := hasher.Write([]byte(plain)) | ||||||
if err != nil { | ||||||
return "", err | ||||||
} | ||||||
hash := hasher.Sum(nil) | ||||||
return hex.EncodeToString(hash), nil | ||||||
} | ||||||
|
||||||
func base64Encode(input []byte) string { | ||||||
str := base64.StdEncoding.EncodeToString(input) | ||||||
str = strings.TrimRight(str, "=") | ||||||
str = strings.ReplaceAll(str, "+", "-") | ||||||
str = strings.ReplaceAll(str, "/", "_") | ||||||
return str | ||||||
} | ||||||
func convertToHTTPRequest(ctx *fasthttp.RequestCtx) *http.Request { | ||||||
req := new(http.Request) | ||||||
err := fasthttpadaptor.ConvertRequest(ctx, req, true) | ||||||
if err != nil { | ||||||
return nil | ||||||
} | ||||||
return req | ||||||
} | ||||||
|
||||||
func CompleteAuth(c *fiber.Ctx) error { | ||||||
tok, err := auth.Token(c.Context(), state, convertToHTTPRequest(c.Context()), | ||||||
oauth2.SetAuthURLParam("code_verifier", codeVerifier)) | ||||||
if err != nil { | ||||||
c.Status(fiber.StatusForbidden).SendString("Couldn't get token") | ||||||
log.Fatal(err) | ||||||
} | ||||||
|
||||||
if st := c.FormValue("state"); st != state { | ||||||
c.Status(fiber.StatusNotFound).SendString("State mismatch") | ||||||
log.Fatalf("State mismatch: %s != %s\n", st, state) | ||||||
} | ||||||
|
||||||
// use the token to get an authenticated client | ||||||
client := spotify.New(auth.Client(c.Context(), tok)) | ||||||
c.SendString("Login Completed!") | ||||||
ch <- client | ||||||
return c.Status(fiber.StatusOK).JSON(ch) | ||||||
} | ||||||
|
||||||
func configurePKCE() spotify.Client { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should this be called on each req? or once on server init? |
||||||
var config config.Config | ||||||
|
||||||
if err := envconfig.Process(context.Background(), &config); err != nil { | ||||||
log.Fatalln("Error processing .env file: ", err) | ||||||
} | ||||||
|
||||||
codeChallenge, err := computeSHA256(codeVerifier) | ||||||
if err != nil { | ||||||
log.Fatalf("couldn't compute SHA256: %v", err) | ||||||
} | ||||||
|
||||||
codeChallenge = base64Encode([]byte(codeChallenge)) | ||||||
url := auth.AuthURL(state, | ||||||
oauth2.SetAuthURLParam("client_id", config.SpClientKey), | ||||||
oauth2.SetAuthURLParam("code_challenge_method", "S256"), | ||||||
oauth2.SetAuthURLParam("code_challenge", codeChallenge), | ||||||
) | ||||||
fmt.Println("Please log in to Spotify by visiting the following page in your browser:", url) | ||||||
|
||||||
// wait for auth to complete | ||||||
client := <-ch | ||||||
// /callback?code=AQDS_gNUxSbt5vMweZKrafGpw-xj1ZCEWvplmdzSpZoQTsshJ7dG1svBCsTw3KaKKfJGjXHm2-SzGhLCtv32YmqoswwHdHmXX5mqN5jNLV-FdWJ2ELG5T-9SUyLL2V9qnNyn6g9EllF2yBAeUAzfKvPX-NR-SLH_F2nIHl9cdCClOjvarT0BailMZMbC3FAgt71cbkRmMoXX04J5Hcx45afy_XFq7RqvehVC_AqIIYYYJm8MBh2apUjMiodz-k9QfFzefpA4CFFoIC5E2L91yCDFm9SuSQVLeXyNOT7Nd7qLfmBtYI1wnDs1dAJDtEVQiAPK&state=abc123 | ||||||
|
||||||
return *client | ||||||
} | ||||||
|
||||||
func configureClientCreds() spotify.Client { | ||||||
var config config.Config | ||||||
|
||||||
if err := envconfig.Process(context.Background(), &config); err != nil { | ||||||
log.Fatalln("Error processing .env file: ", err) | ||||||
} | ||||||
|
||||||
ctx := context.Background() | ||||||
authConfig := &clientcredentials.Config{ | ||||||
ClientID: config.SpClientKey, | ||||||
ClientSecret: config.SpClientSecret, | ||||||
TokenURL: spotifyauth.TokenURL, | ||||||
} | ||||||
|
||||||
accessToken, err := authConfig.Token(context.Background()) | ||||||
if err != nil { | ||||||
log.Fatalf("couldn't get token: %v", err) | ||||||
} | ||||||
|
||||||
httpClient := spotifyauth.New().Client(ctx, accessToken) | ||||||
client := *spotify.New(httpClient) | ||||||
return client | ||||||
} | ||||||
|
||||||
func GetPlatnmPlaylist(c *fiber.Ctx) error { | ||||||
ctx := context.Background() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
try to refrain from |
||||||
client := configurePKCE() | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if a context is used in |
||||||
|
||||||
playlistID := spotify.ID("671uu0Y7jiAgX04Ou82Up9?si=80a629645bb84d42") | ||||||
playlist, err := client.GetPlaylist(ctx, playlistID) | ||||||
if err != nil { | ||||||
log.Fatalf("couldn't get playlist: %v", err) | ||||||
} | ||||||
|
||||||
return c.Status(fiber.StatusOK).JSON(playlist) | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,8 +4,34 @@ import { HelloWave } from '@/components/HelloWave'; | |
import ParallaxScrollView from '@/components/ParallaxScrollView'; | ||
import { ThemedText } from '@/components/ThemedText'; | ||
import { ThemedView } from '@/components/ThemedView'; | ||
import { useEffect, useState } from 'react'; | ||
import axios from 'axios'; | ||
|
||
type User = { | ||
email: string; | ||
first_name: string; | ||
last_name: string; | ||
phone: string; | ||
user_id: string; | ||
}; | ||
|
||
export default function HomeScreen() { | ||
const [isLoading, setLoading] = useState(true); | ||
const [users, setUsers] = useState<User[]>([]); | ||
const baseUrl = 'http://localhost:8080'; | ||
|
||
const getUsers = async () => { | ||
axios.get(`${baseUrl}/users`).then((response) => { | ||
setUsers(response.data); | ||
console.log(response.data); | ||
console.log(users); | ||
}); | ||
}; | ||
|
||
useEffect(() => { | ||
getUsers(); | ||
console.log(users); | ||
}, []); | ||
return ( | ||
<ParallaxScrollView | ||
headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }} | ||
|
@@ -16,28 +42,23 @@ export default function HomeScreen() { | |
/> | ||
}> | ||
<ThemedView style={styles.titleContainer}> | ||
<ThemedText type="title">Welcome!</ThemedText> | ||
<ThemedText type="title">Welcome to Platnm!</ThemedText> | ||
<HelloWave /> | ||
</ThemedView> | ||
<ThemedView style={styles.stepContainer}> | ||
<ThemedText type="subtitle">Step 1: Try it</ThemedText> | ||
<ThemedText> | ||
Edit <ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> to see changes. | ||
Press{' '} | ||
<ThemedText type="defaultSemiBold"> | ||
{Platform.select({ ios: 'cmd + d', android: 'cmd + m' })} | ||
</ThemedText>{' '} | ||
to open developer tools. | ||
</ThemedText> | ||
<ThemedText type="subtitle">Step 1: Backend Setup</ThemedText> | ||
<ThemedText> | ||
Make sure the database is connected and the backend server is running. Go to localhost:8080/users to see the list of users. | ||
</ThemedText> | ||
</ThemedView> | ||
<ThemedView style={styles.stepContainer}> | ||
<ThemedText type="subtitle">Step 2: Explore</ThemedText> | ||
<ThemedText type="subtitle">Step 2: Frontend Setup</ThemedText> | ||
<ThemedText> | ||
Tap the Explore tab to learn more about what's included in this starter app. | ||
If frontend and backend are connected, you should see a phone number here : {users[0].phone}. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yayyy this is cool |
||
</ThemedText> | ||
</ThemedView> | ||
<ThemedView style={styles.stepContainer}> | ||
<ThemedText type="subtitle">Step 3: Get a fresh start</ThemedText> | ||
<ThemedText type="subtitle">Step 3: Spotify Setup</ThemedText> | ||
<ThemedText> | ||
When you're ready, run{' '} | ||
<ThemedText type="defaultSemiBold">npm run reset-project</ThemedText> to get a fresh{' '} | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why all this in global vars? would fields on a struct make more sense? the "constructor" of the struct could be responsible for err handling instead of having a global err