first schema ver

This commit is contained in:
Daniel Bulant 2026-04-19 19:49:37 +02:00
parent 38732a4cf6
commit 6ae23f7c07
No known key found for this signature in database
4 changed files with 297 additions and 4 deletions

View file

@ -4,6 +4,9 @@ import { db } from "./db";
import Elysia, { status, type Context } from "elysia";
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({
database: drizzleAdapter(db, {
provider: "pg",
@ -11,8 +14,8 @@ export const auth = betterAuth({
}),
socialProviders: {
spotify: {
clientId: process.env.SPOTIFY_CLIENT_ID!,
clientSecret: process.env.SPOTIFY_CLIENT_SECRET!,
clientId: SPOTIFY_CLIENT_ID,
clientSecret: SPOTIFY_CLIENT_SECRET,
scope: [
"user-read-playback-state",
"user-read-currently-playing",

View file

@ -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";
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])],
);

View file

@ -1,6 +1,7 @@
import { Elysia, t } from "elysia";
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;

37
api/src/routes/sync.ts Normal file
View 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,
},
);