API: follower lists, blocking, reports, admin; drop Spotify sign-in
- Followers/following lists (paginated), 'follows you', remove a follower - Blocking severs follows both ways and hides each user from the other (profile 404s, search, follows); the blocker sees activity hidden - Reports for moderation (App Store guideline 1.2) - First account on a server becomes admin; admins see the report queue, resolve reports, and suspend accounts (signs out, blocks login, hides the profile, stops sharing). set-role script to promote/demote - users.onboardedAt drives first-run onboarding; existing accounts are backfilled as already onboarded. Empty PATCH /me no longer 500s - Remove Spotify account sign-in and polling: Spotify caps new apps at 5 accounts, so Spotify arrives via its Last.fm/ListenBrainz link or the Android reader. App-level catalog search stays for exact links - Shared: QR code generation and profile-link parsing (verified with jsQR) - Integration test timeout raised; password hashing is slow by design Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
This commit is contained in:
@@ -12,7 +12,7 @@ import {
|
||||
uuid,
|
||||
varchar,
|
||||
} from 'drizzle-orm/pg-core';
|
||||
import type { NowPlayingProvider, Plan, StreamingService } from '@musicshare/shared';
|
||||
import type { NowPlayingProvider, Plan, ReportReason, StreamingService } from '@musicshare/shared';
|
||||
|
||||
const createdAt = () => timestamp('created_at', { withTimezone: true }).notNull().defaultNow();
|
||||
|
||||
@@ -26,6 +26,12 @@ export const users = pgTable('users', {
|
||||
avatarUrl: varchar('avatar_url', { length: 500 }),
|
||||
preferredService: text('preferred_service').$type<StreamingService>(),
|
||||
/** Billing plan. Kept in sync by the billing webhook (RevenueCat/Stripe) — see lib/entitlements.ts */
|
||||
/** The first account on a server becomes admin (moderation). */
|
||||
role: text('role').$type<'user' | 'admin'>().notNull().default('user'),
|
||||
/** Suspended by a moderator: can't sign in, profile hidden, sharing stopped. */
|
||||
suspendedAt: timestamp('suspended_at', { withTimezone: true }),
|
||||
/** Set when the user finishes (or skips through) onboarding; null shows it on next launch. */
|
||||
onboardedAt: timestamp('onboarded_at', { withTimezone: true }),
|
||||
/** While in the future, nothing new is shared: services aren't polled and phone reports are dropped. */
|
||||
sharingPausedUntil: timestamp('sharing_paused_until', { withTimezone: true }),
|
||||
plan: text('plan').$type<Plan>().notNull().default('free'),
|
||||
@@ -63,6 +69,38 @@ export const follows = pgTable(
|
||||
(t) => [primaryKey({ columns: [t.followerId, t.followeeId] }), index('follows_followee_idx').on(t.followeeId)],
|
||||
);
|
||||
|
||||
/** Blocking removes follows both ways and hides each user from the other. */
|
||||
export const blocks = pgTable(
|
||||
'blocks',
|
||||
{
|
||||
blockerId: uuid('blocker_id')
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
blockedId: uuid('blocked_id')
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
createdAt: createdAt(),
|
||||
},
|
||||
(t) => [primaryKey({ columns: [t.blockerId, t.blockedId] }), index('blocks_blocked_idx').on(t.blockedId)],
|
||||
);
|
||||
|
||||
/** User reports for moderation (App Store guideline 1.2 requires a way to report users). */
|
||||
export const reports = pgTable(
|
||||
'reports',
|
||||
{
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
reporterId: uuid('reporter_id').references(() => users.id, { onDelete: 'set null' }),
|
||||
reportedId: uuid('reported_id')
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: 'cascade' }),
|
||||
reason: text('reason').$type<ReportReason>().notNull(),
|
||||
note: varchar('note', { length: 500 }),
|
||||
status: text('status').$type<'open' | 'resolved'>().notNull().default('open'),
|
||||
createdAt: createdAt(),
|
||||
},
|
||||
(t) => [index('reports_status_idx').on(t.status)],
|
||||
);
|
||||
|
||||
export const connections = pgTable(
|
||||
'connections',
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user