mirror of
https://github.com/ManInDark/HabitTrove.git
synced 2026-03-11 04:49:49 +01:00
Merge Tag 'v0.2.31'
This commit is contained in:
22
lib/atoms.ts
22
lib/atoms.ts
@@ -13,22 +13,16 @@ import { atomFamily, atomWithStorage } from "jotai/utils";
|
||||
import { DateTime } from "luxon";
|
||||
import {
|
||||
BrowserSettings,
|
||||
CoinsData,
|
||||
CompletionCache,
|
||||
getDefaultCoinsData,
|
||||
getDefaultHabitsData,
|
||||
getDefaultPublicUsersData,
|
||||
getDefaultServerSettings,
|
||||
getDefaultSettings,
|
||||
getDefaultUsersData,
|
||||
getDefaultWishlistData,
|
||||
Habit,
|
||||
HabitsData,
|
||||
PomodoroAtom,
|
||||
ServerSettings,
|
||||
Settings,
|
||||
UserData,
|
||||
UserId,
|
||||
WishlistData
|
||||
UserId
|
||||
} from "./types";
|
||||
|
||||
export const browserSettingsAtom = atomWithStorage('browserSettings', {
|
||||
@@ -37,13 +31,13 @@ export const browserSettingsAtom = atomWithStorage('browserSettings', {
|
||||
expandedWishlist: false
|
||||
} as BrowserSettings)
|
||||
|
||||
export const usersAtom = atom(getDefaultUsersData<UserData>())
|
||||
export const usersAtom = atom(getDefaultPublicUsersData())
|
||||
export const currentUserIdAtom = atom<UserId | undefined>(undefined);
|
||||
export const settingsAtom = atom(getDefaultSettings<Settings>());
|
||||
export const habitsAtom = atom(getDefaultHabitsData<HabitsData>());
|
||||
export const coinsAtom = atom(getDefaultCoinsData<CoinsData>());
|
||||
export const wishlistAtom = atom(getDefaultWishlistData<WishlistData>());
|
||||
export const serverSettingsAtom = atom(getDefaultServerSettings<ServerSettings>());
|
||||
export const settingsAtom = atom(getDefaultSettings());
|
||||
export const habitsAtom = atom(getDefaultHabitsData());
|
||||
export const coinsAtom = atom(getDefaultCoinsData());
|
||||
export const wishlistAtom = atom(getDefaultWishlistData());
|
||||
export const serverSettingsAtom = atom(getDefaultServerSettings());
|
||||
export const userSelectAtom = atom<boolean>(false)
|
||||
export const aboutOpenAtom = atom<boolean>(false)
|
||||
|
||||
|
||||
25
lib/avatar.ts
Normal file
25
lib/avatar.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export const ALLOWED_AVATAR_EXTENSIONS = new Set([
|
||||
'.png',
|
||||
'.jpg',
|
||||
'.jpeg',
|
||||
'.gif',
|
||||
'.webp',
|
||||
'.avif',
|
||||
])
|
||||
|
||||
export const ALLOWED_AVATAR_MIME_TYPES = new Set([
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/gif',
|
||||
'image/webp',
|
||||
'image/avif',
|
||||
])
|
||||
|
||||
export const AVATAR_CONTENT_TYPE: Record<string, string> = {
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.gif': 'image/gif',
|
||||
'.webp': 'image/webp',
|
||||
'.avif': 'image/avif',
|
||||
}
|
||||
@@ -1,8 +1,19 @@
|
||||
import { auth } from '@/auth'
|
||||
import 'server-only'
|
||||
import { User, UserId } from './types'
|
||||
import { loadUsersData } from '@/app/actions/data'
|
||||
import { User, UserData, UserId, getDefaultUsersData } from './types'
|
||||
import { randomBytes, scryptSync } from 'crypto'
|
||||
import fs from 'fs/promises'
|
||||
import path from 'path'
|
||||
|
||||
async function loadUsersDataFromStore(): Promise<UserData> {
|
||||
try {
|
||||
const filePath = path.join(process.cwd(), 'data', 'auth.json')
|
||||
const data = await fs.readFile(filePath, 'utf8')
|
||||
return JSON.parse(data) as UserData
|
||||
} catch {
|
||||
return getDefaultUsersData()
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCurrentUserId(): Promise<UserId | undefined> {
|
||||
const session = await auth()
|
||||
@@ -15,7 +26,7 @@ export async function getCurrentUser(): Promise<User | undefined> {
|
||||
if (!currentUserId) {
|
||||
return undefined
|
||||
}
|
||||
const usersData = await loadUsersData()
|
||||
const usersData = await loadUsersDataFromStore()
|
||||
return usersData.users.find((u) => u.id === currentUserId)
|
||||
}
|
||||
export function saltAndHashPassword(password: string, salt?: string): string {
|
||||
|
||||
36
lib/types.ts
36
lib/types.ts
@@ -27,6 +27,7 @@ export type SafeUser = SessionUser & {
|
||||
avatarPath?: string
|
||||
permissions?: Permission[]
|
||||
isAdmin?: boolean
|
||||
hasPassword?: boolean
|
||||
}
|
||||
|
||||
export type User = SafeUser & {
|
||||
@@ -34,6 +35,10 @@ export type User = SafeUser & {
|
||||
lastNotificationReadTimestamp?: string // UTC ISO date string
|
||||
}
|
||||
|
||||
export type PublicUser = Omit<User, 'password'> & {
|
||||
hasPassword: boolean
|
||||
}
|
||||
|
||||
export type Habit = {
|
||||
id: string
|
||||
name: string
|
||||
@@ -81,6 +86,10 @@ export interface UserData {
|
||||
users: User[]
|
||||
}
|
||||
|
||||
export interface PublicUserData {
|
||||
users: PublicUser[]
|
||||
}
|
||||
|
||||
export interface HabitsData {
|
||||
habits: Habit[];
|
||||
}
|
||||
@@ -98,7 +107,7 @@ export interface WishlistData {
|
||||
}
|
||||
|
||||
// Default value functions
|
||||
export function getDefaultUsersData<UserData>(): UserData {
|
||||
export function getDefaultUsersData(): UserData {
|
||||
return {
|
||||
users: [
|
||||
{
|
||||
@@ -112,23 +121,30 @@ export function getDefaultUsersData<UserData>(): UserData {
|
||||
} as UserData;
|
||||
};
|
||||
|
||||
export function getDefaultHabitsData<HabitsData>(): HabitsData {
|
||||
return { habits: [] } as HabitsData;
|
||||
}
|
||||
export const getDefaultPublicUsersData = (): PublicUserData => ({
|
||||
users: getDefaultUsersData().users.map(({ password, ...user }) => ({
|
||||
...user,
|
||||
hasPassword: !!password,
|
||||
})),
|
||||
});
|
||||
|
||||
export const getDefaultHabitsData = (): HabitsData => ({
|
||||
habits: []
|
||||
});
|
||||
|
||||
export function getDefaultTasksData<TasksData>(): TasksData {
|
||||
return { tasks: [] } as TasksData;
|
||||
};
|
||||
|
||||
export function getDefaultCoinsData<CoinsData>(): CoinsData {
|
||||
export function getDefaultCoinsData(): CoinsData {
|
||||
return { balance: 0, transactions: [] } as CoinsData;
|
||||
};
|
||||
|
||||
export function getDefaultWishlistData<WishlistData>(): WishlistData {
|
||||
export function getDefaultWishlistData(): WishlistData {
|
||||
return { items: [] } as WishlistData;
|
||||
}
|
||||
|
||||
export function getDefaultSettings<Settings>(): Settings {
|
||||
export function getDefaultSettings(): Settings {
|
||||
return {
|
||||
ui: {
|
||||
useNumberFormatting: true,
|
||||
@@ -144,12 +160,12 @@ export function getDefaultSettings<Settings>(): Settings {
|
||||
} as Settings;
|
||||
};
|
||||
|
||||
export function getDefaultServerSettings<ServerSettings>(): ServerSettings {
|
||||
export function getDefaultServerSettings(): ServerSettings {
|
||||
return { isDemo: false } as ServerSettings;
|
||||
}
|
||||
|
||||
// Map of data types to their default values
|
||||
export const DATA_DEFAULTS: { [key: string]: <T>() => T } = {
|
||||
export const DATA_DEFAULTS = {
|
||||
wishlist: getDefaultWishlistData,
|
||||
habits: getDefaultHabitsData,
|
||||
coins: getDefaultCoinsData,
|
||||
@@ -195,7 +211,7 @@ export interface JotaiHydrateInitialValues {
|
||||
coins: CoinsData;
|
||||
habits: HabitsData;
|
||||
wishlist: WishlistData;
|
||||
users: UserData;
|
||||
users: PublicUserData;
|
||||
serverSettings: ServerSettings;
|
||||
}
|
||||
|
||||
|
||||
10
lib/user-sanitizer.ts
Normal file
10
lib/user-sanitizer.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { PublicUserData, UserData } from './types'
|
||||
|
||||
export function sanitizeUserData(data: UserData): PublicUserData {
|
||||
return {
|
||||
users: data.users.map(({ password, ...user }) => ({
|
||||
...user,
|
||||
hasPassword: !!password,
|
||||
})),
|
||||
}
|
||||
}
|
||||
@@ -942,11 +942,11 @@ describe('convertMachineReadableFrequencyToHumanReadable', () => {
|
||||
})
|
||||
|
||||
describe('freshness utilities', () => {
|
||||
const mockSettings: Settings = getDefaultSettings<Settings>();
|
||||
const mockHabits: HabitsData = getDefaultHabitsData<HabitsData>();
|
||||
const mockCoins: CoinsData = getDefaultCoinsData<CoinsData>();
|
||||
const mockWishlist: WishlistData = getDefaultWishlistData<WishlistData>();
|
||||
const mockUsers: UserData = getDefaultUsersData<UserData>();
|
||||
const mockSettings: Settings = getDefaultSettings();
|
||||
const mockHabits: HabitsData = getDefaultHabitsData();
|
||||
const mockCoins: CoinsData = getDefaultCoinsData();
|
||||
const mockWishlist: WishlistData = getDefaultWishlistData();
|
||||
const mockUsers: UserData = getDefaultUsersData();
|
||||
|
||||
// Add a user to mockUsers for more realistic testing
|
||||
mockUsers.users.push({
|
||||
@@ -991,11 +991,11 @@ describe('freshness utilities', () => {
|
||||
});
|
||||
|
||||
test('should handle empty data consistently', () => {
|
||||
const emptySettings = getDefaultSettings<Settings>();
|
||||
const emptyHabits = getDefaultHabitsData<HabitsData>();
|
||||
const emptyCoins = getDefaultCoinsData<CoinsData>();
|
||||
const emptyWishlist = getDefaultWishlistData<WishlistData>();
|
||||
const emptyUsers = getDefaultUsersData<UserData>();
|
||||
const emptySettings = getDefaultSettings();
|
||||
const emptyHabits = getDefaultHabitsData();
|
||||
const emptyCoins = getDefaultCoinsData();
|
||||
const emptyWishlist = getDefaultWishlistData();
|
||||
const emptyUsers = getDefaultUsersData();
|
||||
|
||||
const string1 = prepareDataForHashing(emptySettings, emptyHabits, emptyCoins, emptyWishlist, emptyUsers);
|
||||
const string2 = prepareDataForHashing(emptySettings, emptyHabits, emptyCoins, emptyWishlist, emptyUsers);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { toast } from "@/hooks/use-toast"
|
||||
import { CoinsData, CoinTransaction, Freq, Habit, HabitsData, ParsedFrequencyResult, ParsedResultType, PomodoroAtom, SafeUser, Settings, User, UserData, WishlistData } from '@/lib/types'
|
||||
import { CoinsData, CoinTransaction, Freq, Habit, HabitsData, ParsedFrequencyResult, ParsedResultType, PomodoroAtom, PublicUserData, SafeUser, Settings, User, UserData, WishlistData } from '@/lib/types'
|
||||
import * as chrono from 'chrono-node'
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { DateTime, DateTimeFormatOptions } from "luxon"
|
||||
@@ -462,7 +462,7 @@ export function prepareDataForHashing(
|
||||
habits: HabitsData,
|
||||
coins: CoinsData,
|
||||
wishlist: WishlistData,
|
||||
users: UserData
|
||||
users: UserData | PublicUserData
|
||||
): string {
|
||||
return JSON.stringify({
|
||||
settings,
|
||||
|
||||
Reference in New Issue
Block a user