## Diff from 4113eda
```diff
diff --git a/api/spec/factories/UserFactory.ts b/api/spec/factories/UserFactory.ts
new file mode 100644
index 0000000..26cdcf8
--- /dev/null
+++ b/api/spec/factories/UserFactory.ts
@@ -0,0 +1,11 @@
+import { UpdateableProperties } from '@rvoh/dream/types'
+import User from '@models/User.js'
+
+let counter = 0
+
+export default async function createUser(attrs: UpdateableProperties<User> = {}) {
+ return await User.create({
+ email: `User email ${++counter}`,
+ ...attrs,
+ })
+}
diff --git a/api/spec/unit/models/User.spec.ts b/api/spec/unit/models/User.spec.ts
new file mode 100644
index 0000000..06dac32
--- /dev/null
+++ b/api/spec/unit/models/User.spec.ts
@@ -0,0 +1,3 @@
+describe('User', () => {
+ it.todo('add a test here to get started building User')
+})
diff --git a/api/src/app/models/User.ts b/api/src/app/models/User.ts
new file mode 100644
index 0000000..0c415a7
--- /dev/null
+++ b/api/src/app/models/User.ts
@@ -0,0 +1,16 @@
+import { Decorators } from '@rvoh/dream'
+import { DreamColumn } from '@rvoh/dream/types'
+import ApplicationModel from '@models/ApplicationModel.js'
+
+const deco = new Decorators<typeof User>()
+
+export default class User extends ApplicationModel {
+ public override get table() {
+ return 'users' as const
+ }
+
+ public id: DreamColumn<User, 'id'>
+ public email: DreamColumn<User, 'email'>
+ public createdAt: DreamColumn<User, 'createdAt'>
+ public updatedAt: DreamColumn<User, 'updatedAt'>
+}
diff --git a/api/src/db/migrations/1762617331279-create-user.ts b/api/src/db/migrations/1762617331279-create-user.ts
new file mode 100644
index 0000000..cea332b
--- /dev/null
+++ b/api/src/db/migrations/1762617331279-create-user.ts
@@ -0,0 +1,20 @@
+import { DreamMigrationHelpers } from '@rvoh/dream/db'
+import { Kysely, sql } from 'kysely'
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export async function up(db: Kysely<any>): Promise<void> {
+ await DreamMigrationHelpers.createExtension(db, 'citext')
+
+ await db.schema
+ .createTable('users')
+ .addColumn('id', 'bigserial', col => col.primaryKey())
+ .addColumn('email', sql`citext`, col => col.notNull().unique())
+ .addColumn('created_at', 'timestamp', col => col.notNull())
+ .addColumn('updated_at', 'timestamp', col => col.notNull())
+ .execute()
+}
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export async function down(db: Kysely<any>): Promise<void> {
+ await db.schema.dropTable('users').execute()
+}
\ No newline at end of file