22 Commits

Author SHA1 Message Date
Francisco Gaona
c50098a55c WIP - add and remove shares 2025-12-30 21:42:42 +01:00
Francisco Gaona
e73126bcb7 WIP - manually sharing records 2025-12-30 18:29:20 +01:00
Francisco Gaona
6c29d18696 WIP - more admin users and roles 2025-12-30 09:10:45 +01:00
Francisco Gaona
3fbc019083 WIP - admin users and roles 2025-12-30 09:06:42 +01:00
Francisco Gaona
3086f78d34 WIp - manage role permissions per object 2025-12-30 06:16:54 +01:00
Francisco Gaona
d15fc918d1 WIP - field level permission 2025-12-30 05:54:56 +01:00
Francisco Gaona
56c0c3838d WIP - permissions working as expected 2025-12-30 04:50:51 +01:00
Francisco Gaona
9ac69e30d0 WIP - better handling of viewAll modifyAll 2025-12-30 04:43:51 +01:00
Francisco Gaona
d37183ba45 WIp - fix displaying related model names in lookup fields 2025-12-30 04:22:56 +01:00
Francisco Gaona
b4bdeeb9f6 WIP - permissions progress 2025-12-30 03:26:50 +01:00
Francisco Gaona
f4143ab106 WIP - Fix objection and model registry 2025-12-27 06:08:25 +01:00
Francisco Gaona
516e132611 WIP - move docs 2025-12-24 21:46:05 +01:00
Francisco Gaona
c5305490c1 WIP - use objection and working lookup field to owner 2025-12-24 21:43:58 +01:00
Francisco Gaona
4520f94b69 WIP - using objection base model to handle objects operations 2025-12-24 20:18:43 +01:00
Francisco Gaona
e4f1ba96ad WIP - custom migrations when object is created 2025-12-24 19:54:13 +01:00
Francisco Gaona
52c0849de2 WIP - manage tenant users from central 2025-12-24 12:17:22 +01:00
Francisco Gaona
b9fa3bd008 WIP - improve login to tenants by domains 2025-12-24 11:42:44 +01:00
Francisco Gaona
2bc672e4c5 WIP - some fixes 2025-12-24 10:54:19 +01:00
Francisco Gaona
962c84e6d2 WIP - fix lookup field 2025-12-24 00:05:15 +01:00
Francisco Gaona
fc1bec4de7 WIP - related lists and look up field 2025-12-23 23:59:04 +01:00
Francisco Gaona
0275b96014 WIP - central operations 2025-12-23 23:38:45 +01:00
Francisco Gaona
e4f3bad971 WIp - fix login into central 2025-12-23 22:16:58 +01:00
3 changed files with 33 additions and 66 deletions

View File

