Modified id usage to uuids, reset migrations, removed token from invites

This commit is contained in:
Viswamedha Nalabotu 2026-02-27 12:53:19 +00:00
parent 529ab95a91
commit c362c79912
18 changed files with 78 additions and 154 deletions

View file

@ -37,11 +37,11 @@ class OrganizationAdmin(ModelAdmin):
@admin.register(Invite) @admin.register(Invite)
class InviteAdmin(ModelAdmin): class InviteAdmin(ModelAdmin):
list_display = ('token', 'organization', 'created_by', 'is_active', 'uses', 'max_uses', 'expires_at') list_display = ('uuid', 'organization', 'created_by', 'is_active', 'uses', 'max_uses', 'expires_at')
search_fields = ('token', 'organization__name', 'created_by__email_address') search_fields = ('uuid', 'organization__name', 'created_by__email_address')
list_filter = ('is_active', 'expires_at') list_filter = ('is_active', 'expires_at')
raw_id_fields = ('organization', 'created_by') raw_id_fields = ('organization', 'created_by')
readonly_fields = ('token', 'created_at') readonly_fields = ('uuid', 'created_at')
@admin.register(Role) @admin.register(Role)
class RoleAdmin(ModelAdmin): class RoleAdmin(ModelAdmin):

View file

@ -1,7 +1,8 @@
import django.db.models.deletion
import uuid import uuid
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -61,17 +62,16 @@ class Migration(migrations.Migration):
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID')), ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
('token', models.UUIDField(default=uuid.uuid4, editable=False, unique=True, verbose_name='Token')),
('expires_at', models.DateTimeField(verbose_name='Expires At')), ('expires_at', models.DateTimeField(verbose_name='Expires At')),
('uses', models.IntegerField(default=0, verbose_name='Uses')), ('uses', models.IntegerField(default=0, verbose_name='Uses')),
('max_uses', models.IntegerField(default=1, verbose_name='Max Uses')), ('max_uses', models.IntegerField(default=1, verbose_name='Max Uses')),
('is_active', models.BooleanField(default=True, verbose_name='Is Active')), ('is_active', models.BooleanField(default=True, verbose_name='Is Active')),
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='created_invites', to=settings.AUTH_USER_MODEL)), ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='created_invites', to=settings.AUTH_USER_MODEL)),
('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invite_tokens', to='accounts.organization')), ('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invites', to='accounts.organization')),
], ],
options={ options={
'verbose_name': 'Invite Token', 'verbose_name': 'Invite',
'verbose_name_plural': 'Invite Tokens', 'verbose_name_plural': 'Invites',
}, },
), ),
migrations.CreateModel( migrations.CreateModel(

View file

@ -1,23 +0,0 @@
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='invite',
options={'verbose_name': 'Invite', 'verbose_name_plural': 'Invites'},
),
migrations.AlterField(
model_name='invite',
name='organization',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='invites', to='accounts.organization'),
),
]

View file

@ -65,7 +65,6 @@ class Organization(IdentifierMixin, TimeStampMixin, Model):
class Invite(IdentifierMixin, TimeStampMixin, Model): class Invite(IdentifierMixin, TimeStampMixin, Model):
token = UUIDField(verbose_name = _("Token"), default = uuid4, unique = True, editable = False)
organization = ForeignKey(Organization, on_delete = CASCADE, related_name = "invites") organization = ForeignKey(Organization, on_delete = CASCADE, related_name = "invites")
created_by = ForeignKey(User, on_delete = CASCADE, related_name = "created_invites") created_by = ForeignKey(User, on_delete = CASCADE, related_name = "created_invites")
expires_at = DateTimeField(verbose_name=_("Expires At")) expires_at = DateTimeField(verbose_name=_("Expires At"))

View file

@ -32,14 +32,14 @@ class InviteSerializer(ModelSerializer):
class Meta: class Meta:
model = Invite model = Invite
fields = ['id', 'token', 'organization', 'created_by', 'expires_at', 'uses', 'max_uses', 'is_active', 'created_at', 'updated_at', 'invite_url', 'is_valid'] fields = ['id', 'uuid', 'organization', 'created_by', 'expires_at', 'uses', 'max_uses', 'is_active', 'created_at', 'updated_at', 'invite_url', 'is_valid']
read_only_fields = ['id', 'token', 'organization', 'created_by', 'created_at', 'updated_at'] read_only_fields = ['id', 'uuid', 'organization', 'created_by', 'created_at', 'updated_at']
def get_invite_url(self, obj: Invite) -> str: def get_invite_url(self, obj: Invite) -> str:
request = self.context.get('request') request = self.context.get('request')
if request: if request:
return request.build_absolute_uri(f'/invite/{obj.token}') return request.build_absolute_uri(f'/invite/{obj.uuid}')
return f'/invite/{obj.token}' return f'/invite/{obj.uuid}'
def get_is_valid(self, obj: Invite) -> bool: def get_is_valid(self, obj: Invite) -> bool:
return obj.is_valid() return obj.is_valid()

