Compare commits

..

No commits in common. "3e75fb6ae24124697d7ec43d6fcfef8dd28def91" and "f3d43c71f7d883e2ef322230d1ded60e2fdf2f95" have entirely different histories.

14 changed files with 164 additions and 519 deletions

View file

@ -36,39 +36,6 @@ export type DateTimeFilter<$PrismaModel = never> = {
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
} }
export type StringFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[]
notIn?: string[]
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringFilter<$PrismaModel> | string
}
export type StringNullableFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
in?: string[] | null
notIn?: string[] | null
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
}
export type SortOrderInput = {
sort: Prisma.SortOrder
nulls?: Prisma.NullsOrder
}
export type IntWithAggregatesFilter<$PrismaModel = never> = { export type IntWithAggregatesFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
in?: number[] in?: number[]
@ -99,40 +66,6 @@ export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
_max?: Prisma.NestedDateTimeFilter<$PrismaModel> _max?: Prisma.NestedDateTimeFilter<$PrismaModel>
} }
export type StringWithAggregatesFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[]
notIn?: string[]
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedStringFilter<$PrismaModel>
_max?: Prisma.NestedStringFilter<$PrismaModel>
}
export type StringNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
in?: string[] | null
notIn?: string[] | null
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
}
export type NestedIntFilter<$PrismaModel = never> = { export type NestedIntFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
in?: number[] in?: number[]
@ -155,34 +88,6 @@ export type NestedDateTimeFilter<$PrismaModel = never> = {
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
} }
export type NestedStringFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[]
notIn?: string[]
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringFilter<$PrismaModel> | string
}
export type NestedStringNullableFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
in?: string[] | null
notIn?: string[] | null
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
}
export type NestedIntWithAggregatesFilter<$PrismaModel = never> = { export type NestedIntWithAggregatesFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
in?: number[] in?: number[]
@ -224,49 +129,4 @@ export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
_max?: Prisma.NestedDateTimeFilter<$PrismaModel> _max?: Prisma.NestedDateTimeFilter<$PrismaModel>
} }
export type NestedStringWithAggregatesFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[]
notIn?: string[]
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedStringFilter<$PrismaModel>
_max?: Prisma.NestedStringFilter<$PrismaModel>
}
export type NestedStringNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
in?: string[] | null
notIn?: string[] | null
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
}
export type NestedIntNullableFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
in?: number[] | null
notIn?: number[] | null
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
}

View file

