diff --git a/api/package.json b/api/package.json index 2d9ce15..5e470bb 100644 --- a/api/package.json +++ b/api/package.json @@ -1,6 +1,6 @@ { "name": "taskview-ce-api-server", - "version": "1.42.3", + "version": "1.42.5", "scripts": { "dev": "bun run --watch ./server.ts", "start": "NODE_ENV=production node ./dist/taskview-server.js", diff --git a/api/src/migrations/taskview/migrate.json b/api/src/migrations/taskview/migrate.json index a40e0aa..9668e26 100644 --- a/api/src/migrations/taskview/migrate.json +++ b/api/src/migrations/taskview/migrate.json @@ -461,5 +461,16 @@ "Added SAML logout URL for SLO", "Added SCIM provisioning support" ] + }, + "37": { + "version": "1.44.0", + "name": "Release 1.44.0", + "releaseDate": "20260418", + "scripts": [ + "/1.44.0/0.create-missing-personal-orgs.sql" + ], + "description": [ + "Create personal organizations for users without any organization membership" + ] } } \ No newline at end of file diff --git a/api/src/migrations/taskview/sql/1.44.0/0.create-missing-personal-orgs.sql b/api/src/migrations/taskview/sql/1.44.0/0.create-missing-personal-orgs.sql new file mode 100644 index 0000000..8896675 --- /dev/null +++ b/api/src/migrations/taskview/sql/1.44.0/0.create-missing-personal-orgs.sql @@ -0,0 +1,22 @@ +-- Create personal organizations for users who don't have any organization membership +-- This covers the default seed user (login: 'user', email: 'test@mail.dest') +-- and any other users who may exist without an organization +INSERT INTO tv_auth.organizations (name, slug, owner_id, is_personal) +SELECT + u.login || '''s workspace', + 'org-' || substr(md5(random()::text), 1, 8), + u.id, + 1 +FROM tv_auth.users u +WHERE NOT EXISTS ( + SELECT 1 FROM tv_auth.organization_members om WHERE om.email = u.email +) +ON CONFLICT DO NOTHING; + +-- Add these users as owners of their new personal organizations +INSERT INTO tv_auth.organization_members (organization_id, email, role) +SELECT o.id, u.email, 'owner' +FROM tv_auth.organizations o +JOIN tv_auth.users u ON u.id = o.owner_id +WHERE o.is_personal = 1 +ON CONFLICT (organization_id, email) DO NOTHING; diff --git a/docs/1.getting-started/2.installation.md b/docs/1.getting-started/2.installation.md index 81d4cf2..6451f2a 100644 --- a/docs/1.getting-started/2.installation.md +++ b/docs/1.getting-started/2.installation.md @@ -171,6 +171,18 @@ Docker will pull the images, start the database, run migrations, and launch the Go to [http://localhost:8888](http://localhost:8888) in your browser. You'll see the login screen. +### Configure the API server + +Before logging in, you need to tell the web app where the API server is running. Click the **server settings** icon on the login page and add the API server URL: + +``` +http://localhost:1725 +``` + +This is the API server that handles authentication, projects, tasks, and all backend operations. The web app (port 8888) serves the frontend, while the API server (port 1725) handles the data. + +### Log in with the default user + The database migration creates a default user so you can log in right away: - **Login:** `user` diff --git a/package.json b/package.json index 3441f1e..7076581 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "taskview-ce-monorepo", - "version": "1.42.3", + "version": "1.42.5", "private": true, "description": "TaskView CE monorepo containing web, API, and packages", "workspaces": [ diff --git a/taskview-packages/taskview-api/src/api/__tests__/organizations.test.ts b/taskview-packages/taskview-api/src/api/__tests__/organizations.test.ts index 154e2ec..73ad511 100644 --- a/taskview-packages/taskview-api/src/api/__tests__/organizations.test.ts +++ b/taskview-packages/taskview-api/src/api/__tests__/organizations.test.ts @@ -22,6 +22,31 @@ afterAll(async () => { await deleteAllGoals() }) +describe('Default user has a personal organization', () => { + it('default user (login: user, email: test@mail.dest) should have at least one organization', async () => { + const orgs = await user1Api.organizations.fetch() + + expect(orgs.length).toBeGreaterThan(0) + + const personal = orgs.find(o => (o as any).isPersonal === 1) + expect(personal).toBeTruthy() + expect(personal!.name).toContain('workspace') + }) + + it('default user should be an owner of their personal organization', async () => { + const orgs = await user1Api.organizations.fetch() + const personal = orgs.find(o => (o as any).isPersonal === 1) + + expect(personal).toBeTruthy() + + const members = await user1Api.organizations.fetchMembers(personal!.id) + const owner = members.find(m => m.email === user1Email) + + expect(owner).toBeTruthy() + expect(owner!.role).toBe('owner') + }) +}) + describe('Organizations: creation and management', () => { let createdOrgId: number diff --git a/web/package.json b/web/package.json index 947561e..7f6b677 100644 --- a/web/package.json +++ b/web/package.json @@ -2,7 +2,7 @@ "name": "web-nuxt-ui", "private": true, "type": "module", - "version": "1.42.3", + "version": "1.42.5", "scripts": { "dev": "vite", "build:packages": "pnpm --filter taskview-db-schemas build && pnpm --filter taskview-api build", diff --git a/web/src/components/features/auth/LoginByCode.vue b/web/src/components/features/auth/LoginByCode.vue index b952f49..03e5f5a 100644 --- a/web/src/components/features/auth/LoginByCode.vue +++ b/web/src/components/features/auth/LoginByCode.vue @@ -146,7 +146,7 @@ async function handleSubmit() { }) emit('success', result.data.access) - redirectToUser(router) + await redirectToUser(router) } } else { await $api.post('/module/auth/send-login-code', { email: state.email }) diff --git a/web/src/components/features/auth/LoginByPassword.vue b/web/src/components/features/auth/LoginByPassword.vue index 8719669..a564e39 100644 --- a/web/src/components/features/auth/LoginByPassword.vue +++ b/web/src/components/features/auth/LoginByPassword.vue @@ -123,7 +123,7 @@ async function onSubmit() { emit('success', result.data.access) - redirectToUser(router) + await redirectToUser(router) } } catch (error: unknown) { const axiosError = error as { response?: { status?: number } }