From a79cc8e1726ad4855ea2687cea2fc83d8679497b Mon Sep 17 00:00:00 2001 From: Jorge Vargas Date: Thu, 29 Aug 2024 23:55:25 -0600 Subject: [PATCH] Migrate to Sequelize V7 --- .eslintrc.cjs | 25 +++-- package.json | 6 +- prettier.config.cjs | 17 +-- src/graphql/apolloClient.mts | 24 ++--- src/graphql/resolvers/index.ts | 8 +- src/graphql/resolvers/mutations/index.js | 12 --- src/graphql/resolvers/mutations/user.js | 72 +++---------- .../resolvers/queries/{album.ts => album.js} | 9 +- src/graphql/resolvers/queries/index.ts | 16 --- src/graphql/resolvers/queries/requests.js | 42 +++++--- src/graphql/resolvers/queries/search.ts | 14 +-- src/graphql/resolvers/queries/site.ts | 30 +++--- .../resolvers/queries/{vgmdb.ts => vgmdb.js} | 0 src/graphql/resolvers/types/album.ts | 4 +- src/graphql/resolvers/types/index.ts | 8 -- src/graphql/resolvers/types/user.js | 41 +++---- src/graphql/typeDefs/user.graphql | 101 +++++++++--------- src/sequelize/index.js | 22 ---- src/sequelize/models/album.js | 22 ---- src/sequelize/models/album.ts | 47 ++++++++ src/sequelize/models/animation.js | 2 +- src/sequelize/models/artist.js | 20 ---- src/sequelize/models/artist.ts | 15 +++ src/sequelize/models/category.js | 19 ---- src/sequelize/models/category.ts | 10 ++ src/sequelize/models/classification.js | 2 +- src/sequelize/models/comment.js | 2 +- src/sequelize/models/config.js | 23 ---- src/sequelize/models/config.ts | 15 +++ src/sequelize/models/disc.js | 2 +- src/sequelize/models/download.js | 2 +- src/sequelize/models/game.js | 2 +- src/sequelize/models/index.js | 45 -------- src/sequelize/models/link.js | 2 +- src/sequelize/models/log.js | 2 +- src/sequelize/models/platform.js | 2 +- src/sequelize/models/publisher.js | 2 +- src/sequelize/models/request.js | 2 +- src/sequelize/models/role.js | 2 +- src/sequelize/models/series.js | 2 +- src/sequelize/models/store.js | 2 +- src/sequelize/models/submission.js | 2 +- src/sequelize/models/user.js | 2 +- src/sequelize/relations.js | 48 +++++++-- tsconfig.json | 4 + 45 files changed, 329 insertions(+), 422 deletions(-) delete mode 100644 src/graphql/resolvers/mutations/index.js rename src/graphql/resolvers/queries/{album.ts => album.js} (90%) delete mode 100644 src/graphql/resolvers/queries/index.ts rename src/graphql/resolvers/queries/{vgmdb.ts => vgmdb.js} (100%) delete mode 100644 src/graphql/resolvers/types/index.ts delete mode 100644 src/sequelize/index.js delete mode 100644 src/sequelize/models/album.js create mode 100644 src/sequelize/models/album.ts delete mode 100644 src/sequelize/models/artist.js create mode 100644 src/sequelize/models/artist.ts delete mode 100644 src/sequelize/models/category.js create mode 100644 src/sequelize/models/category.ts delete mode 100644 src/sequelize/models/config.js create mode 100644 src/sequelize/models/config.ts delete mode 100644 src/sequelize/models/index.js diff --git a/.eslintrc.cjs b/.eslintrc.cjs index a22f0a1..567854e 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,21 +1,24 @@ /** @type {import("eslint").Linter.Config} */ module.exports = { - extends: ["plugin:astro/recommended"], - parser: "@typescript-eslint/parser", + extends: ['plugin:astro/recommended'], + parser: '@typescript-eslint/parser', parserOptions: { tsconfigRootDir: __dirname, - sourceType: "module", - ecmaVersion: "latest", + sourceType: 'module', + ecmaVersion: 'latest' }, overrides: [ { - files: ["*.astro"], - parser: "astro-eslint-parser", + files: ['*.astro'], + parser: 'astro-eslint-parser', parserOptions: { - parser: "@typescript-eslint/parser", - extraFileExtensions: [".astro"], + parser: '@typescript-eslint/parser', + extraFileExtensions: ['.astro'] }, - rules: {}, - }, + rules: {} + } ], -}; + rules: { + 'no-multiple-empty-lines': [2, { max: 1 }] + } +} diff --git a/package.json b/package.json index c1bf914..331e3cc 100644 --- a/package.json +++ b/package.json @@ -27,14 +27,13 @@ "@graphql-tools/resolvers-composition": "^7.0.1", "@graphql-tools/schema": "^10.0.4", "@inlang/paraglide-astro": "^0.2.2", + "@sequelize/core": "^7.0.0-alpha.37", + "@sequelize/mysql": "^7.0.0-alpha.41", "astro": "^4.13.1", "astro-icon": "^1.1.1", "auth-astro": "^4.1.2", "bcrypt": "^5.1.1", "graphql-scalars": "^1.23.0", - "lodash": "^4.17.21", - "mysql2": "^3.11.0", - "sequelize": "^6.37.3", "sharp": "^0.33.5", "tailwindcss": "^3.4.8" }, @@ -42,7 +41,6 @@ "@inlang/paraglide-js": "1.11.2", "@parcel/watcher": "^2.4.1", "@types/bcrypt": "^5.0.2", - "@types/lodash": "^4.17.7", "@typescript-eslint/parser": "^6.21.0", "concurrently": "^8.2.2", "eslint": "^8.57.0", diff --git a/prettier.config.cjs b/prettier.config.cjs index f25a61d..ccfd1d2 100644 --- a/prettier.config.cjs +++ b/prettier.config.cjs @@ -1,14 +1,15 @@ /** @type {import("prettier").Config} */ module.exports = { - ...require("prettier-config-standard"), + ...require('prettier-config-standard'), + pluginSearchDirs: [__dirname], - plugins: [require.resolve("prettier-plugin-astro")], + plugins: [require.resolve('prettier-plugin-astro')], overrides: [ { - files: "*.astro", + files: '*.astro', options: { - parser: "astro", - }, - }, - ], -}; + parser: 'astro' + } + } + ] +} diff --git a/src/graphql/apolloClient.mts b/src/graphql/apolloClient.mts index a7bd788..d17688f 100644 --- a/src/graphql/apolloClient.mts +++ b/src/graphql/apolloClient.mts @@ -2,26 +2,26 @@ import ApolloPackage from '@apollo/client' const { ApolloClient, InMemoryCache } = ApolloPackage; import { SchemaLink } from "@apollo/client/link/schema" import { makeExecutableSchema } from "@graphql-tools/schema"; -import { loadFilesSync } from '@graphql-tools/load-files' -import { mergeResolvers } from '@graphql-tools/merge' -import path from "node:path" - +import { getSession } from 'auth-astro/server'; +import type { Session } from '@auth/core/types'; import { typeDefs } from "./__generated__/typeDefs.generated"; -// import { resolvers } from "./__generated__/resolvers.generated"; -import db from "@/sequelize"; -import resolverArray from '@/graphql/resolvers' - -export const resolvers = mergeResolvers(resolverArray) +import resolvers from '@/graphql/resolvers' +import type { MySqlDialect } from '@sequelize/mysql'; +import { Sequelize, type ModelStatic, type Options } from '@sequelize/core' const schema = makeExecutableSchema({ typeDefs, resolvers }) -export type ResolverContext = { request?: Request, db: any /*session?: Session */ } +export type ResolverContext = { request?: Request, session?: Session } + +const envOptions: Options = JSON.parse(import.meta.env.SEQUELIZE) || {} export async function getApolloClient(request?: Request) { - // const session = request ? await getSession(request) : undefined + const session = request ? await getSession(request) : null + const db = new Sequelize(envOptions) + envOptions.models = Object.values(await import.meta.glob('@/sequelize/models/**/*.ts', { eager: true, import: 'default' })) return new ApolloClient({ ssrMode: true, - link: new SchemaLink({ schema, context: { request, db } }), + link: new SchemaLink({ schema, context: { request, session } }), cache: new InMemoryCache() }) } \ No newline at end of file diff --git a/src/graphql/resolvers/index.ts b/src/graphql/resolvers/index.ts index 4606712..03b52ed 100644 --- a/src/graphql/resolvers/index.ts +++ b/src/graphql/resolvers/index.ts @@ -1,7 +1,7 @@ -// import mutations from './mutations' -import queries from './queries' -import types from './types' +import { mergeResolvers } from '@graphql-tools/merge' +import type { IResolvers } from '@graphql-tools/utils' -const resolvers = { /*...mutations,*/ ...queries, ...types } +const imports: IResolvers[] = Object.values(import.meta.glob('./**/*.ts', { eager: true, import: 'default' })) +const resolvers = mergeResolvers(imports) export default resolvers diff --git a/src/graphql/resolvers/mutations/index.js b/src/graphql/resolvers/mutations/index.js deleted file mode 100644 index 4e41fce..0000000 --- a/src/graphql/resolvers/mutations/index.js +++ /dev/null @@ -1,12 +0,0 @@ -import merge from 'lodash/merge' - -import comments from './comments' -import create from './create' -import requests from './requests' -import site from './site' -import update from './update' -import user from './user' - -const mutations = merge(comments, create, requests, site, update, user) - -export default mutations diff --git a/src/graphql/resolvers/mutations/user.js b/src/graphql/resolvers/mutations/user.js index d5a7817..6e0835e 100644 --- a/src/graphql/resolvers/mutations/user.js +++ b/src/graphql/resolvers/mutations/user.js @@ -1,26 +1,11 @@ -import bcrypt from 'bcrypt' -import generator from 'generate-password' import { composeResolvers } from '@graphql-tools/resolvers-composition' -import { DateTime } from 'luxon' -import { Op } from 'sequelize' -import path from 'path' -import fs from 'fs-extra' -import sharp from 'sharp' - -import { createForgor } from '@/server/utils/forgor' -import { isAuthedApp } from '@/server/utils/resolvers' -import { processImage } from '@/server/utils/image' -import { getSession, getUser } from '@/next/utils/getSession' -import { - ForbiddenError, - UserInputError -} from '@/next/server/utils/graphQLErrors' +import type { Resolvers } from '@/graphql/__generated__/types.generated' const resolversComposition = { - 'Mutation.updateUser': [isAuthedApp] + //'Mutation.updateUser': [isAuthedApp] } -const streamToString = (stream) => { +/* const streamToString = (stream) => { const chunks = [] return new Promise((resolve, reject) => { stream.on('data', (chunk) => chunks.push(Buffer.from(chunk))) @@ -45,51 +30,28 @@ async function cropPFP(streamItem, username, imgId) { sharpImage = sharpImage.extract( width > height ? { - left: Math.floor((width - height) / 2), - top: 0, - width: height, - height - } + left: Math.floor((width - height) / 2), + top: 0, + width: height, + height + } : { - left: 0, - top: Math.floor((height - width) / 2), - width, - height: width - } + left: 0, + top: Math.floor((height - width) / 2), + width, + height: width + } ) } await sharpImage.resize({ width: 200, height: 200 }).png().toFile(fullPath) return await processImage(fullPath) -} +}*/ -const resolvers = { +const resolvers: Resolvers = { Mutation: { - login: async (_, { username, password }, { db }) => { - const user = await db.models.user.findByPk(username) - if (!user) throw UserInputError() - - const valid = await bcrypt.compare(password, user.password) - if (!valid) throw UserInputError() - - const session = await getSession() - session.username = user.username - // Remove this when new site version is fully implemented - session.permissions = (await user.getRoles()) - .map((r) => r.permissions) - .flat() - await session.save() - - return 200 - }, - logout: async () => { - const session = await getSession() - await session.destroy() - - return 200 - }, - registerUser: async (_, { username, email, pfp }, { db }) => { + /*registerUser: async (_, { username, email, pfp }, { db }) => { await Promise.all([ db.models.user.findByPk(username).then((result) => { if (result) throw UserInputError('Username already in use') @@ -213,7 +175,7 @@ const resolvers = { await role.destroy() return name - } + }*/ } } diff --git a/src/graphql/resolvers/queries/album.ts b/src/graphql/resolvers/queries/album.js similarity index 90% rename from src/graphql/resolvers/queries/album.ts rename to src/graphql/resolvers/queries/album.js index b9b31d4..8c621d0 100644 --- a/src/graphql/resolvers/queries/album.ts +++ b/src/graphql/resolvers/queries/album.js @@ -1,9 +1,8 @@ -import type { Resolvers } from '@/graphql/__generated__/types.generated' -import Sequelize from 'sequelize' +import Sequelize from '@/sequelize' const { Op } = Sequelize -const resolvers: Resolvers = { +const resolvers = { Query: { artists: (parent, args, { db }, info) => db.models.artist.findAll(), platforms: (parent, args, { db }, info) => db.models.platform.findAll(), @@ -19,9 +18,7 @@ const resolvers: Resolvers = { album: (_, { id }, { db }) => db.models.album.findByPk(id), downloads: (parent, { id }, { db }) => db.models.download.findAll({ where: { albumId: id } }), - albums: (_, __, { db }, info) => - db.models.album.findAll({ - }), + albums: (_, __, { db }, info) => db.models.album.findAll({}), platform: async (parent, { id }, { db }) => db.models.platform.findByPk(id), animation: (parent, { id }, { db }) => db.models.animation.findByPk(id), diff --git a/src/graphql/resolvers/queries/index.ts b/src/graphql/resolvers/queries/index.ts deleted file mode 100644 index 7c94e8c..0000000 --- a/src/graphql/resolvers/queries/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import merge from 'lodash/merge' - -import search from './search' -import site from './site' -/* -import album from './album' -import requests from './requests' - - -import user from './user' -import vgmdb from './vgmdb' -*/ - -const queries = merge(search, site) - -export default queries diff --git a/src/graphql/resolvers/queries/requests.js b/src/graphql/resolvers/queries/requests.js index f124470..a62760d 100644 --- a/src/graphql/resolvers/queries/requests.js +++ b/src/graphql/resolvers/queries/requests.js @@ -1,24 +1,30 @@ -import { Op, fn, col, where } from 'sequelize' +import { Op, fn, col, where } from '@/sequelize' const resolvers = { Query: { - requests: (_, { - state = ['complete', 'hold', 'pending'], - donator = [true, false] - }, { db }) => db.models.request.findAll({ where: { state, donator } }), - request: (_, { link }, { db }) => db.models.request.findOne({ where: { link } }), + requests: ( + _, + { state = ['complete', 'hold', 'pending'], donator = [true, false] }, + { db } + ) => db.models.request.findAll({ where: { state, donator } }), + request: (_, { link }, { db }) => + db.models.request.findOne({ where: { link } }), - searchRequests: async (_, { - state = ['complete', 'hold', 'pending'], - donator = [true, false], - limit = 10, - page = 0, - filter - }, { db }) => { + searchRequests: async ( + _, + { + state = ['complete', 'hold', 'pending'], + donator = [true, false], + limit = 10, + page = 0, + filter + }, + { db } + ) => { const options = { limit, offset: limit * page } const optionsWhere = { state, donator } - async function exactSearch () { + async function exactSearch() { if (!filter) return const results = await db.models.request.findAndCountAll({ @@ -37,7 +43,7 @@ const resolvers = { if (results.rows.length > 0) return results } - function looseSearch () { + function looseSearch() { return db.models.request.findAndCountAll({ where: [ optionsWhere, @@ -47,7 +53,7 @@ const resolvers = { }) } - return await exactSearch() || looseSearch() + return (await exactSearch()) || looseSearch() }, submissions: (_, args, context) => { @@ -63,7 +69,9 @@ const resolvers = { { id: filter }, { vgmdb: filter }, { userUsername: filter }, - where(fn('LOWER', col('title')), { [Op.like]: `%${filter.toLowerCase()}%` }) + where(fn('LOWER', col('title')), { + [Op.like]: `%${filter.toLowerCase()}%` + }) ] } ] diff --git a/src/graphql/resolvers/queries/search.ts b/src/graphql/resolvers/queries/search.ts index 31fae50..e892c30 100644 --- a/src/graphql/resolvers/queries/search.ts +++ b/src/graphql/resolvers/queries/search.ts @@ -1,14 +1,14 @@ -//@ts-ignore -import { Op, literal } from 'sequelize' +import { Op, literal } from '@sequelize/core' import type { Resolvers } from '@/graphql/__generated__/types.generated' - +import Category from '@/sequelize/models/category' +import Album from '@/sequelize/models/album' const fuzzySearch = (words: string[]) => `^${words.map(w => `(?=.*\b${w}\b)`)}.+/i` const resolvers: Resolvers = { Query: { - searchAlbum: (_, args, { db }) => { + searchAlbum: async (_, args, { db }) => { const { title, categories, limit = 10, offset = 0, order = ['createdAt'], mode = 'DESC', status = ['show'] } = args const fuzzyCondition = title ? { [Op.or]: [ @@ -18,9 +18,9 @@ const resolvers: Resolvers = { } : {} const include = [] - if (categories) include.push({ model: db.models.category, where: { name: { [Op.in]: categories } } }) + if (categories) include.push({ model: Category, where: { name: { [Op.in]: categories } } }) - return db.models.album.findAndCountAll({ + const result = await Album.findAndCountAll({ limit, offset, where: { ...fuzzyCondition, @@ -29,6 +29,8 @@ const resolvers: Resolvers = { include, order: [literal('`album`.`status` = \'coming\' DESC'), ...order.map(o => [o, mode])] }) + + return result } /* searchAlbumByArtist: async (parent, { name, categories, limit, page = 0, order = ['createdAt'], mode = 'DESC', status = ['show'] }, { db }) => { const include = [{ model: db.models.artist, where: { name: { [Op.like]: `%${name}%` } } }] diff --git a/src/graphql/resolvers/queries/site.ts b/src/graphql/resolvers/queries/site.ts index 0b1aa0c..247fdf7 100644 --- a/src/graphql/resolvers/queries/site.ts +++ b/src/graphql/resolvers/queries/site.ts @@ -2,6 +2,8 @@ import type { Resolvers } from '@/graphql/__generated__/types.generated' import { composeResolvers } from '@graphql-tools/resolvers-composition' +import Config from '@/sequelize/models/config' + // import { hasRole } from '@/server/utils/resolvers' const resolversComposition = { @@ -9,33 +11,31 @@ const resolversComposition = { } const resolvers: Resolvers = { Query: { - config: (_, args, context) => { + config: async (_, args) => { const { name } = args - const { db } = context + const [result] = await Config.findOrCreate({ where: { name } }) - return db.models.config - .findOrCreate({ where: { name } }) - .then(() => db.models.config.findByPk(name)) + return result }, - highlight: async (parent, args, { db }) => { + /* highlight: async (parent, args, { db }) => { const { value } = await db.models.config.findByPk('highlight') return db.models.album.findByPk(value) - }, + }, - /* banners: async (parent, args) => { + banners: async (parent, args) => { const filePaths = await fg(['/var/www/soc_img/img/live/**/ /**.png']) const images = filePaths.map((f) => f.split('/').pop()) return images -}, */ +}, - recentComments: async (parent, { limit = 5 }, { db }) => { - return db.models.comment.findAll({ - limit, - order: [['updatedAt', 'DESC']] - }) - } + recentComments: async (parent, { limit = 5 }, { db }) => { + return db.models.comment.findAll({ + limit, + order: [['updatedAt', 'DESC']] + }) +} */ } } diff --git a/src/graphql/resolvers/queries/vgmdb.ts b/src/graphql/resolvers/queries/vgmdb.js similarity index 100% rename from src/graphql/resolvers/queries/vgmdb.ts rename to src/graphql/resolvers/queries/vgmdb.js diff --git a/src/graphql/resolvers/types/album.ts b/src/graphql/resolvers/types/album.ts index f15f478..c5e9b0b 100644 --- a/src/graphql/resolvers/types/album.ts +++ b/src/graphql/resolvers/types/album.ts @@ -1,11 +1,11 @@ // import { GraphQLUpload } from 'graphql-upload-minimal' import type { Resolvers } from "@/graphql/__generated__/types.generated" +import type Album from "@/sequelize/models/album" const resolvers: Resolvers = { // Upload: GraphQLUpload, Album: { - // @ts-ignore - artists: (parent, args, context, info) => parent.getArtists(), + artists: (parent: Album, args, context, info) => parent.getArtists(), /* categories: (parent, args, context, info) => parent.getCategories(), classifications: (parent, args, context, info) => parent.getClassifications(), diff --git a/src/graphql/resolvers/types/index.ts b/src/graphql/resolvers/types/index.ts deleted file mode 100644 index bcb0922..0000000 --- a/src/graphql/resolvers/types/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -// import merge from 'lodash/merge' - -import album from './album' -// import user from './user' - -const types = /*(merge(*/album/*, user)*/ - -export default types diff --git a/src/graphql/resolvers/types/user.js b/src/graphql/resolvers/types/user.js index 06c7d98..c5ba52d 100644 --- a/src/graphql/resolvers/types/user.js +++ b/src/graphql/resolvers/types/user.js @@ -1,31 +1,34 @@ -import { Op } from 'sequelize' +import type { Resolvers } from '@/graphql/__generated__/types.generated' -import pages from '@/next/constants/pages.json' -import { getUser } from '@/next/utils/getSession' - -const userResolvable = { - roles: parent => parent.getRoles(), +const userResolvable: Resolvers = { + /* roles: parent => parent.getRoles(), permissions: async parent => { const roles = await parent.getRoles() return roles.map(r => r.permissions).flat() - }, - pages: async parent => { + }, */ + pages: async (_, __, { session }) => { const roles = await parent.getRoles() const permissions = roles.map(r => r.permissions).flat() return pages.filter(({ perms }) => perms.length === 0 || perms.some(r => permissions.includes(r))) }, - comments: (user, _, { db }) => user.getComments({ where: { albumId: { [Op.not]: null } } }), - favorites: user => user.getAlbums(), - imgUrl: async user => `https://cdn.sittingonclouds.net/user/${ - user.imgId ? `${user.username}_${user.imgId}` : 'default' - }.png` + /* comments: (user, _, { db }) => user.getComments({ where: { albumId: { [Op.not]: null } } }), + favorites: user => user.getAlbums(), + imgUrl: async user => `https://cdn.sittingonclouds.net/user/${user.imgId ? `${user.username}_${user.imgId}` : 'default' + }.png` */ } -const funcs = { - User: userResolvable, - UserMe: userResolvable, - Role: { permissions: parent => typeof parent.permissions === 'string' || parent.permissions instanceof String ? JSON.parse(parent.permissions) : parent.permissions }, +const resolvers: Resolvers = { + // User: userResolvable, + UserMe: { + pages: async (_, __, { session }) => { + const roles = await parent.getRoles() + const permissions = roles.map(r => r.permissions).flat() + + return pages.filter(({ perms }) => perms.length === 0 || perms.some(r => permissions.includes(r))) + }, + }, + /*Role: { permissions: parent => typeof parent.permissions === 'string' || parent.permissions instanceof String ? JSON.parse(parent.permissions) : parent.permissions }, Submission: { submitter: submission => submission.getUser(), links: async (submission, _, { db }) => { @@ -40,7 +43,7 @@ const funcs = { return submission.links }, request: submission => submission.getRequest() - } + }*/ } -export default funcs +export default resolvers diff --git a/src/graphql/typeDefs/user.graphql b/src/graphql/typeDefs/user.graphql index 7e7bbec..a9d89e8 100644 --- a/src/graphql/typeDefs/user.graphql +++ b/src/graphql/typeDefs/user.graphql @@ -1,57 +1,60 @@ - type User { - username: String! - roles: [Role]! - permissions: [String]! - pages: [Page]! - createdAt: Float! - placeholder: String! - imgUrl: String! - } +type User { + username: String! + roles: [Role]! + permissions: [String]! + pages: [Page]! + createdAt: Float! + imgUrl: String! +} - type UserMe { - email: String! - username: String! - roles: [Role]! - permissions: [String]! - pages: [Page]! - createdAt: Float! - placeholder: String! - imgUrl: String! - } +type UserMe { + email: String! + username: String! + roles: [Role]! + permissions: [String]! + pages: [Page]! + createdAt: Float! + imgUrl: String! +} - type Page { - url: String! - perms: [String!]! - } +type Page { + url: String! + perms: [String!]! +} - type Role { - name: String! - permissions: [String]! - } +type Role { + name: String! + permissions: [String]! +} - type Query { - me: UserMe - roles: [Role]! - permissions: [String]! - users(search: String): [User]! - user(username: String!): User +type Query { + me: UserMe + roles: [Role]! + permissions: [String]! + users(search: String): [User]! + user(username: String!): User - login(username: String!, password: String!): Int! - } + login(username: String!, password: String!): Int! +} - type Mutation { - login(username: String!, password: String!): Int! - logout: Int! - - registerUser(email: String!, username: String!, pfp: Upload): Boolean! - updateUserRoles(username: String!, roles: [String]!): Boolean! - deleteUser(username: String!): Int +type Mutation { + login(username: String!, password: String!): Int! + logout: Int! - createForgorLink(key: String!): Boolean! - updatePass(key: String!, pass:String!): Boolean! - updateUser(username: String, password: String, email: String, pfp: Upload): Boolean! + registerUser(email: String!, username: String!, pfp: Upload): Boolean! + updateUserRoles(username: String!, roles: [String]!): Boolean! + deleteUser(username: String!): Int - createRole(name: String!, permissions: [String]!): Role - updateRole(key:String!, name: String!, permissions: [String]!): Role - deleteRole(name: String!): String - } + createForgorLink(key: String!): Boolean! + updatePass(key: String!, pass: String!): Boolean! + updateUser( + username: String + password: String + email: String + pfp: Upload + ): Boolean! + + createRole(name: String!, permissions: [String]!): Role + updateRole(key: String!, name: String!, permissions: [String]!): Role + deleteRole(name: String!): String +} diff --git a/src/sequelize/index.js b/src/sequelize/index.js deleted file mode 100644 index f942e49..0000000 --- a/src/sequelize/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import mysql2 from 'mysql2' -import { Sequelize } from 'sequelize' - -import relations from './relations' -import models from './models' - -const options = process.env.GITHUB_ACTIONS - ? 'sqlite::memory:' - : JSON.parse(import.meta.env.SEQUELIZE) - -if (!process.env.GITHUB_ACTIONS && options.dialect === 'mysql') - options.dialectModule = mysql2 - -if (import.meta.env.DEV && options.logging === undefined) - options.logging = console.log - -const db = new Sequelize(options) - -Object.values(models).forEach((model) => model(db)) -relations(db) - -export default db diff --git a/src/sequelize/models/album.js b/src/sequelize/models/album.js deleted file mode 100644 index e0c067a..0000000 --- a/src/sequelize/models/album.js +++ /dev/null @@ -1,22 +0,0 @@ -import { DataTypes } from 'sequelize' -import { PLACEHOLDER } from '@/constants' - -const model = (sequelize) => - sequelize.define('album', { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true - }, - title: DataTypes.STRING, - subTitle: DataTypes.TEXT, - releaseDate: DataTypes.DATEONLY, - label: DataTypes.STRING, - vgmdb: DataTypes.STRING, - description: DataTypes.STRING, - status: { type: DataTypes.STRING, defaultValue: 'show' }, - placeholder: { type: DataTypes.TEXT, defaultValue: PLACEHOLDER }, - headerColor: { type: DataTypes.STRING, defaultValue: '#ffffff' } - }) - -export default model diff --git a/src/sequelize/models/album.ts b/src/sequelize/models/album.ts new file mode 100644 index 0000000..a6b60f9 --- /dev/null +++ b/src/sequelize/models/album.ts @@ -0,0 +1,47 @@ +import { DataTypes, Model, type BelongsToManyGetAssociationsMixin, type CreationOptional, type InferAttributes, type InferCreationAttributes, type NonAttribute } from '@sequelize/core'; +import { Attribute, PrimaryKey, AutoIncrement, Default, BelongsToMany } from '@sequelize/core/decorators-legacy'; + +import Category from './category'; +import Artist from './artist'; + +export default class Album extends Model, InferCreationAttributes> { + @Attribute(DataTypes.INTEGER) + @PrimaryKey + @AutoIncrement + declare id: CreationOptional + + @Attribute(DataTypes.STRING) + declare title: string + + @Attribute(DataTypes.TEXT) + declare subTitle: string + + @Attribute(DataTypes.DATEONLY) + declare releaseDate: string + + @Attribute(DataTypes.STRING) + declare label: string + + @Attribute(DataTypes.STRING) + declare vgmdb: string + + @Attribute(DataTypes.STRING) + declare description: string + + @Attribute(DataTypes.ENUM('show', 'hidden', 'coming')) + @Default('hidden') + declare status: CreationOptional + + @Attribute(DataTypes.STRING) + @Default('#ff7c12') + declare headerColor: CreationOptional + + @BelongsToMany(() => Category, { through: 'Album_Category', foreignKey: { onDelete: 'SET NULL' } }) + declare categories?: NonAttribute + + @BelongsToMany(() => Artist, { through: 'Album_Artist', foreignKey: { onDelete: 'SET NULL' } }) + declare artists?: NonAttribute + + declare getArtists: BelongsToManyGetAssociationsMixin +} + diff --git a/src/sequelize/models/animation.js b/src/sequelize/models/animation.js index e86b34a..af7ad42 100644 --- a/src/sequelize/models/animation.js +++ b/src/sequelize/models/animation.js @@ -1,4 +1,4 @@ -import { DataTypes } from 'sequelize' +import { DataTypes } from '@/sequelize' import { PLACEHOLDER } from '@/constants' const animation = (sequelize) => { diff --git a/src/sequelize/models/artist.js b/src/sequelize/models/artist.js deleted file mode 100644 index 72226dd..0000000 --- a/src/sequelize/models/artist.js +++ /dev/null @@ -1,20 +0,0 @@ -import { DataTypes } from 'sequelize' - -const model = (sequelize) => { - const Artist = sequelize.define( - 'artist', - { - slug: { - type: DataTypes.STRING, - primaryKey: true - }, - name: DataTypes.STRING - }, - { - freezeTableName: true - } - ) - return Artist -} - -export default model diff --git a/src/sequelize/models/artist.ts b/src/sequelize/models/artist.ts new file mode 100644 index 0000000..36c3f19 --- /dev/null +++ b/src/sequelize/models/artist.ts @@ -0,0 +1,15 @@ +import { DataTypes, Model, type CreationOptional, type InferAttributes, type InferCreationAttributes, type NonAttribute } from '@sequelize/core'; +import { Attribute, PrimaryKey, AutoIncrement, Default, BelongsToMany, Table, NotNull, HasMany } from '@sequelize/core/decorators-legacy'; + +import Album from './album'; + +@Table({ freezeTableName: true }) +export default class Artist extends Model, InferCreationAttributes> { + @PrimaryKey + @Attribute(DataTypes.STRING) + declare slug: string + + @Attribute(DataTypes.STRING) + @NotNull + declare name: string +} diff --git a/src/sequelize/models/category.js b/src/sequelize/models/category.js deleted file mode 100644 index 5fdabe0..0000000 --- a/src/sequelize/models/category.js +++ /dev/null @@ -1,19 +0,0 @@ -import { DataTypes } from 'sequelize' -const model = (sequelize) => { - const Category = sequelize.define( - 'category', - { - name: { - type: DataTypes.STRING, - primaryKey: true - } - }, - { - freezeTableName: true - } - ) - - return Category -} - -export default model diff --git a/src/sequelize/models/category.ts b/src/sequelize/models/category.ts new file mode 100644 index 0000000..ce052ff --- /dev/null +++ b/src/sequelize/models/category.ts @@ -0,0 +1,10 @@ +import { DataTypes, Model, type CreationOptional, type InferAttributes, type InferCreationAttributes } from '@sequelize/core'; +import { Attribute, PrimaryKey, Table } from '@sequelize/core/decorators-legacy'; + +@Table({ freezeTableName: true }) +export default class Category extends Model, InferCreationAttributes> { + @PrimaryKey + @Attribute(DataTypes.STRING) + declare name: string +} + diff --git a/src/sequelize/models/classification.js b/src/sequelize/models/classification.js index 31ecb5b..ccc0b20 100644 --- a/src/sequelize/models/classification.js +++ b/src/sequelize/models/classification.js @@ -1,4 +1,4 @@ -import { DataTypes } from 'sequelize' +import { DataTypes } from '@/sequelize' const model = (sequelize) => { const Classification = sequelize.define( 'classification', diff --git a/src/sequelize/models/comment.js b/src/sequelize/models/comment.js index def2e3f..f9bf4f4 100644 --- a/src/sequelize/models/comment.js +++ b/src/sequelize/models/comment.js @@ -1,4 +1,4 @@ -import { DataTypes } from 'sequelize' +import { DataTypes } from '@/sequelize' const model = (sequelize) => { sequelize.define('comment', { diff --git a/src/sequelize/models/config.js b/src/sequelize/models/config.js deleted file mode 100644 index 834aa3c..0000000 --- a/src/sequelize/models/config.js +++ /dev/null @@ -1,23 +0,0 @@ -import { DataTypes } from 'sequelize' -const model = (sequelize) => { - const config = sequelize.define( - 'config', - { - name: { - type: DataTypes.STRING, - primaryKey: true - }, - value: { - type: DataTypes.STRING, - defaultValue: '' - } - }, - { - freezeTableName: true - } - ) - - return config -} - -export default model diff --git a/src/sequelize/models/config.ts b/src/sequelize/models/config.ts new file mode 100644 index 0000000..24af329 --- /dev/null +++ b/src/sequelize/models/config.ts @@ -0,0 +1,15 @@ +import { DataTypes, Model, type CreationOptional, type InferAttributes, type InferCreationAttributes } from '@sequelize/core'; +import { Attribute, PrimaryKey, AutoIncrement, Default, Table } from '@sequelize/core/decorators-legacy'; + +@Table({ + freezeTableName: true, +}) +export default class Config extends Model, InferCreationAttributes> { + @Attribute(DataTypes.STRING) + @PrimaryKey + declare name: string + + @Attribute(DataTypes.STRING) + declare value: string +} + diff --git a/src/sequelize/models/disc.js b/src/sequelize/models/disc.js index 1d9ceea..a645653 100644 --- a/src/sequelize/models/disc.js +++ b/src/sequelize/models/disc.js @@ -1,4 +1,4 @@ -import { DataTypes } from 'sequelize' +import { DataTypes } from '@/sequelize' const model = (sequelize) => { const Disc = sequelize.define('disc', { number: DataTypes.INTEGER, diff --git a/src/sequelize/models/download.js b/src/sequelize/models/download.js index 72b7743..3746d35 100644 --- a/src/sequelize/models/download.js +++ b/src/sequelize/models/download.js @@ -1,4 +1,4 @@ -import { DataTypes } from 'sequelize' +import { DataTypes } from '@/sequelize' const model = (sequelize) => { const Download = sequelize.define('download', { title: DataTypes.STRING, diff --git a/src/sequelize/models/game.js b/src/sequelize/models/game.js index 1530de0..103b03f 100644 --- a/src/sequelize/models/game.js +++ b/src/sequelize/models/game.js @@ -1,4 +1,4 @@ -import { DataTypes } from 'sequelize' +import { DataTypes } from '@/sequelize' import { PLACEHOLDER } from '@/constants' const model = (sequelize) => { diff --git a/src/sequelize/models/index.js b/src/sequelize/models/index.js deleted file mode 100644 index 4ffec39..0000000 --- a/src/sequelize/models/index.js +++ /dev/null @@ -1,45 +0,0 @@ -import album from './album' -import animation from './animation' -import artist from './artist' -import category from './category' -import classification from './classification' -import comment from './comment' -import config from './config' -import disc from './disc' -import download from './download' -import game from './game' -import link from './link' -import log from './log' -import platform from './platform' -import publisher from './publisher' -import request from './request' -import role from './role' -import series from './series' -import store from './store' -import submission from './submission' -import user from './user' - -const models = { - album, - animation, - artist, - category, - classification, - comment, - config, - disc, - download, - game, - link, - log, - platform, - publisher, - request, - role, - series, - store, - submission, - user -} - -export default models diff --git a/src/sequelize/models/link.js b/src/sequelize/models/link.js index 27604ec..c042b6c 100644 --- a/src/sequelize/models/link.js +++ b/src/sequelize/models/link.js @@ -1,4 +1,4 @@ -import { DataTypes } from 'sequelize' +import { DataTypes } from '@/sequelize' const model = (sequelize) => { const Link = sequelize.define('link', { url: DataTypes.STRING, diff --git a/src/sequelize/models/log.js b/src/sequelize/models/log.js index 5a60c5c..cc0b90f 100644 --- a/src/sequelize/models/log.js +++ b/src/sequelize/models/log.js @@ -1,4 +1,4 @@ -import { DataTypes } from 'sequelize' +import { DataTypes } from '@/sequelize' const model = (sequelize) => { return sequelize.define('log', { id: { diff --git a/src/sequelize/models/platform.js b/src/sequelize/models/platform.js index 2a6297b..2f87071 100644 --- a/src/sequelize/models/platform.js +++ b/src/sequelize/models/platform.js @@ -1,4 +1,4 @@ -import { DataTypes } from 'sequelize' +import { DataTypes } from '@/sequelize' const model = (sequelize) => { const Platform = sequelize.define( 'platform', diff --git a/src/sequelize/models/publisher.js b/src/sequelize/models/publisher.js index a0ccdf2..909d2fe 100644 --- a/src/sequelize/models/publisher.js +++ b/src/sequelize/models/publisher.js @@ -1,4 +1,4 @@ -import { DataTypes } from 'sequelize' +import { DataTypes } from '@/sequelize' const model = (sequelize) => { const Publisher = sequelize.define( 'publisher', diff --git a/src/sequelize/models/request.js b/src/sequelize/models/request.js index e9c6d1b..7b20eb7 100644 --- a/src/sequelize/models/request.js +++ b/src/sequelize/models/request.js @@ -1,4 +1,4 @@ -import { DataTypes } from 'sequelize' +import { DataTypes } from '@/sequelize' const request = (sequelize) => sequelize.define('request', { diff --git a/src/sequelize/models/role.js b/src/sequelize/models/role.js index 1e6b189..75a0bd9 100644 --- a/src/sequelize/models/role.js +++ b/src/sequelize/models/role.js @@ -1,4 +1,4 @@ -import { DataTypes } from 'sequelize' +import { DataTypes } from '@/sequelize' const model = (sequelize) => { const Role = sequelize.define('role', { name: { diff --git a/src/sequelize/models/series.js b/src/sequelize/models/series.js index da1f94c..ac4b05e 100644 --- a/src/sequelize/models/series.js +++ b/src/sequelize/models/series.js @@ -1,4 +1,4 @@ -import { DataTypes } from 'sequelize' +import { DataTypes } from '@/sequelize' import { PLACEHOLDER } from '@/constants' const model = (sequelize) => { diff --git a/src/sequelize/models/store.js b/src/sequelize/models/store.js index 2d85e21..9ab1458 100644 --- a/src/sequelize/models/store.js +++ b/src/sequelize/models/store.js @@ -1,4 +1,4 @@ -import { DataTypes } from 'sequelize' +import { DataTypes } from '@/sequelize' const model = (sequelize) => { const Store = sequelize.define('store', { url: DataTypes.STRING, diff --git a/src/sequelize/models/submission.js b/src/sequelize/models/submission.js index 0d0de12..2fe7844 100644 --- a/src/sequelize/models/submission.js +++ b/src/sequelize/models/submission.js @@ -1,4 +1,4 @@ -import { DataTypes } from 'sequelize' +import { DataTypes } from '@/sequelize' const model = (sequelize) => sequelize.define('submission', { diff --git a/src/sequelize/models/user.js b/src/sequelize/models/user.js index 69212f2..b329cbf 100644 --- a/src/sequelize/models/user.js +++ b/src/sequelize/models/user.js @@ -1,4 +1,4 @@ -import { DataTypes } from 'sequelize' +import { DataTypes } from '@/sequelize' const model = (sequelize) => { const User = sequelize.define('user', { diff --git a/src/sequelize/relations.js b/src/sequelize/relations.js index 7d66ac5..6629730 100644 --- a/src/sequelize/relations.js +++ b/src/sequelize/relations.js @@ -1,11 +1,27 @@ -export default function relations (sequelize) { +export default function relations(sequelize) { const { - album, classification, disc, download, link, - publisher, game, series, - platform, artist, category, store, - animation, studio, - user, role, forgor, log, comment, rating, - submission, request + album, + classification, + disc, + download, + link, + publisher, + game, + series, + platform, + artist, + category, + store, + animation, + studio, + user, + role, + forgor, + log, + comment, + rating, + submission, + request } = sequelize.models user.belongsToMany(role, { through: 'User_Role' }) @@ -32,15 +48,25 @@ export default function relations (sequelize) { platform.belongsToMany(game, { through: 'Game_Platform' }) album.belongsToMany(artist, { onDelete: 'SET NULL', through: 'Album_Artist' }) - album.belongsToMany(classification, { onDelete: 'SET NULL', through: 'Album_Classification' }) - album.belongsToMany(category, { onDelete: 'SET NULL', through: 'Album_Category' }) - album.belongsToMany(platform, { onDelete: 'SET NULL', through: 'Album_Platform' }) + album.belongsToMany(classification, { + onDelete: 'SET NULL', + through: 'Album_Classification' + }) + // album.belongsToMany(category, { onDelete: 'SET NULL', through: 'Album_Category' }) + album.belongsToMany(platform, { + onDelete: 'SET NULL', + through: 'Album_Platform' + }) album.belongsToMany(game, { onDelete: 'SET NULL', through: 'Album_Game' }) album.belongsToMany(animation, { through: 'Album_Animation' }) album.hasMany(disc, { onDelete: 'SET NULL' }) album.hasMany(download, { onDelete: 'SET NULL' }) album.hasMany(store, { onDelete: 'SET NULL' }) - album.belongsToMany(album, { onDelete: 'SET NULL', through: 'related_album', as: 'related' }) + album.belongsToMany(album, { + onDelete: 'SET NULL', + through: 'related_album', + as: 'related' + }) platform.belongsToMany(album, { through: 'Album_Platform' }) diff --git a/tsconfig.json b/tsconfig.json index a6465c7..ccfca88 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,12 +1,16 @@ { "extends": "astro/tsconfigs/strict", "compilerOptions": { + "experimentalDecorators": true, "moduleResolution": "Bundler", "allowJs": true, "baseUrl": "src", "paths": { "@/*": [ "*" + ], + "@/root/*": [ + "../*" ] } }