Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion data/records.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
[]
[]
299 changes: 153 additions & 146 deletions src/app/api/records/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,182 +2,189 @@ import { NextResponse } from 'next/server'
import fs from 'fs'
import path from 'path'
import { Duration } from 'luxon'
// DailyRecord 型をここでも再定義(src/types/dailyRecord.ts を変更せず扱うため)
type DailyRecord = {
bedTime: Date
wakeUpTime: Date
studyTime: Duration
mediaTime: Duration
exercise: boolean
reading: boolean
breakfast: boolean
assistance: boolean
}
import { DailyRecord } from '@/types/dailyRecord'


// 保存先ファイル
const DATA_FILE = path.join(process.cwd(), 'data', 'records.json')

// DailyRecord -> JSON 用オブジェクト
function serializeRecord(r: DailyRecord) {
return {
bedTime: r.bedTime.toISOString(),
wakeUpTime: r.wakeUpTime.toISOString(),
studyTime: r.studyTime.toISO(),
mediaTime: r.mediaTime.toISO(),
exercise: r.exercise,
reading: r.reading,
breakfast: r.breakfast,
assistance: r.assistance,
}
return {
bedTime: r.bedTime.toISOString(),
wakeUpTime: r.wakeUpTime.toISOString(),
studyTime: r.studyTime.toISO(),
mediaTime: r.mediaTime.toISO(),
exercise: r.exercise,
reading: r.reading,
breakfast: r.breakfast,
assistance: r.assistance,
}
}

// JSON オブジェクト -> DailyRecord
function deserializeRecord(o: any): DailyRecord {
return {
bedTime: new Date(o.bedTime),
wakeUpTime: new Date(o.wakeUpTime),
studyTime: Duration.fromISO(o.studyTime),
mediaTime: Duration.fromISO(o.mediaTime),
exercise: !!o.exercise,
reading: !!o.reading,
breakfast: !!o.breakfast,
assistance: !!o.assistance,
}
return {
bedTime: new Date(o.bedTime),
wakeUpTime: new Date(o.wakeUpTime),
studyTime: Duration.fromISO(o.studyTime),
mediaTime: Duration.fromISO(o.mediaTime),
exercise: !!o.exercise,
reading: !!o.reading,
breakfast: !!o.breakfast,
assistance: !!o.assistance,
}
}

function readRecords(): DailyRecord[] {
try {
if (!fs.existsSync(DATA_FILE)) return []
const raw = fs.readFileSync(DATA_FILE, 'utf8')
const arr = JSON.parse(raw)
if (!Array.isArray(arr)) return []
return arr.map(deserializeRecord)
} catch (err) {
console.error('readRecords error', err)
return []
}
try {
if (!fs.existsSync(DATA_FILE)) return []
const raw = fs.readFileSync(DATA_FILE, 'utf8')
const arr = JSON.parse(raw)
if (!Array.isArray(arr)) return []
return arr.map(deserializeRecord)
} catch (err) {
console.error('readRecords error', err)
return []
}
}

// 生データをそのまま返す(recordDate メタ情報などを保持)
function readRawRecords(): any[] {
try {
if (!fs.existsSync(DATA_FILE)) return []
const raw = fs.readFileSync(DATA_FILE, 'utf8')
const arr = JSON.parse(raw)
if (!Array.isArray(arr)) return []
return arr
} catch (err) {
console.error('readRawRecords error', err)
return []
}
try {
if (!fs.existsSync(DATA_FILE)) return []
const raw = fs.readFileSync(DATA_FILE, 'utf8')
const arr = JSON.parse(raw)
if (!Array.isArray(arr)) return []
return arr
} catch (err) {
console.error('readRawRecords error', err)
return []
}
}

function writeRawRecords(records: any[]): boolean {
try {
fs.writeFileSync(DATA_FILE, JSON.stringify(records, null, 2), 'utf8')
return true
} catch (err) {
console.error('writeRawRecords error:', err)
return false
}
try {
fs.writeFileSync(DATA_FILE, JSON.stringify(records, null, 2), 'utf8')
return true
} catch (err) {
console.error('writeRawRecords error:', err)
return false
}
}