@ -20,7 +20,7 @@ const config: runtime.GetPrismaClientConfig = {
"clientVersion": "7.4.2", "clientVersion": "7.4.2",
"engineVersion": "94a226be1cf2967af2541cca5529f0f7ba866919", "engineVersion": "94a226be1cf2967af2541cca5529f0f7ba866919",
"activeProvider": "sqlite", "activeProvider": "sqlite",
"inlineSchema": "generator client {\n provider = \"prisma-client\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"sqlite\"\n}\n\nmodel PhotoPost {\n id Int @id @default(autoincrement())\n createdAt DateTime @default(now())\n fileName String @unique\n title String\n description String?\n}\n", "inlineSchema": "generator client {\n provider = \"prisma-client\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"sqlite\"\n}\n\nmodel PhotoPost {\n id Int @id @default(autoincrement())\n createdAt DateTime @default(now())\n}\n",
"runtimeDataModel": { "runtimeDataModel": {
"models": {}, "models": {},
"enums": {}, "enums": {},
@ -32,10 +32,10 @@ const config: runtime.GetPrismaClientConfig = {
} }
} }
config.runtimeDataModel = JSON.parse("{\"models\":{\"PhotoPost\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"fileName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") config.runtimeDataModel = JSON.parse("{\"models\":{\"PhotoPost\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
config.parameterizationSchema = { config.parameterizationSchema = {
strings: JSON.parse("[\"where\",\"PhotoPost.findUnique\",\"PhotoPost.findUniqueOrThrow\",\"orderBy\",\"cursor\",\"PhotoPost.findFirst\",\"PhotoPost.findFirstOrThrow\",\"PhotoPost.findMany\",\"data\",\"PhotoPost.createOne\",\"PhotoPost.createMany\",\"PhotoPost.createManyAndReturn\",\"PhotoPost.updateOne\",\"PhotoPost.updateMany\",\"PhotoPost.updateManyAndReturn\",\"create\",\"update\",\"PhotoPost.upsertOne\",\"PhotoPost.deleteOne\",\"PhotoPost.deleteMany\",\"having\",\"_count\",\"_avg\",\"_sum\",\"_min\",\"_max\",\"PhotoPost.groupBy\",\"PhotoPost.aggregate\",\"AND\",\"OR\",\"NOT\",\"id\",\"createdAt\",\"fileName\",\"title\",\"description\",\"equals\",\"in\",\"notIn\",\"lt\",\"lte\",\"gt\",\"gte\",\"contains\",\"startsWith\",\"endsWith\",\"not\",\"set\",\"increment\",\"decrement\",\"multiply\",\"divide\"]"), strings: JSON.parse("[\"where\",\"PhotoPost.findUnique\",\"PhotoPost.findUniqueOrThrow\",\"orderBy\",\"cursor\",\"PhotoPost.findFirst\",\"PhotoPost.findFirstOrThrow\",\"PhotoPost.findMany\",\"data\",\"PhotoPost.createOne\",\"PhotoPost.createMany\",\"PhotoPost.createManyAndReturn\",\"PhotoPost.updateOne\",\"PhotoPost.updateMany\",\"PhotoPost.updateManyAndReturn\",\"create\",\"update\",\"PhotoPost.upsertOne\",\"PhotoPost.deleteOne\",\"PhotoPost.deleteMany\",\"having\",\"_count\",\"_avg\",\"_sum\",\"_min\",\"_max\",\"PhotoPost.groupBy\",\"PhotoPost.aggregate\",\"AND\",\"OR\",\"NOT\",\"id\",\"createdAt\",\"equals\",\"in\",\"notIn\",\"lt\",\"lte\",\"gt\",\"gte\",\"not\",\"set\",\"increment\",\"decrement\",\"multiply\",\"divide\"]"),
graph: "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" graph: "KwsQBRwAACIAMB0AAAQAEB4AACIAMB8CAAAAASBAACQAIQEAAAABACABAAAAAQAgBRwAACIAMB0AAAQAEB4AACIAMB8CACMAISBAACQAIQADAAAABAAgAwAABQAwBAAAAQAgAwAAAAQAIAMAAAUAMAQAAAEAIAMAAAAEACADAAAFADAEAAABACACHwIAAAABIEAAAAABAQgAAAkAIAIfAgAAAAEgQAAAAAEBCAAACwAwAQgAAAsAMAIfAgArACEgQAAqACECAAAAAQAgCAAADgAgAh8CACsAISBAACoAIQIAAAAEACAIAAAQACACAAAABAAgCAAAEAAgAwAAAAEAIA8AAAkAIBAAAA4AIAEAAAABACABAAAABAAgBRUAACUAIBYAACYAIBcAACkAIBgAACgAIBkAACcAIAUcAAAaADAdAAAXABAeAAAaADAfAgAbACEgQAAcACEDAAAABAAgAwAAFgAwFAAAFwAgAwAAAAQAIAMAAAUAMAQAAAEAIAUcAAAaADAdAAAXABAeAAAaADAfAgAbACEgQAAcACENFQAAHgAgFgAAIQAgFwAAHgAgGAAAHgAgGQAAHgAgIQIAAAABIgIAAAAEIwIAAAAEJAIAAAABJQIAAAABJgIAAAABJwIAAAABKAIAIAAhCxUAAB4AIBgAAB8AIBkAAB8AICFAAAAAASJAAAAABCNAAAAABCRAAAAAASVAAAAAASZAAAAAASdAAAAAAShAAB0AIQsVAAAeACAYAAAfACAZAAAfACAhQAAAAAEiQAAAAAQjQAAAAAQkQAAAAAElQAAAAAEmQAAAAAEnQAAAAAEoQAAdACEIIQIAAAABIgIAAAAEIwIAAAAEJAIAAAABJQIAAAABJgIAAAABJwIAAAABKAIAHgAhCCFAAAAAASJAAAAABCNAAAAABCRAAAAAASVAAAAAASZAAAAAASdAAAAAAShAAB8AIQ0VAAAeACAWAAAhACAXAAAeACAYAAAeACAZAAAeACAhAgAAAAEiAgAAAAQjAgAAAAQkAgAAAAElAgAAAAEmAgAAAAEnAgAAAAEoAgAgACEIIQgAAAABIggAAAAEIwgAAAAEJAgAAAABJQgAAAABJggAAAABJwgAAAABKAgAIQAhBRwAACIAMB0AAAQAEB4AACIAMB8CACMAISBAACQAIQghAgAAAAEiAgAAAAQjAgAAAAQkAgAAAAElAgAAAAEmAgAAAAEnAgAAAAEoAgAeACEIIUAAAAABIkAAAAAEI0AAAAAEJEAAAAABJUAAAAABJkAAAAABJ0AAAAABKEAAHwAhAAAAAAABKUAAAAABBSkCAAAAASoCAAAAASsCAAAAASwCAAAAAS0CAAAAAQAAAAAFFQAGFgAHFwAIGAAJGQAKAAAAAAAFFQAGFgAHFwAIGAAJGQAKAQIBAgMBBQYBBgcBBwgBCQoBCgwCCw0DDA8BDRECDhIEERMBEhQBExUCGhgFGxkL"
} }
async function decodeBase64AsWasm(wasmBase64: string): Promise<WebAssembly.Module> { async function decodeBase64AsWasm(wasmBase64: string): Promise<WebAssembly.Module> {

View file

@ -516,10 +516,7 @@ export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof
export const PhotoPostScalarFieldEnum = { export const PhotoPostScalarFieldEnum = {
id: 'id', id: 'id',
createdAt: 'createdAt', createdAt: 'createdAt'
fileName: 'fileName',
title: 'title',
description: 'description'
} as const } as const
export type PhotoPostScalarFieldEnum = (typeof PhotoPostScalarFieldEnum)[keyof typeof PhotoPostScalarFieldEnum] export type PhotoPostScalarFieldEnum = (typeof PhotoPostScalarFieldEnum)[keyof typeof PhotoPostScalarFieldEnum]
@ -533,14 +530,6 @@ export const SortOrder = {
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder] export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]
export const NullsOrder = {
first: 'first',
last: 'last'
} as const
export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder]
/** /**
* Field references * Field references
@ -561,13 +550,6 @@ export type DateTimeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel
/**
* Reference to a field of type 'String'
*/
export type StringFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'String'>
/** /**
* Reference to a field of type 'Float' * Reference to a field of type 'Float'
*/ */

View file

@ -69,10 +69,7 @@ export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof
export const PhotoPostScalarFieldEnum = { export const PhotoPostScalarFieldEnum = {
id: 'id', id: 'id',
createdAt: 'createdAt', createdAt: 'createdAt'
fileName: 'fileName',
title: 'title',
description: 'description'
} as const } as const
export type PhotoPostScalarFieldEnum = (typeof PhotoPostScalarFieldEnum)[keyof typeof PhotoPostScalarFieldEnum] export type PhotoPostScalarFieldEnum = (typeof PhotoPostScalarFieldEnum)[keyof typeof PhotoPostScalarFieldEnum]
@ -85,11 +82,3 @@ export const SortOrder = {
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder] export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]
export const NullsOrder = {
first: 'first',
last: 'last'
} as const
export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder]

View file

