implement sessions; retry to create spotify client
This commit is contained in:
		
							parent
							
								
									11778e2629
								
							
						
					
					
						commit
						46dc3dc83f
					
				@ -1,6 +1,6 @@
 | 
			
		||||
import { store } from "../store.mjs";
 | 
			
		||||
import { randomString } from "../lib/randomString.mjs";
 | 
			
		||||
import { UserStore } from "../db/schemas.mjs";
 | 
			
		||||
import { SessionStore, UserStore } from "../db/schemas.mjs";
 | 
			
		||||
import { createLocalUser, findUserBySpotifyId } from "../lib/helpers.mjs";
 | 
			
		||||
 | 
			
		||||
export const applyAuthRoutes = (router) => {
 | 
			
		||||
@ -29,7 +29,6 @@ export const applyAuthRoutes = (router) => {
 | 
			
		||||
        { 'spotify.userId': newUser.client.user.id },
 | 
			
		||||
        {
 | 
			
		||||
          accessToken,
 | 
			
		||||
          role: 'none',
 | 
			
		||||
          spotify: {
 | 
			
		||||
            refreshToken: newUser.client.refreshMeta.refreshToken,
 | 
			
		||||
            userId: newUser.client.user.id,
 | 
			
		||||
@ -39,6 +38,13 @@ export const applyAuthRoutes = (router) => {
 | 
			
		||||
        { upsert: true },
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      if (!await SessionStore.findOne().bySpotifyId(newUser.client.user.id))
 | 
			
		||||
        await new SessionStore({
 | 
			
		||||
          host: userStore,
 | 
			
		||||
          clients: [],
 | 
			
		||||
          queue: [],
 | 
			
		||||
        }).save();
 | 
			
		||||
 | 
			
		||||
      res.status(200);
 | 
			
		||||
      res.send({ message: 'authorized', accessToken });
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
import { applyUserRoutes, applyUserRoutesPublic } from "./user.mjs";
 | 
			
		||||
import { applySessionRoutes } from "./session.mjs";
 | 
			
		||||
 | 
			
		||||
export const applyApiRoutes = (router) => {
 | 
			
		||||
 | 
			
		||||
@ -8,6 +9,7 @@ export const applyApiRoutes = (router) => {
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  applyUserRoutes(router);
 | 
			
		||||
  applySessionRoutes(router);
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										95
									
								
								backend/api/session.mjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								backend/api/session.mjs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,95 @@
 | 
			
		||||
import { SessionStore } from "../db/schemas.mjs";
 | 
			
		||||
 | 
			
		||||
export const applySessionRoutes = (router) => {
 | 
			
		||||
 | 
			
		||||
  router.post('/session', async (req, res) => {
 | 
			
		||||
    const user = res.locals.user;
 | 
			
		||||
    if (await SessionStore.findOne().bySpotifyId(user.id)) {
 | 
			
		||||
      res.status(400);
 | 
			
		||||
      res.send({ message: 'you are already in a session' });
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const sessionStore = new SessionStore({
 | 
			
		||||
      host: user,
 | 
			
		||||
      clients: [],
 | 
			
		||||
    });
 | 
			
		||||
    await sessionStore.save();
 | 
			
		||||
 | 
			
		||||
    res.status(201);
 | 
			
		||||
    res.send({ message: 'created' });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  router.get('/session', async (req, res) => {
 | 
			
		||||
    const user = res.locals.user;
 | 
			
		||||
    const sessionStore = await SessionStore.findOne().byHostSpotifyId(user.id);
 | 
			
		||||
 | 
			
		||||
    if (!sessionStore) {
 | 
			
		||||
      res.status(404);
 | 
			
		||||
      res.send('you are not hosting a session');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    res.status(200);
 | 
			
		||||
    res.send({ session: sessionStore });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  router.delete('/session', async (req, res) => {
 | 
			
		||||
    const user = res.locals.user;
 | 
			
		||||
    const sessionStore = await SessionStore.findOne().byHostSpotifyId(user.id);
 | 
			
		||||
 | 
			
		||||
    if (!sessionStore) {
 | 
			
		||||
      res.status(404);
 | 
			
		||||
      res.send('you are not hosting a session');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await sessionStore.delete();
 | 
			
		||||
 | 
			
		||||
    res.status(204);
 | 
			
		||||
    res.send({ message: 'session deleted' });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  router.post('/session/join', async (req, res) => {
 | 
			
		||||
    if (!req.body?.hostId) {
 | 
			
		||||
      res.status(400);
 | 
			
		||||
      res.send({ message: 'hostId is undefined' });
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const { hostId } = req.body.hostId;
 | 
			
		||||
    const user = await res.locals.user;
 | 
			
		||||
    if (await SessionStore.findOne().bySpotifyId(user.id)) {
 | 
			
		||||
      res.status(400);
 | 
			
		||||
      res.send({ message: 'you are already in a session' });
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const sessionStore = SessionStore.findOne().byHostSpotifyId(hostId);
 | 
			
		||||
    if (!sessionStore) {
 | 
			
		||||
      res.status(400);
 | 
			
		||||
      res.send({ message: 'session does not exist' });
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sessionStore.clients.push(user);
 | 
			
		||||
    await sessionStore.save();
 | 
			
		||||
 | 
			
		||||
    res.status(200);
 | 
			
		||||
    res.send({ message: 'you joined' });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  router.post('/session/leave', async (req, res) => {
 | 
			
		||||
    const user = await res.locals.user;
 | 
			
		||||
    const sessionStore = SessionStore.findOne().byClientSpotifyId(user.id);
 | 
			
		||||
    if (!sessionStore) {
 | 
			
		||||
      res.status(400);
 | 
			
		||||
      res.send({ message: 'you are not a client of any session' });
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sessionStore.clients = sessionStore.clients.filter(client => client.id !== user.id);
 | 
			
		||||
    await sessionStore.save();
 | 
			
		||||
 | 
			
		||||
    res.status(200);
 | 
			
		||||
    res.send({ message: 'you left' });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
@ -1,79 +1,21 @@
 | 
			
		||||
import { store } from "../store.mjs";
 | 
			
		||||
import { UserStore } from "../db/schemas.mjs";
 | 
			
		||||
import { SessionStore, UserStore } from "../db/schemas.mjs";
 | 
			
		||||
 | 
			
		||||
export const applyUserRoutes = (router) => {
 | 
			
		||||
 | 
			
		||||
  applyUserRoutesPublic(router);
 | 
			
		||||
 | 
			
		||||
  router.post('/user/joinSession', async (req, res) => {
 | 
			
		||||
    if (!req.body?.userId) {
 | 
			
		||||
      res.status(400);
 | 
			
		||||
      res.send({ message: 'userId is undefined' });
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const { userId } = req.body.userId;
 | 
			
		||||
    if (res.locals.user.role === 'host') {
 | 
			
		||||
      res.status(400);
 | 
			
		||||
      res.send({ message: 'user is host' });
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const host = store.users.find(({ client }) => client.user.id === userId)
 | 
			
		||||
    host.listeners.push(res.locals.user);
 | 
			
		||||
    res.locals.user.role = 'listener';
 | 
			
		||||
    res.locals.user.listeningTo = host;
 | 
			
		||||
    res.status(200);
 | 
			
		||||
    res.send({ message: 'joined' });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  router.delete('/user/leaveSession', async (req, res) => {
 | 
			
		||||
    if (res.locals.user.role === 'host') {
 | 
			
		||||
      res.status(400);
 | 
			
		||||
      res.send({ message: 'user is host' });
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    const host = store.users.find(({ client }) => client.user.id === userId)
 | 
			
		||||
    host.listeners.push(res.locals.user);
 | 
			
		||||
    res.locals.user.role = 'none';
 | 
			
		||||
    res.locals.user.listeningTo.listeners = res.locals.user.listeningTo.listeners.filter(
 | 
			
		||||
      ({ client }) => client.user.id !== res.locals.user.client.user.id
 | 
			
		||||
    );
 | 
			
		||||
    res.locals.user.listeningTo = null;
 | 
			
		||||
    res.status(200);
 | 
			
		||||
    res.send({ message: 'left' });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  router.get('/user/currentlyPlaying', async (req, res) => {
 | 
			
		||||
  router.get('/me/currentlyPlaying', async (req, res) => {
 | 
			
		||||
    const currentlyPlaying = await (await res.locals.user.spotify.local)?.player?.getCurrentlyPlaying('track');
 | 
			
		||||
    res.status(200);
 | 
			
		||||
    res.send({ currentlyPlaying });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  router.get('/user/role', (req, res) => {
 | 
			
		||||
  router.get('/me/role', (req, res) => {
 | 
			
		||||
    res.status(200);
 | 
			
		||||
    res.send({ role: res.locals.user?.role });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  /*router.post('/user/role', async (req, res) => {
 | 
			
		||||
    if (
 | 
			
		||||
      req.body.role !== 'host' ||
 | 
			
		||||
      req.body.role !== 'listener' ||
 | 
			
		||||
      req.body.role !== 'none'
 | 
			
		||||
    ) {
 | 
			
		||||
      res.status(400);
 | 
			
		||||
      res.send({ message: 'role value is invalid' });
 | 
			
		||||
    }
 | 
			
		||||
    const { role } = req.body;
 | 
			
		||||
    try {
 | 
			
		||||
      res.locals.user.listeners = [];
 | 
			
		||||
      res.locals.user.role = role;
 | 
			
		||||
      res.status(200);
 | 
			
		||||
      res.send({ role });
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      res.status(500);
 | 
			
		||||
      res.send({ message: 'server error' });
 | 
			
		||||
    }
 | 
			
		||||
  });*/
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const applyUserRoutesPublic = (router) => {
 | 
			
		||||
 | 
			
		||||
@ -30,4 +30,26 @@ const userSchema = new Schema({
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const sessionSchema = new Schema({
 | 
			
		||||
  host: userSchema,
 | 
			
		||||
  clients: Array,
 | 
			
		||||
  queue: Array,
 | 
			
		||||
}, {
 | 
			
		||||
  query: {
 | 
			
		||||
    byHostSpotifyId(id) {
 | 
			
		||||
      return this.where({ 'host.spotify.userId': id });
 | 
			
		||||
    },
 | 
			
		||||
    byClientSpotifyId(id) {
 | 
			
		||||
      return this.where({ 'clients.userId': id });
 | 
			
		||||
    },
 | 
			
		||||
    bySpotifyId(id) {
 | 
			
		||||
      return this.where({ '$or': [
 | 
			
		||||
        { 'host.spotify.userId': id },
 | 
			
		||||
        { 'clients.spotify.userId': id },
 | 
			
		||||
      ]});
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const UserStore = model('User', userSchema);
 | 
			
		||||
export const SessionStore = model('Session', sessionSchema);
 | 
			
		||||
 | 
			
		||||
@ -2,26 +2,33 @@ import { Client, Player } from "spotify-api.js";
 | 
			
		||||
import { store } from "../store.mjs";
 | 
			
		||||
import { UserStore } from "../db/schemas.mjs";
 | 
			
		||||
 | 
			
		||||
export const createLocalUser = async ({ refreshToken = undefined, code = undefined }) => {
 | 
			
		||||
  const client = await Client.create({
 | 
			
		||||
    refreshToken: true,
 | 
			
		||||
    retryOnRateLimit: true,
 | 
			
		||||
    token: {
 | 
			
		||||
      clientID: store.clientID,
 | 
			
		||||
      clientSecret: store.clientSecret,
 | 
			
		||||
      redirectURL: store.redirectURL,
 | 
			
		||||
      refreshToken,
 | 
			
		||||
      code,
 | 
			
		||||
    },
 | 
			
		||||
    async onRefresh() {
 | 
			
		||||
      await UserStore.findOneAndUpdate(
 | 
			
		||||
        { 'spotify.userId': client.user.id },
 | 
			
		||||
        { 'spotify.refreshToken': client.refreshMeta.refreshToken },
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
  const player = new Player(client);
 | 
			
		||||
  return { client, player };
 | 
			
		||||
export const createLocalUser = async ({ refreshToken = undefined, code = undefined }, retry = 4) => {
 | 
			
		||||
  try {
 | 
			
		||||
    const client = await Client.create({
 | 
			
		||||
      refreshToken: true,
 | 
			
		||||
      retryOnRateLimit: true,
 | 
			
		||||
      token: {
 | 
			
		||||
        clientID: store.clientID,
 | 
			
		||||
        clientSecret: store.clientSecret,
 | 
			
		||||
        redirectURL: store.redirectURL,
 | 
			
		||||
        refreshToken,
 | 
			
		||||
        code,
 | 
			
		||||
      },
 | 
			
		||||
      async onRefresh() {
 | 
			
		||||
        await UserStore.findOneAndUpdate(
 | 
			
		||||
          { 'spotify.userId': client.user.id },
 | 
			
		||||
          { 'spotify.refreshToken': client.refreshMeta.refreshToken },
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
    const player = new Player(client);
 | 
			
		||||
    return { client, player };
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    if (retry-- < 1) throw e;
 | 
			
		||||
    if (e.response.data.status === 503) await new Promise(_ => setTimeout(_, 500));
 | 
			
		||||
    else throw e;
 | 
			
		||||
    return await createLocalUser({ refreshToken, code }, retry);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const findUserBySpotifyId = async (
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								frontend
									
									
									
									
									
								
							
							
								
								
								
								
								
								
							
						
						
									
										2
									
								
								frontend
									
									
									
									
									
								
							@ -1 +1 @@
 | 
			
		||||
Subproject commit f3b95916688df6b4a9834bb5d4adfb9ceec6093d
 | 
			
		||||
Subproject commit cf9626402434ff2e2449457c40182bab92a8ad55
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user