function writeRecords(records: DailyRecord[]): boolean {
try {
const out = records.map(serializeRecord)
fs.writeFileSync(DATA_FILE, JSON.stringify(out, null, 2), 'utf8')
return true
} catch (err) {
console.error('writeRecords error', err)
return false
}
try {
const out = records.map(serializeRecord)
fs.writeFileSync(DATA_FILE, JSON.stringify(out, null, 2), 'utf8')
return true
} catch (err) {
console.error('writeRecords error', err)
return false
}
}
Comment on lines 66 to 85
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

前のPRでコメント出来ていなくて申し訳ないんだけど、これだと、意図したとおりに保存できる実装じゃないんじゃないかなぁ...
テキストで説明するの難しいので、次回のプロジェクトの時にでも...


// GET: ?days=14 (デフォルト14日)
export async function GET(request: Request) {
try {
const url = new URL(request.url)
const daysParam = url.searchParams.get('days')
const dateParam = url.searchParams.get('date') // YYYY-MM-DD
const days = daysParam ? Number(daysParam) : 14

const records = readRecords()

// date 指定がある場合はその日のレコードを返す(upsertの基準は bedTime の日付)
if (dateParam) {
// search raw records for explicit recordDate metadata first, then fall back to bedTime date
const raw = readRawRecords()
const byMeta = raw.find((r: any) => r.recordDate === dateParam)
if (byMeta) return NextResponse.json(byMeta)
const target = records.find(r => r.bedTime.toISOString().slice(0, 10) === dateParam)
if (!target) return NextResponse.json({ message: 'not found' }, { status: 404 })
return NextResponse.json(serializeRecord(target))
}

// return list with recordDate metadata so front-end can match by logical day
const raw = readRawRecords()
const listOut = raw.map((r: any) => ({
bedTime: r.bedTime,
wakeUpTime: r.wakeUpTime,
studyTime: r.studyTime,
mediaTime: r.mediaTime,
exercise: !!r.exercise,
reading: !!r.reading,
breakfast: !!r.breakfast,
assistance: !!r.assistance,
recordDate: r.recordDate || (r.bedTime ? r.bedTime.slice(0,10) : undefined),
}))

if (Number.isFinite(days) && days > 0) {
const cutoff = new Date()
cutoff.setDate(cutoff.getDate() - days + 1) // include today and past (days-1) days
const filtered = listOut.filter((r: any) => new Date(r.bedTime) >= cutoff)
return NextResponse.json(filtered)
}

return NextResponse.json(listOut)
} catch (err) {
console.error('GET /api/records error', err)
return NextResponse.json({ message: '読み込みエラー' }, { status: 500 })
}
try {
const url = new URL(request.url)
const daysParam = url.searchParams.get('days')
const dateParam = url.searchParams.get('date') // YYYY-MM-DD
const days = daysParam ? Number(daysParam) : 14

const records = readRecords()

// date 指定がある場合はその日のレコードを返す(upsertの基準は bedTime の日付)
if (dateParam) {
// search raw records for explicit recordDate metadata first, then fall back to bedTime date
const raw = readRawRecords()
const byMeta = raw.find((r: any) => r.recordDate === dateParam)
if (byMeta) return NextResponse.json(byMeta)
const target = records.find(r => r.bedTime.toISOString().slice(0, 10) === dateParam)
if (!target) return NextResponse.json({ message: 'not found' }, { status: 404 })
return NextResponse.json(serializeRecord(target))
}

// return list with recordDate metadata so front-end can match by logical day
const raw = readRawRecords()
const listOut = raw.map((r: any) => ({
bedTime: r.bedTime,
wakeUpTime: r.wakeUpTime,
studyTime: r.studyTime,
mediaTime: r.mediaTime,
exercise: !!r.exercise,
reading: !!r.reading,
breakfast: !!r.breakfast,
assistance: !!r.assistance,
recordDate: r.recordDate || (r.bedTime ? r.bedTime.slice(0, 10) : undefined),
}))

if (Number.isFinite(days) && days > 0) {
const cutoff = new Date()
cutoff.setDate(cutoff.getDate() - days + 1) // include today and past (days-1) days
// normalize cutoff to local midnight so day comparisons use whole days
cutoff.setHours(0, 0, 0, 0)
// Use recordDate (logical day) when present; otherwise fall back to bedTime's calendar date.
const filtered = listOut.filter((r: any) => {
const dayStr = r.recordDate || (r.bedTime ? r.bedTime.slice(0, 10) : undefined)
if (!dayStr) return false
// interpret as local midnight for that day
const dayDate = new Date(dayStr + 'T00:00')
return dayDate >= cutoff
})
return NextResponse.json(filtered)
}

return NextResponse.json(listOut)
} catch (err) {
console.error('GET /api/records error', err)
return NextResponse.json({ message: '読み込みエラー' }, { status: 500 })
}
}