@ -37,25 +37,16 @@ export type PhotoPostSumAggregateOutputType = {
export type PhotoPostMinAggregateOutputType = { export type PhotoPostMinAggregateOutputType = {
id: number | null id: number | null
createdAt: Date | null createdAt: Date | null
fileName: string | null
title: string | null
description: string | null
} }
export type PhotoPostMaxAggregateOutputType = { export type PhotoPostMaxAggregateOutputType = {
id: number | null id: number | null
createdAt: Date | null createdAt: Date | null
fileName: string | null
title: string | null
description: string | null
} }
export type PhotoPostCountAggregateOutputType = { export type PhotoPostCountAggregateOutputType = {
id: number id: number
createdAt: number createdAt: number
fileName: number
title: number
description: number
_all: number _all: number
} }
@ -71,25 +62,16 @@ export type PhotoPostSumAggregateInputType = {
export type PhotoPostMinAggregateInputType = { export type PhotoPostMinAggregateInputType = {
id?: true id?: true
createdAt?: true createdAt?: true
fileName?: true
title?: true
description?: true
} }
export type PhotoPostMaxAggregateInputType = { export type PhotoPostMaxAggregateInputType = {
id?: true id?: true
createdAt?: true createdAt?: true
fileName?: true
title?: true
description?: true
} }
export type PhotoPostCountAggregateInputType = { export type PhotoPostCountAggregateInputType = {
id?: true id?: true
createdAt?: true createdAt?: true
fileName?: true
title?: true
description?: true
_all?: true _all?: true
} }
@ -182,9 +164,6 @@ export type PhotoPostGroupByArgs<ExtArgs extends runtime.Types.Extensions.Intern
export type PhotoPostGroupByOutputType = { export type PhotoPostGroupByOutputType = {
id: number id: number
createdAt: Date createdAt: Date
fileName: string
title: string
description: string | null
_count: PhotoPostCountAggregateOutputType | null _count: PhotoPostCountAggregateOutputType | null
_avg: PhotoPostAvgAggregateOutputType | null _avg: PhotoPostAvgAggregateOutputType | null
_sum: PhotoPostSumAggregateOutputType | null _sum: PhotoPostSumAggregateOutputType | null
@ -213,36 +192,24 @@ export type PhotoPostWhereInput = {
NOT?: Prisma.PhotoPostWhereInput | Prisma.PhotoPostWhereInput[] NOT?: Prisma.PhotoPostWhereInput | Prisma.PhotoPostWhereInput[]
id?: Prisma.IntFilter<"PhotoPost"> | number id?: Prisma.IntFilter<"PhotoPost"> | number
createdAt?: Prisma.DateTimeFilter<"PhotoPost"> | Date | string createdAt?: Prisma.DateTimeFilter<"PhotoPost"> | Date | string
fileName?: Prisma.StringFilter<"PhotoPost"> | string
title?: Prisma.StringFilter<"PhotoPost"> | string
description?: Prisma.StringNullableFilter<"PhotoPost"> | string | null
} }
export type PhotoPostOrderByWithRelationInput = { export type PhotoPostOrderByWithRelationInput = {
id?: Prisma.SortOrder id?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
fileName?: Prisma.SortOrder
title?: Prisma.SortOrder
description?: Prisma.SortOrderInput | Prisma.SortOrder
} }
export type PhotoPostWhereUniqueInput = Prisma.AtLeast<{ export type PhotoPostWhereUniqueInput = Prisma.AtLeast<{
id?: number id?: number
fileName?: string
AND?: Prisma.PhotoPostWhereInput | Prisma.PhotoPostWhereInput[] AND?: Prisma.PhotoPostWhereInput | Prisma.PhotoPostWhereInput[]
OR?: Prisma.PhotoPostWhereInput[] OR?: Prisma.PhotoPostWhereInput[]
NOT?: Prisma.PhotoPostWhereInput | Prisma.PhotoPostWhereInput[] NOT?: Prisma.PhotoPostWhereInput | Prisma.PhotoPostWhereInput[]
createdAt?: Prisma.DateTimeFilter<"PhotoPost"> | Date | string createdAt?: Prisma.DateTimeFilter<"PhotoPost"> | Date | string
title?: Prisma.StringFilter<"PhotoPost"> | string }, "id">
description?: Prisma.StringNullableFilter<"PhotoPost"> | string | null
}, "id" | "fileName">
export type PhotoPostOrderByWithAggregationInput = { export type PhotoPostOrderByWithAggregationInput = {
id?: Prisma.SortOrder id?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
fileName?: Prisma.SortOrder
title?: Prisma.SortOrder
description?: Prisma.SortOrderInput | Prisma.SortOrder
_count?: Prisma.PhotoPostCountOrderByAggregateInput _count?: Prisma.PhotoPostCountOrderByAggregateInput
_avg?: Prisma.PhotoPostAvgOrderByAggregateInput _avg?: Prisma.PhotoPostAvgOrderByAggregateInput
_max?: Prisma.PhotoPostMaxOrderByAggregateInput _max?: Prisma.PhotoPostMaxOrderByAggregateInput
@ -256,70 +223,43 @@ export type PhotoPostScalarWhereWithAggregatesInput = {
NOT?: Prisma.PhotoPostScalarWhereWithAggregatesInput | Prisma.PhotoPostScalarWhereWithAggregatesInput[] NOT?: Prisma.PhotoPostScalarWhereWithAggregatesInput | Prisma.PhotoPostScalarWhereWithAggregatesInput[]
id?: Prisma.IntWithAggregatesFilter<"PhotoPost"> | number id?: Prisma.IntWithAggregatesFilter<"PhotoPost"> | number
createdAt?: Prisma.DateTimeWithAggregatesFilter<"PhotoPost"> | Date | string createdAt?: Prisma.DateTimeWithAggregatesFilter<"PhotoPost"> | Date | string
fileName?: Prisma.StringWithAggregatesFilter<"PhotoPost"> | string
title?: Prisma.StringWithAggregatesFilter<"PhotoPost"> | string
description?: Prisma.StringNullableWithAggregatesFilter<"PhotoPost"> | string | null
} }
export type PhotoPostCreateInput = { export type PhotoPostCreateInput = {
createdAt?: Date | string createdAt?: Date | string
fileName: string
title: string
description?: string | null
} }
export type PhotoPostUncheckedCreateInput = { export type PhotoPostUncheckedCreateInput = {
id?: number id?: number
createdAt?: Date | string createdAt?: Date | string
fileName: string
title: string
description?: string | null
} }
export type PhotoPostUpdateInput = { export type PhotoPostUpdateInput = {
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
fileName?: Prisma.StringFieldUpdateOperationsInput | string
title?: Prisma.StringFieldUpdateOperationsInput | string
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
} }
export type PhotoPostUncheckedUpdateInput = { export type PhotoPostUncheckedUpdateInput = {
id?: Prisma.IntFieldUpdateOperationsInput | number id?: Prisma.IntFieldUpdateOperationsInput | number
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
fileName?: Prisma.StringFieldUpdateOperationsInput | string
title?: Prisma.StringFieldUpdateOperationsInput | string
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
} }
export type PhotoPostCreateManyInput = { export type PhotoPostCreateManyInput = {
id?: number id?: number
createdAt?: Date | string createdAt?: Date | string
fileName: string
title: string
description?: string | null
} }
export type PhotoPostUpdateManyMutationInput = { export type PhotoPostUpdateManyMutationInput = {
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
fileName?: Prisma.StringFieldUpdateOperationsInput | string
title?: Prisma.StringFieldUpdateOperationsInput | string
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
} }
export type PhotoPostUncheckedUpdateManyInput = { export type PhotoPostUncheckedUpdateManyInput = {
id?: Prisma.IntFieldUpdateOperationsInput | number id?: Prisma.IntFieldUpdateOperationsInput | number
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
fileName?: Prisma.StringFieldUpdateOperationsInput | string
title?: Prisma.StringFieldUpdateOperationsInput | string
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
} }
export type PhotoPostCountOrderByAggregateInput = { export type PhotoPostCountOrderByAggregateInput = {
id?: Prisma.SortOrder id?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
fileName?: Prisma.SortOrder
title?: Prisma.SortOrder
description?: Prisma.SortOrder
} }
export type PhotoPostAvgOrderByAggregateInput = { export type PhotoPostAvgOrderByAggregateInput = {
@ -329,17 +269,11 @@ export type PhotoPostAvgOrderByAggregateInput = {
export type PhotoPostMaxOrderByAggregateInput = { export type PhotoPostMaxOrderByAggregateInput = {
id?: Prisma.SortOrder id?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
fileName?: Prisma.SortOrder
title?: Prisma.SortOrder
description?: Prisma.SortOrder
} }
export type PhotoPostMinOrderByAggregateInput = { export type PhotoPostMinOrderByAggregateInput = {
id?: Prisma.SortOrder id?: Prisma.SortOrder
createdAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder
fileName?: Prisma.SortOrder
title?: Prisma.SortOrder
description?: Prisma.SortOrder
} }
export type PhotoPostSumOrderByAggregateInput = { export type PhotoPostSumOrderByAggregateInput = {
@ -350,14 +284,6 @@ export type DateTimeFieldUpdateOperationsInput = {
set?: Date | string set?: Date | string
} }
export type StringFieldUpdateOperationsInput = {
set?: string
}
export type NullableStringFieldUpdateOperationsInput = {
set?: string | null
}
export type IntFieldUpdateOperationsInput = { export type IntFieldUpdateOperationsInput = {
set?: number set?: number
increment?: number increment?: number
@ -371,36 +297,24 @@ export type IntFieldUpdateOperationsInput = {
export type PhotoPostSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{ export type PhotoPostSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
id?: boolean id?: boolean
createdAt?: boolean createdAt?: boolean
fileName?: boolean
title?: boolean
description?: boolean
}, ExtArgs["result"]["photoPost"]> }, ExtArgs["result"]["photoPost"]>
export type PhotoPostSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{ export type PhotoPostSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
id?: boolean id?: boolean
createdAt?: boolean createdAt?: boolean
fileName?: boolean
title?: boolean
description?: boolean
}, ExtArgs["result"]["photoPost"]> }, ExtArgs["result"]["photoPost"]>
export type PhotoPostSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{ export type PhotoPostSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
id?: boolean id?: boolean
createdAt?: boolean createdAt?: boolean
fileName?: boolean
title?: boolean
description?: boolean
}, ExtArgs["result"]["photoPost"]> }, ExtArgs["result"]["photoPost"]>
export type PhotoPostSelectScalar = { export type PhotoPostSelectScalar = {
id?: boolean id?: boolean
createdAt?: boolean createdAt?: boolean
fileName?: boolean
title?: boolean
description?: boolean
} }
export type PhotoPostOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "createdAt" | "fileName" | "title" | "description", ExtArgs["result"]["photoPost"]> export type PhotoPostOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "createdAt", ExtArgs["result"]["photoPost"]>
export type $PhotoPostPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = { export type $PhotoPostPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
name: "PhotoPost" name: "PhotoPost"
@ -408,9 +322,6 @@ export type $PhotoPostPayload<ExtArgs extends runtime.Types.Extensions.InternalA
scalars: runtime.Types.Extensions.GetPayloadResult<{ scalars: runtime.Types.Extensions.GetPayloadResult<{
id: number id: number
createdAt: Date createdAt: Date
fileName: string
title: string
description: string | null
}, ExtArgs["result"]["photoPost"]> }, ExtArgs["result"]["photoPost"]>
composites: {} composites: {}
} }
@ -836,9 +747,6 @@ export interface Prisma__PhotoPostClient<T, Null = never, ExtArgs extends runtim
export interface PhotoPostFieldRefs { export interface PhotoPostFieldRefs {
readonly id: Prisma.FieldRef<"PhotoPost", 'Int'> readonly id: Prisma.FieldRef<"PhotoPost", 'Int'>
readonly createdAt: Prisma.FieldRef<"PhotoPost", 'DateTime'> readonly createdAt: Prisma.FieldRef<"PhotoPost", 'DateTime'>
readonly fileName: Prisma.FieldRef<"PhotoPost", 'String'>
readonly title: Prisma.FieldRef<"PhotoPost", 'String'>
readonly description: Prisma.FieldRef<"PhotoPost", 'String'>
} }
@ -1033,7 +941,7 @@ export type PhotoPostCreateArgs<ExtArgs extends runtime.Types.Extensions.Interna
/** /**
* The data needed to create a PhotoPost. * The data needed to create a PhotoPost.
*/ */
data: Prisma.XOR<Prisma.PhotoPostCreateInput, Prisma.PhotoPostUncheckedCreateInput> data?: Prisma.XOR<Prisma.PhotoPostCreateInput, Prisma.PhotoPostUncheckedCreateInput>
} }
/** /**

View file

@ -1,24 +0,0 @@
/*
Warnings:
- Added the required column `description` to the `PhotoPost` table without a default value. This is not possible if the table is not empty.
- Added the required column `filePath` to the `PhotoPost` table without a default value. This is not possible if the table is not empty.
- Added the required column `title` to the `PhotoPost` table without a default value. This is not possible if the table is not empty.
*/
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_PhotoPost" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"filePath" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT NOT NULL
);
INSERT INTO "new_PhotoPost" ("createdAt", "id") SELECT "createdAt", "id" FROM "PhotoPost";
DROP TABLE "PhotoPost";
ALTER TABLE "new_PhotoPost" RENAME TO "PhotoPost";
CREATE UNIQUE INDEX "PhotoPost_filePath_key" ON "PhotoPost"("filePath");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View file

@ -1,30 +0,0 @@
/*
Warnings:
- You are about to drop the column `filePath` on the `PhotoPost` table. All the data in the column will be lost.
- Added the required column `fileName` to the `PhotoPost` table without a default value. This is not possible if the table is not empty.
*/
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_PhotoPost" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"fileName" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT
);
-- The filename field is the filepath, split by '/', and it's the last part,
-- so we need to modify the filePath column to fileName
ALTER TABLE "PhotoPost" ADD COLUMN "fileName" TEXT NOT NULL DEFAULT '';
UPDATE "PhotoPost" SET "fileName" = SUBSTR("filePath", INSTR("filePath", '/') + 1);
INSERT INTO "new_PhotoPost" ("createdAt", "description", "fileName", "id", "title") SELECT "createdAt", "description", "fileName", "id", "title" FROM "PhotoPost";
DROP TABLE "PhotoPost";
ALTER TABLE "new_PhotoPost" RENAME TO "PhotoPost";
CREATE UNIQUE INDEX "PhotoPost_fileName_key" ON "PhotoPost"("fileName");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View file

@ -10,8 +10,4 @@ datasource db {
model PhotoPost { model PhotoPost {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
fileName String @unique
title String
description String?
} }

View file

@ -5,124 +5,120 @@ title: >-
date: 2026-02-20T17:26:06.111Z date: 2026-02-20T17:26:06.111Z
slug: 2026-02-20-immich-object-storage-upgrade slug: 2026-02-20-immich-object-storage-upgrade
author: Thomas Wilson-Cook author: Thomas Wilson-Cook
tag:
- technical
- self-hosting
- immich
--- ---
In my 2026 effort to write more about my personal computing setup, I want to document the switchover from Volume Mount SSD to Object Storage for my personal photo storage. In my 2026 effort to write more about my personal computing setup, I want to document the switchover from Volume Mount SSD to Object Storage for my personal photo storage.
I have been using [Immich](https://immich.app) as a self-hosted Apple/Google photos alternative for several months now. I grew concerned that images I took with a device I owned could (quite easily) be taken away from me. I have been using [Immich](https://immich.app) as a self-hosted Apple/Google photos alternative for several months now. I grew concerned that images I took with a device I owned could (quite easily) be taken away from me.
I have long used Immich for photo sharing with my (Android-using) spouse, largely for building a shared album of our cat but also for its great collaborative tools for friends/family ("everyone add all your photos/videos from the holiday here"). I have long used Immich for photo sharing with my (Android-using) spouse, largely for building a shared album of our cat but also for its great collaborative tools for friends/family ("everyone add all your photos/videos from the holiday here").
I run a lot of my personal software on a Virtual Private Server (a VPS), a small linux "computer" in a data centre somewhere, and [Coolify](https://coolify.io) as my platform. I'm running a couple of web services and databases from it, and it's working great. I'm paying ~£15/mo for my server, and the relevant space. I run a lot of my personal software on a Virtual Private Server (a VPS), a small linux "computer" in a data centre somewhere, and [Coolify](https://coolify.io) as my platform. I'm running a couple of web services and databases from it, and it's working great. I'm paying ~£15/mo for my server, and the relevant space.
Over time I've watched my storage use increase. Especially as a hobbyist and [freelance](https://www.wilsoncookphotography.com) photographer, a couple of gigabytes can fill up pretty fast. Over time I've watched my storage use increase. Especially as a hobbyist and [freelance](https://www.wilsoncookphotography.com) photographer, a couple of gigabytes can fill up pretty fast.
I was previously using an SSD volume mounted to my VPS (because it was the easiest option to expand the storage with my host). However, as I put more media onto the server, I wanted a way to increase capacity without the (steep) linear price increase. I was previously using an SSD volume mounted to my VPS (because it was the easiest option to expand the storage with my host). However, as I put more media onto the server, I wanted a way to increase capacity without the (steep) linear price increase.
Over a morning I was able to migrate from a mounted SSD volume on my VPS to an S3 compatible object storage. The result is cheaper storage (per gb/month), and a pragmatically higher limit. Over a morning I was able to migrate from a mounted SSD volume on my VPS to an S3 compatible object storage. The result is cheaper storage (per gb/month), and a pragmatically higher limit.
## 1: Create your Object Storage ## 1: Create your Object Storage
Simpleton that I am, I did this through click-ops. I went to my cloud provider and created a new bucket on their S3-compatible storage. Simpleton that I am, I did this through click-ops. I went to my cloud provider and created a new bucket on their S3-compatible storage.
I created the access credentials so that my VPS could access it. I created the access credentials so that my VPS could access it.
If you want to access your Object Storage from elsewhere (like your personal workstation, or through another service) - create another set of credentials for them. If you want to access your Object Storage from elsewhere (like your personal workstation, or through another service) - create another set of credentials for them.
## 2: Connect through rclone ## 2: Connect through rclone
For simplicity, we want our VPS to treat the storage bucket as if it's just another directory on the file system. Luckily, there's an open source tool for that: [rclone](https://github.com/rclone/rclone), a CLI for connecting to cloud-storage providers (including S3 compatible storage). For simplicity, we want our VPS to treat the storage bucket as if it's just another directory on the file system. Luckily, there's an open source tool for that: [rclone](https://github.com/rclone/rclone), a CLI for connecting to cloud-storage providers (including S3 compatible storage).
After installing it (my VPS runs the Ubuntu distro, so `sudo apt install rclone`). You can set it up with the command line: After installing it (my VPS runs the Ubuntu distro, so `sudo apt install rclone`). You can set it up with the command line:
```sh ```sh
$ rclone config $ rclone config
``` ```
Which will take you through a nice interactive installer. Or you can just update the config: Which will take you through a nice interactive installer. Or you can just update the config:
```txt ```txt
# ~/.config/rclone/rclone.conf # ~/.config/rclone/rclone.conf
[s3] [s3]
type = s3 type = s3
provider = Other provider = Other
access_key_id = $ACCESS_KEY access_key_id = $ACCESS_KEY
secret_access_key = $SECRET_KEY secret_access_key = $SECRET_KEY
endpoint = $REGION_NAME.some-provider.com endpoint = $REGION_NAME.some-provider.com
acl = private acl = private
``` ```
Replace all those `$VARIABLE_NAMES` with your own values. Replace all those `$VARIABLE_NAMES` with your own values.
**Important**: note how your bucket name isn't there? You'll need to provide your bucket name specifically when running rclone from the terminal. In the examples below, I'll be using `my-bucket-name`. This is the (globally unique) name you gave your S3-compatible bucket when you created it in step 1. **Important**: note how your bucket name isn't there? You'll need to provide your bucket name specifically when running rclone from the terminal. In the examples below, I'll be using `my-bucket-name`. This is the (globally unique) name you gave your S3-compatible bucket when you created it in step 1.
As a proof of life, I created a "directory" (S3 doesn't have "real" directories so to speak) in my bucket at `/immich` (more click-ops in my cloud provider's web UI), and then: As a proof of life, I created a "directory" (S3 doesn't have "real" directories so to speak) in my bucket at `/immich` (more click-ops in my cloud provider's web UI), and then:
```sh ```sh
$ rclone lsd s3:my-bucket-name $ rclone lsd s3:my-bucket-name
``` ```
...and expected to see the directory (of size 0) listed out. ...and expected to see the directory (of size 0) listed out.
## 3: Clone your data ## 3: Clone your data
Because I have an in-flight Immich instance, I need to make an exact copy of my immich data within my Object Store bucket: Because I have an in-flight Immich instance, I need to make an exact copy of my immich data within my Object Store bucket:
```sh ```sh
$ rclone sync $CURRENT_IMMICH_LOCATION s3:my-bucket-name/immich -p $ rclone sync $CURRENT_IMMICH_LOCATION s3:my-bucket-name/immich -p
``` ```
- `-p` is the Progress flag, shows what's uploading and how far through you are. I had several GB of data, so it was useful for knowing it hadn't got stuck. - `-p` is the Progress flag, shows what's uploading and how far through you are. I had several GB of data, so it was useful for knowing it hadn't got stuck.
## 4: Mount your volume ## 4: Mount your volume
I wanted to create a clean mount to the `/immich` directory in the root of my bucket. I'm trying to put these commands in my `/usr/local/bin` directory, instead of just hitting `UP` enough times in my terminal until I find the right thing. I wanted to create a clean mount to the `/immich` directory in the root of my bucket. I'm trying to put these commands in my `/usr/local/bin` directory, instead of just hitting `UP` enough times in my terminal until I find the right thing.
Here's a script for mounting: Here's a script for mounting:
```sh ```sh
# /usr/local/bin/mount-s3-immich # /usr/local/bin/mount-s3-immich
rclone mount hetzner:my-bucket-name/immich /mnt/s3-immich \ rclone mount hetzner:my-bucket-name/immich /mnt/s3-immich \
--allow-other \ --allow-other \
--vfs-cache-mode writes \ --vfs-cache-mode writes \
--daemon --daemon
``` ```
* `--allow-other`: Allows other users on the VPS to access the mount (like our containerised Immich service). * `--allow-other`: Allows other users on the VPS to access the mount (like our containerised Immich service).
* `--vfs-cache-mode` Immich's write patterns may not be atomic, and this creates a buffer for those writes before they're uploaded. * `--vfs-cache-mode` Immich's write patterns may not be atomic, and this creates a buffer for those writes before they're uploaded.
* `--daemon` runs the process as a daemon (in the background). If you're a tmux wiz, you can use that but I'm not. * `--daemon` runs the process as a daemon (in the background). If you're a tmux wiz, you can use that but I'm not.
You'll need to make sure that `user_allow_other` is enabled in your `/etc/fuse.conf`. Because this will be a Fuse mount, the `--allow-other` flag for rclone needs to be cleared by the filesystem itself. You'll need to make sure that `user_allow_other` is enabled in your `/etc/fuse.conf`. Because this will be a Fuse mount, the `--allow-other` flag for rclone needs to be cleared by the filesystem itself.
And then for unmounting: And then for unmounting:
```sh ```sh
# /usr/local/bin/unmount-s3-immich # /usr/local/bin/unmount-s3-immich
fusermount -u /mnt/s3-immich fusermount -u /mnt/s3-immich
``` ```
## 5: Update your Immich docker-compose ## 5: Update your Immich docker-compose
Over to your Coolify dashboard now (I'm assuming you have an Immich service running there). Over to your Coolify dashboard now (I'm assuming you have an Immich service running there).
You need to update the Volume on your Docker Compose to use your new mount as the source of data. That means mapping that new mounted volume to `/usr/src/app/upload`: You need to update the Volume on your Docker Compose to use your new mount as the source of data. That means mapping that new mounted volume to `/usr/src/app/upload`:
```yaml ```yaml
# Immich Service Dockerfile (e.g. in Coolify) # Immich Service Dockerfile (e.g. in Coolify)
services: services:
immich: immich:
image: 'ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}' image: 'ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}'
volumes: volumes:
- '/mnt/s3-immich:/usr/src/app/upload' - '/mnt/s3-immich:/usr/src/app/upload'
#...rest of docker-compose.yaml #...rest of docker-compose.yaml
``` ```
## 6: 🎉 ## 6: 🎉
Y'all good! You're now storing photos at a _very_ cheap £/GB _and_ you have a little more ownership. Y'all good! You're now storing photos at a _very_ cheap £/GB _and_ you have a little more ownership.

View file

@ -3,31 +3,28 @@ title: 📖 Started reading "The Other Pandemic"
date: 2026-02-27T07:36:44.006Z date: 2026-02-27T07:36:44.006Z
slug: 2026-02-27--started-reading-the-other-pandemic slug: 2026-02-27--started-reading-the-other-pandemic
author: Thomas Wilson-Cook author: Thomas Wilson-Cook
tags:
- book-review
- reading
--- ---
I picked up James Ball's *The Other Pandemic: How QAnon Contaminated the World* ([bookshop.org](https://uk.bookshop.org/p/books/the-other-pandemic-how-qanon-contaminated-the-world-james-ball/7618064?ean=9781526642516&next=t)) from the library. Ball appeared on an episode of the podcast *Oh God What Now?* ([Apple Podcasts](https://podcasts.apple.com/gb/podcast/withstanding-the-farage-barrage-can-we-reform-proof-the-uk/id1245265763?i=1000750517915)) and the book sounded interesting. I've just crossed the one hundred page mark. I picked up James Ball's *The Other Pandemic: How QAnon Contaminated the World* ([bookshop.org](https://uk.bookshop.org/p/books/the-other-pandemic-how-qanon-contaminated-the-world-james-ball/7618064?ean=9781526642516&next=t)) from the library. Ball appeared on an episode of the podcast *Oh God What Now?* ([Apple Podcasts](https://podcasts.apple.com/gb/podcast/withstanding-the-farage-barrage-can-we-reform-proof-the-uk/id1245265763?i=1000750517915)) and the book sounded interesting. I've just crossed the one hundred page mark.
Ball makes the useful (if tired) comparison between the spread of conspiratorial beliefs, like QAnon, and a virus[^1]. You could make the same comparison about other bits of being a human: music, language, or stories are all transmitted and evolve like viruses. Beautiful, sure, but sentimental. What do I *do* with this? Ball makes the useful (if tired) comparison between the spread of conspiratorial beliefs, like QAnon, and a virus[^1]. You could make the same comparison about other bits of being a human: music, language, or stories are all transmitted and evolve like viruses. Beautiful, sure, but sentimental. What do I *do* with this?
Fortunately, Ball can think at least one step ahead of some writers and journalists I've read. They tell us *how* it's like a virus, and it's actually pretty interesting. Fortunately, Ball can think at least one step ahead of some writers and journalists I've read. They tell us *how* it's like a virus, and it's actually pretty interesting.
Ball reminds us of survivorship bias ([wikipedia](https://en.wikipedia.org/wiki/Survivorship_bias)) and natural reservoir ([wikipedia](https://en.wikipedia.org/wiki/Natural_reservoir)) of conspiratorial beliefs. In nature, a natural reservoir might be a population of animals (cows, bats, mice) where a pathogen can live and be transmitted before being passed on to another population. Ball reminds us of survivorship bias ([wikipedia](https://en.wikipedia.org/wiki/Survivorship_bias)) and natural reservoir ([wikipedia](https://en.wikipedia.org/wiki/Natural_reservoir)) of conspiratorial beliefs. In nature, a natural reservoir might be a population of animals (cows, bats, mice) where a pathogen can live and be transmitted before being passed on to another population.
In the context of conspiratorial beliefs, there are countless forums, group chats, and social media accounts constantly trying new ideas and framings. Naturally, the ideas that break containment from one place to another will be the most attention grabbing: the most outrageous, or maybe even the most plausible (to a certain population, if not the general public). In the context of conspiratorial beliefs, there are countless forums, group chats, and social media accounts constantly trying new ideas and framings. Naturally, the ideas that break containment from one place to another will be the most attention grabbing: the most outrageous, or maybe even the most plausible (to a certain population, if not the general public).
Although countless people (earnest believers, state actors, and now generative artificial intelligence) are posting a new conspiracy every second of every day - the vast, vast majority of them are lost to time. Shame. Although countless people (earnest believers, state actors, and now generative artificial intelligence) are posting a new conspiracy every second of every day - the vast, vast majority of them are lost to time. Shame.
It's no accident that QAnon started on 4chan, says Ball. A site with a set number of posts visible at any one time. To hang around, a post would have to have more engagement than its peers. The users of 4chan were already a self-selecting group of a quite libertarian-leaning, pseudonymous internet image board, initially with an anonymous founder. It's an environment that could allow a lot of different ideas to appear and then spread. It's no accident that QAnon started on 4chan, says Ball. A site with a set number of posts visible at any one time. To hang around, a post would have to have more engagement than its peers. The users of 4chan were already a self-selecting group of a quite libertarian-leaning, pseudonymous internet image board, initially with an anonymous founder. It's an environment that could allow a lot of different ideas to appear and then spread.
The book suffers a little from the same condition as Naomi Klein's *Doppleganger*: the author details an overwhelming, negative phenomenon (bad for both individuals, communities, and societies) and then just stands back with you and is like "wow, seems bad". The book suffers a little from the same condition as Naomi Klein's *Doppleganger*: the author details an overwhelming, negative phenomenon (bad for both individuals, communities, and societies) and then just stands back with you and is like "wow, seems bad".
But also, it's not a book about de-radicalisation or de-programming. It's a book about the spread of QAnon beliefs. But also, it's not a book about de-radicalisation or de-programming. It's a book about the spread of QAnon beliefs.
Who knows, maybe Ball will wrap it up nicely in the next 120 pages. So far, I'm enjoying it. Who knows, maybe Ball will wrap it up nicely in the next 120 pages. So far, I'm enjoying it.
--- ---
[^1]: The comparison seems well considered in this case, but it reminds me a little of how the human mind is always compared to the most modern technology of the day. There was a time when the human brain was complex like a water mill, or like a wax tablet. ([the *Guardian*](https://www.theguardian.com/science/2014/jul/26/photography-supercomputers-see-ourselves-in-our-inventions-brain-neuroscience)) [^1]: The comparison seems well considered in this case, but it reminds me a little of how the human mind is always compared to the most modern technology of the day. There was a time when the human brain was complex like a water mill, or like a wax tablet. ([the *Guardian*](https://www.theguardian.com/science/2014/jul/26/photography-supercomputers-see-ourselves-in-our-inventions-brain-neuroscience))

View file

@ -3,25 +3,22 @@ title: '📖 Book Review: The Evening and the Morning'
date: 2026-02-28T09:35:28.579Z date: 2026-02-28T09:35:28.579Z
slug: 2026-02-28-book-review-the-evening-and-the-morning slug: 2026-02-28-book-review-the-evening-and-the-morning
author: Thomas Wilson-Cook author: Thomas Wilson-Cook
tags:
- book-review
- reading
--- ---
This audiobook kept me company in the garden as I got through a handful of long-running chores. At twenty-four hours in length I'm happy to say that the garden is at best half-finished! This audiobook kept me company in the garden as I got through a handful of long-running chores. At twenty-four hours in length I'm happy to say that the garden is at best half-finished!
About a decade ago, I picked up Follett's first three *Kingbridge* books[^1] for a similar reason: I was spending more time outdoors on my bike, and I used to love having audiobooks to keep me company. About a decade ago, I picked up Follett's first three *Kingbridge* books[^1] for a similar reason: I was spending more time outdoors on my bike, and I used to love having audiobooks to keep me company.
What I have come to love most about Follett's writing is his focus on character narrative. Especially as they run over many hundreds of pages. At time I think he is prone to archetypes or simplification, but I'll leave it to the reader to consider how well any work of art has captured the internal experience of many people. What I have come to love most about Follett's writing is his focus on character narrative. Especially as they run over many hundreds of pages. At time I think he is prone to archetypes or simplification, but I'll leave it to the reader to consider how well any work of art has captured the internal experience of many people.
Although I take the historical accuracy with a pinch of salt, especially when it comes to the spiritual or religious lives of people at the time, Follett's writing about a time where we have infamously indecisive historical evidence. I don't think this book would be much improved by higher maternal and juvenile mortality, or the comparatively much higher risk of food- and water-borne illness. I think the good of humanising people that feel so distant to us does far more good than could be done by painting flawless historical accuracy. Although I take the historical accuracy with a pinch of salt, especially when it comes to the spiritual or religious lives of people at the time, Follett's writing about a time where we have infamously indecisive historical evidence. I don't think this book would be much improved by higher maternal and juvenile mortality, or the comparatively much higher risk of food- and water-borne illness. I think the good of humanising people that feel so distant to us does far more good than could be done by painting flawless historical accuracy.
The book is pulled along by a cast of characters, neatly split into those that you like and those that you shouldn't. He crafts villains and villainy well enough that I feel my blood boil when they get away with things, and sing when their past misdeeds finally catch up with them. And although I praise him for making the characters feel understandable, I don't think he does much to help you sympathise with the villains. To that extent, the writing can feel a little cartoonish, or more like a caricature than a studied portrait. The book is pulled along by a cast of characters, neatly split into those that you like and those that you shouldn't. He crafts villains and villainy well enough that I feel my blood boil when they get away with things, and sing when their past misdeeds finally catch up with them. And although I praise him for making the characters feel understandable, I don't think he does much to help you sympathise with the villains. To that extent, the writing can feel a little cartoonish, or more like a caricature than a studied portrait.
The moral narrative of the books seems to be that honest hard work and natural skill will eventually improve things, and that it's possible to improve a community as well as an individual. That being kind and empathetic to people will give you a better social credit than being threatening. I think that's a good lesson we should be teaching people, but I also think it has its limits. But so does everything. The moral narrative of the books seems to be that honest hard work and natural skill will eventually improve things, and that it's possible to improve a community as well as an individual. That being kind and empathetic to people will give you a better social credit than being threatening. I think that's a good lesson we should be teaching people, but I also think it has its limits. But so does everything.
I have no doubt that, in several years, I will dig this audiobook back out so it can accompany as I lay fences, level some earth, or re-decorate some part of my house. I have no doubt that, in several years, I will dig this audiobook back out so it can accompany as I lay fences, level some earth, or re-decorate some part of my house.
--- ---
[^1]: I initially thought of these as a trilogy. But reviewing their release dates as 1989, 2007, and 2017, I'm sure Follett's experience is a little more nuanced than "a trilogy". [^1]: I initially thought of these as a trilogy. But reviewing their release dates as 1989, 2007, and 2017, I'm sure Follett's experience is a little more nuanced than "a trilogy".

View file

@ -5,7 +5,6 @@ slug: 2026-03-13--book-review-the-other-pandemic
author: Thomas Wilson-Cook author: Thomas Wilson-Cook
tags: tags:
- book-review - book-review
- reading
--- ---

View file

@ -1,23 +0,0 @@
---
title: 📖 Started reading "Fundamentally" by Nussaibah Younis
date: 2026-03-15T17:27:13.661Z
slug: 2026-03-15-started-reading-fundamentally
author: Thomas Wilson-Cook
tags:
- journal
- reading
---
I picked up the 2025 Women's Prize shortlister "Fundamentally" by Nussaibah Younis. This was a Christmas gift from a family member, after I remember putting it down on my list because it ended up on a few end of year lists.
I remember being intrigued by the author. That she'd written this piece of fiction based heavily on her own experience in the humanitarian sector, working on deradicalisation at the UN.
At their worst, books like this can turn into strange power fantasies, or self-mythologies that strip away a lot of the nuance. It's how we can end up with the Manic Pixie Dream Girl ([wikipedia](https://en.wikipedia.org/wiki/Manic_Pixie_Dream_Girl)) or Mary Sue ([wikipedia](https://en.wikipedia.org/wiki/Mary_Sue))[^1].
I like Younis' interview in the Womens Prize ([link](https://womensprize.com/in-conversation-with-nussaibah-younis/)) because it seems like she might have gone the reverse direction, where the novel came after (or was part of) deconstructing that self-aggrandising narrative:
> I studied Arabic, read a lot of books, got a PhD. Then I went to Iraq. The trouble is, having an Iraqi father did not make me Iraqi, and having a PhD did not make me competent. I was just another idiot foreigner; well-meaning but totally out of my depth. I spent ten years as an NGO worker, trying to save Iraq. I lived an insane life, pursuing hair-brained schemes to build peace. My naivety shone like a spotlight as I played out my brown saviour fantasies, all the while being mocked and extorted by locals. Before long, I was embroiled in the farcical humanitarian sector. Id started out by asking: how can I help? But soon I was asking: how can I maximise my budgets? How can I curry favour with corrupt Iraqi ministers? And how can I screw over my rivals? As a workplace, humanitarianism is as aggressive as investment banking, except that lives hang in the balance
Like I said, I'm only really about an hour in but as I hurtle towards page 100 (the point at which I give myself complete freedom to did-not-finish a book), I can easily foresee making my way through this one.
[^1]: While one can gender the Mary Sue to a "Gary Stu" or "Marty Stu" , it's interesting that the Manic Pixie Dream Girl archetype relies a bit more on gender dynamics. I certainly don't think we see any Manic Pixie Dream Boys, and personally I wouldn't care to.

View file

@ -33,10 +33,8 @@
</svelte:head> </svelte:head>
<Navbar /> <Navbar />
<main class="thomaswilson-container blog"> <main class="thomaswilson-container blog">
<header class="blog__header"> <header class="blog__header">
<a href="/blog" class="weblog-link">Return to blog</a>
<h1 class="title post-title">{post.title}</h1> <h1 class="title post-title">{post.title}</h1>
<p class="post-author"> <p class="post-author">
{#if post.author} {#if post.author}