Skip to content

Commit 23ef574

Browse files
chore: wip
1 parent 2e5e653 commit 23ef574

File tree

13 files changed

+174
-49
lines changed

13 files changed

+174
-49
lines changed

app/Models/Post.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,5 @@ export default {
3232

3333
factory: () => faker.datatype.string(),
3434
},
35-
subscriber_id: {
36-
validator: {
37-
rule: schema.number(),
38-
message: '',
39-
},
40-
},
4135
},
4236
} satisfies Model

app/Models/Subscriber.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,6 @@ export default {
2525

2626
factory: () => faker.datatype.boolean(),
2727
},
28-
29-
user_id: {
30-
validator: {
31-
rule: schema.number(),
32-
message: '',
33-
},
34-
},
3528
},
3629

3730
dashboard: {

app/Models/User.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,15 @@ export default {
4040
// useUuid: true, // defaults to false
4141
},
4242

43-
hasOneThrough: [
43+
hasMany: [
4444
{
4545
model: Post,
46-
through: Subscriber,
46+
},
47+
],
48+
49+
hasOne: [
50+
{
51+
model: Subscriber,
4752
},
4853
],
4954

config/database.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default {
1313

1414
connections: {
1515
sqlite: {
16-
database: env.DB_DATABASE || 'database/stacks.sqlite',
16+
database: env.DB_DATABASE || 'stacks',
1717
prefix: '',
1818
},
1919

storage/framework/core/actions/src/database/mysql.ts

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { db } from '@stacksjs/database'
33
import { ok } from '@stacksjs/error-handling'
44
import { path } from '@stacksjs/path'
55
import { fs, glob } from '@stacksjs/storage'
6-
import type { Attributes } from '@stacksjs/types'
6+
import type { Attributes, ModelDefault, RelationConfig } from '@stacksjs/types'
77
import { checkPivotMigration, getLastMigrationFields, hasTableBeenMigrated, mapFieldTypeToColumnType } from '.'
88

99
export async function resetMysqlDatabase() {
@@ -15,8 +15,11 @@ export async function resetMysqlDatabase() {
1515
await db.schema.dropTable('migration_locks').ifExists().execute()
1616

1717
const files = await fs.readdir(path.userMigrationsPath())
18+
const filesForeign = await fs.readdir(path.userMigrationsPath('foreign'))
1819
const modelFiles = await fs.readdir(path.frameworkPath('database/models'))
1920

21+
console.log(filesForeign)
22+
2023
const userModelFiles = glob.sync(path.userModelsPath('*.ts'))
2124

2225
for (const userModel of userModelFiles) {
@@ -37,6 +40,14 @@ export async function resetMysqlDatabase() {
3740
}
3841
}
3942

43+
if (filesForeign.length) {
44+
for (const foreignFile of filesForeign) {
45+
const foreignPath = path.userMigrationsPath(`foreign/${foreignFile}`)
46+
47+
if (fs.existsSync(foreignPath)) await Bun.$`rm ${foreignPath}`
48+
}
49+
}
50+
4051
if (files.length) {
4152
for (const file of files) {
4253
if (file.endsWith('.ts')) {
@@ -70,6 +81,7 @@ export async function generateMysqlMigration(modelPath: string) {
7081
}
7182

7283
const model = await import(modelPath)
84+
7385
const fileName = path.basename(modelPath)
7486
const tableName = model.default.table
7587

@@ -107,10 +119,49 @@ export async function generateMysqlMigration(modelPath: string) {
107119

108120
log.debug(`Has ${tableName} been migrated? ${hasBeenMigrated}`)
109121

122+
await createForeignKeysMigration(model)
123+
110124
if (haveFieldsChanged) await createAlterTableMigration(modelPath)
111125
else await createTableMigration(modelPath)
112126
}
113127

128+
129+
130+
async function getRelations(model: ModelDefault): Promise<RelationConfig[]> {
131+
const relationsArray = ['hasOne', 'belongsTo', 'hasMany', 'belongsToMany', 'hasOneThrough']
132+
133+
const relationships = []
134+
135+
for (const relation of relationsArray) {
136+
if (hasRelations(model.default, relation)) {
137+
for (const relationInstance of model.default[relation]) {
138+
const modelRelationPath = path.userModelsPath(`${relationInstance.model.name}.ts`)
139+
140+
const modelRelation = await import(modelRelationPath)
141+
142+
const formattedModelName = model.default.name.toLowerCase()
143+
144+
relationships.push({
145+
relationship: relation,
146+
model: relationInstance.model.name,
147+
table: modelRelation.default.table,
148+
foreignKey: relationInstance.foreignKey || `${formattedModelName}_id`,
149+
relationName: relationInstance.relationName || '',
150+
throughModel: relationInstance.through || '',
151+
throughForeignKey: relationInstance.throughForeignKey || '',
152+
pivotTable: relationInstance?.pivotTable || `${formattedModelName}_${modelRelation.default.table}`,
153+
})
154+
}
155+
}
156+
}
157+
158+
return relationships
159+
}
160+
161+
function hasRelations(obj: any, key: string): boolean {
162+
return key in obj
163+
}
164+
114165
async function getPivotTables(
115166
model: any,
116167
): Promise<{ table: string; firstForeignKey: string; secondForeignKey: string }[]> {
@@ -141,7 +192,7 @@ async function createTableMigration(modelPath: string) {
141192

142193
const model = await import(modelPath)
143194
const tableName = model.default.table
144-
195+
145196
await createPivotTableMigration(model)
146197

147198
const fields = model.default.attributes
@@ -193,7 +244,7 @@ async function createTableMigration(modelPath: string) {
193244
log.success(`Created migration: ${migrationFileName}`)
194245
}
195246

196-
async function createPivotTableMigration(model: any) {
247+
async function createPivotTableMigration(model: ModelDefault) {
197248
const pivotTables = await getPivotTables(model)
198249

199250
if (!pivotTables.length) return
@@ -224,6 +275,28 @@ async function createPivotTableMigration(model: any) {
224275
}
225276
}
226277

278+
async function createForeignKeysMigration(model: ModelDefault) {
279+
const relations = await getRelations(model)
280+
281+
282+
for (const relation of relations) {
283+
let migrationContent = `import type { Database } from '@stacksjs/database'\n`
284+
migrationContent += `export async function up(db: Database<any>) {\n`
285+
migrationContent += ` await db.schema\n`
286+
migrationContent += ` .alterTable('${relation.table}')\n`
287+
288+
migrationContent += ` .addColumn('${relation.foreignKey}', 'integer')\n`
289+
migrationContent += ` .execute()\n`
290+
migrationContent += ` }\n`
291+
292+
const timestamp = new Date().getTime().toString()
293+
const migrationFileName = `${timestamp}-create-${relation.table}-foreign-table.ts`
294+
const migrationFilePath = path.userMigrationsPath(`foreign/${migrationFileName}`)
295+
296+
Bun.write(migrationFilePath, migrationContent)
297+
}
298+
}
299+
227300
export async function createAlterTableMigration(modelPath: string) {
228301
console.log('createAlterTableMigration')
229302

storage/framework/core/actions/src/migrate/database.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import process from 'node:process'
2-
import { generateMigrations, runDatabaseMigration } from '@stacksjs/database'
2+
import { generateMigrations, runDatabaseMigration, runDatabaseMigrationForeign } from '@stacksjs/database'
33
import { log } from '@stacksjs/logging'
44

55
// this is run and checks whether new created or update migrations need to be generated
@@ -13,10 +13,19 @@ if (result?.isErr()) {
1313

1414
const result2 = await runDatabaseMigration()
1515

16+
1617
if (result2.isErr()) {
1718
log.error('runDatabaseMigration failed')
1819
log.error(result2.error)
1920
process.exit(1)
2021
}
2122

23+
const result3 = await runDatabaseMigrationForeign()
24+
25+
if (result3.isErr()) {
26+
log.error('runDatabaseMigration failed')
27+
log.error(result3.error)
28+
process.exit(1)
29+
}
30+
2231
process.exit(0)

storage/framework/core/actions/src/orm/generate-model.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { path } from '@stacksjs/path'
22
import { fs, glob } from '@stacksjs/storage'
3-
import type { ModelOptions } from '@stacksjs/types'
3+
import type { ModelDefault, RelationConfig } from '@stacksjs/types'
44

55
export interface FieldArrayElement {
66
entity: string
@@ -15,10 +15,6 @@ export interface ModelElement {
1515
fieldArray: FieldArrayElement | null
1616
}
1717

18-
interface ModelDefault {
19-
default: ModelOptions
20-
}
21-
2218
await initiateModelGeneration()
2319
await setKyselyTypes()
2420

@@ -145,7 +141,7 @@ async function initiateModelGeneration(): Promise<void> {
145141
}
146142
}
147143

148-
async function getRelations(model: ModelDefault): Promise<any[]> {
144+
async function getRelations(model: ModelDefault): Promise<RelationConfig[]> {
149145
const relationsArray = ['hasOne', 'belongsTo', 'hasMany', 'belongsToMany', 'hasOneThrough']
150146

151147
const relationships = []

storage/framework/core/database/src/migrations.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,17 @@ export const migrator = new Migrator({
2828
migrationLockTableName: database.migrationLocks,
2929
})
3030

31+
const migratorForeign = new Migrator({
32+
db,
33+
34+
provider: new FileMigrationProvider({
35+
fs,
36+
path,
37+
// This needs to be an absolute path.
38+
migrationFolder: path.userMigrationsPath('foreign'),
39+
}),
40+
})
41+
3142
export async function runDatabaseMigration() {
3243
try {
3344
log.info('Migrating database...')
@@ -61,6 +72,39 @@ export async function runDatabaseMigration() {
6172
}
6273
}
6374

75+
export async function runDatabaseMigrationForeign() {
76+
try {
77+
log.info('Migrating database...')
78+
79+
const migration = await migratorForeign.migrateToLatest()
80+
81+
if (migration.error) {
82+
log.error(migration.error)
83+
return err(migration.error)
84+
}
85+
86+
if (migration.results?.length === 0) {
87+
log.success('No new migrations were executed')
88+
return ok('No new migrations were executed')
89+
}
90+
91+
if (migration.results) {
92+
migration.results.forEach(({ migrationName }) => {
93+
console.log(italic(`${dim(` - Migration Name:`)} ${migrationName}`))
94+
})
95+
96+
log.success('Database migrated successfully.')
97+
return ok(migration)
98+
}
99+
100+
log.success('Database migration completed with no new migrations.')
101+
return ok('Database migration completed with no new migrations.')
102+
} catch (error) {
103+
console.error('Migration failed:', error)
104+
return err(error)
105+
}
106+
}
107+
64108
export interface MigrationOptions {
65109
name: string
66110
up: string
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
export { Kysely as Database } from 'kysely'
2+
3+
export { sql } from 'kysely'

storage/framework/core/types/src/model.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,19 @@ export interface Attributes {
117117
}
118118

119119
export type Model = Partial<ModelOptions>
120+
121+
122+
export interface ModelDefault {
123+
default: ModelOptions
124+
}
125+
126+
export interface RelationConfig {
127+
relationship: string;
128+
model: string;
129+
table: string;
130+
foreignKey: string;
131+
relationName: string;
132+
throughModel: string;
133+
throughForeignKey: string;
134+
pivotTable: string;
135+
}

0 commit comments

Comments
 (0)