Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
586c09f
build: add docker setup and migrate static assets to public/
dpuscher Jan 23, 2020
8b082ee
fix: make lastfm auth callback url dynamic
dpuscher Jan 23, 2020
674c70b
build: improve dockerfile and add wait-for script
dpuscher Jan 23, 2020
3f1bc6a
build: update dockerfile and modernize dependencies
dpuscher Feb 18, 2026
60e0329
ci: add GHCR build and Dokploy deploy workflows
dpuscher Feb 18, 2026
3616db4
fix: resolve Docker build failures with Node 22
dpuscher Feb 18, 2026
21f0703
fix: build multi-platform Docker images (amd64 + arm64)
dpuscher Feb 18, 2026
eeb53f4
feat: update dev tooling (ESLint 9, Jest 29, Stylelint 16, TypeScript)
dpuscher Feb 19, 2026
075bfd8
feat: remove abandonware and update safe packages
dpuscher Feb 19, 2026
64e856f
feat: update Redis v2→v5 and connect-redis v4→v9
dpuscher Feb 19, 2026
6429973
feat: update Mongoose v5→v8
dpuscher Feb 19, 2026
5ab8e5a
feat: update Express v4→v5, remove body-parser, update Helmet v3→v8
dpuscher Feb 19, 2026
8ccd9ac
feat: update React ecosystem 16→18
dpuscher Feb 19, 2026
ff01f6b
feat: update Next.js 9→14
dpuscher Feb 19, 2026
87db2c3
feat: migrate codebase to TypeScript
dpuscher Feb 19, 2026
017dcea
feat: migrate Express routes to Next.js API routes
dpuscher Feb 19, 2026
33c12b4
feat: remove Express server, use Next.js exclusively
dpuscher Feb 19, 2026
7f14749
fix: resolve React 18 warnings from docker logs
dpuscher Feb 19, 2026
0f78045
fix: prevent OverwriteModelError in Next.js dev mode
dpuscher Feb 19, 2026
fbcd570
fix: handle unauthenticated session state correctly
dpuscher Feb 19, 2026
90b49ac
fix: convert detected/scrobbled to dynamic routes
dpuscher Feb 19, 2026
b58de69
fix: pass full app props to useWrappedStore in _app.tsx
dpuscher Feb 19, 2026
555b760
refactor: replace propTypes/defaultProps with TypeScript interfaces
dpuscher Feb 19, 2026
6f34550
refactor: migrate profile page from getInitialProps to getServerSideP…
dpuscher Feb 19, 2026
9f992db
fix: update ProfileHistoryItem link href to use dynamic route path
dpuscher Feb 19, 2026
3d479b0
fix: guard against null state.data in autoScrobbleReducer
dpuscher Feb 19, 2026
6188ca7
fix: check response.ok before parsing json in history and autoscrobbl…
dpuscher Feb 19, 2026
c75588e
fix: serialize error message correctly in history api error response
dpuscher Feb 19, 2026
bc8241d
fix: rename ProfileHistorys class to ProfileHistory
dpuscher Feb 19, 2026
44ed288
fix: update QueryRelease link href to use dynamic route path
dpuscher Feb 19, 2026
c4197b6
fix: surface Discogs errors and guard release fetch response
dpuscher Feb 19, 2026
c0f0ef2
fix: handle duplicate key race condition in Release.firstOrCreate
dpuscher Feb 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
["styled-components", {
"displayName": false,
"ssr": true
}],
"transform-class-properties"
}]
]
}
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.git
*Dockerfile*
*docker-compose*
node_modules
.next
.env
.env.*
data
coverage
27 changes: 0 additions & 27 deletions .eslintrc.js

This file was deleted.

43 changes: 43 additions & 0 deletions .github/workflows/build-production.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Build and Deploy (Production)

on:
push:
branches:
- production

jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: ghcr.io/dpuscher/code-scrobble:production

- name: Trigger Dokploy redeploy
run: |
curl -s -o /dev/null -w "%{http_code}" \
-X POST "${{ secrets.DOKPLOY_PRODUCTION_DEPLOY_WEBHOOK }}"
43 changes: 43 additions & 0 deletions .github/workflows/build-staging.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Build and Deploy (Staging)

on:
push:
branches:
- staging

jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: ghcr.io/dpuscher/code-scrobble:staging

- name: Trigger Dokploy redeploy
run: |
curl -s -o /dev/null -w "%{http_code}" \
-X POST "${{ secrets.DOKPLOY_STAGING_DEPLOY_WEBHOOK }}"
12 changes: 11 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,15 @@ bundles/
coverage
log/
node_modules/
static/service-worker.js
public/static/service-worker.js
yarn-error.log

.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
data
.mcp.json
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v10.15.1
v22
10 changes: 0 additions & 10 deletions .stylelintrc