View file

@ -140,30 +140,30 @@ class OrganizationViewSet(ModelViewSet):
) )
return Response(InviteSerializer(invitation, context={'request': request}).data) return Response(InviteSerializer(invitation, context={'request': request}).data)
@action(detail=True, methods=['delete'], url_path=r'revoke-invite/(?P<token>[0-9a-f-]{36})') @action(detail=True, methods=['delete'], url_path=r'revoke-invite/(?P<invite_uuid>[0-9a-f-]{36})')
def revoke_invite(self, request, uuid=None, token=None): def revoke_invite(self, request, uuid=None, invite_uuid=None):
organization = self.get_object() organization = self.get_object()
if not request.user.is_manager: if not request.user.is_manager:
return Response({'error': 'Only managers can revoke invites'}, status=HTTP_403_FORBIDDEN) return Response({'error': 'Only managers can revoke invites'}, status=HTTP_403_FORBIDDEN)
invite = organization.invites.filter(token=token).first() invite = organization.invites.filter(uuid=invite_uuid).first()
if not invite: if not invite:
return Response({'error': 'Invalid invitation token or not found in this organization'}, status=HTTP_404_NOT_FOUND) return Response({'error': 'Invalid invitation uuid or not found in this organization'}, status=HTTP_404_NOT_FOUND)
invite.is_active = False invite.is_active = False
invite.save() invite.save()
return Response({'message': 'Invitation successfully revoked'}, status=HTTP_200_OK) return Response({'message': 'Invitation successfully revoked'}, status=HTTP_200_OK)
@action(detail=False, methods=['post'], url_path='join/(?P<token>[0-9a-f-]{36})') @action(detail=False, methods=['post'], url_path='join/(?P<invite_uuid>[0-9a-f-]{36})')
def join(self, request, token=None): def join(self, request, invite_uuid=None):
try: try:
invitation = Invite.objects.get(token=token) invitation = Invite.objects.get(uuid=invite_uuid)
except Invite.DoesNotExist: except Invite.DoesNotExist:
return Response({'error': 'Not Found'}, status=HTTP_404_NOT_FOUND) return Response({'error': 'Not Found'}, status=HTTP_404_NOT_FOUND)
if not invitation.is_valid(): if not invitation.is_valid():
return Response({'error': 'Invalid or expired token'}, status=HTTP_400_BAD_REQUEST) return Response({'error': 'Invalid or expired invitation'}, status=HTTP_400_BAD_REQUEST)
organization = invitation.organization organization = invitation.organization
if organization.members.filter(id=request.user.id).exists(): if organization.members.filter(uuid=request.user.uuid).exists():
return Response({'error': 'Already a member'}, status=HTTP_403_FORBIDDEN) return Response({'error': 'Already a member'}, status=HTTP_403_FORBIDDEN)
organization.members.add(request.user) organization.members.add(request.user)
@ -184,7 +184,7 @@ class OrganizationViewSet(ModelViewSet):
if organization.owner == request.user: if organization.owner == request.user:
return Response({'error': 'Owner cannot leave'}, status=HTTP_403_FORBIDDEN) return Response({'error': 'Owner cannot leave'}, status=HTTP_403_FORBIDDEN)
if not organization.members.filter(id=request.user.id).exists(): if not organization.members.filter(uuid=request.user.uuid).exists():
return Response({'error': 'Not a member'}, status=HTTP_400_BAD_REQUEST) return Response({'error': 'Not a member'}, status=HTTP_400_BAD_REQUEST)
organization.members.remove(request.user) organization.members.remove(request.user)
@ -196,16 +196,16 @@ class OrganizationViewSet(ModelViewSet):
serializer = UserSerializer(organization.members.all(), many=True) serializer = UserSerializer(organization.members.all(), many=True)
return Response(serializer.data) return Response(serializer.data)
@action(detail=True, methods=['post'], url_path=r'member/(?P<user_id>\d+)/remove') @action(detail=True, methods=['post'], url_path=r'member/(?P<user_uuid>[0-9a-f-]{36})/remove')
def remove_member(self, request, uuid=None, user_id=None): def remove_member(self, request, uuid=None, user_uuid=None):
if not request.user.is_manager: if not request.user.is_manager:
return Response({'error': 'Forbidden'}, status=HTTP_403_FORBIDDEN) return Response({'error': 'Forbidden'}, status=HTTP_403_FORBIDDEN)
organization = self.get_object() organization = self.get_object()
if str(organization.owner.id) == str(user_id): if str(organization.owner.uuid) == str(user_uuid):
return Response({'error': 'Cannot remove owner'}, status=HTTP_403_FORBIDDEN) return Response({'error': 'Cannot remove owner'}, status=HTTP_403_FORBIDDEN)
user_to_remove = organization.members.filter(id=user_id).first() user_to_remove = organization.members.filter(uuid=user_uuid).first()
if not user_to_remove: if not user_to_remove:
return Response({'error': 'Not found'}, status=HTTP_404_NOT_FOUND) return Response({'error': 'Not found'}, status=HTTP_404_NOT_FOUND)