@@ -21,13 +21,13 @@ export class RecordShare extends BaseModel {
};
}
// Don't auto-set timestamps - let DB defaults handle them
$beforeInsert() {
// Don't call super - skip BaseModel's timestamp logic
// Override BaseModel hooks to prevent automatic timestamp handling
$beforeInsert(queryContext: any) {
// Don't set timestamps - let database defaults handle it
}
$beforeUpdate() {
// Don't call super - skip BaseModel's timestamp logic
$beforeUpdate(opt: any, queryContext: any) {
// Don't set timestamps - let database defaults handle it
}
id!: string;

View File

@@ -147,43 +147,39 @@ export class RecordSharingController {
if (existingShare) {
// Update existing share
const updated = await RecordShare.query(knex)
.patchAndFetchById(existingShare.id, {
accessLevel: {
await knex('record_shares')
.where({ id: existingShare.id })
.update({
accessLevel: JSON.stringify({
canRead: data.canRead,
canEdit: data.canEdit,
canDelete: data.canDelete,
},
// Convert ISO string to MySQL datetime format
expiresAt: data.expiresAt
? knex.raw('?', [new Date(data.expiresAt).toISOString().slice(0, 19).replace('T', ' ')])
: null,
} as any);
}),
expiresAt: data.expiresAt ? data.expiresAt : null,
updatedAt: knex.fn.now(),
});
return RecordShare.query(knex)
.findById(updated.id)
.findById(existingShare.id)
.withGraphFetched('[granteeUser]');
}
// Create new share
const share = await RecordShare.query(knex).insertAndFetch({
const [shareId] = await knex('record_shares').insert({
objectDefinitionId: objectDef.id,
recordId,
granteeUserId: data.granteeUserId,
grantedByUserId: currentUser.userId,
accessLevel: {
accessLevel: JSON.stringify({
canRead: data.canRead,
canEdit: data.canEdit,
canDelete: data.canDelete,
},
// Convert ISO string to MySQL datetime format: YYYY-MM-DD HH:MM:SS
expiresAt: data.expiresAt
? knex.raw('?', [new Date(data.expiresAt).toISOString().slice(0, 19).replace('T', ' ')])
: null,
} as any);
}),
expiresAt: data.expiresAt ? data.expiresAt : null,
});
return RecordShare.query(knex)
.findById(share.id)
.findById(shareId)
.withGraphFetched('[granteeUser]');
}
@@ -239,9 +235,11 @@ export class RecordSharingController {
}
// Revoke the share (soft delete)
await RecordShare.query(knex)
.patchAndFetchById(shareId, {
revokedAt: knex.fn.now() as any,
await knex('record_shares')
.where({ id: shareId })
.update({
revokedAt: knex.fn.now(),
updatedAt: knex.fn.now(),
});
return { success: true };

View File

@@ -146,13 +146,12 @@
<div class="space-y-2">
<Label for="expiresAt">Expires At (Optional)</Label>
<div class="flex gap-2">
<DatePicker
v-model="expiresDate"
placeholder="Select date"
class="flex-1"
/>
</div>
<Input
id="expiresAt"
v-model="newShare.expiresAt"
type="datetime-local"
placeholder="Never"
/>
</div>
</div>
<DialogFooter>
@@ -179,7 +178,6 @@ import { Input } from '~/components/ui/input';
import { Label } from '~/components/ui/label';
import { Badge } from '~/components/ui/badge';
import Checkbox from '~/components/ui/checkbox.vue';
import DatePicker from '~/components/ui/date-picker/DatePicker.vue';
import { UserPlus, Trash2, Users } from 'lucide-vue-next';
interface Props {
@@ -208,24 +206,6 @@ const newShare = ref({
expiresAt: '',
});
const expiresDate = ref<Date | null>(null);
const expiresTime = ref('');
// Computed property to combine date and time into ISO string
const combinedExpiresAt = computed(() => {
if (!expiresDate.value) return '';
const date = new Date(expiresDate.value);
if (expiresTime.value) {
const [hours, minutes] = expiresTime.value.split(':');
date.setHours(parseInt(hours), parseInt(minutes), 0, 0);
} else {
date.setHours(23, 59, 59, 999); // Default to end of day
}
return date.toISOString();
});
// Filter out users who already have shares
const availableUsers = computed(() => {
const sharedUserIds = new Set(shares.value.map(s => s.granteeUserId));
@@ -264,10 +244,6 @@ const loadUsers = async () => {
const createShare = async () => {
try {
sharing.value = true;
const expiresAtValue = combinedExpiresAt.value;
console.log('Creating share, expiresAt value:', expiresAtValue);
const payload: any = {
granteeUserId: newShare.value.userId,
canRead: newShare.value.canRead,
@@ -276,15 +252,10 @@ const createShare = async () => {
};
// Only include expiresAt if it has a value
if (expiresAtValue) {
payload.expiresAt = expiresAtValue;
console.log('Including expiresAt in payload:', payload.expiresAt);
} else {
console.log('Skipping expiresAt - no date selected');
if (newShare.value.expiresAt && newShare.value.expiresAt.trim()) {
payload.expiresAt = newShare.value.expiresAt;
}
console.log('Final payload:', payload);
await api.post(
`/runtime/objects/${props.objectApiName}/records/${props.recordId}/shares`,
payload
@@ -298,8 +269,6 @@ const createShare = async () => {
canDelete: false,
expiresAt: '',
};
expiresDate.value = null;
expiresTime.value = '';
await loadShares();
} catch (e: any) {
console.error('Failed to share record:', e);