mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
Add options to change share visibility (#108)
Co-authored-by: cte <cestreich@gmail.com>
This commit is contained in:
parent
008c548c07
commit
1ab782244b
11 changed files with 1239 additions and 80 deletions
|
|
@ -309,17 +309,24 @@ export const getTasks = async ({
|
|||
userId,
|
||||
taskId,
|
||||
allowCrossUserAccess = false,
|
||||
skipAuth = false,
|
||||
}: {
|
||||
orgId?: string | null;
|
||||
userId?: string | null;
|
||||
taskId?: string | null;
|
||||
allowCrossUserAccess?: boolean;
|
||||
skipAuth?: boolean;
|
||||
}): Promise<TaskWithUser[]> => {
|
||||
const { effectiveUserId } = await authorizeAnalytics({
|
||||
requestedOrgId: orgId,
|
||||
requestedUserId: userId,
|
||||
allowCrossUserAccess,
|
||||
});
|
||||
let effectiveUserId = userId;
|
||||
|
||||
if (!skipAuth) {
|
||||
const authResult = await authorizeAnalytics({
|
||||
requestedOrgId: orgId,
|
||||
requestedUserId: userId,
|
||||
allowCrossUserAccess,
|
||||
});
|
||||
effectiveUserId = authResult.effectiveUserId;
|
||||
}
|
||||
|
||||
if (!orgId) {
|
||||
return [];
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ import {
|
|||
createTaskShareSchema,
|
||||
shareIdSchema,
|
||||
} from '@/types';
|
||||
import type { SharedByUser } from '@/types';
|
||||
import type { SharedByUser } from '@/types/task-sharing';
|
||||
import { TaskShareVisibility } from '@/types/task-sharing';
|
||||
import { type TaskShare, AuditLogTargetType } from '@/db';
|
||||
import { client as db, taskShares, users } from '@/db/server';
|
||||
import { handleError, generateShareToken } from '@/lib/server';
|
||||
|
|
@ -41,6 +42,7 @@ export async function canShareTask(taskId: string): Promise<{
|
|||
orgRole?: string;
|
||||
}> {
|
||||
try {
|
||||
// Get authentication info
|
||||
const authResult = await authorize();
|
||||
|
||||
if (!authResult.success) {
|
||||
|
|
@ -56,6 +58,7 @@ export async function canShareTask(taskId: string): Promise<{
|
|||
orgId,
|
||||
allowCrossUserAccess: true,
|
||||
});
|
||||
|
||||
const task = tasks[0];
|
||||
|
||||
if (!task) {
|
||||
|
|
@ -100,7 +103,11 @@ export async function createTaskShare(data: CreateTaskShareRequest) {
|
|||
return { success: false, error: 'Invalid request data' };
|
||||
}
|
||||
|
||||
const { taskId, expirationDays } = result.data;
|
||||
const {
|
||||
taskId,
|
||||
expirationDays,
|
||||
visibility = TaskShareVisibility.ORGANIZATION,
|
||||
} = result.data;
|
||||
|
||||
const orgSettingsData = await getOrganizationSettings();
|
||||
|
||||
|
|
@ -142,6 +149,7 @@ export async function createTaskShare(data: CreateTaskShareRequest) {
|
|||
orgId,
|
||||
createdByUserId: userId,
|
||||
shareToken,
|
||||
visibility,
|
||||
expiresAt,
|
||||
})
|
||||
.returning();
|
||||
|
|
@ -158,11 +166,12 @@ export async function createTaskShare(data: CreateTaskShareRequest) {
|
|||
newValue: {
|
||||
action: 'created',
|
||||
shareId: insertedShare[0].id,
|
||||
visibility,
|
||||
expiresAt: expiresAt.toISOString(),
|
||||
taskOwnerId: task.userId,
|
||||
sharedByAdmin: orgRole === 'org:admin' && task.userId !== userId,
|
||||
},
|
||||
description: `Created task share for task ${taskId}${
|
||||
description: `Created ${visibility} task share for task ${taskId}${
|
||||
orgRole === 'org:admin' && task.userId !== userId
|
||||
? ` (admin sharing task created by ${task.user.name})`
|
||||
: ''
|
||||
|
|
@ -182,8 +191,8 @@ export async function createTaskShare(data: CreateTaskShareRequest) {
|
|||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Task share created successfully',
|
||||
data: { shareUrl, shareId: newShare.id, expiresAt },
|
||||
message: 'Task share created successfully',
|
||||
};
|
||||
} catch (error) {
|
||||
return handleError(error, 'task_sharing');
|
||||
|
|
@ -198,18 +207,14 @@ export async function getTaskByShareToken(token: string): Promise<{
|
|||
messages: Message[];
|
||||
sharedBy: SharedByUser;
|
||||
sharedAt: Date;
|
||||
visibility: string;
|
||||
} | null> {
|
||||
try {
|
||||
const authResult = await authorize();
|
||||
|
||||
if (!authResult.success) {
|
||||
throw new Error('Authentication required');
|
||||
}
|
||||
|
||||
if (!isValidShareToken(token)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// First, get the share without auth check to determine visibility
|
||||
const [shareWithUser] = await db
|
||||
.select({
|
||||
share: taskShares,
|
||||
|
|
@ -221,12 +226,7 @@ export async function getTaskByShareToken(token: string): Promise<{
|
|||
})
|
||||
.from(taskShares)
|
||||
.innerJoin(users, eq(taskShares.createdByUserId, users.id))
|
||||
.where(
|
||||
and(
|
||||
eq(taskShares.shareToken, token),
|
||||
eq(taskShares.orgId, authResult.orgId),
|
||||
),
|
||||
)
|
||||
.where(eq(taskShares.shareToken, token))
|
||||
.limit(1);
|
||||
|
||||
if (!shareWithUser) {
|
||||
|
|
@ -239,12 +239,25 @@ export async function getTaskByShareToken(token: string): Promise<{
|
|||
return null;
|
||||
}
|
||||
|
||||
// Check visibility and auth requirements
|
||||
if (share.visibility === TaskShareVisibility.ORGANIZATION) {
|
||||
const authResult = await authorize();
|
||||
const userId = authResult.success ? authResult.userId : null;
|
||||
const orgId = authResult.success ? authResult.orgId : null;
|
||||
|
||||
if (!userId || !orgId || orgId !== share.orgId) {
|
||||
throw new Error('Authentication required for organization shares');
|
||||
}
|
||||
}
|
||||
// For public shares, no auth check needed
|
||||
|
||||
// Get task data based on visibility
|
||||
const tasks = await getTasks({
|
||||
taskId: share.taskId,
|
||||
orgId: share.orgId,
|
||||
allowCrossUserAccess: true,
|
||||
skipAuth: share.visibility === TaskShareVisibility.PUBLIC, // Skip auth for public shares
|
||||
});
|
||||
|
||||
const task = tasks[0];
|
||||
|
||||
if (!task) {
|
||||
|
|
@ -258,6 +271,7 @@ export async function getTaskByShareToken(token: string): Promise<{
|
|||
messages,
|
||||
sharedBy: sharedByUser,
|
||||
sharedAt: share.createdAt,
|
||||
visibility: share.visibility,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(
|
||||
|
|
@ -281,6 +295,7 @@ export async function deleteTaskShare(shareId: string) {
|
|||
}
|
||||
|
||||
const { userId, orgId, orgRole } = authResult;
|
||||
|
||||
const shareIdResult = shareIdSchema.safeParse(shareId);
|
||||
|
||||
if (!shareIdResult.success) {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,22 @@
|
|||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { authorizeApi } from '@/actions/auth';
|
||||
import { createTaskShare } from '@/actions/taskSharing';
|
||||
import { getTasks } from '@/actions/analytics';
|
||||
import { getOrganizationSettings } from '@/actions/organizationSettings';
|
||||
import { authorize } from '@/actions/auth';
|
||||
import { TaskShareVisibility } from '@/types/task-sharing';
|
||||
|
||||
const createShareRequestSchema = z.object({
|
||||
taskId: z.string().min(1, 'Task ID is required'),
|
||||
visibility: z
|
||||
.nativeEnum(TaskShareVisibility)
|
||||
.default(TaskShareVisibility.ORGANIZATION),
|
||||
});
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const authResult = await authorizeApi(request);
|
||||
const authResult = await authorize();
|
||||
|
||||
if (!authResult.success) {
|
||||
return NextResponse.json(
|
||||
|
|
@ -33,7 +37,7 @@ export async function POST(request: NextRequest) {
|
|||
);
|
||||
}
|
||||
|
||||
const { taskId } = result.data;
|
||||
const { taskId, visibility } = result.data;
|
||||
|
||||
// Check if task sharing is enabled for the organization
|
||||
const orgSettings = await getOrganizationSettings();
|
||||
|
|
@ -59,7 +63,7 @@ export async function POST(request: NextRequest) {
|
|||
);
|
||||
}
|
||||
|
||||
const shareResponse = await createTaskShare({ taskId });
|
||||
const shareResponse = await createTaskShare({ taskId, visibility });
|
||||
|
||||
if (!shareResponse.success || !shareResponse.data) {
|
||||
return NextResponse.json(
|
||||
|
|
|
|||
|
|
@ -1,19 +1,16 @@
|
|||
import { notFound, redirect } from 'next/navigation';
|
||||
|
||||
import { authorize } from '@/actions/auth';
|
||||
import { getTaskByShareToken } from '@/actions/taskSharing';
|
||||
|
||||
import { SharedTaskView } from '@/components/task-sharing/SharedTaskView';
|
||||
import { Badge } from '@/components/ui';
|
||||
|
||||
type SharedTaskPageProps = { params: { token: string } };
|
||||
type SharedTaskPageProps = {
|
||||
params: Promise<{
|
||||
token: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export default async function SharedTaskPage({ params }: SharedTaskPageProps) {
|
||||
const authResult = await authorize();
|
||||
|
||||
if (!authResult.success) {
|
||||
redirect('/select-org');
|
||||
}
|
||||
|
||||
try {
|
||||
const { token } = await params;
|
||||
const result = await getTaskByShareToken(token);
|
||||
|
|
@ -22,13 +19,16 @@ export default async function SharedTaskPage({ params }: SharedTaskPageProps) {
|
|||
notFound();
|
||||
}
|
||||
|
||||
const { task, messages, sharedBy, sharedAt } = result;
|
||||
const { task, messages, sharedBy, sharedAt, visibility } = result;
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-6">
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<span>Shared Task</span>
|
||||
{visibility === 'public' && (
|
||||
<Badge variant="secondary">Public</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<SharedTaskView
|
||||
|
|
@ -40,22 +40,15 @@ export default async function SharedTaskPage({ params }: SharedTaskPageProps) {
|
|||
</div>
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.includes('Access denied')) {
|
||||
return (
|
||||
<div className="container mx-auto py-6">
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-bold text-destructive mb-4">
|
||||
Access Denied
|
||||
</h1>
|
||||
<p className="text-muted-foreground mb-4">
|
||||
You must be a member of the organization to view this shared task.
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Please contact the person who shared this link to ensure you have
|
||||
the correct organization access.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
// Handle auth errors for org shares
|
||||
if (
|
||||
error instanceof Error &&
|
||||
error.message.includes('Authentication required')
|
||||
) {
|
||||
// For organization shares that require auth, redirect to sign-in
|
||||
const { token } = await params;
|
||||
redirect(
|
||||
`/sign-in?redirect_url=${encodeURIComponent(`/share/${token}`)}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -74,12 +67,14 @@ export async function generateMetadata({ params }: SharedTaskPageProps) {
|
|||
};
|
||||
}
|
||||
|
||||
const { task } = result;
|
||||
const { task, visibility } = result;
|
||||
const title = task.title || `Task by ${task.user.name}`;
|
||||
|
||||
return {
|
||||
title: `Shared Task: ${title}`,
|
||||
description: `View shared task details and conversation history`,
|
||||
description: `View shared task details and conversation history${
|
||||
visibility === 'public' ? ' (Public)' : ''
|
||||
}`,
|
||||
};
|
||||
} catch (_error) {
|
||||
return {
|
||||
|
|
@ -1,11 +1,20 @@
|
|||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Copy, Share, Trash2, ExternalLink } from 'lucide-react';
|
||||
import {
|
||||
Copy,
|
||||
Share,
|
||||
Trash2,
|
||||
ExternalLink,
|
||||
Users,
|
||||
Globe,
|
||||
Check,
|
||||
} from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import type { TaskShare } from '@/db';
|
||||
import type { TaskWithUser } from '@/actions/analytics';
|
||||
import { TaskShareVisibility } from '@/types/task-sharing';
|
||||
import {
|
||||
createTaskShare,
|
||||
deleteTaskShare,
|
||||
|
|
@ -25,6 +34,8 @@ import {
|
|||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
Label,
|
||||
Badge,
|
||||
} from '@/components/ui';
|
||||
|
||||
type ShareButtonProps = {
|
||||
|
|
@ -36,6 +47,9 @@ export const ShareButton = ({ task }: ShareButtonProps) => {
|
|||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [shares, setShares] = useState<TaskShare[]>([]);
|
||||
const [newShareUrl, setNewShareUrl] = useState<string | null>(null);
|
||||
const [visibility, setVisibility] = useState<TaskShareVisibility>(
|
||||
TaskShareVisibility.ORGANIZATION,
|
||||
);
|
||||
|
||||
const { data: orgSettings } = useOrganizationSettings();
|
||||
|
||||
|
|
@ -58,13 +72,17 @@ export const ShareButton = ({ task }: ShareButtonProps) => {
|
|||
if (open) {
|
||||
loadShares();
|
||||
setNewShareUrl(null);
|
||||
setVisibility(TaskShareVisibility.ORGANIZATION); // Reset to default when opening
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateShare = async () => {
|
||||
setIsCreating(true);
|
||||
try {
|
||||
const response = await createTaskShare({ taskId: task.taskId });
|
||||
const response = await createTaskShare({
|
||||
taskId: task.taskId,
|
||||
visibility,
|
||||
});
|
||||
|
||||
if (response.success && response.data) {
|
||||
setNewShareUrl(response.data.shareUrl);
|
||||
|
|
@ -119,20 +137,31 @@ export const ShareButton = ({ task }: ShareButtonProps) => {
|
|||
Share
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-md">
|
||||
<DialogContent className="max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Share Task</DialogTitle>
|
||||
<DialogDescription>
|
||||
Create a link to share this task with your team.
|
||||
{newShareUrl
|
||||
? 'Your share link is ready to use.'
|
||||
: visibility === TaskShareVisibility.ORGANIZATION
|
||||
? 'Create a link to share this task with your team.'
|
||||
: 'Create a public link that anyone can access.'}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Create New Share */}
|
||||
{newShareUrl ? (
|
||||
<div className="space-y-3">
|
||||
<div className="p-3 border rounded-md">
|
||||
<p className="text-sm font-medium mb-2">Share link created!</p>
|
||||
<div className="space-y-4">
|
||||
<div className="p-4 border-2 border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950/30 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<div className="flex-shrink-0">
|
||||
<Check className="size-5 text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
<p className="font-medium text-green-800 dark:text-green-200">
|
||||
Share link created!
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={(e) => {
|
||||
|
|
@ -156,21 +185,117 @@ export const ShareButton = ({ task }: ShareButtonProps) => {
|
|||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setNewShareUrl(null)}
|
||||
className="w-full"
|
||||
>
|
||||
Create Another Link
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
onClick={handleCreateShare}
|
||||
disabled={isCreating}
|
||||
className="w-full"
|
||||
>
|
||||
{isCreating ? 'Creating...' : 'Create Share Link'}
|
||||
</Button>
|
||||
<>
|
||||
{/* Visibility Selector */}
|
||||
<div className="space-y-3">
|
||||
<Label className="text-sm font-medium text-foreground">
|
||||
Who can access this link?
|
||||
</Label>
|
||||
<div className="grid gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setVisibility(TaskShareVisibility.ORGANIZATION)
|
||||
}
|
||||
className={`relative flex items-start gap-3 p-4 rounded-lg border-2 transition-all hover:bg-muted/50 ${
|
||||
visibility === TaskShareVisibility.ORGANIZATION
|
||||
? 'border-primary bg-primary/5 ring-1 ring-primary/20'
|
||||
: 'border-border'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`flex-shrink-0 mt-0.5 ${
|
||||
visibility === TaskShareVisibility.ORGANIZATION
|
||||
? 'text-primary'
|
||||
: 'text-muted-foreground'
|
||||
}`}
|
||||
>
|
||||
<Users className="size-5" />
|
||||
</div>
|
||||
<div className="flex-1 text-left">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-foreground">
|
||||
Organization
|
||||
</span>
|
||||
{visibility === TaskShareVisibility.ORGANIZATION && (
|
||||
<Check className="size-4 text-primary" />
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Only members of your organization can view
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setVisibility(TaskShareVisibility.PUBLIC)}
|
||||
className={`relative flex items-start gap-3 p-4 rounded-lg border-2 transition-all hover:bg-muted/50 ${
|
||||
visibility === TaskShareVisibility.PUBLIC
|
||||
? 'border-primary bg-primary/5 ring-1 ring-primary/20'
|
||||
: 'border-border'
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className={`flex-shrink-0 mt-0.5 ${
|
||||
visibility === TaskShareVisibility.PUBLIC
|
||||
? 'text-primary'
|
||||
: 'text-muted-foreground'
|
||||
}`}
|
||||
>
|
||||
<Globe className="size-5" />
|
||||
</div>
|
||||
<div className="flex-1 text-left">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-foreground">
|
||||
Public
|
||||
</span>
|
||||
{visibility === TaskShareVisibility.PUBLIC && (
|
||||
<Check className="size-4 text-primary" />
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
Anyone with the link can view
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={handleCreateShare}
|
||||
disabled={isCreating}
|
||||
className="w-full h-11 text-base font-medium"
|
||||
size="lg"
|
||||
>
|
||||
{isCreating ? (
|
||||
<>
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-2 border-current border-t-transparent mr-2" />
|
||||
Creating...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Share className="size-4 mr-2" />
|
||||
Create Share Link
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Existing Shares */}
|
||||
{shares.length > 0 && (
|
||||
<div>
|
||||
<h4 className="text-sm font-medium mb-2">
|
||||
<div className="space-y-3">
|
||||
<h4 className="text-sm font-medium text-foreground">
|
||||
Previous Links ({shares.length})
|
||||
</h4>
|
||||
<div className="space-y-2">
|
||||
|
|
@ -179,19 +304,34 @@ export const ShareButton = ({ task }: ShareButtonProps) => {
|
|||
return (
|
||||
<div
|
||||
key={share.id}
|
||||
className="flex items-center justify-between p-2 border rounded"
|
||||
className="flex items-center gap-3 p-3 border rounded-lg hover:bg-muted/30 transition-colors"
|
||||
>
|
||||
<div className="flex-shrink-0">
|
||||
{share.visibility === TaskShareVisibility.PUBLIC ? (
|
||||
<Globe className="size-4 text-muted-foreground" />
|
||||
) : (
|
||||
<Users className="size-4 text-muted-foreground" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-xs text-muted-foreground truncate">
|
||||
Created{' '}
|
||||
{new Date(share.createdAt).toLocaleDateString()}
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<p className="text-xs text-muted-foreground truncate">
|
||||
Created{' '}
|
||||
{new Date(share.createdAt).toLocaleDateString()}
|
||||
</p>
|
||||
{share.visibility === TaskShareVisibility.PUBLIC && (
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
Public
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleCopyLink(shareUrl)}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<Copy className="size-3" />
|
||||
</Button>
|
||||
|
|
@ -199,6 +339,7 @@ export const ShareButton = ({ task }: ShareButtonProps) => {
|
|||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleDeleteShare(share.id)}
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<Trash2 className="size-3" />
|
||||
</Button>
|
||||
|
|
@ -207,7 +348,7 @@ export const ShareButton = ({ task }: ShareButtonProps) => {
|
|||
);
|
||||
})}
|
||||
{shares.length > 3 && (
|
||||
<p className="text-xs text-muted-foreground text-center">
|
||||
<p className="text-xs text-muted-foreground text-center py-2">
|
||||
+{shares.length - 3} more links
|
||||
</p>
|
||||
)}
|
||||
|
|
@ -216,10 +357,14 @@ export const ShareButton = ({ task }: ShareButtonProps) => {
|
|||
)}
|
||||
|
||||
{/* Simple Info */}
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Links expire in {expirationDays} days and are only accessible to
|
||||
your organization members.
|
||||
</p>
|
||||
{!newShareUrl && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Links expire in {expirationDays} days
|
||||
{visibility === TaskShareVisibility.ORGANIZATION
|
||||
? ' and are only accessible to your organization members.'
|
||||
: '. Anyone with the link will be able to view this task.'}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
|
|
|||
2
apps/web/src/db/migrations/0007_tidy_blue_blade.sql
Normal file
2
apps/web/src/db/migrations/0007_tidy_blue_blade.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "task_shares" ADD COLUMN "visibility" text DEFAULT 'organization' NOT NULL;--> statement-breakpoint
|
||||
CREATE INDEX "task_shares_visibility_idx" ON "task_shares" USING btree ("visibility");
|
||||
973
apps/web/src/db/migrations/meta/0007_snapshot.json
Normal file
973
apps/web/src/db/migrations/meta/0007_snapshot.json
Normal file
|
|
@ -0,0 +1,973 @@
|
|||
{
|
||||
"id": "a6128d1e-dc00-490a-8c2c-70da368301bd",
|
||||
"prevId": "d720a01c-3f4b-4000-a6fd-c5abfc6816b7",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.agent_request_logs": {
|
||||
"name": "agent_request_logs",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"agent_id": {
|
||||
"name": "agent_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"organization_id": {
|
||||
"name": "organization_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"endpoint": {
|
||||
"name": "endpoint",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"method": {
|
||||
"name": "method",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status_code": {
|
||||
"name": "status_code",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"response_time_ms": {
|
||||
"name": "response_time_ms",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_agent": {
|
||||
"name": "user_agent",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"ip_address": {
|
||||
"name": "ip_address",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"agent_request_logs_agent_id_idx": {
|
||||
"name": "agent_request_logs_agent_id_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "agent_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"agent_request_logs_org_id_idx": {
|
||||
"name": "agent_request_logs_org_id_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "organization_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"agent_request_logs_created_at_idx": {
|
||||
"name": "agent_request_logs_created_at_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"agent_request_logs_agent_id_agents_id_fk": {
|
||||
"name": "agent_request_logs_agent_id_agents_id_fk",
|
||||
"tableFrom": "agent_request_logs",
|
||||
"tableTo": "agents",
|
||||
"columnsFrom": ["agent_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"agent_request_logs_organization_id_organizations_id_fk": {
|
||||
"name": "agent_request_logs_organization_id_organizations_id_fk",
|
||||
"tableFrom": "agent_request_logs",
|
||||
"tableTo": "organizations",
|
||||
"columnsFrom": ["organization_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.agents": {
|
||||
"name": "agents",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"organization_id": {
|
||||
"name": "organization_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"display_name": {
|
||||
"name": "display_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"is_active": {
|
||||
"name": "is_active",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 1
|
||||
},
|
||||
"last_used_at": {
|
||||
"name": "last_used_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"total_requests": {
|
||||
"name": "total_requests",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"created_by_user_id": {
|
||||
"name": "created_by_user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"agents_org_id_idx": {
|
||||
"name": "agents_org_id_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "organization_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"agents_active_idx": {
|
||||
"name": "agents_active_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "is_active",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"where": "\"agents\".\"is_active\" = 1",
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"agents_last_used_idx": {
|
||||
"name": "agents_last_used_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "last_used_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"agents_organization_id_organizations_id_fk": {
|
||||
"name": "agents_organization_id_organizations_id_fk",
|
||||
"tableFrom": "agents",
|
||||
"tableTo": "organizations",
|
||||
"columnsFrom": ["organization_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"agents_created_by_user_id_users_id_fk": {
|
||||
"name": "agents_created_by_user_id_users_id_fk",
|
||||
"tableFrom": "agents",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": ["created_by_user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.audit_logs": {
|
||||
"name": "audit_logs",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"organization_id": {
|
||||
"name": "organization_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"target_type": {
|
||||
"name": "target_type",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"target_id": {
|
||||
"name": "target_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"new_value": {
|
||||
"name": "new_value",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"audit_logs_user_id_idx": {
|
||||
"name": "audit_logs_user_id_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"audit_logs_organization_id_idx": {
|
||||
"name": "audit_logs_organization_id_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "organization_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"audit_logs_target_idx": {
|
||||
"name": "audit_logs_target_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "target_type",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "target_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"audit_logs_created_at_idx": {
|
||||
"name": "audit_logs_created_at_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"audit_logs_user_id_users_id_fk": {
|
||||
"name": "audit_logs_user_id_users_id_fk",
|
||||
"tableFrom": "audit_logs",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"audit_logs_organization_id_organizations_id_fk": {
|
||||
"name": "audit_logs_organization_id_organizations_id_fk",
|
||||
"tableFrom": "audit_logs",
|
||||
"tableTo": "organizations",
|
||||
"columnsFrom": ["organization_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.organization_settings": {
|
||||
"name": "organization_settings",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"organization_id": {
|
||||
"name": "organization_id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"version": {
|
||||
"name": "version",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 1
|
||||
},
|
||||
"cloud_settings": {
|
||||
"name": "cloud_settings",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'::jsonb"
|
||||
},
|
||||
"default_settings": {
|
||||
"name": "default_settings",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{}'::jsonb"
|
||||
},
|
||||
"allow_list": {
|
||||
"name": "allow_list",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'{\"allowAll\":true,\"providers\":{}}'::jsonb"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"organization_settings_created_at_idx": {
|
||||
"name": "organization_settings_created_at_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"organization_settings_organization_id_organizations_id_fk": {
|
||||
"name": "organization_settings_organization_id_organizations_id_fk",
|
||||
"tableFrom": "organization_settings",
|
||||
"tableTo": "organizations",
|
||||
"columnsFrom": ["organization_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.organizations": {
|
||||
"name": "organizations",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"image_url": {
|
||||
"name": "image_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"entity": {
|
||||
"name": "entity",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_sync_at": {
|
||||
"name": "last_sync_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"organizations_slug_idx": {
|
||||
"name": "organizations_slug_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"organizations_created_at_idx": {
|
||||
"name": "organizations_created_at_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.task_shares": {
|
||||
"name": "task_shares",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"task_id": {
|
||||
"name": "task_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"organization_id": {
|
||||
"name": "organization_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_by_user_id": {
|
||||
"name": "created_by_user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"share_token": {
|
||||
"name": "share_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"visibility": {
|
||||
"name": "visibility",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'organization'"
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"task_shares_share_token_idx": {
|
||||
"name": "task_shares_share_token_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "share_token",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"task_shares_task_id_idx": {
|
||||
"name": "task_shares_task_id_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "task_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"task_shares_org_id_idx": {
|
||||
"name": "task_shares_org_id_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "organization_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"task_shares_expires_at_idx": {
|
||||
"name": "task_shares_expires_at_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "expires_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"task_shares_created_by_user_id_idx": {
|
||||
"name": "task_shares_created_by_user_id_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "created_by_user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"task_shares_visibility_idx": {
|
||||
"name": "task_shares_visibility_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "visibility",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"task_shares_organization_id_organizations_id_fk": {
|
||||
"name": "task_shares_organization_id_organizations_id_fk",
|
||||
"tableFrom": "task_shares",
|
||||
"tableTo": "organizations",
|
||||
"columnsFrom": ["organization_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"task_shares_created_by_user_id_users_id_fk": {
|
||||
"name": "task_shares_created_by_user_id_users_id_fk",
|
||||
"tableFrom": "task_shares",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": ["created_by_user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"task_shares_share_token_unique": {
|
||||
"name": "task_shares_share_token_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": ["share_token"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"organization_id": {
|
||||
"name": "organization_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"organization_role": {
|
||||
"name": "organization_role",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"image_url": {
|
||||
"name": "image_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"entity": {
|
||||
"name": "entity",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_sync_at": {
|
||||
"name": "last_sync_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"users_organization_id_idx": {
|
||||
"name": "users_organization_id_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "organization_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"users_organization_role_idx": {
|
||||
"name": "users_organization_role_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "organization_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "organization_role",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"users_email_idx": {
|
||||
"name": "users_email_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"users_created_at_idx": {
|
||||
"name": "users_created_at_idx",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "created_at",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"users_organization_id_organizations_id_fk": {
|
||||
"name": "users_organization_id_organizations_id_fk",
|
||||
"tableFrom": "users",
|
||||
"tableTo": "organizations",
|
||||
"columnsFrom": ["organization_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -50,6 +50,13 @@
|
|||
"when": 1750289379751,
|
||||
"tag": "0006_ambiguous_professor_monster",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 7,
|
||||
"version": "7",
|
||||
"when": 1750363919215,
|
||||
"tag": "0007_tidy_blue_blade",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -178,6 +178,7 @@ export const taskShares = pgTable(
|
|||
.notNull()
|
||||
.references(() => users.id),
|
||||
shareToken: text('share_token').notNull().unique(),
|
||||
visibility: text('visibility').notNull().default('organization'),
|
||||
expiresAt: timestamp('expires_at'),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow(),
|
||||
updatedAt: timestamp('updated_at').notNull().defaultNow(),
|
||||
|
|
@ -188,6 +189,7 @@ export const taskShares = pgTable(
|
|||
index('task_shares_org_id_idx').on(table.orgId),
|
||||
index('task_shares_expires_at_idx').on(table.expiresAt),
|
||||
index('task_shares_created_by_user_id_idx').on(table.createdByUserId),
|
||||
index('task_shares_visibility_idx').on(table.visibility),
|
||||
],
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ const isUnprotectedRoute = createRouteMatcher([
|
|||
'/sign-up(.*)',
|
||||
'/extension/sign-in(.*)',
|
||||
'/api/marketplace(.*)',
|
||||
'/share(.*)',
|
||||
'/',
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,16 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
export enum TaskShareVisibility {
|
||||
ORGANIZATION = 'organization',
|
||||
PUBLIC = 'public',
|
||||
}
|
||||
|
||||
export const createTaskShareSchema = z.object({
|
||||
taskId: z.string().min(1, 'Task ID is required'),
|
||||
expirationDays: z.number().int().positive().max(365).optional(),
|
||||
visibility: z
|
||||
.nativeEnum(TaskShareVisibility)
|
||||
.default(TaskShareVisibility.ORGANIZATION),
|
||||
});
|
||||
|
||||
export type CreateTaskShareRequest = z.infer<typeof createTaskShareSchema>;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue