mirror of
https://github.com/Oak-and-Sprout/sprout-track.git
synced 2026-01-06 08:20:33 -06:00
added write protection util and updated logs write actions
This commit is contained in:
@@ -3,8 +3,15 @@ import prisma from '../db';
|
||||
import { ApiResponse, BathLogCreate, BathLogResponse } from '../types';
|
||||
import { withAuthContext, AuthResult } from '../utils/auth';
|
||||
import { toUTC, formatForResponse } from '../utils/timezone';
|
||||
import { checkWritePermission } from '../utils/writeProtection';
|
||||
|
||||
async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId, caretakerId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
@@ -60,6 +67,12 @@ async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
}
|
||||
|
||||
async function handlePut(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
@@ -217,6 +230,12 @@ async function handleGet(req: NextRequest, authContext: AuthResult) {
|
||||
}
|
||||
|
||||
async function handleDelete(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
|
||||
@@ -3,8 +3,15 @@ import prisma from '../db';
|
||||
import { ApiResponse, DiaperLogCreate, DiaperLogResponse } from '../types';
|
||||
import { withAuthContext, AuthResult } from '../utils/auth';
|
||||
import { toUTC, formatForResponse } from '../utils/timezone';
|
||||
import { checkWritePermission } from '../utils/writeProtection';
|
||||
|
||||
async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId, caretakerId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
@@ -58,6 +65,12 @@ async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
}
|
||||
|
||||
async function handlePut(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
@@ -212,6 +225,12 @@ async function handleGet(req: NextRequest, authContext: AuthResult) {
|
||||
}
|
||||
|
||||
async function handleDelete(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
|
||||
@@ -4,8 +4,15 @@ import { ApiResponse, FeedLogCreate, FeedLogResponse } from '../types';
|
||||
import { FeedType } from '@prisma/client';
|
||||
import { withAuthContext, AuthResult } from '../utils/auth';
|
||||
import { toUTC, formatForResponse } from '../utils/timezone';
|
||||
import { checkWritePermission } from '../utils/writeProtection';
|
||||
|
||||
async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const body: FeedLogCreate = await req.json();
|
||||
const { familyId, caretakerId } = authContext;
|
||||
@@ -72,6 +79,12 @@ async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
}
|
||||
|
||||
async function handlePut(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const id = searchParams.get('id');
|
||||
@@ -243,6 +256,12 @@ async function handleGet(req: NextRequest, authContext: AuthResult) {
|
||||
}
|
||||
|
||||
async function handleDelete(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const id = searchParams.get('id');
|
||||
|
||||
@@ -3,8 +3,15 @@ import prisma from '../db';
|
||||
import { ApiResponse, MeasurementCreate, MeasurementResponse } from '../types';
|
||||
import { withAuthContext, AuthResult } from '../utils/auth';
|
||||
import { toUTC, formatForResponse } from '../utils/timezone';
|
||||
import { checkWritePermission } from '../utils/writeProtection';
|
||||
|
||||
async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId, caretakerId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
@@ -59,6 +66,12 @@ async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
}
|
||||
|
||||
async function handlePut(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
@@ -247,6 +260,12 @@ async function handleGet(req: NextRequest, authContext: AuthResult) {
|
||||
}
|
||||
|
||||
async function handleDelete(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
|
||||
@@ -3,11 +3,18 @@ import prisma from '../db';
|
||||
import { ApiResponse, MedicineLogCreate, MedicineLogResponse } from '../types';
|
||||
import { withAuthContext, AuthResult } from '../utils/auth';
|
||||
import { toUTC, formatForResponse } from '../utils/timezone';
|
||||
import { checkWritePermission } from '../utils/writeProtection';
|
||||
|
||||
/**
|
||||
* Handle POST request to create a new medicine log entry
|
||||
*/
|
||||
async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId, caretakerId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
@@ -69,6 +76,12 @@ async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
* Handle PUT request to update a medicine log entry
|
||||
*/
|
||||
async function handlePut(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
@@ -271,6 +284,12 @@ async function handleGet(req: NextRequest, authContext: AuthResult) {
|
||||
* Handle DELETE request to hard delete a medicine log
|
||||
*/
|
||||
async function handleDelete(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
|
||||
@@ -3,8 +3,15 @@ import prisma from '../db';
|
||||
import { ApiResponse, MilestoneCreate, MilestoneResponse } from '../types';
|
||||
import { withAuthContext, AuthResult } from '../utils/auth';
|
||||
import { toUTC, formatForResponse } from '../utils/timezone';
|
||||
import { checkWritePermission } from '../utils/writeProtection';
|
||||
|
||||
async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId, caretakerId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
@@ -59,6 +66,12 @@ async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
}
|
||||
|
||||
async function handlePut(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
@@ -248,6 +261,12 @@ async function handleGet(req: NextRequest, authContext: AuthResult) {
|
||||
}
|
||||
|
||||
async function handleDelete(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
|
||||
@@ -3,8 +3,15 @@ import prisma from '../db';
|
||||
import { ApiResponse, PumpLogCreate, PumpLogResponse } from '../types';
|
||||
import { withAuthContext, AuthResult } from '../utils/auth';
|
||||
import { toUTC, formatForResponse, calculateDurationMinutes } from '../utils/timezone';
|
||||
import { checkWritePermission } from '../utils/writeProtection';
|
||||
|
||||
async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId, caretakerId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
@@ -80,6 +87,12 @@ async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
}
|
||||
|
||||
async function handlePut(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
@@ -280,6 +293,12 @@ async function handleGet(req: NextRequest, authContext: AuthResult) {
|
||||
}
|
||||
|
||||
async function handleDelete(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
|
||||
@@ -3,8 +3,15 @@ import prisma from '../db';
|
||||
import { ApiResponse, SleepLogCreate, SleepLogResponse } from '../types';
|
||||
import { withAuthContext, AuthResult } from '../utils/auth';
|
||||
import { toUTC, formatForResponse, calculateDurationMinutes } from '../utils/timezone';
|
||||
import { checkWritePermission } from '../utils/writeProtection';
|
||||
|
||||
async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId, caretakerId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
@@ -66,6 +73,12 @@ async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
}
|
||||
|
||||
async function handlePut(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
@@ -237,6 +250,12 @@ async function handleGet(req: NextRequest, authContext: AuthResult) {
|
||||
}
|
||||
|
||||
async function handleDelete(req: NextRequest, authContext: AuthResult) {
|
||||
// Check write permissions for expired accounts
|
||||
const writeCheck = checkWritePermission(authContext);
|
||||
if (!writeCheck.allowed) {
|
||||
return writeCheck.response!;
|
||||
}
|
||||
|
||||
try {
|
||||
const { familyId: userFamilyId } = authContext;
|
||||
if (!userFamilyId) {
|
||||
|
||||
97
app/api/utils/writeProtection.ts
Normal file
97
app/api/utils/writeProtection.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { AuthResult } from './auth';
|
||||
import type { ApiResponse } from '../types';
|
||||
|
||||
export type WriteProtectionResponse = {
|
||||
allowed: boolean;
|
||||
response?: NextResponse<ApiResponse<any>>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a write operation should be allowed based on account expiration status
|
||||
* Use this with an existing authContext (from withAuthContext wrapper)
|
||||
*
|
||||
* IMPORTANT: This only enforces write protection in SaaS mode.
|
||||
* In self-hosted mode, all write operations are allowed (maintains backward compatibility).
|
||||
*
|
||||
* @param authContext - The authentication context (from withAuthContext)
|
||||
* @returns WriteProtectionResponse with allowed flag and response (if blocked)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* async function handlePost(req: NextRequest, authContext: AuthResult) {
|
||||
* const writeCheck = checkWritePermission(authContext);
|
||||
* if (!writeCheck.allowed) {
|
||||
* return writeCheck.response; // Returns 403 with expiration info
|
||||
* }
|
||||
* // ... rest of endpoint
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function checkWritePermission(
|
||||
authContext: AuthResult
|
||||
): WriteProtectionResponse {
|
||||
|
||||
if (!authContext.authenticated) {
|
||||
return {
|
||||
allowed: false,
|
||||
response: NextResponse.json<ApiResponse<null>>(
|
||||
{
|
||||
success: false,
|
||||
error: authContext.error || 'Authentication required',
|
||||
},
|
||||
{ status: 401 }
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
// Check if account is expired and write operations are blocked
|
||||
// This will only happen in SaaS mode, as auth.ts only sets isExpired in SaaS mode
|
||||
if (authContext.isExpired) {
|
||||
const { trialEnds, planExpires } = authContext;
|
||||
|
||||
// Determine expiration type for user-friendly messaging
|
||||
let expirationType: 'TRIAL_EXPIRED' | 'PLAN_EXPIRED' | 'NO_PLAN' = 'NO_PLAN';
|
||||
let expirationDate: string | undefined;
|
||||
|
||||
if (trialEnds) {
|
||||
expirationType = 'TRIAL_EXPIRED';
|
||||
expirationDate = trialEnds;
|
||||
} else if (planExpires) {
|
||||
expirationType = 'PLAN_EXPIRED';
|
||||
expirationDate = planExpires;
|
||||
}
|
||||
|
||||
// Generate user-friendly error message
|
||||
let errorMessage = 'Your account has expired. Please upgrade to continue.';
|
||||
if (expirationType === 'TRIAL_EXPIRED') {
|
||||
errorMessage = 'Your free trial has ended. Upgrade to continue tracking.';
|
||||
} else if (expirationType === 'PLAN_EXPIRED') {
|
||||
errorMessage = 'Your subscription has expired. Please renew to continue.';
|
||||
} else if (expirationType === 'NO_PLAN') {
|
||||
errorMessage = 'No active subscription found. Please subscribe to continue.';
|
||||
}
|
||||
|
||||
return {
|
||||
allowed: false,
|
||||
response: NextResponse.json<ApiResponse<any>>(
|
||||
{
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
data: {
|
||||
expirationInfo: {
|
||||
type: expirationType,
|
||||
date: expirationDate,
|
||||
familySlug: authContext.familySlug
|
||||
}
|
||||
}
|
||||
},
|
||||
{ status: 403 }
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
allowed: true
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user