first schema ver
This commit is contained in:
parent
38732a4cf6
commit
6ae23f7c07
4 changed files with 297 additions and 4 deletions
|
|
@ -4,6 +4,9 @@ import { db } from "./db";
|
||||||
import Elysia, { status, type Context } from "elysia";
|
import Elysia, { status, type Context } from "elysia";
|
||||||
import * as schema from "./db/auth-schema";
|
import * as schema from "./db/auth-schema";
|
||||||
|
|
||||||
|
export const SPOTIFY_CLIENT_ID = process.env.SPOTIFY_CLIENT_ID!;
|
||||||
|
export const SPOTIFY_CLIENT_SECRET = process.env.SPOTIFY_CLIENT_SECRET!;
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
database: drizzleAdapter(db, {
|
database: drizzleAdapter(db, {
|
||||||
provider: "pg",
|
provider: "pg",
|
||||||
|
|
@ -11,8 +14,8 @@ export const auth = betterAuth({
|
||||||
}),
|
}),
|
||||||
socialProviders: {
|
socialProviders: {
|
||||||
spotify: {
|
spotify: {
|
||||||
clientId: process.env.SPOTIFY_CLIENT_ID!,
|
clientId: SPOTIFY_CLIENT_ID,
|
||||||
clientSecret: process.env.SPOTIFY_CLIENT_SECRET!,
|
clientSecret: SPOTIFY_CLIENT_SECRET,
|
||||||
scope: [
|
scope: [
|
||||||
"user-read-playback-state",
|
"user-read-playback-state",
|
||||||
"user-read-currently-playing",
|
"user-read-currently-playing",
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,254 @@
|
||||||
import { integer, pgTable, varchar } from "drizzle-orm/pg-core";
|
import {
|
||||||
|
boolean,
|
||||||
|
index,
|
||||||
|
integer,
|
||||||
|
pgEnum,
|
||||||
|
pgTable,
|
||||||
|
primaryKey,
|
||||||
|
text,
|
||||||
|
timestamp,
|
||||||
|
uuid,
|
||||||
|
varchar,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
|
import { user } from "./auth-schema";
|
||||||
export * from "./auth-schema";
|
export * from "./auth-schema";
|
||||||
|
|
||||||
|
const platform = pgEnum("platform", ["spotify"]);
|
||||||
|
|
||||||
|
export const artist = pgTable("artist", {
|
||||||
|
id: uuid().defaultRandom().primaryKey().notNull(),
|
||||||
|
platform_id: text().notNull(),
|
||||||
|
platform: platform().notNull(),
|
||||||
|
name: text().notNull(),
|
||||||
|
type: text(),
|
||||||
|
popularity: integer(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const genre = pgTable("genre", {
|
||||||
|
id: uuid().defaultRandom().primaryKey().notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const artistGenre = pgTable(
|
||||||
|
"artist_genre",
|
||||||
|
{
|
||||||
|
artist: uuid()
|
||||||
|
.references(() => artist.id)
|
||||||
|
.notNull(),
|
||||||
|
genre: uuid()
|
||||||
|
.references(() => genre.id)
|
||||||
|
.notNull(),
|
||||||
|
},
|
||||||
|
(artistGenres) => [
|
||||||
|
primaryKey({
|
||||||
|
columns: [artistGenres.artist, artistGenres.genre],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const platformImage = pgTable("platform_image", {
|
||||||
|
id: uuid().defaultRandom().primaryKey().notNull(),
|
||||||
|
platform: platform().notNull(),
|
||||||
|
url: text(),
|
||||||
|
height: integer(),
|
||||||
|
width: integer(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const artistImage = pgTable(
|
||||||
|
"artist_image",
|
||||||
|
{
|
||||||
|
artist: uuid()
|
||||||
|
.references(() => artist.id)
|
||||||
|
.notNull(),
|
||||||
|
image: uuid()
|
||||||
|
.references(() => platformImage.id)
|
||||||
|
.notNull(),
|
||||||
|
},
|
||||||
|
(artistImages) => [
|
||||||
|
primaryKey({
|
||||||
|
columns: [artistImages.artist, artistImages.image],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const album = pgTable("album", {
|
||||||
|
id: uuid().defaultRandom().primaryKey().notNull(),
|
||||||
|
type: text(),
|
||||||
|
name: text(),
|
||||||
|
platform: platform(),
|
||||||
|
platform_id: text(),
|
||||||
|
popularity: integer(),
|
||||||
|
release_date: timestamp(),
|
||||||
|
label: text(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const albumImage = pgTable(
|
||||||
|
"album_image",
|
||||||
|
{
|
||||||
|
album: uuid()
|
||||||
|
.references(() => album.id)
|
||||||
|
.notNull(),
|
||||||
|
image: uuid()
|
||||||
|
.references(() => platformImage.id)
|
||||||
|
.notNull(),
|
||||||
|
},
|
||||||
|
(albumImage) => [
|
||||||
|
primaryKey({
|
||||||
|
columns: [albumImage.album, albumImage.image],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const track = pgTable(
|
||||||
|
"track",
|
||||||
|
{
|
||||||
|
id: uuid().defaultRandom().primaryKey().notNull(),
|
||||||
|
album: uuid()
|
||||||
|
.references(() => album.id)
|
||||||
|
.notNull(),
|
||||||
|
name: text(),
|
||||||
|
platform: platform(),
|
||||||
|
platform_id: text(),
|
||||||
|
popularity: integer(),
|
||||||
|
duration: integer(),
|
||||||
|
explicit: boolean(),
|
||||||
|
disc_number: integer(),
|
||||||
|
track_number: integer(),
|
||||||
|
},
|
||||||
|
(track) => [index().on([track.album])],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const trackArtist = pgTable(
|
||||||
|
"track_artist",
|
||||||
|
{
|
||||||
|
track: uuid()
|
||||||
|
.references(() => track.id)
|
||||||
|
.notNull(),
|
||||||
|
artist: uuid()
|
||||||
|
.references(() => artist.id)
|
||||||
|
.notNull(),
|
||||||
|
},
|
||||||
|
(trackArtist) => [
|
||||||
|
primaryKey({
|
||||||
|
columns: [trackArtist.track, trackArtist.artist],
|
||||||
|
}),
|
||||||
|
index().on([trackArtist.track]),
|
||||||
|
index().on([trackArtist.artist]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const topTimeline = pgEnum("top_timeline", [
|
||||||
|
"short_term",
|
||||||
|
"medium_term",
|
||||||
|
"long_term",
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const topTrack = pgTable(
|
||||||
|
"top_track",
|
||||||
|
{
|
||||||
|
track: uuid()
|
||||||
|
.references(() => track.id)
|
||||||
|
.notNull(),
|
||||||
|
position: integer().notNull(),
|
||||||
|
user: text()
|
||||||
|
.references(() => user.id)
|
||||||
|
.notNull(),
|
||||||
|
timeline: topTimeline().notNull(),
|
||||||
|
},
|
||||||
|
(topTrack) => [
|
||||||
|
primaryKey({
|
||||||
|
columns: [topTrack.track, topTrack.user, topTrack.timeline],
|
||||||
|
}),
|
||||||
|
index().on([topTrack.user]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const topArtist = pgTable(
|
||||||
|
"top_artist",
|
||||||
|
{
|
||||||
|
artist: uuid()
|
||||||
|
.references(() => artist.id)
|
||||||
|
.notNull(),
|
||||||
|
position: integer().notNull(),
|
||||||
|
user: text()
|
||||||
|
.references(() => user.id)
|
||||||
|
.notNull(),
|
||||||
|
timeline: topTimeline().notNull(),
|
||||||
|
},
|
||||||
|
(topArtist) => [
|
||||||
|
primaryKey({
|
||||||
|
columns: [topArtist.artist, topArtist.user, topArtist.timeline],
|
||||||
|
}),
|
||||||
|
index().on([topArtist.user]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const savedAlbum = pgTable(
|
||||||
|
"saved_album",
|
||||||
|
{
|
||||||
|
album: uuid()
|
||||||
|
.references(() => album.id)
|
||||||
|
.notNull(),
|
||||||
|
user: text()
|
||||||
|
.references(() => user.id)
|
||||||
|
.notNull(),
|
||||||
|
saved_at: timestamp("saved_at").defaultNow().notNull(),
|
||||||
|
},
|
||||||
|
(savedAlbum) => [
|
||||||
|
primaryKey({
|
||||||
|
columns: [savedAlbum.album, savedAlbum.user],
|
||||||
|
}),
|
||||||
|
index().on([savedAlbum.user]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const savedTrack = pgTable(
|
||||||
|
"saved_track",
|
||||||
|
{
|
||||||
|
track: uuid()
|
||||||
|
.references(() => track.id)
|
||||||
|
.notNull(),
|
||||||
|
user: text()
|
||||||
|
.references(() => user.id)
|
||||||
|
.notNull(),
|
||||||
|
saved_at: timestamp("saved_at").defaultNow().notNull(),
|
||||||
|
},
|
||||||
|
(savedTrack) => [
|
||||||
|
primaryKey({
|
||||||
|
columns: [savedTrack.track, savedTrack.user],
|
||||||
|
}),
|
||||||
|
index().on([savedTrack.user]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const followedArtist = pgTable(
|
||||||
|
"followed_artist",
|
||||||
|
{
|
||||||
|
artist: uuid()
|
||||||
|
.references(() => artist.id)
|
||||||
|
.notNull(),
|
||||||
|
user: text()
|
||||||
|
.references(() => user.id)
|
||||||
|
.notNull(),
|
||||||
|
},
|
||||||
|
(followedArtist) => [
|
||||||
|
primaryKey({
|
||||||
|
columns: [followedArtist.artist, followedArtist.user],
|
||||||
|
}),
|
||||||
|
index().on([followedArtist.user]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
export const playbackHistory = pgTable(
|
||||||
|
"playback_history",
|
||||||
|
{
|
||||||
|
id: uuid().defaultRandom().primaryKey().notNull(),
|
||||||
|
track: uuid()
|
||||||
|
.references(() => track.id)
|
||||||
|
.notNull(),
|
||||||
|
user: text()
|
||||||
|
.references(() => user.id)
|
||||||
|
.notNull(),
|
||||||
|
played_at: timestamp("played_at").defaultNow().notNull(),
|
||||||
|
},
|
||||||
|
(playbackHistory) => [index().on([playbackHistory.user])],
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { Elysia, t } from "elysia";
|
import { Elysia, t } from "elysia";
|
||||||
import { betterAuthElysia } from "./auth";
|
import { betterAuthElysia } from "./auth";
|
||||||
|
import { syncApp } from "./routes/sync";
|
||||||
|
|
||||||
const app = new Elysia().use(betterAuthElysia).listen(4000);
|
const app = new Elysia().use(betterAuthElysia).use(syncApp).listen(4000);
|
||||||
|
|
||||||
export type App = typeof app;
|
export type App = typeof app;
|
||||||
|
|
|
||||||
37
api/src/routes/sync.ts
Normal file
37
api/src/routes/sync.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import Elysia from "elysia";
|
||||||
|
import { auth, betterAuthElysia, SPOTIFY_CLIENT_ID } from "../auth";
|
||||||
|
import { SpotifyApi } from "@spotify/web-api-ts-sdk";
|
||||||
|
|
||||||
|
export const syncApp = new Elysia().use(betterAuthElysia).post(
|
||||||
|
"/sync",
|
||||||
|
async ({ user }) => {
|
||||||
|
const accessToken = await auth.api.getAccessToken({
|
||||||
|
body: {
|
||||||
|
userId: user.id,
|
||||||
|
providerId: "spotify",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const sdk = SpotifyApi.withAccessToken(SPOTIFY_CLIENT_ID, {
|
||||||
|
access_token: accessToken.accessToken,
|
||||||
|
expires_in: Date.now() - Number(accessToken.accessTokenExpiresAt!),
|
||||||
|
expires: Number(accessToken.accessTokenExpiresAt),
|
||||||
|
refresh_token: "",
|
||||||
|
token_type: "",
|
||||||
|
});
|
||||||
|
for (const timeline of [
|
||||||
|
"short_term",
|
||||||
|
"medium_term",
|
||||||
|
"long_term",
|
||||||
|
] as const) {
|
||||||
|
const topArtists = await sdk.currentUser.topItems(
|
||||||
|
"artists",
|
||||||
|
timeline,
|
||||||
|
50,
|
||||||
|
);
|
||||||
|
const topTracks = await sdk.currentUser.topItems("tracks", timeline, 50);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
auth: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
Loading…
Reference in a new issue