inject SpotifyTokenService in relevant classes
This commit is contained in:
@@ -2,10 +2,11 @@ import axios from "axios";
|
||||
import {OAuthTokenResponse} from "../../interfaces/OAuthTokenResponse";
|
||||
|
||||
const url = "https://accounts.spotify.com/api/token";
|
||||
const clientId = process.env.SPOTIFY_CLIENT_ID;
|
||||
const clientSecret = process.env.SPOTIFY_CLIENT_SECRET;
|
||||
|
||||
export class SpotifyTokenService {
|
||||
constructor(private readonly clientId: string, private readonly clientSecret: string) {
|
||||
}
|
||||
|
||||
public async refreshToken(refreshToken: string) {
|
||||
console.log("refreshToken")
|
||||
const response = await axios.post(
|
||||
@@ -15,7 +16,7 @@ export class SpotifyTokenService {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Authorization: `Basic ${Buffer.from(
|
||||
`${clientId}:${clientSecret}`,
|
||||
`${this.clientId}:${this.clientSecret}`,
|
||||
).toString("base64")}`,
|
||||
},
|
||||
},
|
||||
@@ -33,7 +34,7 @@ export class SpotifyTokenService {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Authorization: `Basic ${Buffer.from(
|
||||
`${clientId}:${clientSecret}`,
|
||||
`${this.clientId}:${this.clientSecret}`,
|
||||
).toString("base64")}`,
|
||||
},
|
||||
},
|
||||
|
||||
+19
-5
@@ -15,15 +15,15 @@ import {randomUUID} from "crypto";
|
||||
import {JwtAuthenticator} from "./utils/jwtAuthenticator";
|
||||
import {authenticateJwt} from "./rest/middleware/authenticateJwt";
|
||||
import {disconnectFromDatabase} from "./db/services/db/database.service";
|
||||
import {SpotifyTokenService} from "./db/services/spotifyTokenService";
|
||||
|
||||
export async function startServer(jwtSecret: string) {
|
||||
export async function startServer(jwtSecret: string, spotifyClientId: string, spotifyClientSecret: string) {
|
||||
const app = express();
|
||||
const port = config.port;
|
||||
|
||||
app.set("trust proxy", 1);
|
||||
app.use(cookieParser());
|
||||
|
||||
// test
|
||||
app.use(cors({
|
||||
origin: config.cors.origin,
|
||||
credentials: config.cors.credentials,
|
||||
@@ -47,18 +47,20 @@ export async function startServer(jwtSecret: string) {
|
||||
const userService = await UserService.create();
|
||||
console.log("UserService created successfully.");
|
||||
|
||||
const spotifyTokenService = new SpotifyTokenService(spotifyClientId, spotifyClientSecret);
|
||||
|
||||
const _authenticateJwt = authenticateJwt(new JwtAuthenticator(jwtSecret));
|
||||
|
||||
const server = app.listen(port, () => {
|
||||
console.log(`Server is running on port ${port}`);
|
||||
});
|
||||
|
||||
const webSocketServer = new ExtendedWebSocketServer(server, userService);
|
||||
const webSocketServer = new ExtendedWebSocketServer(server, userService, spotifyTokenService);
|
||||
const restWebSocket = new RestWebSocket(webSocketServer);
|
||||
const restUser = new RestUser(userService);
|
||||
const auth = new RestAuth(userService);
|
||||
const jwtTokenPropertiesExtractor = new JwtTokenPropertiesExtractor();
|
||||
const spotify = new SpotifyTokenGenerator();
|
||||
const spotify = new SpotifyTokenGenerator(spotifyTokenService);
|
||||
|
||||
app.use("/api/auth", authLimiter, auth.createRouter());
|
||||
|
||||
@@ -119,13 +121,25 @@ export async function startServer(jwtSecret: string) {
|
||||
|
||||
if (process.env.NODE_ENV !== 'test') {
|
||||
const JWT_SECRET = process.env.SECRET_KEY;
|
||||
const CLIENT_ID = process.env.SPOTIFY_CLIENT_ID;
|
||||
const CLIENT_SECRET = process.env.SPOTIFY_CLIENT_SECRET;
|
||||
|
||||
if (!JWT_SECRET || JWT_SECRET.length < 32) {
|
||||
console.error("CRITICAL ERROR: SECRET_KEY environment variable is not set or too short. Aborting.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
startServer(JWT_SECRET).catch(error => {
|
||||
if (!CLIENT_ID) {
|
||||
console.error("CRITICAL ERROR: CLIENT_ID environment variable is not set. Aborting.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!CLIENT_SECRET) {
|
||||
console.error("CRITICAL ERROR: CLIENT_SECRET environment variable is not set. Aborting.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
startServer(JWT_SECRET, CLIENT_ID, CLIENT_SECRET).catch(error => {
|
||||
console.error("Fatal error during server startup:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import express from "express";
|
||||
import {SpotifyTokenService} from "../db/services/spotifyTokenService";
|
||||
import { asyncHandler } from "./middleware/asyncHandler";
|
||||
import { validateBody, v } from "./middleware/validate";
|
||||
import { ok, internalError } from "./utils/responses";
|
||||
import {asyncHandler} from "./middleware/asyncHandler";
|
||||
import {validateBody, v} from "./middleware/validate";
|
||||
import {ok, internalError} from "./utils/responses";
|
||||
|
||||
export class SpotifyTokenGenerator {
|
||||
|
||||
private tokenService = new SpotifyTokenService();
|
||||
constructor(private spotifyTokenService: SpotifyTokenService) {
|
||||
}
|
||||
|
||||
public createRouter() {
|
||||
const router = express.Router();
|
||||
@@ -14,29 +15,29 @@ export class SpotifyTokenGenerator {
|
||||
router.post(
|
||||
"/token/refresh",
|
||||
validateBody({
|
||||
refreshToken: { required: true, validator: v.isString({ nonEmpty: true }) },
|
||||
refreshToken: {required: true, validator: v.isString({nonEmpty: true})},
|
||||
}),
|
||||
asyncHandler(async (req, res) => {
|
||||
const { refreshToken } = req.body as { refreshToken: string };
|
||||
const {refreshToken} = req.body as { refreshToken: string };
|
||||
|
||||
const token = await this.tokenService.refreshToken(refreshToken);
|
||||
const token = await this.spotifyTokenService.refreshToken(refreshToken);
|
||||
|
||||
return ok(res, { token });
|
||||
return ok(res, {token});
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/token/generate",
|
||||
validateBody({
|
||||
authCode: { required: true, validator: v.isString({ nonEmpty: true }) },
|
||||
redirectUri: { required: true, validator: v.isUrl() },
|
||||
authCode: {required: true, validator: v.isString({nonEmpty: true})},
|
||||
redirectUri: {required: true, validator: v.isUrl()},
|
||||
}),
|
||||
asyncHandler(async (req, res) => {
|
||||
const { authCode, redirectUri } = req.body as { authCode: string; redirectUri: string };
|
||||
const {authCode, redirectUri} = req.body as { authCode: string; redirectUri: string };
|
||||
|
||||
const token = await this.tokenService.generateToken(authCode, redirectUri);
|
||||
const token = await this.spotifyTokenService.generateToken(authCode, redirectUri);
|
||||
|
||||
return ok(res, { token });
|
||||
return ok(res, {token});
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import {SpotifyTokenService} from "../../../db/services/spotifyTokenService";
|
||||
import {getCurrentlyPlaying} from "../../../db/services/spotifyApiService";
|
||||
import {CustomWebsocketEventUserService} from "./customWebsocketEventUserService";
|
||||
import {NoData} from "./NoData";
|
||||
import {UserService} from "../../../db/services/db/UserService";
|
||||
import {ExtendedWebSocket} from "../../../interfaces/extendedWebsocket";
|
||||
|
||||
export const SpotifyAsyncUpdateEvent = "SPOTIFY_UPDATE";
|
||||
|
||||
@@ -29,8 +31,15 @@ export class GetSpotifyUpdatesEvent extends CustomWebsocketEvent<NoData> {
|
||||
|
||||
export class GetSingleSpotifyUpdateEvent extends CustomWebsocketEventUserService<NoData> {
|
||||
|
||||
private readonly spotifyTokenService: SpotifyTokenService;
|
||||
|
||||
event = WebsocketEventType.GET_SINGLE_SPOTIFY_UPDATE;
|
||||
|
||||
constructor(ws: ExtendedWebSocket, userService: UserService, spotifyTokenService: SpotifyTokenService) {
|
||||
super(ws, userService);
|
||||
this.spotifyTokenService = spotifyTokenService;
|
||||
}
|
||||
|
||||
handler = async () => {
|
||||
console.log("Getting single Spotify update event");
|
||||
await this.spotifyUpdates();
|
||||
@@ -49,7 +58,7 @@ export class GetSingleSpotifyUpdateEvent extends CustomWebsocketEventUserService
|
||||
if (Date.now() > spotifyConfig.expirationDate.getTime()) {
|
||||
console.log("Token expired");
|
||||
|
||||
const token = await new SpotifyTokenService().refreshToken(spotifyConfig.refreshToken);
|
||||
const token = await this.spotifyTokenService.refreshToken(spotifyConfig.refreshToken);
|
||||
const newSpotifyConfig = {
|
||||
// use old refresh token because you don't get a new one
|
||||
refreshToken: user.spotifyConfig!.refreshToken,
|
||||
@@ -58,9 +67,10 @@ export class GetSingleSpotifyUpdateEvent extends CustomWebsocketEventUserService
|
||||
scope: token.scope,
|
||||
};
|
||||
await this.userService.updateUserById(user.id, {spotifyConfig: newSpotifyConfig});
|
||||
this.ws.user.spotifyConfig = newSpotifyConfig;
|
||||
console.log("Token refreshed and database updated");
|
||||
}
|
||||
const musicData = await getCurrentlyPlaying(user.spotifyConfig!.accessToken);
|
||||
const musicData = await getCurrentlyPlaying(this.ws.user.spotifyConfig!.accessToken);
|
||||
if (!musicData) {
|
||||
console.log("No music data found, maybe error from spotify, skipping this update");
|
||||
return;
|
||||
|
||||
@@ -10,12 +10,13 @@ import {UpdateUserEvent, UpdateUserSingleEvent} from "./updateUserEvent";
|
||||
import {CustomWebsocketEvent} from "./customWebsocketEvent";
|
||||
import {StopUpdateUserEvent} from "./stopUpdateUserEvent";
|
||||
import {UserService} from "../../../db/services/db/UserService";
|
||||
import {SpotifyTokenService} from "../../../db/services/spotifyTokenService";
|
||||
|
||||
export function getEventListeners(ws: ExtendedWebSocket, userService: UserService): CustomWebsocketEvent[] {
|
||||
export function getEventListeners(ws: ExtendedWebSocket, userService: UserService, spotifyTokenService: SpotifyTokenService): CustomWebsocketEvent[] {
|
||||
return [
|
||||
new GetStateEvent(ws),
|
||||
new GetSettingsEvent(ws),
|
||||
new GetSingleSpotifyUpdateEvent(ws, userService),
|
||||
new GetSingleSpotifyUpdateEvent(ws, userService, spotifyTokenService),
|
||||
new GetSpotifyUpdatesEvent(ws),
|
||||
new StopSpotifyUpdatesEvent(ws),
|
||||
new GetSingleWeatherUpdateEvent(ws),
|
||||
|
||||
@@ -2,9 +2,10 @@ import {ExtendedWebSocket} from "../../interfaces/extendedWebsocket";
|
||||
import {CustomWebsocketEvent} from "./websocketCustomEvents/customWebsocketEvent";
|
||||
import {UserService} from "../../db/services/db/UserService";
|
||||
import {getEventListeners} from "./websocketCustomEvents/websocketEventUtils";
|
||||
import {SpotifyTokenService} from "../../db/services/spotifyTokenService";
|
||||
|
||||
export class WebsocketEventHandler {
|
||||
constructor(private webSocket: ExtendedWebSocket, private userService: UserService) {
|
||||
constructor(private webSocket: ExtendedWebSocket, private userService: UserService, private spotifyTokenService: SpotifyTokenService) {
|
||||
}
|
||||
|
||||
public enableErrorEvent() {
|
||||
@@ -45,7 +46,7 @@ export class WebsocketEventHandler {
|
||||
}
|
||||
|
||||
public registerCustomEvents() {
|
||||
const events = getEventListeners(this.webSocket, this.userService);
|
||||
const events = getEventListeners(this.webSocket, this.userService, this.spotifyTokenService);
|
||||
events.forEach(this.registerCustomEvent, this);
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -7,18 +7,21 @@ import {WebsocketServerEventHandler} from "./utils/websocket/websocketServerEven
|
||||
import {WebsocketEventHandler} from "./utils/websocket/websocketEventHandler";
|
||||
import {WebsocketEventType} from "./utils/websocket/websocketCustomEvents/websocketEventType";
|
||||
import {UserService} from "./db/services/db/UserService";
|
||||
import {SpotifyTokenService} from "./db/services/spotifyTokenService";
|
||||
|
||||
export class ExtendedWebSocketServer {
|
||||
private readonly _wss: WebSocketServer;
|
||||
private readonly userService: UserService;
|
||||
private readonly spotifyTokenService: SpotifyTokenService;
|
||||
|
||||
constructor(server: Server, userService: UserService) {
|
||||
constructor(server: Server, userService: UserService, spotifyTokenService: SpotifyTokenService) {
|
||||
this._wss = new WebSocketServer({
|
||||
server,
|
||||
verifyClient: (info, callback) => verifyClient(info.req, callback),
|
||||
});
|
||||
|
||||
this.userService = userService;
|
||||
this.spotifyTokenService = spotifyTokenService;
|
||||
|
||||
this.setupWebSocket();
|
||||
}
|
||||
@@ -55,7 +58,7 @@ export class ExtendedWebSocketServer {
|
||||
private setupWebSocket() {
|
||||
const serverEventHandler = new WebsocketServerEventHandler(this.wss, this.userService);
|
||||
serverEventHandler.enableConnectionEvent((ws) => {
|
||||
const socketEventHandler = new WebsocketEventHandler(ws, this.userService);
|
||||
const socketEventHandler = new WebsocketEventHandler(ws, this.userService, this.spotifyTokenService);
|
||||
|
||||
console.log("WebSocket client connected");
|
||||
|
||||
|
||||
@@ -17,11 +17,8 @@ describe("SpotifyTokenService - Successful Initialization", () => {
|
||||
let spotifyTokenService: SpotifyTokenServiceType;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.stubEnv("SPOTIFY_CLIENT_ID", "test-client-id");
|
||||
vi.stubEnv("SPOTIFY_CLIENT_SECRET", "test-client-secret");
|
||||
|
||||
const { SpotifyTokenService } = await import("../../../src/db/services/spotifyTokenService");
|
||||
spotifyTokenService = new SpotifyTokenService();
|
||||
spotifyTokenService = new SpotifyTokenService("test-client-id","test-client-secret");
|
||||
});
|
||||
|
||||
const getExpectedAuthHeader = () => {
|
||||
|
||||
@@ -58,7 +58,7 @@ let server: http.Server;
|
||||
beforeAll(async () => {
|
||||
const { startServer } = await import("../src/index");
|
||||
|
||||
const instances = await startServer("not-used");
|
||||
const instances = await startServer("not-used", "not-used", "not-used");
|
||||
app = instances.app;
|
||||
server = instances.server;
|
||||
});
|
||||
|
||||
@@ -17,9 +17,7 @@ describe("SpotifyTokenGenerator", () => {
|
||||
|
||||
mockTokenService = createMockSpotifyTokenService();
|
||||
|
||||
vi.mocked(SpotifyTokenService).mockImplementation(() => mockTokenService as any);
|
||||
|
||||
const spotifyGenerator = new SpotifyTokenGenerator();
|
||||
const spotifyGenerator = new SpotifyTokenGenerator(mockTokenService as any);
|
||||
app = createTestApp(spotifyGenerator.createRouter(), "/spotify");
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { UserService } from "../../../../src/db/services/db/UserService";
|
||||
import {
|
||||
CustomWebsocketEventUserService
|
||||
} from "../../../../src/utils/websocket/websocketCustomEvents/customWebsocketEventUserService";
|
||||
import {createMockSpotifyTokenService} from "../../../helpers/testSetup";
|
||||
|
||||
type MockWs = {
|
||||
user: {
|
||||
@@ -41,7 +42,9 @@ describe("websocketEventUtils.getEventListeners", () => {
|
||||
updateUser: vi.fn(),
|
||||
} as any;
|
||||
|
||||
listeners = getEventListeners(mockWs as any, mockUserService);
|
||||
|
||||
|
||||
listeners = getEventListeners(mockWs as any, mockUserService, createMockSpotifyTokenService() as any);
|
||||
});
|
||||
|
||||
it("should return an array of event listener objects", () => {
|
||||
|
||||
@@ -3,11 +3,13 @@ import { WebsocketEventHandler } from "../../../src/utils/websocket/websocketEve
|
||||
import { ExtendedWebSocket } from "../../../src/interfaces/extendedWebsocket";
|
||||
import { CustomWebsocketEvent } from "../../../src/utils/websocket/websocketCustomEvents/customWebsocketEvent";
|
||||
import {UserService} from "../../../src/db/services/db/UserService";
|
||||
import {SpotifyTokenService} from "../../../src/db/services/spotifyTokenService";
|
||||
|
||||
describe("WebsocketEventHandler", () => {
|
||||
let mockWebSocket: Mocked<ExtendedWebSocket>;
|
||||
let websocketEventHandler: WebsocketEventHandler;
|
||||
let mockUserService: Mocked<UserService>;
|
||||
let mockSpotifyTokenService: Mocked<SpotifyTokenService>
|
||||
let registeredHandlers: Map<string, (...args: any[]) => void>;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -31,8 +33,10 @@ describe("WebsocketEventHandler", () => {
|
||||
|
||||
// not used in this test
|
||||
mockUserService = {} as Mocked<UserService>;
|
||||
mockSpotifyTokenService = {} as Mocked<SpotifyTokenService>;
|
||||
|
||||
websocketEventHandler = new WebsocketEventHandler(mockWebSocket, mockUserService);
|
||||
|
||||
websocketEventHandler = new WebsocketEventHandler(mockWebSocket, mockUserService, mockSpotifyTokenService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -6,7 +6,8 @@ import { WebsocketServerEventHandler } from "../src/utils/websocket/websocketSer
|
||||
import { WebsocketEventHandler } from "../src/utils/websocket/websocketEventHandler";
|
||||
import { getEventListeners } from "../src/utils/websocket/websocketCustomEvents/websocketEventUtils";
|
||||
import {UserService} from "../src/db/services/db/UserService";
|
||||
import {createMockUserService} from "./helpers/testSetup";
|
||||
import {createMockSpotifyTokenService, createMockUserService} from "./helpers/testSetup";
|
||||
import {SpotifyTokenService} from "../src/db/services/spotifyTokenService";
|
||||
|
||||
let mockWssInstance: Mocked<WebSocketServer>;
|
||||
let mockServerEventHandler: Mocked<WebsocketServerEventHandler>;
|
||||
@@ -27,6 +28,7 @@ describe("ExtendedWebSocketServer", () => {
|
||||
let mockHttpServer: Mocked<Server>;
|
||||
let extendedWss: ExtendedWebSocketServer;
|
||||
let mockUserService: Mocked<UserService>;
|
||||
let mockSpotifyService : Mocked<SpotifyTokenService>;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
@@ -46,8 +48,10 @@ describe("ExtendedWebSocketServer", () => {
|
||||
} as unknown as Mocked<WebSocketServer>;
|
||||
|
||||
mockUserService = createMockUserService();
|
||||
mockSpotifyService = createMockSpotifyTokenService() as any;
|
||||
|
||||
extendedWss = new ExtendedWebSocketServer(mockHttpServer, mockUserService);
|
||||
|
||||
extendedWss = new ExtendedWebSocketServer(mockHttpServer, mockUserService, mockSpotifyService);
|
||||
});
|
||||
|
||||
describe("Constructor and Setup", () => {
|
||||
@@ -117,7 +121,7 @@ describe("ExtendedWebSocketServer", () => {
|
||||
|
||||
it("should create and configure a WebsocketEventHandler for new clients", () => {
|
||||
connectionHandler(mockWsClient, {});
|
||||
expect(vi.mocked(WebsocketEventHandler)).toHaveBeenCalledWith(mockWsClient, mockUserService);
|
||||
expect(vi.mocked(WebsocketEventHandler)).toHaveBeenCalledWith(mockWsClient, mockUserService, mockSpotifyService);
|
||||
expect(mockClientEventHandler.enableErrorEvent).toHaveBeenCalled();
|
||||
expect(mockClientEventHandler.enablePongEvent).toHaveBeenCalled();
|
||||
expect(mockClientEventHandler.enableMessageEvent).toHaveBeenCalled();
|
||||
|
||||
Reference in New Issue
Block a user