Skip to content

Commit 9994541

Browse files
authored
Merge pull request #132 from liblaber/release
Release
2 parents 04b9933 + 32fba03 commit 9994541

File tree

306 files changed

+32770
-6978
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

306 files changed

+32770
-6978
lines changed

.github/workflows/e2e-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
e2e-tests:
1616
name: 🧪 Run E2E Tests
1717
runs-on: ubuntu-latest
18-
timeout-minutes: 10
18+
timeout-minutes: 25
1919

2020
steps:
2121
- name: 📥 Checkout code

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,5 @@ snapshots
6161
tsconfig.tsbuildinfo
6262
.instanceid
6363
tunnel.config
64+
65+
next-env.d.ts

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ RUN npm install -g pnpm && pnpm install
2020
# Last supported deno version for netlify is 2.2.4
2121
RUN npm install -g deno@2.2.4
2222

23+
RUN npm install -g sst
24+
2325
RUN echo "LC_ALL=en_US.UTF-8" >> /etc/environment
2426
RUN echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
2527
RUN echo "LANG=en_US.UTF-8" > /etc/locale.conf

README.md

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,62 @@ Run the following command to set up and start the app:
177177
pnpm run quickstart
178178
```
179179

180-
**That's it! 🎉** The app will be available at [http://localhost:3000](http://localhost:3000/)
180+
**That's it! 🎉** The app will be available at [http://localhost:3000](http://localhost:3000/)
181+
182+
### **Quickstart Behavior**
183+
184+
The `pnpm run quickstart` command now **always pulls the latest code and Docker images** to ensure you're running the most up-to-date version. Here's what happens:
185+
186+
-**Always rebuilds** Docker images with latest code
187+
-**Preserves your database** by default (keeps existing data)
188+
-**Interactive prompts** if you have existing data
189+
-**Migration support** for database schema changes
190+
191+
**Additional quickstart options:**
192+
193+
```bash
194+
# Standard quickstart (preserves database)
195+
pnpm run quickstart
196+
197+
# Fresh start (removes all existing data)
198+
pnpm run quickstart:fresh
199+
200+
# Explicitly preserve database
201+
pnpm run quickstart:preserve
202+
```
203+
204+
**Database Migration**
205+
206+
If you encounter database issues after updating, use the migration tool:
207+
208+
```bash
209+
pnpm run docker:migrate
210+
```
211+
212+
This provides options to:
213+
214+
- Auto-migrate database schema
215+
- Create backups before migrating
216+
- Reset database (⚠️ **loses all data**)
217+
218+
### How Quickstart Handles Your Data
219+
220+
**Your data is PRESERVED when:**
221+
222+
- You run `pnpm run quickstart:preserve`
223+
- You run the standard `pnpm run quickstart` and choose to preserve data when prompted (this is the default)
224+
225+
**Your data is REMOVED (fresh start) when:**
226+
227+
- You run `pnpm run quickstart:fresh`
228+
- You run the standard `pnpm run quickstart` and choose to reset the database when prompted
229+
- No existing data is found (e.g., on first-time setup)
230+
231+
**Important Notes:**
232+
233+
- 🔄 **Code is always updated** - Docker images are rebuilt with latest code
234+
- 💾 **Database behavior is configurable** - You control whether to keep or reset data
235+
- ⚠️ **Schema changes may require migration** - Use `pnpm run docker:migrate` if needed
181236

182237
### **Option 2: Manual Installation**
183238

@@ -347,8 +402,11 @@ pnpm run dev
347402
- [**Contributing Guidelines**](https://github.com/liblaber/ai/blob/main/CONTRIBUTING.md) - How to contribute to the project
348403
- [Security & Privacy](docs/security-and-privacy.md)
349404
- [Configuration](docs/configuration.md)
405+
- [Deploy on EC2 with HTTPS & Auto-Restart](docs/ec2.md)
350406
- [Getting Started](docs/getting-started.md)
351407
- [Features](docs/features.md)
408+
- [Environments](docs/environments.md)
409+
- [Team Roles and Permissions](docs/team-roles-and-permissions.md)
352410
- [Tips](docs/tips.md)
353411
- [Governance](docs/governance.md)
354412
- [License](LICENSE)

app/access-denied/page.tsx

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
'use client';
2+
3+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '~/components/ui/Card';
4+
import { Button } from '~/components/ui/Button';
5+
import { Logo } from '~/components/Logo';
6+
import { signOut } from '~/auth/auth-client';
7+
import { useRouter } from 'next/navigation';
8+
import { useState } from 'react';
9+
10+
export default function AccessDeniedPage() {
11+
const router = useRouter();
12+
const [isRefreshing, setIsRefreshing] = useState(false);
13+
14+
const handleSignOut = async () => {
15+
await signOut();
16+
};
17+
18+
const handleRefresh = async () => {
19+
setIsRefreshing(true);
20+
21+
try {
22+
// Check permissions again
23+
const response = await fetch('/api/me/permissions/check', {
24+
method: 'GET',
25+
headers: {
26+
'Content-Type': 'application/json',
27+
},
28+
});
29+
30+
if (response.ok) {
31+
const data = await response.json();
32+
33+
if (data && typeof data === 'object' && 'hasPermissions' in data) {
34+
const hasPermissions = data.hasPermissions as boolean;
35+
36+
if (hasPermissions) {
37+
// User now has permissions, redirect to main app
38+
router.push('/');
39+
return;
40+
}
41+
}
42+
}
43+
44+
// If still no permissions, refresh the page to show updated state
45+
router.refresh();
46+
} catch (error) {
47+
console.error('Error checking permissions:', error);
48+
// On error, just refresh the page
49+
router.refresh();
50+
} finally {
51+
setIsRefreshing(false);
52+
}
53+
};
54+
55+
return (
56+
<div className="min-h-screen bg-depth-1 flex items-center justify-center p-4">
57+
<div className="w-full max-w-md">
58+
{/* Logo */}
59+
<div className="flex justify-center mb-8">
60+
<Logo />
61+
</div>
62+
63+
{/* Access Denied Card */}
64+
<Card className="w-full">
65+
<CardHeader className="text-center">
66+
<div className="mx-auto mb-4 w-16 h-16 bg-red-500/10 rounded-full flex items-center justify-center">
67+
<svg
68+
className="w-8 h-8 text-red-500"
69+
fill="none"
70+
stroke="currentColor"
71+
viewBox="0 0 24 24"
72+
xmlns="http://www.w3.org/2000/svg"
73+
>
74+
<path
75+
strokeLinecap="round"
76+
strokeLinejoin="round"
77+
strokeWidth={2}
78+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
79+
/>
80+
</svg>
81+
</div>
82+
<CardTitle className="text-xl text-red-500">Access Denied</CardTitle>
83+
<CardDescription className="text-secondary">
84+
You don't have permission to access this application
85+
</CardDescription>
86+
</CardHeader>
87+
88+
<CardContent className="space-y-4">
89+
<div className="text-center text-sm text-secondary space-y-2">
90+
<p>Your account has been created, but you don't have any permissions assigned yet.</p>
91+
<p>Please contact your administrator to get the appropriate access levels.</p>
92+
</div>
93+
94+
<div className="flex flex-col space-y-3">
95+
<Button onClick={handleRefresh} variant="primary" className="w-full" disabled={isRefreshing}>
96+
{isRefreshing ? (
97+
<>
98+
<svg
99+
className="animate-spin -ml-1 mr-2 h-4 w-4 text-white"
100+
xmlns="http://www.w3.org/2000/svg"
101+
fill="none"
102+
viewBox="0 0 24 24"
103+
>
104+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
105+
<path
106+
className="opacity-75"
107+
fill="currentColor"
108+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
109+
/>
110+
</svg>
111+
Checking Permissions...
112+
</>
113+
) : (
114+
<>
115+
<svg
116+
className="w-4 h-4 mr-2"
117+
fill="none"
118+
stroke="currentColor"
119+
viewBox="0 0 24 24"
120+
xmlns="http://www.w3.org/2000/svg"
121+
>
122+
<path
123+
strokeLinecap="round"
124+
strokeLinejoin="round"
125+
strokeWidth={2}
126+
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
127+
/>
128+
</svg>
129+
Refresh & Check Permissions
130+
</>
131+
)}
132+
</Button>
133+
134+
<Button onClick={handleSignOut} variant="outline" className="w-full">
135+
Sign Out
136+
</Button>
137+
</div>
138+
</CardContent>
139+
</Card>
140+
141+
{/* Help Text */}
142+
<div className="mt-6 text-center text-xs text-secondary">
143+
<p>
144+
Need help? Contact your system administrator or check the{' '}
145+
<a
146+
href="https://docs.liblab.ai"
147+
target="_blank"
148+
rel="noopener noreferrer"
149+
className="text-accent-500 hover:text-accent-400 underline"
150+
>
151+
documentation
152+
</a>
153+
.
154+
</p>
155+
</div>
156+
</div>
157+
</div>
158+
);
159+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { getResourceConfig, getEligibleMembersForResource } from '~/lib/utils/resource-utils';
3+
import { requireUserAbility } from '~/auth/session';
4+
import { PermissionAction, Prisma } from '@prisma/client';
5+
import { subject } from '@casl/ability';
6+
7+
export async function GET(
8+
request: NextRequest,
9+
{ params }: { params: Promise<{ resource: string; resourceId: string }> },
10+
) {
11+
try {
12+
const { userAbility } = await requireUserAbility(request);
13+
const { resource, resourceId } = await params;
14+
const resourceConfig = getResourceConfig(resource);
15+
16+
const { fetchResource, permissionResource, roleScope } = resourceConfig;
17+
18+
const resourceData = await fetchResource(resourceId);
19+
20+
if (userAbility.cannot(PermissionAction.read, subject(permissionResource, resourceData))) {
21+
return NextResponse.json({ success: false, error: 'Forbidden' }, { status: 403 });
22+
}
23+
24+
const eligibleMembers = await getEligibleMembersForResource(resourceId, roleScope, permissionResource);
25+
26+
return NextResponse.json({ success: true, eligibleMembers });
27+
} catch (error) {
28+
if (error instanceof Error && error.message.startsWith('Invalid resource type')) {
29+
return NextResponse.json({ success: false, error: 'Invalid route' }, { status: 400 });
30+
}
31+
32+
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2025') {
33+
return NextResponse.json(
34+
{ success: false, error: `${error.meta?.modelName || 'Resource'} not found` },
35+
{ status: 404 },
36+
);
37+
}
38+
39+
return NextResponse.json({ success: false, error: 'Failed to retrieve eligible members' }, { status: 500 });
40+
}
41+
}

0 commit comments

Comments
 (0)