diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 88c7be3f..a7fc8adf 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -3,3 +3,4 @@ # Linting commits 6efc953b9f7f648f2be59295a78ce1180f12b32d +337cd93ac40295935a5bbf9268d820bddf9630e0 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1b7ade67..c0a954b0 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,6 +13,9 @@ on: jobs: lint: runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write env: THUMBNAIL_URL: ${{ vars.THUMBNAIL_URL }} @@ -21,6 +24,7 @@ jobs: uses: actions/checkout@v4 with: token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 - name: Install bun uses: oven-sh/setup-bun@v2 @@ -30,21 +34,3 @@ jobs: - name: Run linter run: bun run lint - - - name: Check for changes - id: verify-changed-files - run: | - if [ -n "$(git status --porcelain)" ]; then - echo "changed=true" >> $GITHUB_OUTPUT - else - echo "changed=false" >> $GITHUB_OUTPUT - fi - - - name: Commit linter changes - if: steps.verify-changed-files.outputs.changed == 'true' - run: | - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git add . - git commit -m "🔧 Auto-fix: ESLint formatting and fixes" - git push \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f22358bb..98103878 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,9 +22,11 @@ jobs: - name: Install bun uses: oven-sh/setup-bun@v2 + with: + bun-version: latest - name: Install dependencies run: bun install - name: Run tests - run: bun test \ No newline at end of file + run: bun test diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..f2d66f61 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +bun run lint-staged diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..fbcbb0ce --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +**/.next/* +**/dist/* +*.hbs +node_modules/* \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index c956363e..1d56f120 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,7 +2,5 @@ "tabWidth": 2, "trailingComma": "all", "singleQuote": true, - "jsxSingleQuote": true, - "cssEnable": ["css", "less", "sass", "scss"], - "jsonEnable": ["json", "jsonc"] + "jsxSingleQuote": true } diff --git a/.vscode/settings.json b/.vscode/settings.json index 640488af..6a2f950e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,32 +1,17 @@ { - "editor.formatOnSave": true, - "eslint.validate": [ - "typescript" - ], - "eslint.run": "onType", - "eslint.format.enable": true, - "mdx.server.enable": true, - "editor.codeActionsOnSave": { - "source.fixAll": "explicit" - }, - "workbench.colorCustomizations": { - "activityBar.activeBackground": "#fac977", - "activityBar.background": "#fac977", - "activityBar.foreground": "#15202b", - "activityBar.inactiveForeground": "#15202b99", - "activityBarBadge.background": "#069a62", - "activityBarBadge.foreground": "#e7e7e7", - "commandCenter.border": "#15202b99", - "sash.hoverBorder": "#fac977", - "statusBar.background": "#f8b646", - "statusBar.foreground": "#15202b", - "statusBarItem.hoverBackground": "#f6a315", - "statusBarItem.remoteBackground": "#f8b646", - "statusBarItem.remoteForeground": "#15202b", - "titleBar.activeBackground": "#f8b646", - "titleBar.activeForeground": "#15202b", - "titleBar.inactiveBackground": "#f8b64699", - "titleBar.inactiveForeground": "#15202b99" - }, - "peacock.color": "#f8b646" -} \ No newline at end of file + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "eslint.validate": ["typescript", "javascript"], + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "eslint.run": "onType", + "eslint.useFlatConfig": true, + "eslint.format.enable": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + } +} diff --git a/NoteBlockWorld.code-workspace b/NoteBlockWorld.code-workspace index 0368f60e..0d67024d 100644 --- a/NoteBlockWorld.code-workspace +++ b/NoteBlockWorld.code-workspace @@ -1,74 +1,72 @@ { - "folders": [ - { - "path": ".", - "name": "Root" - }, - { - "path": "./apps/backend", - "name": "Backend" - }, - { - "path": "./apps/frontend", - "name": "Frontend" - }, - { - "path": "./packages/configs", - "name": "configs" - }, - { - "path": "./packages/database", - "name": "database" - }, - { - "path": "./packages/song", - "name": "song" - }, - { - "path": "./packages/sounds", - "name": "sounds" - }, - { - "path": "./packages/thumbnail", - "name": "thumbnail" - }, - ], - "settings": { - "window.title": "${dirty}${rootName}${separator}${profileName}${separator}${appName}", - "editor.formatOnSave": true, - "eslint.validate": [ - "typescript" - ], - "eslint.run": "onType", - "eslint.format.enable": true, - "mdx.server.enable": true, - "editor.codeActionsOnSave": { - "source.fixAll": "explicit" - }, - "search.exclude": { - "**/.git": true, - "**/node_modules": true, - "**/dist": true, - }, - "workbench.colorCustomizations": { - "activityBar.activeBackground": "#fac977", - "activityBar.background": "#fac977", - "activityBar.foreground": "#15202b", - "activityBar.inactiveForeground": "#15202b99", - "activityBarBadge.background": "#069a62", - "activityBarBadge.foreground": "#e7e7e7", - "commandCenter.border": "#15202b99", - "sash.hoverBorder": "#fac977", - "statusBar.background": "#f8b646", - "statusBar.foreground": "#15202b", - "statusBarItem.hoverBackground": "#f6a315", - "statusBarItem.remoteBackground": "#f8b646", - "statusBarItem.remoteForeground": "#15202b", - "titleBar.activeBackground": "#f8b646", - "titleBar.activeForeground": "#15202b", - "titleBar.inactiveBackground": "#f8b64699", - "titleBar.inactiveForeground": "#15202b99" - }, - "peacock.color": "#f8b646" - } -} \ No newline at end of file + "folders": [ + { + "path": ".", + "name": "Root", + }, + { + "path": "./apps/backend", + "name": "Backend", + }, + { + "path": "./apps/frontend", + "name": "Frontend", + }, + { + "path": "./packages/configs", + "name": "configs", + }, + { + "path": "./packages/database", + "name": "database", + }, + { + "path": "./packages/song", + "name": "song", + }, + { + "path": "./packages/sounds", + "name": "sounds", + }, + { + "path": "./packages/thumbnail", + "name": "thumbnail", + }, + ], + "settings": { + "window.title": "${dirty}${rootName}${separator}${profileName}${separator}${appName}", + "editor.formatOnSave": true, + "eslint.validate": ["typescript"], + "eslint.run": "onType", + "eslint.format.enable": true, + "mdx.server.enable": true, + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + }, + "search.exclude": { + "**/.git": true, + "**/node_modules": true, + "**/dist": true, + }, + "workbench.colorCustomizations": { + "activityBar.activeBackground": "#fac977", + "activityBar.background": "#fac977", + "activityBar.foreground": "#15202b", + "activityBar.inactiveForeground": "#15202b99", + "activityBarBadge.background": "#069a62", + "activityBarBadge.foreground": "#e7e7e7", + "commandCenter.border": "#15202b99", + "sash.hoverBorder": "#fac977", + "statusBar.background": "#f8b646", + "statusBar.foreground": "#15202b", + "statusBarItem.hoverBackground": "#f6a315", + "statusBarItem.remoteBackground": "#f8b646", + "statusBarItem.remoteForeground": "#15202b", + "titleBar.activeBackground": "#f8b646", + "titleBar.activeForeground": "#15202b", + "titleBar.inactiveBackground": "#f8b64699", + "titleBar.inactiveForeground": "#15202b99", + }, + "peacock.color": "#f8b646", + }, +} diff --git a/apps/backend/nest-cli.json b/apps/backend/nest-cli.json index 6893c53e..17fad250 100644 --- a/apps/backend/nest-cli.json +++ b/apps/backend/nest-cli.json @@ -1,21 +1,19 @@ { - "$schema": "https://json.schemastore.org/nest-cli", - "collection": "@nestjs/schematics", - "sourceRoot": "src", - "entryFile": "server/src/main.js", - "compilerOptions": { - "deleteOutDir": true, - "plugins": [ - "@nestjs/swagger/plugin" - ], - "assets": [ - { - "include": "mailing/templates/**/*.{hbs,png}", - "outDir": "dist/server/src/", - "exclude": "dist/**/*" - }, - "**/assets/**/*" - ], - "watchAssets": true - } -} \ No newline at end of file + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "entryFile": "server/src/main.js", + "compilerOptions": { + "deleteOutDir": true, + "plugins": ["@nestjs/swagger/plugin"], + "assets": [ + { + "include": "mailing/templates/**/*.{hbs,png}", + "outDir": "dist/server/src/", + "exclude": "dist/**/*" + }, + "**/assets/**/*" + ], + "watchAssets": true + } +} diff --git a/apps/backend/src/app.module.ts b/apps/backend/src/app.module.ts index b3f2e3bd..455b6144 100644 --- a/apps/backend/src/app.module.ts +++ b/apps/backend/src/app.module.ts @@ -21,7 +21,7 @@ import { UserModule } from './user/user.module'; imports: [ ConfigModule.forRoot({ isGlobal: true, - envFilePath: ['.env.development', '.env.production'], + envFilePath: ['.env.test', '.env.development', '.env.production'], validate, }), //DatabaseModule, diff --git a/apps/backend/src/auth/auth.service.ts b/apps/backend/src/auth/auth.service.ts index b590e003..aab47ca4 100644 --- a/apps/backend/src/auth/auth.service.ts +++ b/apps/backend/src/auth/auth.service.ts @@ -1,10 +1,10 @@ -import type { UserDocument } from '@nbw/database'; -import { CreateUser } from '@nbw/database'; import { Inject, Injectable, Logger } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import axios from 'axios'; import type { Request, Response } from 'express'; +import { CreateUser } from '@nbw/database'; +import type { UserDocument } from '@nbw/database'; import { UserService } from '@server/user/user.service'; import { DiscordUser } from './types/discordProfile'; diff --git a/apps/backend/src/file/file.service.spec.ts b/apps/backend/src/file/file.service.spec.ts index 8d5e9e64..742a2dfc 100644 --- a/apps/backend/src/file/file.service.spec.ts +++ b/apps/backend/src/file/file.service.spec.ts @@ -5,13 +5,14 @@ import { beforeEach, describe, expect, it, jest, mock } from 'bun:test'; import { FileService } from './file.service'; -mock.module('@aws-sdk/client-s3', () => { - const mS3Client = { - send: jest.fn(), - }; +// Create a mock S3Client instance +const mockS3Client = { + send: jest.fn(), +}; +mock.module('@aws-sdk/client-s3', () => { return { - S3Client: jest.fn(() => mS3Client), + S3Client: jest.fn(() => mockS3Client), GetObjectCommand: jest.fn(), PutObjectCommand: jest.fn(), HeadBucketCommand: jest.fn(), @@ -31,6 +32,10 @@ describe('FileService', () => { let s3Client: S3Client; beforeEach(async () => { + // Reset the mock before each test + mockS3Client.send.mockClear(); + mockS3Client.send.mockResolvedValue({}); + const module: TestingModule = await Test.createTestingModule({ providers: [ FileService, @@ -62,8 +67,7 @@ describe('FileService', () => { }).compile(); fileService = module.get(FileService); - - s3Client = new S3Client({}); + s3Client = mockS3Client as any; }); it('should be defined', () => { @@ -72,13 +76,15 @@ describe('FileService', () => { describe('verifyBucket', () => { it('should verify the buckets successfully', async () => { + // The constructor already called verifyBucket, so we expect 2 calls from constructor + // When we call verifyBucket again, we expect 2 more calls (total 4) (s3Client.send as jest.Mock) .mockResolvedValueOnce({}) // Mock for the first bucket .mockResolvedValueOnce({}); // Mock for the second bucket await fileService['verifyBucket'](); - // Ensure the mock was called twice + // Ensure the mock was called 4 times total (2 from constructor + 2 from explicit call) expect(s3Client.send).toHaveBeenCalledTimes(4); }); diff --git a/apps/backend/src/lib/GetRequestUser.ts b/apps/backend/src/lib/GetRequestUser.ts index ab2d581c..b7415928 100644 --- a/apps/backend/src/lib/GetRequestUser.ts +++ b/apps/backend/src/lib/GetRequestUser.ts @@ -1,4 +1,3 @@ -import type { UserDocument } from '@nbw/database'; import { ExecutionContext, HttpException, @@ -7,6 +6,8 @@ import { } from '@nestjs/common'; import type { Request } from 'express'; +import type { UserDocument } from '@nbw/database'; + export const GetRequestToken = createParamDecorator( (data: unknown, ctx: ExecutionContext) => { const req = ctx diff --git a/apps/backend/src/seed/seed.service.ts b/apps/backend/src/seed/seed.service.ts index af3798eb..89dd28d1 100644 --- a/apps/backend/src/seed/seed.service.ts +++ b/apps/backend/src/seed/seed.service.ts @@ -1,5 +1,13 @@ import { Instrument, Note, Song } from '@encode42/nbs.js'; import { faker } from '@faker-js/faker'; +import { + HttpException, + HttpStatus, + Inject, + Injectable, + Logger, +} from '@nestjs/common'; + import { UPLOAD_CONSTANTS } from '@nbw/config'; import { CategoryType, @@ -9,14 +17,6 @@ import { UserDocument, VisibilityType, } from '@nbw/database'; -import { - HttpException, - HttpStatus, - Inject, - Injectable, - Logger, -} from '@nestjs/common'; - import { SongService } from '@server/song/song.service'; import { UserService } from '@server/user/user.service'; diff --git a/apps/backend/src/song-browser/song-browser.controller.ts b/apps/backend/src/song-browser/song-browser.controller.ts index f9746d1e..6a045e43 100644 --- a/apps/backend/src/song-browser/song-browser.controller.ts +++ b/apps/backend/src/song-browser/song-browser.controller.ts @@ -1,4 +1,3 @@ -import { FeaturedSongsDto, PageQueryDTO, SongPreviewDto } from '@nbw/database'; import { BadRequestException, Controller, @@ -8,6 +7,8 @@ import { } from '@nestjs/common'; import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { FeaturedSongsDto, PageQueryDTO, SongPreviewDto } from '@nbw/database'; + import { SongBrowserService } from './song-browser.service'; @Controller('song-browser') diff --git a/apps/backend/src/song-browser/song-browser.service.ts b/apps/backend/src/song-browser/song-browser.service.ts index 0739d5c0..ff7d98fd 100644 --- a/apps/backend/src/song-browser/song-browser.service.ts +++ b/apps/backend/src/song-browser/song-browser.service.ts @@ -1,3 +1,5 @@ +import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; + import { BROWSER_SONGS } from '@nbw/config'; import { FeaturedSongsDto, @@ -6,8 +8,6 @@ import { SongWithUser, TimespanType, } from '@nbw/database'; -import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; - import { SongService } from '@server/song/song.service'; @Injectable() @@ -50,9 +50,8 @@ export class SongBrowserService { ) { const missing = BROWSER_SONGS.paddedFeaturedPageSize - songPage.length; - const additionalSongs = await this.songService.getSongsBeforeTimespan( - time, - ); + const additionalSongs = + await this.songService.getSongsBeforeTimespan(time); songPage.push(...additionalSongs.slice(0, missing)); } diff --git a/apps/backend/src/song/my-songs/my-songs.controller.ts b/apps/backend/src/song/my-songs/my-songs.controller.ts index 986ac5f3..c1e5b8ec 100644 --- a/apps/backend/src/song/my-songs/my-songs.controller.ts +++ b/apps/backend/src/song/my-songs/my-songs.controller.ts @@ -1,9 +1,9 @@ -import type { UserDocument } from '@nbw/database'; -import { PageQueryDTO, SongPageDto } from '@nbw/database'; import { Controller, Get, Query, UseGuards } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { PageQueryDTO, SongPageDto } from '@nbw/database'; +import type { UserDocument } from '@nbw/database'; import { GetRequestToken, validateUser } from '@server/lib/GetRequestUser'; import { SongService } from '../song.service'; diff --git a/apps/backend/src/song/song-upload/song-upload.service.ts b/apps/backend/src/song/song-upload/song-upload.service.ts index 400d8739..9e390939 100644 --- a/apps/backend/src/song/song-upload/song-upload.service.ts +++ b/apps/backend/src/song/song-upload/song-upload.service.ts @@ -1,4 +1,13 @@ import { Song, fromArrayBuffer, toArrayBuffer } from '@encode42/nbs.js'; +import { + HttpException, + HttpStatus, + Inject, + Injectable, + Logger, +} from '@nestjs/common'; +import { Types } from 'mongoose'; + import { SongDocument, Song as SongEntity, @@ -14,15 +23,6 @@ import { obfuscateAndPackSong, } from '@nbw/song'; import { drawToImage } from '@nbw/thumbnail'; -import { - HttpException, - HttpStatus, - Inject, - Injectable, - Logger, -} from '@nestjs/common'; -import { Types } from 'mongoose'; - import { FileService } from '@server/file/file.service'; import { UserService } from '@server/user/user.service'; @@ -115,7 +115,7 @@ export class SongUploadService { song.originalAuthor = removeExtraSpaces(body.originalAuthor); song.description = removeExtraSpaces(body.description); song.category = body.category; - song.allowDownload = true || body.allowDownload; //TODO: implement allowDownload; + song.allowDownload = true; //|| body.allowDownload; //TODO: implement allowDownload; song.visibility = body.visibility; song.license = body.license; diff --git a/apps/backend/src/song/song-webhook/song-webhook.service.ts b/apps/backend/src/song/song-webhook/song-webhook.service.ts index 90e16745..a730aeb3 100644 --- a/apps/backend/src/song/song-webhook/song-webhook.service.ts +++ b/apps/backend/src/song/song-webhook/song-webhook.service.ts @@ -1,8 +1,9 @@ -import { Song as SongEntity, SongWithUser } from '@nbw/database'; import { Inject, Injectable, Logger, OnModuleInit } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; +import { Song as SongEntity, SongWithUser } from '@nbw/database'; + import { getUploadDiscordEmbed } from '../song.util'; @Injectable() diff --git a/apps/backend/src/song/song.controller.ts b/apps/backend/src/song/song.controller.ts index d6711cee..55329563 100644 --- a/apps/backend/src/song/song.controller.ts +++ b/apps/backend/src/song/song.controller.ts @@ -1,12 +1,3 @@ -import { UPLOAD_CONSTANTS } from '@nbw/config'; -import type { UserDocument } from '@nbw/database'; -import { - PageQueryDTO, - SongPreviewDto, - SongViewDto, - UploadSongDto, - UploadSongResponseDto, -} from '@nbw/database'; import type { RawBodyRequest } from '@nestjs/common'; import { Body, @@ -38,6 +29,15 @@ import { } from '@nestjs/swagger'; import type { Response } from 'express'; +import { UPLOAD_CONSTANTS } from '@nbw/config'; +import { + PageQueryDTO, + SongPreviewDto, + SongViewDto, + UploadSongDto, + UploadSongResponseDto, +} from '@nbw/database'; +import type { UserDocument } from '@nbw/database'; import { FileService } from '@server/file/file.service'; import { GetRequestToken, validateUser } from '@server/lib/GetRequestUser'; diff --git a/apps/backend/src/song/song.module.ts b/apps/backend/src/song/song.module.ts index 52218af7..61bfb395 100644 --- a/apps/backend/src/song/song.module.ts +++ b/apps/backend/src/song/song.module.ts @@ -1,8 +1,8 @@ -import { Song, SongSchema } from '@nbw/database'; import { Module } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { MongooseModule } from '@nestjs/mongoose'; +import { Song, SongSchema } from '@nbw/database'; import { AuthModule } from '@server/auth/auth.module'; import { FileModule } from '@server/file/file.module'; import { UserModule } from '@server/user/user.module'; diff --git a/apps/backend/src/song/song.service.ts b/apps/backend/src/song/song.service.ts index 1da06c08..55c2a195 100644 --- a/apps/backend/src/song/song.service.ts +++ b/apps/backend/src/song/song.service.ts @@ -1,3 +1,13 @@ +import { + HttpException, + HttpStatus, + Inject, + Injectable, + Logger, +} from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model } from 'mongoose'; + import { BROWSER_SONGS } from '@nbw/config'; import type { UserDocument } from '@nbw/database'; import { @@ -10,16 +20,6 @@ import { UploadSongDto, UploadSongResponseDto, } from '@nbw/database'; -import { - HttpException, - HttpStatus, - Inject, - Injectable, - Logger, -} from '@nestjs/common'; -import { InjectModel } from '@nestjs/mongoose'; -import { Model } from 'mongoose'; - import { FileService } from '@server/file/file.service'; import { SongUploadService } from './song-upload/song-upload.service'; @@ -73,9 +73,8 @@ export class SongService { 'username profileImage -_id', )) as unknown as SongWithUser; - const webhookMessageId = await this.songWebhookService.syncSongWebhook( - populatedSong, - ); + const webhookMessageId = + await this.songWebhookService.syncSongWebhook(populatedSong); songDocument.webhookMessageId = webhookMessageId; @@ -171,9 +170,8 @@ export class SongService { 'username profileImage -_id', )) as unknown as SongWithUser; - const webhookMessageId = await this.songWebhookService.syncSongWebhook( - populatedSong, - ); + const webhookMessageId = + await this.songWebhookService.syncSongWebhook(populatedSong); foundSong.webhookMessageId = webhookMessageId; @@ -420,13 +418,16 @@ export class SongService { ])) as unknown as { _id: string; count: number }[]; // Return object with category names as keys and counts as values - return categories.reduce((acc, category) => { - if (category._id) { - acc[category._id] = category.count; - } + return categories.reduce( + (acc, category) => { + if (category._id) { + acc[category._id] = category.count; + } - return acc; - }, {} as Record); + return acc; + }, + {} as Record, + ); } public async getSongsByCategory( diff --git a/apps/backend/src/song/song.util.ts b/apps/backend/src/song/song.util.ts index 50168171..02aadec7 100644 --- a/apps/backend/src/song/song.util.ts +++ b/apps/backend/src/song/song.util.ts @@ -1,6 +1,7 @@ +import { customAlphabet } from 'nanoid'; + import { UPLOAD_CONSTANTS } from '@nbw/config'; import { SongWithUser } from '@nbw/database'; -import { customAlphabet } from 'nanoid'; export const formatDuration = (totalSeconds: number) => { const minutes = Math.floor(Math.ceil(totalSeconds) / 60); diff --git a/apps/backend/src/user/user.controller.ts b/apps/backend/src/user/user.controller.ts index 193551fa..739c9aa9 100644 --- a/apps/backend/src/user/user.controller.ts +++ b/apps/backend/src/user/user.controller.ts @@ -1,8 +1,8 @@ -import type { UserDocument } from '@nbw/database'; -import { GetUser, PageQueryDTO, UpdateUsernameDto } from '@nbw/database'; import { Body, Controller, Get, Inject, Patch, Query } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; +import type { UserDocument } from '@nbw/database'; +import { GetUser, PageQueryDTO, UpdateUsernameDto } from '@nbw/database'; import { GetRequestToken, validateUser } from '@server/lib/GetRequestUser'; import { UserService } from './user.service'; diff --git a/apps/backend/src/user/user.module.ts b/apps/backend/src/user/user.module.ts index 53a58c90..d6822afd 100644 --- a/apps/backend/src/user/user.module.ts +++ b/apps/backend/src/user/user.module.ts @@ -1,7 +1,8 @@ -import { User, UserSchema } from '@nbw/database'; import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; +import { User, UserSchema } from '@nbw/database'; + import { UserController } from './user.controller'; import { UserService } from './user.service'; diff --git a/apps/backend/src/user/user.service.ts b/apps/backend/src/user/user.service.ts index 051da77e..0b4af322 100644 --- a/apps/backend/src/user/user.service.ts +++ b/apps/backend/src/user/user.service.ts @@ -1,3 +1,8 @@ +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { validate } from 'class-validator'; +import { Model } from 'mongoose'; + import { CreateUser, GetUser, @@ -7,10 +12,6 @@ import { UserDocument, UserDto, } from '@nbw/database'; -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { InjectModel } from '@nestjs/mongoose'; -import { validate } from 'class-validator'; -import { Model } from 'mongoose'; @Injectable() export class UserService { diff --git a/apps/backend/tsconfig.build.json b/apps/backend/tsconfig.build.json index 5f1af2fc..64f86c6b 100644 --- a/apps/backend/tsconfig.build.json +++ b/apps/backend/tsconfig.build.json @@ -1,9 +1,4 @@ { - "extends": "./tsconfig.json", - "exclude": [ - "node_modules", - "test", - "dist", - "**/*spec.ts" - ] -} \ No newline at end of file + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] +} diff --git a/apps/backend/tsconfig.json b/apps/backend/tsconfig.json index bc4e305d..870815dd 100644 --- a/apps/backend/tsconfig.json +++ b/apps/backend/tsconfig.json @@ -1,37 +1,33 @@ { - "extends": "../../tsconfig.base.json", - "compilerOptions": { - // NestJS specific settings - "module": "commonjs", - "target": "ES2021", - "declaration": true, - "removeComments": true, - "allowSyntheticDefaultImports": true, - "sourceMap": true, - "outDir": "./dist", - "baseUrl": "./", - // Relaxed strict settings for backend - "strictNullChecks": false, - "noImplicitAny": false, - "strictBindCallApply": false, - "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false, - // Path mapping - "paths": { - "@server/*": [ - "src/*" - ] - } - }, - "include": [ - "src/**/*.ts" - ], - "exclude": [ - "node_modules", - "dist", - "**/*.spec.ts", - "**/*.test.ts", - "e2e/**/*", - "test/**/*" - ] -} \ No newline at end of file + "extends": "../../tsconfig.base.json", + "compilerOptions": { + // NestJS specific settings + "module": "commonjs", + "target": "ES2021", + "declaration": true, + "removeComments": true, + "allowSyntheticDefaultImports": true, + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + // Relaxed strict settings for backend + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false, + // Path mapping + "paths": { + "@server/*": ["src/*"] + } + }, + "include": ["src/**/*.ts"], + "exclude": [ + "node_modules", + "dist", + "**/*.spec.ts", + "**/*.test.ts", + "e2e/**/*", + "test/**/*" + ] +} diff --git a/apps/frontend/posts/blog/2024-10-07_changelog-october-2024.md b/apps/frontend/posts/blog/2024-10-07_changelog-october-2024.md index 66a74bae..75aae6ce 100644 --- a/apps/frontend/posts/blog/2024-10-07_changelog-october-2024.md +++ b/apps/frontend/posts/blog/2024-10-07_changelog-october-2024.md @@ -21,7 +21,6 @@ Here's what we've been up to since the last update: - The idea is to host guest writers from the community once in a while, so stay tuned! - Added the _About_ page! We're proud of our history and want to share it with you. We also want to acknowledge all the great people that have made this website possible, and everyone who's helped us along the way. Make sure to drop by and read about how Note Block World came to be! - The navigation bar at the top has been redesigned: - - Now features _Songs_, _Help_, _Blog_, and _About_ tabs. - The _Upload_ icon is no longer shown on mobile devices. - The external links (_GitHub_, _Changelog_, and _Donate_) have been moved from inside your user menu into a 'Settings' menu, so they're now accessible even if you aren't logged in. @@ -32,7 +31,6 @@ Here's what we've been up to since the last update: ### Bugfixes and improvements - Added more mob sounds to the available custom instruments: - - Polar Bear - Phantom - Evoker diff --git a/apps/frontend/posts/help/1_creating-song.md b/apps/frontend/posts/help/1_creating-song.md index 4e9a1fed..32b2fe34 100644 --- a/apps/frontend/posts/help/1_creating-song.md +++ b/apps/frontend/posts/help/1_creating-song.md @@ -64,7 +64,6 @@ Once you've made a selection, you can: Selecting notes also lets you change them in other ways, all of which are available in the context menu that appears when you right-click the workspace, or by using the keyboard shortcuts below: - **Transpose**: You can transpose the selected notes up or down by a certain number of semitones. This is useful for changing the key of a section of the song, or for creating harmonies. - - **Transpose up** (`Ctrl+R`): Transpose the selected notes up by one semitone. - **Transpose down** (`Ctrl+F`): Transpose the selected notes down by one semitone. - **Transpose one octave up**: Transpose the selected notes up by one octave (12 semitones). diff --git a/apps/frontend/posts/help/5_custom-instruments.md b/apps/frontend/posts/help/5_custom-instruments.md index bb26b867..4df6b004 100644 --- a/apps/frontend/posts/help/5_custom-instruments.md +++ b/apps/frontend/posts/help/5_custom-instruments.md @@ -38,7 +38,6 @@ You've added a custom instrument to your song! You can now use it just like any There are a few limitations to keep in mind when working with custom instruments in Note Block Studio: - **Custom instruments are not saved with the song file**: Song files only store the custom instrument settings, not the sound files themselves. This means that if you share your song with someone else, they won't have access to your custom instruments unless you also share the sound files you used. There are two ways to work around this limitation: - - **Share the sound files** along with the song file, so others can use the custom instruments in your song. You can save the song in a ZIP file that includes the sound files you used by going to _File_ > _Save song with custom sounds..._. Alternatively, you can save only the instruments to a ZIP file by going to _Settings_ > _Instrument settings..._ and clicking _Export sounds_. - **Use the same sound files** for custom instruments in all your songs, so you don't have to share them every time you share a song. In the section _Downloading songs that use custom instruments_ further down this article, you'll learn how to obtain Minecraft sound files that you can use to standardize the reference to your custom sound files. diff --git a/apps/frontend/public/icons/README.md b/apps/frontend/public/icons/README.md index de716c85..6e08ac5c 100644 --- a/apps/frontend/public/icons/README.md +++ b/apps/frontend/public/icons/README.md @@ -20,4 +20,4 @@ Insert the following code in the `head` section of your pages: -*Optional* - Check your favicon with the [favicon checker](https://realfavicongenerator.net/favicon_checker) \ No newline at end of file +_Optional_ - Check your favicon with the [favicon checker](https://realfavicongenerator.net/favicon_checker) diff --git a/apps/frontend/public/icons/site.webmanifest b/apps/frontend/public/icons/site.webmanifest index 05a89bdf..e9b9ce42 100644 --- a/apps/frontend/public/icons/site.webmanifest +++ b/apps/frontend/public/icons/site.webmanifest @@ -1,19 +1,19 @@ { - "name": "Note Block World", - "short_name": "Note Block World", - "icons": [ - { - "src": "/icons/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/icons/android-chrome-256x256.png", - "sizes": "256x256", - "type": "image/png" - } - ], - "theme_color": "#3295ff", - "background_color": "#3295ff", - "display": "standalone" + "name": "Note Block World", + "short_name": "Note Block World", + "icons": [ + { + "src": "/icons/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/icons/android-chrome-256x256.png", + "sizes": "256x256", + "type": "image/png" + } + ], + "theme_color": "#3295ff", + "background_color": "#3295ff", + "display": "standalone" } diff --git a/apps/frontend/src/app/(content)/page.tsx b/apps/frontend/src/app/(content)/page.tsx index ac9ee47c..638c3ab5 100644 --- a/apps/frontend/src/app/(content)/page.tsx +++ b/apps/frontend/src/app/(content)/page.tsx @@ -1,6 +1,6 @@ -import { FeaturedSongsDtoType, SongPreviewDtoType } from '@nbw/database'; import { Metadata } from 'next'; +import { FeaturedSongsDtoType, SongPreviewDtoType } from '@nbw/database'; import axiosInstance from '@web/lib/axios'; import { HomePageProvider } from '@web/modules/browse/components/client/context/HomePage.context'; import { HomePageComponent } from '@web/modules/browse/components/HomePageComponent'; diff --git a/apps/frontend/src/app/(content)/song/[id]/page.tsx b/apps/frontend/src/app/(content)/song/[id]/page.tsx index 198a9c96..38b25e29 100644 --- a/apps/frontend/src/app/(content)/song/[id]/page.tsx +++ b/apps/frontend/src/app/(content)/song/[id]/page.tsx @@ -1,7 +1,7 @@ -import { SongViewDtoType } from '@nbw/database'; import type { Metadata } from 'next'; import { cookies } from 'next/headers'; +import { SongViewDtoType } from '@nbw/database'; import axios from '@web/lib/axios'; import { SongPage } from '@web/modules/song/components/SongPage'; diff --git a/apps/frontend/src/app/enableRecaptchaBadge.css b/apps/frontend/src/app/enableRecaptchaBadge.css index a6ce0b72..6ad17a6d 100644 --- a/apps/frontend/src/app/enableRecaptchaBadge.css +++ b/apps/frontend/src/app/enableRecaptchaBadge.css @@ -1,3 +1,3 @@ -.grecaptcha-badge { - visibility: visible !important; -} \ No newline at end of file +.grecaptcha-badge { + visibility: visible !important; +} diff --git a/apps/frontend/src/app/globals.css b/apps/frontend/src/app/globals.css index d23e7f6e..50b8d0d4 100644 --- a/apps/frontend/src/app/globals.css +++ b/apps/frontend/src/app/globals.css @@ -74,15 +74,33 @@ body { } @keyframes shake { - 0% { transform: translateX(0) translateY(-1px); } - 12.5% { transform: translateX(1px) translateY(3px); } - 25% { transform: translateX(-3px) translateY(1px); } - 37.5% { transform: translateX(3px) translateY(-1px); } - 50% { transform: translateX(1px) translateY(-3px); } - 62.5% { transform: translateX(-2px) translateY(3px); } - 75% { transform: translateX(-1px) translateY(2px) } - 87.5% { transform: translateX(3px) translateY(-2px); } - 100% { transform: translateX(0) translateY(-1px); } + 0% { + transform: translateX(0) translateY(-1px); + } + 12.5% { + transform: translateX(1px) translateY(3px); + } + 25% { + transform: translateX(-3px) translateY(1px); + } + 37.5% { + transform: translateX(3px) translateY(-1px); + } + 50% { + transform: translateX(1px) translateY(-3px); + } + 62.5% { + transform: translateX(-2px) translateY(3px); + } + 75% { + transform: translateX(-1px) translateY(2px); + } + 87.5% { + transform: translateX(3px) translateY(-2px); + } + 100% { + transform: translateX(0) translateY(-1px); + } } /************** Loading bar **************/ @@ -126,7 +144,6 @@ body { /************** Prevent trailing lines on Chrome **************/ /* https://stackoverflow.com/a/25799659/9045426 */ - .bevel { position: relative; } @@ -151,11 +168,10 @@ body { top: -4px; } - /************** Google AdSense **************/ /* Hide unfilled ads */ /* https://support.google.com/adsense/answer/10762946?hl=en */ -ins.adsbygoogle[data-ad-status="unfilled"] { +ins.adsbygoogle[data-ad-status='unfilled'] { display: none !important; } diff --git a/apps/frontend/src/lib/posts.ts b/apps/frontend/src/lib/posts.ts index 109edefb..2ab15c9c 100644 --- a/apps/frontend/src/lib/posts.ts +++ b/apps/frontend/src/lib/posts.ts @@ -16,21 +16,27 @@ export type PostType = { const blogPostIds = fs .readdirSync(path.join(process.cwd(), 'posts', 'blog')) - .reduce((acc, fileName) => { - // Remove ".md" and date prefix to get post ID - const postId = fileName.replace(/\.md$/, '').split('_')[1]; - acc[postId] = fileName; - return acc; - }, {} as Record); + .reduce( + (acc, fileName) => { + // Remove ".md" and date prefix to get post ID + const postId = fileName.replace(/\.md$/, '').split('_')[1]; + acc[postId] = fileName; + return acc; + }, + {} as Record, + ); const helpPostIds = fs .readdirSync(path.join(process.cwd(), 'posts', 'help')) - .reduce((acc, fileName) => { - // Remove ".md" and number prefix to get help article ID - const helpId = fileName.replace(/\.md$/, '').split('_')[1]; - acc[helpId] = fileName; - return acc; - }, {} as Record); + .reduce( + (acc, fileName) => { + // Remove ".md" and number prefix to get help article ID + const helpId = fileName.replace(/\.md$/, '').split('_')[1]; + acc[helpId] = fileName; + return acc; + }, + {} as Record, + ); export function getSortedPostsData( postsPath: 'help' | 'blog', diff --git a/apps/frontend/src/modules/auth/components/loginWithEmailPage.tsx b/apps/frontend/src/modules/auth/components/loginWithEmailPage.tsx index 9ba806f1..06367fd3 100644 --- a/apps/frontend/src/modules/auth/components/loginWithEmailPage.tsx +++ b/apps/frontend/src/modules/auth/components/loginWithEmailPage.tsx @@ -1,7 +1,8 @@ -import { LoginForm } from './client/LoginFrom'; import { CopyrightFooter } from '../../shared/components/layout/CopyrightFooter'; import { NoteBlockWorldLogo } from '../../shared/components/NoteBlockWorldLogo'; +import { LoginForm } from './client/LoginFrom'; + export const LoginWithEmailPage = () => { return (
{ const { featuredSongsPage } = useFeaturedSongsProvider(); diff --git a/apps/frontend/src/modules/browse/components/SongCard.tsx b/apps/frontend/src/modules/browse/components/SongCard.tsx index d5025023..33e62bfe 100644 --- a/apps/frontend/src/modules/browse/components/SongCard.tsx +++ b/apps/frontend/src/modules/browse/components/SongCard.tsx @@ -2,10 +2,10 @@ import { faPlay } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { SongPreviewDtoType } from '@nbw/database'; import Link from 'next/link'; import Skeleton from 'react-loading-skeleton'; +import { SongPreviewDtoType } from '@nbw/database'; import { formatDuration, formatTimeAgo } from '@web/modules/shared/util/format'; import SongThumbnail from '../../shared/components/layout/SongThumbnail'; diff --git a/apps/frontend/src/modules/browse/components/client/CategoryButton.tsx b/apps/frontend/src/modules/browse/components/client/CategoryButton.tsx index 0de6bb2c..b91a5572 100644 --- a/apps/frontend/src/modules/browse/components/client/CategoryButton.tsx +++ b/apps/frontend/src/modules/browse/components/client/CategoryButton.tsx @@ -1,7 +1,6 @@ 'use client'; import { UPLOAD_CONSTANTS } from '@nbw/config'; import type { CategoryType } from '@nbw/database'; - import { Carousel, CarouselContent, diff --git a/apps/frontend/src/modules/browse/components/client/context/FeaturedSongs.context.tsx b/apps/frontend/src/modules/browse/components/client/context/FeaturedSongs.context.tsx index fa314966..e18b9204 100644 --- a/apps/frontend/src/modules/browse/components/client/context/FeaturedSongs.context.tsx +++ b/apps/frontend/src/modules/browse/components/client/context/FeaturedSongs.context.tsx @@ -1,8 +1,10 @@ 'use client'; +import { createContext, useContext, useEffect, useState } from 'react'; + import { TIMESPANS } from '@nbw/config'; import { type FeaturedSongsDto, type SongPreviewDto } from '@nbw/database'; -import { createContext, useContext, useEffect, useState } from 'react'; + type TimespanType = (typeof TIMESPANS)[number]; type FeaturedSongsContextType = { diff --git a/apps/frontend/src/modules/browse/components/client/context/HomePage.context.tsx b/apps/frontend/src/modules/browse/components/client/context/HomePage.context.tsx index c65e7ab1..2cbae16a 100644 --- a/apps/frontend/src/modules/browse/components/client/context/HomePage.context.tsx +++ b/apps/frontend/src/modules/browse/components/client/context/HomePage.context.tsx @@ -1,8 +1,9 @@ 'use client'; -import { FeaturedSongsDtoType, SongPreviewDtoType } from '@nbw/database'; import { createContext, useContext } from 'react'; +import { FeaturedSongsDtoType, SongPreviewDtoType } from '@nbw/database'; + import { FeaturedSongsProvider } from './FeaturedSongs.context'; import { RecentSongsProvider } from './RecentSongs.context'; diff --git a/apps/frontend/src/modules/browse/components/client/context/RecentSongs.context.tsx b/apps/frontend/src/modules/browse/components/client/context/RecentSongs.context.tsx index 8d7fe9b7..bebf6725 100644 --- a/apps/frontend/src/modules/browse/components/client/context/RecentSongs.context.tsx +++ b/apps/frontend/src/modules/browse/components/client/context/RecentSongs.context.tsx @@ -1,6 +1,5 @@ 'use client'; -import { SongPreviewDtoType } from '@nbw/database'; import { createContext, useCallback, @@ -9,6 +8,7 @@ import { useState, } from 'react'; +import { SongPreviewDtoType } from '@nbw/database'; import axiosInstance from '@web/lib/axios'; type RecentSongsContextType = { diff --git a/apps/frontend/src/modules/my-songs/components/MySongsPage.tsx b/apps/frontend/src/modules/my-songs/components/MySongsPage.tsx index 93ee1b15..18985c18 100644 --- a/apps/frontend/src/modules/my-songs/components/MySongsPage.tsx +++ b/apps/frontend/src/modules/my-songs/components/MySongsPage.tsx @@ -1,11 +1,11 @@ import { MY_SONGS } from '@nbw/config'; import type { SongPageDtoType, SongsFolder } from '@nbw/database'; - import axiosInstance from '@web/lib/axios'; +import { getTokenServer } from '../../auth/features/auth.utils'; + import { MySongProvider } from './client/context/MySongs.context'; import { MySongsPageComponent } from './client/MySongsTable'; -import { getTokenServer } from '../../auth/features/auth.utils'; async function fetchSongsPage( page: number, diff --git a/apps/frontend/src/modules/my-songs/components/client/MySongsTable.tsx b/apps/frontend/src/modules/my-songs/components/client/MySongsTable.tsx index 2140824d..e67450fc 100644 --- a/apps/frontend/src/modules/my-songs/components/client/MySongsTable.tsx +++ b/apps/frontend/src/modules/my-songs/components/client/MySongsTable.tsx @@ -4,12 +4,12 @@ import { faChevronRight, } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { MY_SONGS } from '@nbw/config'; -import type { SongPageDtoType, SongPreviewDtoType } from '@nbw/database'; import Image from 'next/image'; import Link from 'next/link'; import Skeleton from 'react-loading-skeleton'; +import { MY_SONGS } from '@nbw/config'; +import type { SongPageDtoType, SongPreviewDtoType } from '@nbw/database'; import { ErrorBox } from '@web/modules/shared/components/client/ErrorBox'; import { useMySongsProvider } from './context/MySongs.context'; diff --git a/apps/frontend/src/modules/my-songs/components/client/SongRow.tsx b/apps/frontend/src/modules/my-songs/components/client/SongRow.tsx index 7bc60297..a875b484 100644 --- a/apps/frontend/src/modules/my-songs/components/client/SongRow.tsx +++ b/apps/frontend/src/modules/my-songs/components/client/SongRow.tsx @@ -5,20 +5,21 @@ import { faPlay, } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { SongPreviewDtoType } from '@nbw/database'; import Link from 'next/link'; import Skeleton from 'react-loading-skeleton'; +import { SongPreviewDtoType } from '@nbw/database'; import SongThumbnail from '@web/modules/shared/components/layout/SongThumbnail'; import { formatDuration } from '@web/modules/shared/util/format'; -import { useMySongsProvider } from './context/MySongs.context'; import { DeleteButton, DownloadSongButton, EditButton, } from '../client/MySongsButtons'; +import { useMySongsProvider } from './context/MySongs.context'; + export const SongRow = ({ song }: { song?: SongPreviewDtoType | null }) => { const { setIsDeleteDialogOpen, setSongToDelete } = useMySongsProvider(); diff --git a/apps/frontend/src/modules/my-songs/components/client/context/MySongs.context.tsx b/apps/frontend/src/modules/my-songs/components/client/context/MySongs.context.tsx index 72737afd..dc570acc 100644 --- a/apps/frontend/src/modules/my-songs/components/client/context/MySongs.context.tsx +++ b/apps/frontend/src/modules/my-songs/components/client/context/MySongs.context.tsx @@ -1,11 +1,5 @@ 'use client'; -import { MY_SONGS } from '@nbw/config'; -import type { - SongPageDtoType, - SongPreviewDtoType, - SongsFolder, -} from '@nbw/database'; import { createContext, useCallback, @@ -15,6 +9,12 @@ import { } from 'react'; import { toast } from 'react-hot-toast'; +import { MY_SONGS } from '@nbw/config'; +import type { + SongPageDtoType, + SongPreviewDtoType, + SongsFolder, +} from '@nbw/database'; import axiosInstance from '@web/lib/axios'; import { getTokenLocal } from '@web/lib/axios/token.utils'; diff --git a/apps/frontend/src/modules/shared/components/client/ads/AdSlots.tsx b/apps/frontend/src/modules/shared/components/client/ads/AdSlots.tsx index 9128fba9..a7d7e0ac 100644 --- a/apps/frontend/src/modules/shared/components/client/ads/AdSlots.tsx +++ b/apps/frontend/src/modules/shared/components/client/ads/AdSlots.tsx @@ -18,10 +18,10 @@ const HideAdButton = ({