Skip to content

Commit 50679dd

Browse files
committed
Merge branch 'develop' of github.com:OpenNBS/NoteBlockWorld into develop
2 parents cd3ad18 + d7899b1 commit 50679dd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+293
-343
lines changed

.vscode/settings.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
{
2+
/***********************************
3+
Linting and formatting settings
4+
**********************************/
25
"editor.defaultFormatter": "esbenp.prettier-vscode",
36
"editor.formatOnSave": true,
47
"eslint.validate": [
@@ -13,10 +16,27 @@
1316
"editor.codeActionsOnSave": {
1417
"source.fixAll.eslint": "always"
1518
},
19+
20+
/***********************************
21+
Tailwind CSS IntelliSense settings
22+
***********************************/
23+
// Enable suggestions inside strings for Tailwind CSS class names
24+
// https://github.com/tailwindlabs/tailwindcss-intellisense#editorquicksuggestions
25+
"editor.quickSuggestions": {
26+
"strings": "on"
27+
},
28+
"tailwindCSS.experimental.configFile": {
29+
"apps/frontend/src/app/globals.css": "apps/frontend/src/**"
30+
},
31+
"tailwindCSS.classFunctions": ["tw", "clsx", "cn"],
1632
"files.associations": {
1733
".css": "tailwindcss",
1834
"*.scss": "tailwindcss"
1935
},
36+
37+
/***********************************
38+
Use the workspace version of TypeScript
39+
***********************************/
2040
"typescript.tsdk": "./node_modules/typescript/lib",
2141
"typescript.enablePromptUseWorkspaceTsdk": true
2242
}

apps/backend/src/song/song.controller.spec.ts

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ const mockSongService = {
3131
getSongEdit: jest.fn(),
3232
patchSong: jest.fn(),
3333
getSongDownloadUrl: jest.fn(),
34-
getSongFileBuffer: jest.fn(),
3534
deleteSong: jest.fn(),
3635
uploadSong: jest.fn(),
3736
getRandomSongs: jest.fn(),
@@ -803,40 +802,31 @@ describe('SongController', () => {
803802
});
804803

805804
describe('getSongFile', () => {
806-
it('should get song .nbs file', async () => {
805+
it('should redirect to download URL', async () => {
807806
const id = 'test-id';
808807
const src = 'test-src';
809808
const user: UserDocument = {
810809
_id: 'test-user-id',
811810
} as unknown as UserDocument;
811+
const downloadUrl = 'https://example.com/download/song.nbs';
812812

813813
const res = {
814814
set: jest.fn(),
815-
send: jest.fn(),
815+
redirect: jest.fn(),
816816
} as unknown as Response;
817817

818-
const buffer = Buffer.from('test-song-data');
819-
const filename = 'test-song.nbs';
820-
821-
mockSongService.getSongFileBuffer.mockResolvedValueOnce({
822-
buffer,
823-
filename,
824-
});
818+
mockSongService.getSongDownloadUrl.mockResolvedValueOnce(downloadUrl);
825819

826820
await songController.getSongFile(id, src, user, res);
827821

828822
expect(res.set).toHaveBeenCalledWith({
829-
'Content-Type': 'application/octet-stream',
830-
'Content-Disposition': `attachment; filename="${filename.replace(
831-
/[/"]/g,
832-
'_',
833-
)}"`,
823+
'Content-Disposition': 'attachment; filename="song.nbs"',
834824
'Access-Control-Expose-Headers': 'Content-Disposition',
835825
});
836826

837-
expect(res.send).toHaveBeenCalledWith(Buffer.from(buffer));
827+
expect(res.redirect).toHaveBeenCalledWith(HttpStatus.FOUND, downloadUrl);
838828

839-
expect(songService.getSongFileBuffer).toHaveBeenCalledWith(
829+
expect(songService.getSongDownloadUrl).toHaveBeenCalledWith(
840830
id,
841831
user,
842832
src,
@@ -853,16 +843,16 @@ describe('SongController', () => {
853843

854844
const res = {
855845
set: jest.fn(),
856-
send: jest.fn(),
846+
redirect: jest.fn(),
857847
} as unknown as Response;
858848

859-
mockSongService.getSongFileBuffer.mockRejectedValueOnce(
849+
mockSongService.getSongDownloadUrl.mockRejectedValueOnce(
860850
new Error('Error'),
861851
);
862852

863853
await expect(
864854
songController.getSongFile(id, src, user, res),
865-
).rejects.toThrow('An error occurred while retrieving the song file');
855+
).rejects.toThrow('Error');
866856
});
867857
});
868858

apps/backend/src/song/song.controller.ts

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -321,37 +321,15 @@ export class SongController {
321321
): Promise<void> {
322322
user = validateUser(user);
323323

324-
try {
325-
// Get file directly from S3/MinIO and proxy it to avoid CORS issues
326-
// This bypasses presigned URLs and CORS entirely
327-
const { buffer, filename } = await this.songService.getSongFileBuffer(
328-
id,
329-
user,
330-
src,
331-
false,
332-
);
333-
334-
// Set headers and send file
335-
res.set({
336-
'Content-Type': 'application/octet-stream',
337-
'Content-Disposition': `attachment; filename="${filename.replace(
338-
/[/"]/g,
339-
'_',
340-
)}"`,
341-
'Access-Control-Expose-Headers': 'Content-Disposition',
342-
});
324+
// TODO: no longer used
325+
res.set({
326+
'Content-Disposition': 'attachment; filename="song.nbs"',
327+
// Expose the Content-Disposition header to the client
328+
'Access-Control-Expose-Headers': 'Content-Disposition',
329+
});
343330

344-
res.send(Buffer.from(buffer));
345-
} catch (error) {
346-
this.logger.error('Error downloading song file:', error);
347-
if (error instanceof HttpException) {
348-
throw error;
349-
}
350-
throw new HttpException(
351-
'An error occurred while retrieving the song file',
352-
HttpStatus.INTERNAL_SERVER_ERROR,
353-
);
354-
}
331+
const url = await this.songService.getSongDownloadUrl(id, user, src, false);
332+
res.redirect(HttpStatus.FOUND, url);
355333
}
356334

357335
@Get('/:id/open')

apps/backend/src/song/song.service.ts

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -393,57 +393,6 @@ export class SongService {
393393
}
394394
}
395395

396-
public async getSongFileBuffer(
397-
publicId: string,
398-
user: UserDocument | null,
399-
src?: string,
400-
packed: boolean = false,
401-
): Promise<{ buffer: ArrayBuffer; filename: string }> {
402-
const foundSong = await this.songModel.findOne({ publicId: publicId });
403-
404-
if (!foundSong) {
405-
throw new HttpException('Song not found with ID', HttpStatus.NOT_FOUND);
406-
}
407-
408-
if (foundSong.visibility !== 'public') {
409-
if (!user || foundSong.uploader.toString() !== user._id.toString()) {
410-
throw new HttpException(
411-
'This song is private',
412-
HttpStatus.UNAUTHORIZED,
413-
);
414-
}
415-
}
416-
417-
if (!packed && !foundSong.allowDownload) {
418-
throw new HttpException(
419-
'The uploader has disabled downloads of this song',
420-
HttpStatus.UNAUTHORIZED,
421-
);
422-
}
423-
424-
const fileKey = packed ? foundSong.packedSongUrl : foundSong.nbsFileUrl;
425-
const fileExt = packed ? '.zip' : '.nbs';
426-
const fileName = `${foundSong.title}${fileExt}`;
427-
428-
try {
429-
const fileBuffer = await this.fileService.getSongFile(fileKey);
430-
431-
// increment download count
432-
if (!packed && src === 'downloadButton') {
433-
foundSong.downloadCount++;
434-
await foundSong.save();
435-
}
436-
437-
return { buffer: fileBuffer, filename: fileName };
438-
} catch (e) {
439-
this.logger.error('Error getting song file', e);
440-
throw new HttpException(
441-
'An error occurred while retrieving the song file',
442-
HttpStatus.INTERNAL_SERVER_ERROR,
443-
);
444-
}
445-
}
446-
447396
public async getMySongsPage({
448397
query,
449398
user,

apps/frontend/components.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"rsc": true,
1313
"tsx": true
1414
},
15-
"iconLibrary": "lucide",
15+
"iconLibrary": "fontawesome",
1616
"aliases": {
1717
"components": "@web/modules/shared/components",
1818
"utils": "@web/lib/utils",

apps/frontend/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,19 +56,19 @@
5656
"react-infinite-scroll-component": "^6.1.1",
5757
"react-loading-skeleton": "^3.5.0",
5858
"react-markdown": "^10.1.0",
59+
"remove-markdown": "^0.6.3",
5960
"schema-dts": "^1.1.5",
6061
"sharp": "^0.34.5",
6162
"tailwind-merge": "^3.4.0",
62-
"tailwindcss": "4.1.17",
63+
"tailwindcss": "^4.1.18",
6364
"tailwindcss-animate": "^1.0.7",
6465
"typescript": "^5.9.3",
6566
"zod": "^4.1.13",
6667
"zod-validation-error": "^5.0.0",
6768
"zustand": "^5.0.9"
6869
},
6970
"devDependencies": {
70-
"@shrutibalasa/tailwind-grid-auto-fit": "^1.1.0",
71-
"@tailwindcss/postcss": "^4.1.17",
71+
"@tailwindcss/postcss": "^4.1.18",
7272
"@types/mdx": "^2.0.13",
7373
"@types/react-modal": "^3.16.3",
7474
"eslint-config-next": "16.0.8",
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
module.exports = {
1+
const config = {
22
plugins: {
33
'@tailwindcss/postcss': {},
4-
autoprefixer: {},
54
},
65
};
6+
7+
export default config;

apps/frontend/src/app/(content)/(info)/about/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const metadata: Metadata = {
1212
const AboutPage = () => {
1313
return (
1414
<>
15-
<article className='max-w-screen-md mx-auto mb-36'>
15+
<article className='max-w-(--breakpoint-md) mx-auto mb-36'>
1616
<NoteBlockWorldLogo
1717
size={112}
1818
orientation='horizontal'

apps/frontend/src/app/(content)/(info)/blog/[id]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,12 @@ const BlogPost = async ({ params }: BlogPageProps) => {
5050
<Image
5151
src={post.image}
5252
alt=''
53-
className='w-full h-[30vh] md:h-[50vh] object-cover mb-8 mt-[-2.5rem] rounded-xl'
53+
className='w-full h-[30vh] md:h-[50vh] object-cover mb-8 -mt-10 rounded-xl'
5454
width={1920}
5555
height={1080}
5656
/>
5757
)}
58-
<article className='max-w-screen-md mx-auto mb-36'>
58+
<article className='max-w-(--breakpoint-md) mx-auto mb-36'>
5959
<Link
6060
href='/blog'
6161
className='text-zinc-500 hover:text-zinc-400 text-sm'

apps/frontend/src/app/(content)/(info)/blog/page.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
33
import { Metadata } from 'next';
44
import Image from 'next/image';
55
import Link from 'next/link';
6+
import removeMarkdown from 'remove-markdown';
67

78
import { getSortedPostsData } from '@web/lib/posts';
89
import type { PostType } from '@web/lib/posts';
@@ -33,7 +34,7 @@ const BlogPageComponent = ({ posts }: { posts: PostType[] }) => {
3334
{"See what we've been working on!"}
3435
</h2>
3536

36-
<section className='grid grid-auto-fit-xl max-w-screen-md mx-auto justify-center w-full items-center gap-8'>
37+
<section className='grid grid-auto-fit-xl max-w-(--breakpoint-md) mx-auto justify-center w-full items-center gap-8'>
3738
{posts.map((post, i) => (
3839
<Link
3940
key={i}
@@ -46,10 +47,10 @@ const BlogPageComponent = ({ posts }: { posts: PostType[] }) => {
4647
width={480}
4748
height={360}
4849
alt=''
49-
className='rounded-md aspect-[16/9] w-full object-cover transition-all duration-300 mb-2'
50+
className='rounded-md aspect-video w-full object-cover transition-all duration-300 mb-2'
5051
/>
5152

52-
<h3 className='text-lg font-bold text-opacity-50 mb-2 leading-6 flex-grow'>
53+
<h3 className='text-lg font-bold mb-2 leading-6 grow'>
5354
{post.title}
5455
</h3>
5556
<p className='text-zinc-300 tracking-wide text-sm mb-2'>
@@ -58,7 +59,7 @@ const BlogPageComponent = ({ posts }: { posts: PostType[] }) => {
5859
.replace(/\//g, '.')}
5960
</p>
6061
<p className='self-end line-clamp-3 text-sm text-zinc-400 leading-[1.3]'>
61-
{post.content.slice(0, 200)}
62+
{removeMarkdown(post.content).slice(0, 250)}
6263
</p>
6364
</article>
6465
</Link>

0 commit comments

Comments
 (0)