This file was deleted.

5 changes: 5 additions & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
enableScripts: false

nodeLinker: node-modules

npmMinimalAgeGate: 4320
38 changes: 38 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
FROM node:22-alpine AS base
RUN corepack enable

FROM base AS deps
WORKDIR /app
RUN apk add --no-cache git
COPY package.json yarn.lock .yarnrc.yml ./
RUN yarn install --immutable && yarn allow-scripts run

FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN yarn build

FROM base AS runner
WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000

RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs

COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/app ./app
COPY --from=builder /app/lib ./lib
COPY package.json ./

USER nextjs

EXPOSE 3000

CMD ["node_modules/.bin/next", "start"]
15 changes: 0 additions & 15 deletions app/__mocks__/redis.js

This file was deleted.

25 changes: 25 additions & 0 deletions app/__mocks__/redis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* eslint-disable no-underscore-dangle */

const store = new Map();

const mockGet = jest.fn(async (key) => store.get(key) || null);
const mockSet = jest.fn(async (key, value) => {
store.set(key, value);
return 'OK';
});
const mockConnect = jest.fn(async () => {});
const mockOn = jest.fn();

const createClient = jest.fn(() => ({
get: mockGet,
set: mockSet,
connect: mockConnect,
on: mockOn,
}));

module.exports = {
createClient,
_get: mockGet,
_set: mockSet,
_reset: () => store.clear(),
};
30 changes: 13 additions & 17 deletions app/cache.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
const redis = require('redis');
const { promisify } = require('util');
const { createClient } = require('redis');

const client = redis.createClient(process.env.REDISCLOUD_URL);

const getAsync = promisify(client.get).bind(client);
const setAsync = promisify(client.set).bind(client);
const client = createClient({ url: process.env.REDISCLOUD_URL });
client.connect().catch(console.error);
client.on('error', console.error);

module.exports = {
set: (key, value, ttl = 86400) => (
setAsync(key, JSON.stringify(value), 'EX', ttl)
client.set(key, JSON.stringify(value), { EX: ttl })
),

get: key => (
new Promise(async (resolve, reject) => {
const value = await getAsync(key);
if (value) {
resolve(JSON.parse(value));
} else {
reject();
}
})
),
get: async (key) => {
const value = await client.get(key);
if (value) {
return JSON.parse(value);
}
// eslint-disable-next-line prefer-promise-reject-errors
return Promise.reject();
},
};
5 changes: 2 additions & 3 deletions app/discogs.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const Discogs = require('disconnect').Client;
const dig = require('object-dig');
const orderBy = require('lodash/orderBy');
const pick = require('lodash/pick');
const find = require('lodash/find');
Expand Down Expand Up @@ -101,13 +100,13 @@ module.exports = {
new Promise((resolve, reject) => {
Database.getRelease(id, (err, data) => {
if (err || !data) {
reject();
reject(err || new Error('No data returned from Discogs'));
} else {
resolve({
id,
artist: data.artists.map(a => a.name).join(', '),
title: data.title,
image: dig(data, 'images', 0, 'uri'),
image: data?.images?.[0]?.uri,
url: data.uri,
year: data.year,
tracks: normalizeTracklist(data.tracklist)
Expand Down
4 changes: 0 additions & 4 deletions app/middlewares/loggedIn.js

This file was deleted.

6 changes: 0 additions & 6 deletions app/middlewares/loggedInApi.js

This file was deleted.

4 changes: 0 additions & 4 deletions app/middlewares/notLoggedIn.js

This file was deleted.

11 changes: 9 additions & 2 deletions app/models/release.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,14 @@ releaseSchema.statics.firstOrCreate = async function firstOrCreate(param) {
const id = param.id || await Discogs.barcode(param.barcode);
if (!id) return null;

return this.createFromDiscogs(id, param.barcode);
try {
return await this.createFromDiscogs(id, param.barcode);
} catch (err) {
if (err.code === 11000) {
return this.findOne(param).exec();
}
throw err;
}
}

// Data is older than one week
Expand All @@ -77,4 +84,4 @@ releaseSchema.statics.firstOrCreate = async function firstOrCreate(param) {
return release;
};

module.exports = mongoose.model('Release', releaseSchema);
module.exports = mongoose.models.Release || mongoose.model('Release', releaseSchema);
2 changes: 1 addition & 1 deletion app/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ userSchema.methods.isInstantScrobble = function isInstantScrobble(id) {
return (this.instantScrobbles || []).includes(String(id));
};

module.exports = mongoose.model('User', userSchema);
module.exports = mongoose.models.User || mongoose.model('User', userSchema);
21 changes: 0 additions & 21 deletions app/routes/api/barcode.js

This file was deleted.

19 changes: 0 additions & 19 deletions app/routes/api/index.js

This file was deleted.

Loading