// POST: 単一日の記録を追加。期待する body 形:{ bedTime, wakeUpTime, studyTime, mediaTime, exercise, reading, breakfast, assistance }
export async function POST(request: Request) {
try {
const body = await request.json()

if (!body || !body.bedTime || !body.wakeUpTime) {
return NextResponse.json({ message: '就寝時刻と起床時刻は必須です' }, { status: 400 })
}
// Build stored object; accept optional recordDate to indicate logical day this record belongs to
const recordDate = body.recordDate || new Date(body.bedTime).toISOString().slice(0, 10)
const stored = {
bedTime: new Date(body.bedTime).toISOString(),
wakeUpTime: new Date(body.wakeUpTime).toISOString(),
studyTime: Duration.fromObject({ minutes: body.studyTime ?? 0 }).toISO(),
mediaTime: Duration.fromObject({ minutes: body.mediaTime ?? 0 }).toISO(),
exercise: !!body.exercise,
reading: !!body.reading,
breakfast: !!body.breakfast,
assistance: !!body.assistance,
recordDate: recordDate,
}

const raw = readRawRecords()
// upsert by recordDate if exists, otherwise by bedTime date
const idx = raw.findIndex((r: any) => r.recordDate === recordDate || (r.bedTime && r.bedTime.slice(0, 10) === recordDate))
if (idx >= 0) raw[idx] = stored
else raw.push(stored)

if (!writeRawRecords(raw)) {
return NextResponse.json({ message: '保存に失敗しました' }, { status: 500 })
}

return NextResponse.json(stored)
} catch (err) {
console.error('POST /api/records error', err)
return NextResponse.json({ message: '処理エラー' }, { status: 500 })
}
try {
const body = await request.json()
console.log('POST /api/records received:', { body })

if (!body || !body.bedTime || !body.wakeUpTime) {
return NextResponse.json({ message: '就寝時刻と起床時刻は必須です' }, { status: 400 })
}
// Build stored object; accept optional recordDate to indicate logical day this record belongs to
const recordDate = body.recordDate || new Date(body.bedTime).toISOString().slice(0, 10)
console.log('Using recordDate:', recordDate)
const stored = {
bedTime: new Date(body.bedTime).toISOString(),
wakeUpTime: new Date(body.wakeUpTime).toISOString(),
studyTime: Duration.fromObject({ minutes: body.studyTime ?? 0 }).toISO(),
mediaTime: Duration.fromObject({ minutes: body.mediaTime ?? 0 }).toISO(),
exercise: !!body.exercise,
reading: !!body.reading,
breakfast: !!body.breakfast,
assistance: !!body.assistance,
recordDate: recordDate,
}
console.log('Prepared stored object:', stored)

const raw = readRawRecords()
console.log('Current records:', raw)
// upsert by recordDate if exists, otherwise by bedTime date
const idx = raw.findIndex((r: any) => r.recordDate === recordDate || (r.bedTime && r.bedTime.slice(0, 10) === recordDate))
console.log('Found existing record at index:', idx)
if (idx >= 0) raw[idx] = stored
else raw.push(stored)

if (!writeRawRecords(raw)) {
console.error('Failed to write records to file')
return NextResponse.json({ message: '保存に失敗しました' }, { status: 500 })
}

console.log('Successfully saved record')
return NextResponse.json(stored)
} catch (err) {
console.error('POST /api/records error', err)
return NextResponse.json({ message: '処理エラー' }, { status: 500 })
}
}

2 changes: 1 addition & 1 deletion src/types/dailyRecord.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Duration } from 'luxon'

type DailyRecord = {
export type DailyRecord = {
bedTime: Date; // 寝た時刻
wakeUpTime: Date; // 起きた時刻
studyTime: Duration; // 勉強時間(分)
Expand Down