Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/project #18

Open
wants to merge 158 commits into
base: main
Choose a base branch
from
Open
Changes from 6 commits
Commits
Show all changes
158 commits
Select commit Hold shift + click to select a range
19db2dd
added landing page
amandal-cn Sep 3, 2024
9e42d96
added landing page and dashboard
amandal-cn Sep 3, 2024
6f4b79a
added project overview input form
amandal-cn Sep 4, 2024
5cd4972
added header
amandal-cn Sep 4, 2024
6374b6f
Add projects crud
aprusty-cn Sep 4, 2024
d8505ba
added workspace
amandal-cn Sep 5, 2024
171eaf3
improved the look of the artifact list
amandal-cn Sep 5, 2024
aebac39
integrated codeeditor with the workspace
amandal-cn Sep 5, 2024
2314f08
updated main page
amandal-cn Sep 6, 2024
8551969
Merge branch 'apis' of https://github.com/trilogy-group/llamacoder in…
aprusty-cn Sep 6, 2024
d4ef840
Merge
aprusty-cn Sep 6, 2024
4bd4a15
improved the chat interface
amandal-cn Sep 6, 2024
fc384bc
refactored UpdateArtifact
amandal-cn Sep 6, 2024
3e450c9
Add routing between pages
aprusty-cn Sep 6, 2024
a1887dd
Add my projects button
aprusty-cn Sep 6, 2024
cef21fb
added chat context and support viewing attachments
amandal-cn Sep 6, 2024
b260298
improved the file viewer modal's look
amandal-cn Sep 6, 2024
19dc802
fixed the attachment click issue (was sending message)
amandal-cn Sep 6, 2024
13fab2a
show error message on sending empty message
amandal-cn Sep 6, 2024
9bb7fa7
fixed height issue for chat context
amandal-cn Sep 6, 2024
b0fc270
Added the copy buttons over hovered message
amandal-cn Sep 6, 2024
ccf996f
added border color to differentiate between user and assistant messages
amandal-cn Sep 6, 2024
84ba94a
Add create project fucntionality
aprusty-cn Sep 6, 2024
8e0f555
Add loading indicators in dashboard
aprusty-cn Sep 6, 2024
30c77d8
Support delete and status
aprusty-cn Sep 6, 2024
718dc75
Add project title
aprusty-cn Sep 9, 2024
e9ad274
Style changes
aprusty-cn Sep 9, 2024
d879c8a
Add simple landing page with login/signup + routing
rishabh-ti Sep 6, 2024
81bbd0b
removed deprecated config for rendering images
rishabh-ti Sep 6, 2024
49ac6c4
Add FGA client
rishabh-ti Sep 9, 2024
10187b7
Functionality updates
aprusty-cn Sep 9, 2024
80abca4
remove auth buttons from old header
rishabh-ti Sep 9, 2024
3f77196
Merge conflicts
aprusty-cn Sep 9, 2024
be58be8
Add FGA API
rishabh-ti Sep 9, 2024
948be8c
added context and dispatch event functionality
amandal-cn Sep 9, 2024
c1e41ee
Merge branch 'feat/project' of github-amandal-cn:trilogy-group/llamac…
amandal-cn Sep 9, 2024
7efe25b
Fix listObjects & add test page
rishabh-ti Sep 9, 2024
6369a27
Add project authz
aprusty-cn Sep 9, 2024
c89352d
Handle authz errors in the frontend
aprusty-cn Sep 9, 2024
982147a
Merge branch 'feat/project' of github-amandal-cn:trilogy-group/llamac…
amandal-cn Sep 9, 2024
c7b2974
fixed merge issues
amandal-cn Sep 9, 2024
ec92348
Fix ts errors
aprusty-cn Sep 9, 2024
eca0d92
fixed layout issue
amandal-cn Sep 9, 2024
8aec5ef
integrate project crud with its api client
amandal-cn Sep 9, 2024
a82f4be
Merge branch 'feat/project' of github-amandal-cn:trilogy-group/llamac…
amandal-cn Sep 9, 2024
d266bbf
fixed project apis
amandal-cn Sep 9, 2024
cd1105b
fixed workspace
amandal-cn Sep 9, 2024
90ed99b
added apis for artifacts
amandal-cn Sep 9, 2024
21f5d16
fixed publishing of artifacts
amandal-cn Sep 9, 2024
8ef7460
added capability to stream message
amandal-cn Sep 9, 2024
701f44b
added status field to each artifact
amandal-cn Sep 9, 2024
649a664
added parsing of code, dependencies and name
amandal-cn Sep 9, 2024
2e914a6
fixed render issue
amandal-cn Sep 9, 2024
5256e1e
fixed the rerendering issue in code editor
amandal-cn Sep 9, 2024
568f463
fixed re-rendering issue in the code editor
amandal-cn Sep 9, 2024
31e2962
fixed the status message rendering
amandal-cn Sep 9, 2024
068c558
added key
amandal-cn Sep 10, 2024
6873299
fixed the height issue of the workspace
amandal-cn Sep 10, 2024
2e18349
fixed rendering of messages
amandal-cn Sep 10, 2024
cbae231
added remark-gfm
amandal-cn Sep 10, 2024
198faeb
chat session updates on chaning artifact
amandal-cn Sep 10, 2024
de30b9c
disabled pinning of selected artifact
amandal-cn Sep 10, 2024
1e03252
improved the rendering of chat history
amandal-cn Sep 10, 2024
ee266d4
added code viewer capability
amandal-cn Sep 10, 2024
ebd3d2e
minimized delay in artifaction generation
amandal-cn Sep 10, 2024
8e043ed
removed unnecessary code
amandal-cn Sep 10, 2024
d928f7b
Add share modal
aprusty-cn Sep 10, 2024
af3d662
removed user since we will be using autho0 user
amandal-cn Sep 10, 2024
5882165
Merge branch 'feat/project' of github-amandal-cn:trilogy-group/llamac…
amandal-cn Sep 10, 2024
da0dc39
fixed build and eslint issues
amandal-cn Sep 10, 2024
2b6d44a
Share with user
aprusty-cn Sep 10, 2024
36d4a76
fixed code editor rendering issues
amandal-cn Sep 10, 2024
c8f80a5
minor fix
amandal-cn Sep 10, 2024
7ed046a
Merge branch 'feat/project' of github-amandal-cn:trilogy-group/llamac…
amandal-cn Sep 10, 2024
0703803
fixed code viewer and code editor
amandal-cn Sep 10, 2024
2cd55f9
fixed workspace
amandal-cn Sep 10, 2024
e6a6737
reduced delay between code and rendering
amandal-cn Sep 10, 2024
3a924ca
minimized latency in code rendering
amandal-cn Sep 10, 2024
7c57788
fixed artifacts list
amandal-cn Sep 10, 2024
5141e0b
delete with custom confirmation dialog
amandal-cn Sep 10, 2024
eb4604a
showing confirmation dialog at the center
amandal-cn Sep 10, 2024
7b3dcd9
fixed width of artifact name
amandal-cn Sep 10, 2024
6d10136
showing artifact info on hovering over artifact
amandal-cn Sep 10, 2024
db7ba96
fixed the styling of confirmation dialog
amandal-cn Sep 10, 2024
1942b3c
fixed status icon for artifacts
amandal-cn Sep 10, 2024
f42885c
fixed the menu for artifacts
amandal-cn Sep 10, 2024
c1184b2
added alert
amandal-cn Sep 10, 2024
d50b6b1
temporarily disabled tools
amandal-cn Sep 10, 2024
e5d1aff
fixed the styling for alert
amandal-cn Sep 10, 2024
588f580
fixed rerendering issues for artifacts with error
amandal-cn Sep 10, 2024
aed3a4d
fixed rendering issue
amandal-cn Sep 10, 2024
f1278de
fixed the duplicate issue
amandal-cn Sep 10, 2024
32e0585
added reset button
amandal-cn Sep 10, 2024
902a822
fixed reset button issues
amandal-cn Sep 11, 2024
f6b1b96
fixed refresh button
amandal-cn Sep 11, 2024
fd91503
combined update for project and selected artifact
amandal-cn Sep 11, 2024
0e2016a
capability to take attachments
amandal-cn Sep 11, 2024
262c66c
separated static configs
amandal-cn Sep 11, 2024
ec46897
fixed initial message rendering issue
amandal-cn Sep 11, 2024
2806a63
remove the local attachments on loading the artifacts
amandal-cn Sep 11, 2024
ef04b12
refactored processing of chat history
amandal-cn Sep 11, 2024
e8324a4
added capability to update artifacts
amandal-cn Sep 11, 2024
9951075
fixed required libraries issue
amandal-cn Sep 11, 2024
a2c8ac0
put restriction on the type of supported files
amandal-cn Sep 11, 2024
35c8207
Users with access list
aprusty-cn Sep 10, 2024
fdc6dd7
Access list edit access
aprusty-cn Sep 11, 2024
5a02022
added capability to compose
amandal-cn Sep 11, 2024
9c75aad
Merge branch 'feat/project' of github-amandal-cn:trilogy-group/llamac…
amandal-cn Sep 11, 2024
9183ae0
Add access level checks for workspace
trunapushpa Sep 11, 2024
e3e7ec9
Restrict access for shared projects
trunapushpa Sep 11, 2024
5154e64
Share based on current access level
trunapushpa Sep 11, 2024
5dc0cb3
Remove extra
aprusty-cn Sep 11, 2024
c83bdb7
Add share functionality to the dashboard
aprusty-cn Sep 11, 2024
40d657f
added auto fix button and status tracking for each artifact
amandal-cn Sep 11, 2024
f8a5090
Merge branch 'feat/project' of github-amandal-cn:trilogy-group/llamac…
amandal-cn Sep 11, 2024
06e165c
fixed sharing
amandal-cn Sep 11, 2024
ff14bf8
fixed the progress bar in the landing page
amandal-cn Sep 11, 2024
6bbaf8e
added loading message for dashboard
amandal-cn Sep 11, 2024
05220fc
Change createby
aprusty-cn Sep 11, 2024
459bc29
improved dashboard
amandal-cn Sep 11, 2024
1436034
Show contributors
aprusty-cn Sep 11, 2024
c3d73ba
fixed the styling for dashboard and projects
amandal-cn Sep 11, 2024
0f04957
Merge branch 'feat/project' of github-amandal-cn:trilogy-group/llamac…
amandal-cn Sep 11, 2024
e188a96
fixed build issues
amandal-cn Sep 11, 2024
dcef597
fixed issue with passing empty available components
amandal-cn Sep 11, 2024
d9abffd
fixed code gen issues
amandal-cn Sep 11, 2024
a3e7564
fixed api url issue
amandal-cn Sep 11, 2024
3ef42fd
added support for multiple models and warning for incomplete code
amandal-cn Sep 12, 2024
84222ad
added instruction for sticking to using fetch for api calls
amandal-cn Sep 12, 2024
b3bc98d
Feat/versioning (#17)
rishabh-ti Sep 12, 2024
fda1ebb
Remove explicit maxTokens in gpt4o
rishabh-ti Sep 13, 2024
268c079
Add Posthog (#19)
rishabh-ti Sep 13, 2024
9d8309f
fixed the header
amandal-cn Sep 13, 2024
f5160de
improved the project header
amandal-cn Sep 13, 2024
083fa21
added button to go to dashboard
amandal-cn Sep 13, 2024
a6c2a4d
enabled sharing functionality
amandal-cn Sep 13, 2024
0eda59f
enabled copy button
amandal-cn Sep 13, 2024
67c12f5
updated dependencies
amandal-cn Sep 13, 2024
3ae9441
fixed project header
amandal-cn Sep 13, 2024
c513b70
fixed infinite fga calls
amandal-cn Sep 14, 2024
bcf8165
customized view based on access level
amandal-cn Sep 14, 2024
a970f16
improved the look for edit access model
amandal-cn Sep 14, 2024
d790a66
improved the look for share modal
amandal-cn Sep 14, 2024
d304b90
added capability to publish
amandal-cn Sep 14, 2024
8857be5
fixed status icon for published artifacts
amandal-cn Sep 14, 2024
14630e1
remove temp dir
amandal-cn Sep 14, 2024
4d6bf37
added support for more text files and images
amandal-cn Sep 14, 2024
cb93140
added support for pdf-js
amandal-cn Sep 14, 2024
100388c
support for amr64
amandal-cn Sep 14, 2024
96d9180
added support for docx
amandal-cn Sep 14, 2024
879c6d9
Added api documentation (#21)
amandal-cn Sep 17, 2024
28a6dcd
Adding pipeline
aprusty-cn Sep 17, 2024
8d9307b
Update readme
aprusty-cn Sep 17, 2024
c2ac207
Use Authorization in swagger
rishabh-ti Sep 18, 2024
c7fd4a9
fixed issue where on empty projects it goes into loop
amandal-cn Sep 18, 2024
34ee9a9
Fix swagger page
aprusty-cn Sep 18, 2024
e79f1af
Cleanup
aprusty-cn Sep 18, 2024
9345c2d
Feat/migrate okta fga (#20)
rishabh-ti Sep 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions app/(main)/dashboard.tsx
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import EmptyProjectMessage from "@/components/EmptyProjectMessage";
import HeaderV2 from "@/components/HeaderV2";
import ProjectList from "@/components/ProjectList";
import ProjectOverviewInputForm from "@/components/ProjectOverviewInputForm";
import ProjectShareModal from "@/components/ProjectShareModal";
import { useAppContext } from "@/contexts/AppContext";
import { Project } from "@/types/Project";
import { useUser } from "@auth0/nextjs-auth0/client";
@@ -20,6 +21,8 @@ const Dashboard: React.FC = () => {
const { projects, dispatchProjectsUpdate, projectsLoading } = useAppContext();
const [showCreateForm, setShowCreateForm] = useState(false);
const [isCreatingProject, setIsCreatingProject] = useState(false);
const [showShareModal, setShowShareModal] = useState(false);
const [selectedProjectId, setSelectedProjectId] = useState<string | null>(null);
const router = useRouter();

const handleCreateProject = async (description: string) => {
@@ -53,6 +56,11 @@ const Dashboard: React.FC = () => {
router.push(`/workspaces/${projectId}`);
};

const handleShareClick = (projectId: string) => {
setSelectedProjectId(projectId);
setShowShareModal(true);
};

const handleProjectDeleted = async (deletedProjectId: string) => {
try {
await projectApi.deleteProject(deletedProjectId);
@@ -94,6 +102,7 @@ const Dashboard: React.FC = () => {
onCreateProject={() => setShowCreateForm(true)}
onOpenProject={handleOpenProject}
onProjectDeleted={handleProjectDeleted}
onShareClick={handleShareClick}
/>
</>
) : (
@@ -120,6 +129,15 @@ const Dashboard: React.FC = () => {
</div>
)}
<Toaster position="bottom-right" />
{showShareModal && selectedProjectId && (
<ProjectShareModal
isOpen={showShareModal}
onClose={() => setShowShareModal(false)}
projectId={selectedProjectId}
projectTitle={projects.find(p => p.id === selectedProjectId)?.title || ''}
userAccessLevel="owner" // Assuming the user is the owner on the dashboard
/>
)}
</div>
);
};
20 changes: 15 additions & 5 deletions app/(main)/workspace.tsx
Original file line number Diff line number Diff line change
@@ -28,6 +28,7 @@ import Alert from "@/components/Alert";
import { Attachment } from "@/types/Attachment";
import { defaultDependencies } from "@/utils/config";
import { SandpackError } from "@codesandbox/sandpack-client";
import { checkAccess } from "@/utils/access";

interface WorkspaceProps {
projectId: string;
@@ -54,6 +55,7 @@ const Workspace: React.FC<WorkspaceProps> = memo(({ projectId }) => {
type: "error" | "info" | "warning" | "success";
message: string;
} | null>(null);
const [accessLevel, setAccessLevel] = useState<string>("none");

const router = useRouter();

@@ -74,10 +76,13 @@ const Workspace: React.FC<WorkspaceProps> = memo(({ projectId }) => {
throw new Error("Project ID is undefined");
}
console.log("Fetching project with ID:", projectId);
const fetchedProject = await projectApi.getProject(projectId);
console.log("Fetched project:", fetchedProject);
const { project, accessLevel } = await projectApi.getProject(projectId);
console.log("Fetched project:", project);
const artifacts = await artifactApi.getArtifacts(projectId);
console.log("Fetched artifacts:", artifacts);

console.log("Access level:", accessLevel);
setAccessLevel(accessLevel);

// Update artifacts to ensure each message in the chat session has attachments set to an empty array
const updatedArtifacts = artifacts.map(artifact => ({
@@ -93,7 +98,7 @@ const Workspace: React.FC<WorkspaceProps> = memo(({ projectId }) => {

updateProjectAndArtifact(
(prevProject) => ({
...fetchedProject,
...project,
artifacts: updatedArtifacts,
}),
updatedArtifacts.length > 0 ? updatedArtifacts[0] : null
@@ -169,6 +174,7 @@ const Workspace: React.FC<WorkspaceProps> = memo(({ projectId }) => {
updatedArtifact
);
};
const isViewer = accessLevel === 'viewer';

const showAlert = (
type: "error" | "info" | "warning" | "success",
@@ -636,6 +642,7 @@ ${instructions}
onShareClick={handleShare}
onDeleteClick={handleDelete}
projectId={project.id}
isViewer={isViewer}
/>
{project.artifacts && project.artifacts.length > 0 ? (
<PanelGroup direction="horizontal" className="flex-1">
@@ -654,6 +661,7 @@ ${instructions}
onCreateArtifact={() => setShowCreateForm(true)}
onDeleteArtifact={handleDeleteArtifact}
onArtifactHover={handleArtifactHover}
isViewer={isViewer}
/>
</Panel>
{!isArtifactListCollapsed && (
@@ -690,7 +698,7 @@ ${instructions}
maxSize={100}
collapsible={true}
>
{selectedArtifact && (
{selectedArtifact && !isViewer && (
<UpdateArtifact
artifact={selectedArtifact}
isCollapsed={isUpdateArtifactCollapsed}
@@ -703,7 +711,8 @@ ${instructions}
</PanelGroup>
) : (
<EmptyArtifactsMessage
onCreateArtifact={() => setShowCreateForm(true)}
onCreateArtifact={() => !isViewer && setShowCreateForm(true)}
isViewer={isViewer}
/>
)}
</div>
@@ -748,6 +757,7 @@ ${instructions}
onClose={() => setShowShareModal(false)}
projectId={project.id}
projectTitle={project.title}
userAccessLevel={accessLevel}
/>
)}
{showDeleteArtifactConfirmation && artifactToDelete && (
23 changes: 7 additions & 16 deletions app/api/projects/[projectId]/artifacts/[artifactId]/route.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
import { NextResponse } from 'next/server';
import { ddbClient } from '@/utils/ddbClient';
import { checkAccess } from '@/utils/access';
import { Artifact } from '@/types/Artifact';
// @ts-ignore
import { getSession } from '@auth0/nextjs-auth0';
import fgaClient from "@/lib/oktaFGA";

const TABLE_NAME = process.env.DDB_TABLE_NAME || "ti-artifacts";

// Helper function to check user access
async function checkAccess(userId: string, projectId: string, requiredRelation: string) {
const response = await fgaClient.check({
user: `user:${userId}`,
relation: requiredRelation,
object: `project:${projectId}`,
});
return response.allowed;
}

// Read an artifact by ID
export async function GET(
request: Request,
@@ -31,8 +22,8 @@ export async function GET(
const { projectId, artifactId } = params;

// Check if user has access to view this project
const hasAccess = await checkAccess(session.user.sub, projectId, 'can_view');
if (!hasAccess) {
const { allowed, accessLevel } = await checkAccess(session.user.sub, session.user.email, projectId);
if (!allowed) {
return NextResponse.json({ error: 'Access denied' }, { status: 403 });
}

@@ -66,8 +57,8 @@ export async function PUT(
const now = new Date().toISOString();

// Check if user has access to modify artifacts in this project
const hasAccess = await checkAccess(session.user.sub, projectId, 'can_modify');
if (!hasAccess) {
const { allowed, accessLevel } = await checkAccess(session.user.sub, session.user.email, projectId);
if (!allowed || accessLevel !== 'owner' && accessLevel !== 'editor') {
return NextResponse.json({ error: 'Access denied' }, { status: 403 });
}

@@ -121,8 +112,8 @@ export async function DELETE(
const { projectId, artifactId } = params;

// Check if user has access to delete artifacts in this project
const hasAccess = await checkAccess(session.user.sub, projectId, 'can_modify');
if (!hasAccess) {
const { allowed, accessLevel } = await checkAccess(session.user.sub, session.user.email, projectId);
if (!allowed || accessLevel !== 'owner') {
return NextResponse.json({ error: 'Access denied' }, { status: 403 });
}

19 changes: 5 additions & 14 deletions app/api/projects/[projectId]/artifacts/route.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
import { NextResponse } from 'next/server';
import { ddbClient } from '@/utils/ddbClient';
import { checkAccess } from '@/utils/access';
import { Artifact } from '@/types/Artifact';
// @ts-ignore
import { getSession } from '@auth0/nextjs-auth0';
import fgaClient from "@/lib/oktaFGA";

const TABLE_NAME = process.env.DDB_TABLE_NAME || "ti-artifacts";

// Helper function to check user access
async function checkAccess(userId: string, projectId: string, requiredRelation: string) {
const response = await fgaClient.check({
user: `user:${userId}`,
relation: requiredRelation,
object: `project:${projectId}`,
});
return response.allowed;
}

// Get all artifacts for a project
export async function GET(
request: Request,
@@ -31,8 +22,8 @@ export async function GET(
const { projectId } = params;

// Check if user has access to view this project
const hasAccess = await checkAccess(session.user.sub, projectId, 'can_view');
if (!hasAccess) {
const { allowed, accessLevel } = await checkAccess(session.user.sub, session.user.email, projectId);
if (!allowed) {
return NextResponse.json({ error: 'Access denied' }, { status: 403 });
}

@@ -74,8 +65,8 @@ export async function POST(
};

// Check if user has access to create artifacts in this project
const hasAccess = await checkAccess(session.user.sub, projectId, 'can_modify');
if (!hasAccess) {
const { allowed, accessLevel } = await checkAccess(session.user.sub, session.user.email, projectId);
if (!allowed || accessLevel !== 'owner' && accessLevel !== 'editor') {
return NextResponse.json({ error: 'Access denied' }, { status: 403 });
}

25 changes: 8 additions & 17 deletions app/api/projects/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NextResponse } from 'next/server';
import { ddbClient } from '@/utils/ddbClient';
import { checkAccess } from '@/utils/access';
import { v4 as uuidv4 } from 'uuid';
import { Project } from '@/types/Project';
import { FileContext } from '@/types/FileContext';
@@ -9,16 +10,6 @@ import fgaClient from "@/lib/oktaFGA";

const TABLE_NAME = process.env.DDB_TABLE_NAME || "ti-artifacts";

// Helper function to check user access
async function checkAccess(userId: string, projectId: string, requiredRelation: string) {
const response = await fgaClient.check({
user: `user:${userId}`,
relation: requiredRelation,
object: `project:${projectId}`,
});
return response.allowed;
}

// Create a new project
export async function POST(request: Request) {
try {
@@ -82,8 +73,8 @@ export async function GET(request: Request) {

if (id) {
// Check if user has access to this project
const hasAccess = await checkAccess(session.user.sub, id, 'can_view');
if (!hasAccess) {
const { allowed, accessLevel } = await checkAccess(session.user.sub, session.user.email, id);
if (!allowed) {
return NextResponse.json({ error: 'Access denied' }, { status: 403 });
}

@@ -109,7 +100,7 @@ export async function GET(request: Request) {
publishedUrl: result.Item.publishedUrl,
};

return NextResponse.json(project);
return NextResponse.json({project, accessLevel});
} else {

// Fetch all projects the user has access to
@@ -183,8 +174,8 @@ export async function PUT(request: Request) {
}

// Check if user has access to modify this project
const hasAccess = await checkAccess(session.user.sub, id, 'can_modify');
if (!hasAccess) {
const { allowed, accessLevel } = await checkAccess(session.user.sub, session.user.email, id);
if (!allowed || accessLevel !== 'owner' && accessLevel !== 'editor') {
return NextResponse.json({ error: 'Access denied' }, { status: 403 });
}

@@ -250,8 +241,8 @@ export async function DELETE(request: Request) {
}

// Check if user has access to delete this project
const hasAccess = await checkAccess(session.user.sub, id, 'can_delete');
if (!hasAccess) {
const { allowed, accessLevel } = await checkAccess(session.user.sub, session.user.email, id);
if (!allowed || accessLevel !== 'owner') {
return NextResponse.json({ error: 'Access denied' }, { status: 403 });
}

109 changes: 59 additions & 50 deletions components/ArtifactLIst.tsx
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ interface ArtifactListProps {
onCreateArtifact: () => void;
onDeleteArtifact: (artifact: Artifact) => void;
onArtifactHover: (artifact: Artifact | null, event: React.MouseEvent) => void;
isViewer: boolean;
}

const ArtifactList: React.FC<ArtifactListProps> = ({
@@ -22,6 +23,7 @@ const ArtifactList: React.FC<ArtifactListProps> = ({
onCreateArtifact,
onDeleteArtifact,
onArtifactHover,
isViewer
}) => {
const [searchTerm, setSearchTerm] = useState("");
const [isSearchFocused, setIsSearchFocused] = useState(false);
@@ -95,29 +97,32 @@ const ArtifactList: React.FC<ArtifactListProps> = ({
onPreview={(artifact) => {/* Add preview logic */}}
onEdit={(artifact) => {/* Add edit logic */}}
onHover={onArtifactHover}
isViewer={isViewer}
/>
))}
</ul>
</div>
</div>
<button
className="mt-4 flex items-center justify-center space-x-2 rounded-full bg-blue-600 px-6 py-3 font-semibold text-white shadow-md transition duration-300 ease-in-out hover:bg-blue-700"
onClick={() => {onCreateArtifact()}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
{!isViewer && (
<button
className="mt-4 flex items-center justify-center space-x-2 rounded-full bg-blue-600 px-6 py-3 font-semibold text-white shadow-md transition duration-300 ease-in-out hover:bg-blue-700"
onClick={() => {onCreateArtifact()}}
>
<path
fillRule="evenodd"
d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z"
clipRule="evenodd"
/>
</svg>
<span>Create New Artifact</span>
</button>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z"
clipRule="evenodd"
/>
</svg>
<span>Create New Artifact</span>
</button>
)}
</div>
);
};
@@ -130,6 +135,7 @@ interface ArtifactItemProps {
onPreview: (artifact: Artifact) => void;
onEdit: (artifact: Artifact) => void;
onHover: (artifact: Artifact | null, event: React.MouseEvent) => void;
isViewer: boolean;
}

const ArtifactItem: React.FC<ArtifactItemProps> = ({
@@ -138,6 +144,7 @@ const ArtifactItem: React.FC<ArtifactItemProps> = ({
onClick,
onDelete,
onHover,
isViewer
}) => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
@@ -200,39 +207,41 @@ const ArtifactItem: React.FC<ArtifactItemProps> = ({
</div>
<span className="font-medium truncate max-w-[70%]">{artifact.name}</span>
</div>
<div className="relative" ref={menuRef}>
<button
className="p-1 rounded-full hover:bg-gray-200 transition-colors duration-200"
onClick={(e) => {
e.stopPropagation();
setIsMenuOpen(!isMenuOpen);
}}
>
<FiMoreHorizontal className="w-4 h-4 text-gray-600" />
</button>
{isMenuOpen && (
<div className="absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-10 overflow-hidden">
<button
className="w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100 transition duration-150 ease-in-out flex items-center"
onClick={(e) => {
e.stopPropagation();
onDelete(artifact);
setIsMenuOpen(false);
}}
>
<FiTrash2 className="mr-2 h-4 w-4 text-red-500" />
Delete
</button>
<button
className="w-full px-4 py-2 text-sm text-left text-gray-400 flex items-center cursor-not-allowed"
disabled
>
<FiAlertCircle className="mr-2 h-4 w-4 text-gray-400" />
Deprecate
</button>
</div>
)}
</div>
{!isViewer && (
<div className="relative" ref={menuRef}>
<button
className="p-1 rounded-full hover:bg-gray-200 transition-colors duration-200"
onClick={(e) => {
e.stopPropagation();
setIsMenuOpen(!isMenuOpen);
}}
>
<FiMoreHorizontal className="w-4 h-4 text-gray-600" />
</button>
{isMenuOpen && (
<div className="absolute right-0 mt-2 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 z-10 overflow-hidden">
<button
className="w-full px-4 py-2 text-sm text-left text-gray-700 hover:bg-gray-100 transition duration-150 ease-in-out flex items-center"
onClick={(e) => {
e.stopPropagation();
onDelete(artifact);
setIsMenuOpen(false);
}}
>
<FiTrash2 className="mr-2 h-4 w-4 text-red-500" />
Delete
</button>
<button
className="w-full px-4 py-2 text-sm text-left text-gray-400 flex items-center cursor-not-allowed"
disabled
>
<FiAlertCircle className="mr-2 h-4 w-4 text-gray-400" />
Deprecate
</button>
</div>
)}
</div>
)}
</li>
);
};
7 changes: 4 additions & 3 deletions components/EditAccessModal.tsx
Original file line number Diff line number Diff line change
@@ -8,9 +8,10 @@ interface EditAccessModalProps {
};
onClose: () => void;
onUpdateAccess: (email: string, newAccessLevel: string) => void;
userAccessLevel: string;
}

const EditAccessModal: React.FC<EditAccessModalProps> = ({ user, onClose, onUpdateAccess }) => {
const EditAccessModal: React.FC<EditAccessModalProps> = ({ user, onClose, onUpdateAccess, userAccessLevel }) => {
const [newAccessLevel, setNewAccessLevel] = useState(user.accessLevel);

const handleSubmit = (e: React.FormEvent) => {
@@ -36,8 +37,8 @@ const EditAccessModal: React.FC<EditAccessModalProps> = ({ user, onClose, onUpda
className="w-full rounded border p-2"
>
<option value="viewer">Viewer</option>
<option value="editor">Editor</option>
<option value="revoke">Revoke Access</option>
{userAccessLevel !== 'viewer' && <option value="editor">Editor</option>}
{userAccessLevel === 'owner' && <option value="revoke">Revoke Access</option>}
</select>
</div>
<div className="flex justify-end">
28 changes: 20 additions & 8 deletions components/EmptyArtifactsMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
import CreateArtifactButton from "./CreateArtifactButton";

const EmptyArtifactsMessage: React.FC<{ onCreateArtifact: () => void }> = ({ onCreateArtifact }) => {
interface EmptyArtifactsMessageProps {
onCreateArtifact: () => void;
isViewer: boolean;
}

const EmptyArtifactsMessage: React.FC<EmptyArtifactsMessageProps> = ({ onCreateArtifact, isViewer }) => {
return (
<div className="flex h-[calc(100vh-200px)] flex-col items-center justify-center">
<h3 className="mt-4 text-xl font-semibold text-gray-700">
No artifacts yet
</h3>
<p className="mb-4 mt-2 text-gray-500">
Create a new artifact to get started
<div className="flex flex-col items-center justify-center h-full">
<h2 className="text-2xl font-semibold mb-4">No artifacts yet</h2>
<p className="text-gray-600 mb-8 text-center">
{isViewer
? "This project doesn't have any artifacts yet."
: "Start by creating your first artifact for this project."}
</p>
<CreateArtifactButton onClick={onCreateArtifact} />
{!isViewer && (
<button
onClick={onCreateArtifact}
className="bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 transition-colors"
>
Create New Artifact
</button>
)}
</div>
);
};
24 changes: 14 additions & 10 deletions components/ProjectHeader.tsx
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ interface ProjectHeaderProps {
onShareClick: (projectId: string) => void;
onDeleteClick: () => void;
projectId: string;
isViewer: boolean;
}

const ProjectHeader: React.FC<ProjectHeaderProps> = ({
@@ -17,7 +18,8 @@ const ProjectHeader: React.FC<ProjectHeaderProps> = ({
onMyProjectsClick,
onShareClick,
onDeleteClick,
projectId
projectId,
isViewer
}) => {
const [showMenu, setShowMenu] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
@@ -67,15 +69,17 @@ const ProjectHeader: React.FC<ProjectHeaderProps> = ({
>
<FiShare2 className="mr-2" /> Share
</button>
<button
onClick={() => {
onDeleteClick();
setShowMenu(false);
}}
className="w-full text-left px-4 py-2 hover:bg-red-50 flex items-center text-red-600"
>
<FiTrash2 className="mr-2" /> Delete
</button>
{!isViewer && (
<button
onClick={() => {
onDeleteClick();
setShowMenu(false);
}}
className="w-full text-left px-4 py-2 hover:bg-red-50 flex items-center text-red-600"
>
<FiTrash2 className="mr-2" /> Delete
</button>
)}
</div>
)}
</div>
4 changes: 3 additions & 1 deletion components/ProjectList.tsx
Original file line number Diff line number Diff line change
@@ -7,16 +7,18 @@ interface ProjectListProps {
onCreateProject: () => void;
onOpenProject: (projectId: string) => void;
onProjectDeleted: (projectId: string) => void;
onShareClick: (projectId: string) => void;
}

const ProjectList: React.FC<ProjectListProps> = ({ projects, onCreateProject, onOpenProject, onProjectDeleted }) => {
const ProjectList: React.FC<ProjectListProps> = ({ projects, onCreateProject, onOpenProject, onProjectDeleted, onShareClick }) => {
return (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{projects.map((project) => (
<ProjectOverview
key={project.id}
project={project}
onProjectDeleted={onProjectDeleted}
onShareClick={onShareClick}
/>
))}
</div>
10 changes: 7 additions & 3 deletions components/ProjectOverview.tsx
Original file line number Diff line number Diff line change
@@ -11,9 +11,10 @@ import logo from './../public/logo.png';
interface ProjectOverviewProps {
project: Project;
onProjectDeleted: (projectId: string) => void;
onShareClick: (projectId: string) => void;
}

const ProjectOverview: React.FC<ProjectOverviewProps> = ({ project, onProjectDeleted }) => {
const ProjectOverview: React.FC<ProjectOverviewProps> = ({ project, onProjectDeleted, onShareClick }) => {
const router = useRouter();
const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
@@ -136,8 +137,11 @@ const ProjectOverview: React.FC<ProjectOverviewProps> = ({ project, onProjectDel

<div className="flex justify-between items-center mt-4">
<div className="flex space-x-2">
<Tooltip content="Share">
<button className="text-gray-500 hover:bg-gray-100 p-2 rounded-full transition duration-300 ease-in-out">
<Tooltip content="Share project">
<button
onClick={() => onShareClick(project.id)}
className="p-2 text-gray-600 hover:bg-gray-100 rounded-full transition duration-300 ease-in-out"
>
<FiShare2 className="w-5 h-5" />
</button>
</Tooltip>
6 changes: 4 additions & 2 deletions components/ProjectShareModal.tsx
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ interface ShareProjectModalProps {
onClose: () => void;
projectId: string;
projectTitle: string;
userAccessLevel: string;
}

interface ProjectUser {
@@ -17,7 +18,7 @@ interface ProjectUser {
accessLevel: string;
}

const ShareProjectModal: React.FC<ShareProjectModalProps> = ({ isOpen, onClose, projectId, projectTitle }) => {
const ShareProjectModal: React.FC<ShareProjectModalProps> = ({ isOpen, onClose, projectId, projectTitle, userAccessLevel }) => {
const [email, setEmail] = useState('');
const [accessLevel, setAccessLevel] = useState('viewer');
const [isSharing, setIsSharing] = useState(false);
@@ -182,7 +183,7 @@ const ShareProjectModal: React.FC<ShareProjectModalProps> = ({ isOpen, onClose,
className="mb-4 w-full rounded border p-2"
>
<option value="viewer">Can view</option>
<option value="editor">Can edit</option>
{userAccessLevel !== 'viewer' && <option value="editor">Can edit</option>}
</select>
<button
onClick={handleShare}
@@ -223,6 +224,7 @@ const ShareProjectModal: React.FC<ShareProjectModalProps> = ({ isOpen, onClose,
user={editingUser}
onClose={() => setEditingUser(null)}
onUpdateAccess={handleAccessUpdate}
userAccessLevel={userAccessLevel}
/>
)}
</div>
29 changes: 29 additions & 0 deletions utils/access.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import fgaClient from "@/lib/oktaFGA";

export async function checkAccess(userId: string, userEmail: string, projectId: string): Promise<{ allowed: boolean, accessLevel: string }> {
const relations = ['owner', 'editor', 'viewer'];
for (const relation of relations) {
const response = await fgaClient.check({
user: `user:${userId}`,
relation: relation,
object: `project:${projectId}`,
});
if (response.allowed) {
return { allowed: true, accessLevel: relation };
}
}

// Check if the project is shared with the user's email
for (const relation of ['editor', 'viewer']) {
const response = await fgaClient.check({
user: `user:${userEmail}`,
relation: relation,
object: `project:${projectId}`,
});
if (response.allowed) {
return { allowed: true, accessLevel: relation };
}
}

return { allowed: false, accessLevel: 'none' };
}
2 changes: 1 addition & 1 deletion utils/apiClients/Project.ts
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ export const projectApi = {
return response.json();
},

getProject: async (id: string): Promise<Project> => {
getProject: async (id: string): Promise<{ project: Project; accessLevel: string }> => {
const response = await fetch(`/api/projects?id=${id}`);
if (!response.ok) {
throw new Error('Failed to fetch project');