View file

@ -1,9 +1,9 @@
import django.db.models.deletion
import pgvector.django
import uuid import uuid
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
from pgvector.django import VectorExtension import django.db.models.deletion
import pgvector.django.vector
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -15,7 +15,7 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
VectorExtension(), pgvector.django.VectorExtension(),
migrations.CreateModel( migrations.CreateModel(
name='TrainingFile', name='TrainingFile',
fields=[ fields=[
@ -48,7 +48,7 @@ class Migration(migrations.Migration):
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
('content', models.TextField()), ('content', models.TextField()),
('content_hash', models.CharField(db_index=True, max_length=64)), ('content_hash', models.CharField(db_index=True, max_length=64)),
('embedding', pgvector.django.VectorField(blank=True, dimensions=1536, null=True)), ('embedding', pgvector.django.vector.VectorField(blank=True, dimensions=1536, null=True)),
('metadata', models.JSONField(blank=True, default=dict)), ('metadata', models.JSONField(blank=True, default=dict)),
('chunk_index', models.IntegerField(default=0)), ('chunk_index', models.IntegerField(default=0)),
('is_active', models.BooleanField(default=True)), ('is_active', models.BooleanField(default=True)),

View file

@ -22,9 +22,9 @@ class Migration(migrations.Migration):
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
('name', models.CharField(max_length=255, verbose_name='Agent Name')), ('name', models.CharField(max_length=255, verbose_name='Agent Name')),
('agent_type', models.CharField(choices=[('curriculum', 'Curriculum Agent (CA)'), ('knowledge', 'Knowledge Agent (KA)'), ('assessment', 'Assessment Agent (AA)'), ('monitor', 'Progress Monitor Agent (PMA)')], max_length=40, verbose_name='Agent Type')), ('agent_type', models.CharField(choices=[('curriculum', 'Curriculum Agent (CA)'), ('knowledge', 'Knowledge Agent (KA)'), ('assessment', 'Assessment Agent (AA)'), ('monitor', 'Progress Monitor Agent (PMA)')], max_length=40, verbose_name='Agent Type')),
('llm_config', models.JSONField(default=dict, verbose_name='LLM Configuration')), ('llm_config', models.JSONField(blank=True, default=dict, null=True, verbose_name='LLM Configuration')),
('system_prompt', models.TextField(verbose_name='System Prompt')), ('system_prompt', models.TextField(blank=True, default='', verbose_name='System Prompt')),
('tool_permissions', models.JSONField(default=list, verbose_name='Tool Permissions')), ('tool_permissions', models.JSONField(blank=True, default=list, null=True, verbose_name='Tool Permissions')),
('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='agent_configs', to='accounts.organization', verbose_name='Organization')), ('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='agent_configs', to='accounts.organization', verbose_name='Organization')),
], ],
options={ options={
@ -40,6 +40,7 @@ class Migration(migrations.Migration):
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')),
('title', models.CharField(max_length=255, verbose_name='Flow Title')), ('title', models.CharField(max_length=255, verbose_name='Flow Title')),
('structure', models.JSONField(blank=True, default=list, verbose_name='Flow Structure')),
('is_active', models.BooleanField(default=True, verbose_name='Is Active')), ('is_active', models.BooleanField(default=True, verbose_name='Is Active')),
('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='flows', to='accounts.role', verbose_name='Role')), ('role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='flows', to='accounts.role', verbose_name='Role')),
], ],

View file

@ -1,28 +0,0 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('onboarding', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='agentconfig',
name='llm_config',
field=models.JSONField(blank=True, default=dict, null=True, verbose_name='LLM Configuration'),
),
migrations.AlterField(
model_name='agentconfig',
name='system_prompt',
field=models.TextField(blank=True, default='', verbose_name='System Prompt'),
),
migrations.AlterField(
model_name='agentconfig',
name='tool_permissions',
field=models.JSONField(blank=True, default=list, null=True, verbose_name='Tool Permissions'),
),
]

View file

@ -1,18 +0,0 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('onboarding', '0002_alter_agentconfig_llm_config_and_more'),
]
operations = [
migrations.AddField(
model_name='onboardingflow',
name='structure',
field=models.JSONField(blank=True, default=list, verbose_name='Flow Structure'),
),
]

View file

@ -94,8 +94,8 @@ export const API = {
byId: (uuid: string) => `organization/${uuid}/`, byId: (uuid: string) => `organization/${uuid}/`,
members: { members: {
list: (uuid: string) => `organization/${uuid}/members/`, list: (uuid: string) => `organization/${uuid}/members/`,
remove: (uuid: string, userId: number) => remove: (uuid: string, userUuid: string) =>
`organization/${uuid}/member/${userId}/remove/`, `organization/${uuid}/member/${userUuid}/remove/`,
}, },
invites: { invites: {
list: (uuid: string) => `organization/${uuid}/invite/`, list: (uuid: string) => `organization/${uuid}/invite/`,
@ -103,7 +103,7 @@ export const API = {
`organization/${uuid}/create-invite/?max_uses=${maxUses}`, `organization/${uuid}/create-invite/?max_uses=${maxUses}`,
revoke: (uuid: string, inviteUuid: string) => revoke: (uuid: string, inviteUuid: string) =>
`organization/${uuid}/revoke-invite/${inviteUuid}/`, `organization/${uuid}/revoke-invite/${inviteUuid}/`,
join: (token: string) => `organization/join/${token}/`, join: (inviteUuid: string) => `organization/join/${inviteUuid}/`,
}, },
leave: (uuid: string) => `organization/${uuid}/leave/`, leave: (uuid: string) => `organization/${uuid}/leave/`,
roles: { roles: {

View file

@ -39,19 +39,19 @@ const router = createRouter({
meta: { requiresAuth: true }, meta: { requiresAuth: true },
}, },
{ {
path: '/organization/:id', path: '/organization/:organizationUuid',
name: 'organization-detail', name: 'organization-detail',
component: () => import('../views/OrganizationView.vue'), component: () => import('../views/OrganizationView.vue'),
meta: { requiresAuth: true }, meta: { requiresAuth: true },
}, },
{ {
path: '/organization/:id/manage', path: '/organization/:organizationUuid/manage',
name: 'organization-manage', name: 'organization-manage',
component: () => import('../views/OrganizationManage.vue'), component: () => import('../views/OrganizationManage.vue'),
meta: { requiresAuth: true, requiresManager: true }, meta: { requiresAuth: true, requiresManager: true },
}, },
{ {
path: '/invite/:token', path: '/invite/:inviteUuid',
name: 'invite-accept', name: 'invite-accept',
component: () => import('../views/InviteAccept.vue'), component: () => import('../views/InviteAccept.vue'),
}, },
@ -62,7 +62,7 @@ const router = createRouter({
meta: { requiresAuth: true, requiresManager: true }, meta: { requiresAuth: true, requiresManager: true },
}, },
{ {
path: '/agents/:id', path: '/agents/:agentUuid',
name: 'agent-detail', name: 'agent-detail',
component: () => import('../views/AgentDetailView.vue'), component: () => import('../views/AgentDetailView.vue'),
meta: { requiresAuth: true, requiresManager: true }, meta: { requiresAuth: true, requiresManager: true },

View file

@ -1,7 +1,6 @@
import { User } from './user' import { User } from './user'
export interface Organization { export interface Organization {
id: number
uuid: string uuid: string
name: string name: string
description: string description: string
@ -13,7 +12,6 @@ export interface Organization {
} }
export interface Role { export interface Role {
id: number
uuid: string uuid: string
name: string name: string
description?: string description?: string
@ -24,8 +22,7 @@ export interface Role {
} }
export interface InviteToken { export interface InviteToken {
id: number uuid: string
token: string
invite_url: string invite_url: string
created_by: User created_by: User
organization: Organization organization: Organization
@ -36,7 +33,6 @@ export interface InviteToken {
uses?: number uses?: number
} }
export interface TrainingFile { export interface TrainingFile {
id: number
uuid: string uuid: string
role: Role role: Role
uploaded_by: User uploaded_by: User

View file

@ -1,5 +1,4 @@
export interface User { export interface User {
id: number
uuid: string uuid: string
email_address: string email_address: string
first_name: string first_name: string

View file

@ -7,7 +7,7 @@ import { apiClient, isAxiosError, API } from '../router/api'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const token = route.params.token as string const inviteUuid = route.params.inviteUuid as string
const loading = ref(false) const loading = ref(false)
const accepting = ref(false) const accepting = ref(false)
const accepted = ref(false) const accepted = ref(false)
@ -18,7 +18,7 @@ const acceptInvite = async () => {
error.value = null error.value = null
try { try {
const response = await apiClient.post<{ message: string; success: boolean; uuid: string }>( const response = await apiClient.post<{ message: string; success: boolean; uuid: string }>(
API.organization.invites.join(token), API.organization.invites.join(inviteUuid),
) )
message.success(response.data?.message || 'Successfully joined organization') message.success(response.data?.message || 'Successfully joined organization')
accepted.value = true accepted.value = true

View file

@ -26,7 +26,7 @@ const route = useRoute()
const router = useRouter() const router = useRouter()
const auth = useUserStore() const auth = useUserStore()
const orgId = route.params.id as string const organizationUuid = route.params.organizationUuid as string
const organization = ref<Organization | null>(null) const organization = ref<Organization | null>(null)
const members = ref<User[]>([]) const members = ref<User[]>([])
const invites = ref<InviteToken[]>([]) const invites = ref<InviteToken[]>([])
@ -48,7 +48,7 @@ const newDescription = ref('')
const fetchOrganization = async () => { const fetchOrganization = async () => {
loading.value = true loading.value = true
try { try {
const response = await apiClient.get<Organization>(API.organization.byId(orgId)) const response = await apiClient.get<Organization>(API.organization.byId(organizationUuid))
organization.value = response.data organization.value = response.data
newDescription.value = response.data.description newDescription.value = response.data.description
} catch (error) { } catch (error) {
@ -61,7 +61,7 @@ const fetchOrganization = async () => {
const fetchMembers = async () => { const fetchMembers = async () => {
try { try {
const response = await apiClient.get<User[]>(API.organization.members.list(orgId)) const response = await apiClient.get<User[]>(API.organization.members.list(organizationUuid))
members.value = response.data members.value = response.data
} catch (error) { } catch (error) {
console.error('Failed to fetch members:', error) console.error('Failed to fetch members:', error)
@ -70,7 +70,7 @@ const fetchMembers = async () => {
const fetchInvites = async () => { const fetchInvites = async () => {
try { try {
const response = await apiClient.get<InviteToken[]>(API.organization.invites.list(orgId)) const response = await apiClient.get<InviteToken[]>(API.organization.invites.list(organizationUuid))
invites.value = response.data invites.value = response.data
} catch (error) { } catch (error) {
console.error('Failed to fetch invites:', error) console.error('Failed to fetch invites:', error)
@ -79,7 +79,7 @@ const fetchInvites = async () => {
const fetchRoles = async () => { const fetchRoles = async () => {
try { try {
const response = await apiClient.get<Role[]>(API.organization.roles.list(orgId)) const response = await apiClient.get<Role[]>(API.organization.roles.list(organizationUuid))
Roles.value = response.data as unknown as Role[] Roles.value = response.data as unknown as Role[]
} catch (error) { } catch (error) {
console.error('Failed to fetch Roles:', error) console.error('Failed to fetch Roles:', error)
@ -109,7 +109,7 @@ const createRole = async () => {
creatingRole.value = true creatingRole.value = true
try { try {
await apiClient.post(API.organization.roles.list(orgId), { name, description }) await apiClient.post(API.organization.roles.list(organizationUuid), { name, description })
message.success('Role created successfully') message.success('Role created successfully')
roleModalVisible.value = false roleModalVisible.value = false
resetRoleForm() resetRoleForm()
@ -136,7 +136,7 @@ const deleteRole = async (role: Role) => {
onOk: async () => { onOk: async () => {
deletingRoleUuid.value = role.uuid deletingRoleUuid.value = role.uuid
try { try {
await apiClient.delete(API.organization.roles.remove(orgId, role.uuid)) await apiClient.delete(API.organization.roles.remove(organizationUuid, role.uuid))
message.success('Role deleted successfully') message.success('Role deleted successfully')
await fetchRoles() await fetchRoles()
} catch (error) { } catch (error) {
@ -156,7 +156,7 @@ const deleteRole = async (role: Role) => {
const createInvite = async () => { const createInvite = async () => {
try { try {
const response = await apiClient.post<InviteToken>( const response = await apiClient.post<InviteToken>(
API.organization.invites.create(orgId, newInviteMaxUses.value), API.organization.invites.create(organizationUuid, newInviteMaxUses.value),
) )
newInviteUrl.value = response.data.invite_url newInviteUrl.value = response.data.invite_url
inviteModalVisible.value = true inviteModalVisible.value = true
@ -177,9 +177,9 @@ const copyUrl = (url: string) => {
message.success('Copied to clipboard') message.success('Copied to clipboard')
} }
const revokeInvite = async (token: string) => { const revokeInvite = async (inviteUuid: string) => {
try { try {
await apiClient.delete(API.organization.invites.revoke(orgId, token)) await apiClient.delete(API.organization.invites.revoke(organizationUuid, inviteUuid))
message.success('Invite revoked') message.success('Invite revoked')
fetchInvites() fetchInvites()
} catch (error) { } catch (error) {
@ -188,9 +188,9 @@ const revokeInvite = async (token: string) => {
} }
} }
const removeMember = async (userId: number) => { const removeMember = async (userUuid: string) => {
try { try {
await apiClient.post(API.organization.members.remove(orgId, userId)) await apiClient.post(API.organization.members.remove(organizationUuid, userUuid))
message.success('Member removed') message.success('Member removed')
fetchMembers() fetchMembers()
} catch (error) { } catch (error) {
@ -203,7 +203,7 @@ const removeMember = async (userId: number) => {
const saveDescription = async () => { const saveDescription = async () => {
try { try {
await apiClient.patch(API.organization.byId(orgId), { await apiClient.patch(API.organization.byId(organizationUuid), {
description: newDescription.value, description: newDescription.value,
}) })
message.success('Description updated') message.success('Description updated')
@ -221,14 +221,14 @@ onMounted(async () => {
await fetchInvites() await fetchInvites()
await fetchRoles() await fetchRoles()
const currentUserId = auth.user?.id const currentUserUuid = auth.user?.uuid
const isOwner = organization.value?.owner?.id === currentUserId const isOwner = organization.value?.owner?.uuid === currentUserUuid
const myMembership = members.value.find((m) => m.id === currentUserId) const myMembership = members.value.find((member) => member.uuid === currentUserUuid)
const isEmployer = myMembership?.is_manager const isEmployer = myMembership?.is_manager
if (!isOwner && !isEmployer) { if (!isOwner && !isEmployer) {
message.error('You do not have permission to manage this organization') message.error('You do not have permission to manage this organization')
router.replace(`/organization/${orgId}`) router.replace(`/organization/${organizationUuid}`)
} }
}) })
</script> </script>
@ -239,7 +239,7 @@ onMounted(async () => {
<Card v-if="organization" class="panel" :bordered="false"> <Card v-if="organization" class="panel" :bordered="false">
<div class="header"> <div class="header">
<Typography.Title :level="2">Manage {{ organization.name }}</Typography.Title> <Typography.Title :level="2">Manage {{ organization.name }}</Typography.Title>
<Button type="default" @click="router.push(`/organization/${orgId}`)"> <Button type="default" @click="router.push(`/organization/${organizationUuid}`)">
Back to Organization Back to Organization
</Button> </Button>
</div> </div>
@ -289,10 +289,10 @@ onMounted(async () => {
/> />
<Space> <Space>
<Button <Button
v-if="item.id !== organization.owner.id" v-if="item.uuid !== organization.owner.uuid"
danger danger
size="small" size="small"
@click="removeMember(item.id)" @click="removeMember(item.uuid)"
> >
Remove Remove
</Button> </Button>
@ -343,7 +343,7 @@ onMounted(async () => {
<Button <Button
danger danger
size="small" size="small"
@click="revokeInvite(item.token)" @click="revokeInvite(item.uuid)"
> >
Revoke Revoke
</Button> </Button>

View file

@ -23,11 +23,11 @@ import type { Role, Organization, TrainingFile } from '../types/organization'
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const orgId = route.params.id as string const organizationUuid = route.params.organizationUuid as string
const organization = ref<Organization | null>(null) const organization = ref<Organization | null>(null)
const roles = ref<Role[]>([]) const roles = ref<Role[]>([])
const members = ref<Array<{ user: { id: number }; role: string }>>([]) const members = ref<Array<{ uuid: string; is_manager?: boolean }>>([])
const trainingFiles = ref<TrainingFile[]>([]) const trainingFiles = ref<TrainingFile[]>([])
const loading = ref(false) const loading = ref(false)
const uploading = ref(false) const uploading = ref(false)
@ -38,14 +38,14 @@ const isManager = computed(() => {
if (!auth.user || !organization.value) return false if (!auth.user || !organization.value) return false
if ((organization.value as Organization & { is_manager?: boolean }).is_manager === true) if ((organization.value as Organization & { is_manager?: boolean }).is_manager === true)
return true return true
if (organization.value.owner?.id === auth.user.id) return true if (organization.value.owner?.uuid === auth.user.uuid) return true
return members.value.some((m) => m.user?.id === auth.user?.id && m.role === 'employer') return members.value.some((member) => member.uuid === auth.user?.uuid && member.is_manager)
}) })
const fetchOrganization = async () => { const fetchOrganization = async () => {
loading.value = true loading.value = true
try { try {
const response = await apiClient.get<Organization>(API.organization.byId(orgId)) const response = await apiClient.get<Organization>(API.organization.byId(organizationUuid))
organization.value = response.data organization.value = response.data
} catch (error) { } catch (error) {
console.error('Failed to fetch organization:', error) console.error('Failed to fetch organization:', error)
@ -86,7 +86,7 @@ const isRoleJoined = (roleUuid: string | undefined) => {
const fetchMembers = async () => { const fetchMembers = async () => {
if (!organization.value?.uuid) return if (!organization.value?.uuid) return
try { try {
const response = await apiClient.get<Array<{ user: { id: number }; role: string }>>( const response = await apiClient.get<Array<{ uuid: string; is_manager?: boolean }>>(
API.organization.members.list(organization.value.uuid), API.organization.members.list(organization.value.uuid),
) )
members.value = response.data members.value = response.data
@ -100,7 +100,7 @@ const selectRole = async (roleUuid: string) => {
message.error('Organization not loaded') message.error('Organization not loaded')
return return
} }
if (!auth.user?.id) { if (!auth.user?.uuid) {
try { try {
await auth.fetchSession(true) await auth.fetchSession(true)
} catch { } catch {
@ -315,7 +315,7 @@ const trainingFileColumns = [
title: 'Action', title: 'Action',
key: 'action', key: 'action',
customRender: ({ record }: { record: TrainingFile }) => { customRender: ({ record }: { record: TrainingFile }) => {
if (isManager.value || auth.user?.id === record.uploaded_by?.id) { if (isManager.value || auth.user?.uuid === record.uploaded_by?.uuid) {
return h( return h(
Button, Button,
{ {

View file

@ -25,8 +25,7 @@ const fetchOrganizations = async () => {
if (organizations.value.length === 1 && !auth.isGeneralManager) { if (organizations.value.length === 1 && !auth.isGeneralManager) {
const onlyOrg = organizations.value[0] const onlyOrg = organizations.value[0]
const id = onlyOrg.uuid || String(onlyOrg.id) await router.replace(`/organization/${onlyOrg.uuid}`)
await router.replace(`/organization/${id}`)
} }
} catch (err: unknown) { } catch (err: unknown) {
console.error('Failed to fetch organizations:', err) console.error('Failed to fetch organizations:', err)
@ -52,8 +51,7 @@ onMounted(() => {
}) })
const openOrg = (org: Organization) => { const openOrg = (org: Organization) => {
const id = org.uuid || String(org.id) router.push(`/organization/${org.uuid}`)
router.push(`/organization/${id}`)
} }
const resetCreateOrganizationForm = () => { const resetCreateOrganizationForm = () => {