-
Notifications
You must be signed in to change notification settings - Fork 4
Dev #24
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
Dev #24
Changes from 5 commits
8f2225c
29c50ab
04f60ad
73b5fea
0abf10e
60f097b
79bd94c
8c86ebd
2ed04b9
c73399f
87cf150
457ab3a
c5fcedd
868e454
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,6 +67,7 @@ model Group { | |
| shareToken String? @unique | ||
| shareEnabled Boolean @default(false) | ||
| createdAt DateTime @default(now()) | ||
| deletedAt DateTime? | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "1) Group queries:"
rg -n --type=ts -C2 'prisma\.group\.(findUnique|findFirst|findMany)\(' apps/backend/src
echo
echo "2) Soft-delete filters present:"
rg -n --type=ts -C2 'deletedAt\s*:\s*null' apps/backend/src/groups apps/backend/src/balancesRepository: Arun-kushwaha007/FairShare Length of output: 3109 🏁 Script executed: #!/bin/bash
echo "=== Checking groups.service.ts around line 114 ==="
sed -n '110,130p' apps/backend/src/groups/groups.service.ts
echo
echo "=== Checking groups.service.ts around line 380 ==="
sed -n '375,390p' apps/backend/src/groups/groups.service.ts
echo
echo "=== Checking groups.service.ts around line 418 ==="
sed -n '415,435p' apps/backend/src/groups/groups.service.ts
echo
echo "=== Checking balances.service.ts around line 50 ==="
sed -n '45,65p' apps/backend/src/balances/balances.service.tsRepository: Arun-kushwaha007/FairShare Length of output: 2835 Enforce soft-delete filters on all user-facing group read operations. Current group queries bypass soft-delete checks in several critical paths:
All group reads must include 🧰 Tools🪛 GitHub Actions: CI[error] Command failed with exit code 1: tsc --noEmit (backend@1.0.0 lint). 🤖 Prompt for AI Agents |
||
| creator User @relation("group_creator", fields: [createdBy], references: [id]) | ||
| members GroupMember[] | ||
| expenses Expense[] | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -83,6 +83,7 @@ export class GroupsService { | |||||||||||||||||||||||||||
| userId, | ||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| deletedAt: null, | ||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| orderBy: { createdAt: 'desc' }, | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
|
@@ -235,7 +236,10 @@ export class GroupsService { | |||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| async getUserSummary(userId: string): Promise<{ totalBalanceCents: string }> { | ||||||||||||||||||||||||||||
| const balances = await this.prisma.balance.findMany({ | ||||||||||||||||||||||||||||
| where: { userId }, | ||||||||||||||||||||||||||||
| where: { | ||||||||||||||||||||||||||||
| userId, | ||||||||||||||||||||||||||||
| group: { deletedAt: null } | ||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| select: { amountCents: true }, | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
@@ -254,6 +258,7 @@ export class GroupsService { | |||||||||||||||||||||||||||
| userId, | ||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| deletedAt: null, | ||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| include: { | ||||||||||||||||||||||||||||
| _count: { | ||||||||||||||||||||||||||||
|
|
@@ -670,6 +675,35 @@ export class GroupsService { | |||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| async delete(groupId: string, actorUserId: string): Promise<{ success: true }> { | ||||||||||||||||||||||||||||
| const membership = await this.prisma.groupMember.findUnique({ | ||||||||||||||||||||||||||||
| where: { | ||||||||||||||||||||||||||||
| groupId_userId: { | ||||||||||||||||||||||||||||
| groupId, | ||||||||||||||||||||||||||||
| userId: actorUserId, | ||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if (!membership || membership.role !== 'OWNER') { | ||||||||||||||||||||||||||||
| throw new ForbiddenException('Only the group owner can delete the group'); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| await this.prisma.group.update({ | ||||||||||||||||||||||||||||
| where: { id: groupId }, | ||||||||||||||||||||||||||||
| data: { | ||||||||||||||||||||||||||||
| deletedAt: new Date(), | ||||||||||||||||||||||||||||
| shareEnabled: false, | ||||||||||||||||||||||||||||
| shareToken: null | ||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
Comment on lines
+678
to
+712
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Soft-deleted groups are still accessible through existing member endpoints. This only hides deleted groups from list/dashboard-style queries. Because Suggested direction- private async assertMembership(groupId: string, userId: string): Promise<void> {
- const membership = await this.prisma.groupMember.findUnique({
- where: {
- groupId_userId: {
- groupId,
- userId,
- },
- },
- });
+ private async assertMembership(groupId: string, userId: string): Promise<void> {
+ const membership = await this.prisma.groupMember.findFirst({
+ where: {
+ groupId,
+ userId,
+ group: {
+ deletedAt: null,
+ },
+ },
+ });
if (!membership) {
throw new ForbiddenException('Actor is not a group member');
}
}The same rule needs to be applied to direct group reads/updates in this file, otherwise deleted groups remain operational through stale clients. 🧰 Tools🪛 Biome (2.4.10)[error] 677-677: Expected a semicolon or an implicit semicolon after a statement, but found none (parse) [error] 677-677: expected (parse) [error] 677-677: the target for a delete operator cannot be a single identifier (parse) [error] 677-677: Expected a semicolon or an implicit semicolon after a statement, but found none (parse) [error] 677-677: Expected a statement but instead found '>'. (parse) 🪛 GitHub Actions: CI[error] Command failed with exit code 1: tsc --noEmit (backend@1.0.0 lint). 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| await this.redis.invalidateGroupCache(groupId); | ||||||||||||||||||||||||||||
| await this.redis.invalidateUserDashboardCache(actorUserId); | ||||||||||||||||||||||||||||
|
Comment on lines
+714
to
+715
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Other group members' dashboard caches are not invalidated. Only the actor's dashboard cache is invalidated. Other group members will continue seeing the deleted group in their cached dashboards until the cache TTL (120s) expires. Consider invalidating dashboard caches for all group members. 🛠️ Suggested approach+ // Fetch all member IDs before invalidation
+ const members = await this.prisma.groupMember.findMany({
+ where: { groupId },
+ select: { userId: true },
+ });
await this.redis.invalidateGroupCache(groupId);
- await this.redis.invalidateUserDashboardCache(actorUserId);
+ // Invalidate dashboard cache for all members
+ await Promise.all(
+ members.map((m) => this.redis.invalidateUserDashboardCache(m.userId))
+ );
return { success: true };📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return { success: true }; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| async resolvePendingInvites(userId: string, email: string): Promise<void> { | ||||||||||||||||||||||||||||
| const invites = await this.prisma.groupInvite.findMany({ | ||||||||||||||||||||||||||||
| where: { email: email.toLowerCase() }, | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -59,13 +59,16 @@ export default async function DashboardPage() { | |
| const totalBalanceCents = dashboard.totalBalanceCents; | ||
| const isPositive = Number(totalBalanceCents) >= 0; | ||
|
|
||
| const primaryCurrency = groups[0]?.currency ?? 'USD'; | ||
| const totalBalanceLabel = groups.length > 1 ? 'Multi-Crew Balance' : 'Total Balance'; | ||
|
|
||
| return ( | ||
| <DashboardLayout> | ||
| <div className="space-y-8"> | ||
| <div className="grid grid-cols-2 gap-3 sm:gap-4 md:grid-cols-4"> | ||
| <SummaryCard | ||
| title="Total Balance" | ||
| value={formatMoney(totalBalanceCents, 'USD')} | ||
| title={totalBalanceLabel} | ||
| value={formatMoney(totalBalanceCents, primaryCurrency)} | ||
|
Comment on lines
+62
to
+71
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't render a mixed-currency sum as one currency.
Suggested guard const primaryCurrency = groups[0]?.currency ?? 'USD';
+ const hasSingleCurrency = new Set(groups.map((group) => group.currency)).size <= 1;
const totalBalanceLabel = groups.length > 1 ? 'Multi-Crew Balance' : 'Total Balance';
@@
<SummaryCard
title={totalBalanceLabel}
- value={formatMoney(totalBalanceCents, primaryCurrency)}
+ value={hasSingleCurrency ? formatMoney(totalBalanceCents, primaryCurrency) : '—'}
icon="dollar"
change="Live"
trend={isPositive ? 'up' : 'down'}
- hint={isPositive ? 'Surplus Protocol' : 'Deficit Detected'}
+ hint={
+ hasSingleCurrency
+ ? isPositive
+ ? 'Surplus Protocol'
+ : 'Deficit Detected'
+ : 'Multiple currencies'
+ }
/>Longer term, this probably wants a per-currency breakdown from the backend instead of a single aggregate. 🤖 Prompt for AI Agents |
||
| icon="dollar" | ||
| change="Live" | ||
| trend={isPositive ? 'up' : 'down'} | ||
|
|
@@ -98,13 +101,13 @@ export default async function DashboardPage() { | |
| </div> | ||
|
|
||
| {attentionItems.length > 0 ? ( | ||
| <GlassCard className="p-5 sm:p-6 border-white/5 bg-white/[0.01]"> | ||
| <GlassCard className="p-5 sm:p-6 border-[var(--fs-border)] bg-[var(--fs-surface)] shadow-[var(--fs-shadow-soft)]"> | ||
| <div className="flex items-start justify-between gap-4 mb-5"> | ||
| <div> | ||
| <p className="text-[10px] font-bold tracking-widest text-zinc-500 uppercase"> | ||
| <p className="text-[10px] font-bold tracking-widest text-[var(--fs-text-secondary)] uppercase"> | ||
| Needs Attention | ||
| </p> | ||
| <h2 className="mt-1 text-xl font-black italic tracking-tight text-white uppercase"> | ||
| <h2 className="mt-1 text-xl font-black italic tracking-tight text-[var(--fs-text-primary)] uppercase"> | ||
| Resolve the next blockers | ||
| </h2> | ||
| </div> | ||
|
|
@@ -116,20 +119,20 @@ export default async function DashboardPage() { | |
| {attentionItems.map((item) => ( | ||
| <div | ||
| key={item.groupId} | ||
| className="rounded-2xl border border-white/5 bg-white/5 p-4" | ||
| className="rounded-2xl border border-[var(--fs-border)] bg-[var(--fs-primary)]/5 p-4" | ||
| > | ||
| <div className="flex items-start justify-between gap-3"> | ||
| <div> | ||
| <p className="text-sm font-black tracking-tight text-white uppercase"> | ||
| <p className="text-sm font-black tracking-tight text-[var(--fs-text-primary)] uppercase"> | ||
| {item.groupName} | ||
| </p> | ||
| <p className="mt-1 text-[11px] font-bold uppercase tracking-[0.16em] text-zinc-500"> | ||
| <p className="mt-1 text-[11px] font-bold uppercase tracking-[0.16em] text-[var(--fs-text-secondary)]"> | ||
| {item.currency} · {item.memberCount} members | ||
| </p> | ||
| </div> | ||
| <Link | ||
| href={`/dashboard/groups/${item.groupId}`} | ||
| className="rounded-xl border border-white/10 bg-white/5 px-3 py-2 text-[10px] font-black uppercase tracking-[0.14em] text-white hover:bg-white/10" | ||
| className="rounded-xl border border-[var(--fs-border)] bg-[var(--fs-primary)]/10 px-3 py-2 text-[10px] font-black uppercase tracking-[0.14em] text-[var(--fs-text-primary)] hover:bg-[var(--fs-primary)]/20" | ||
| > | ||
| Open | ||
| </Link> | ||
|
|
@@ -153,7 +156,7 @@ export default async function DashboardPage() { | |
| {item.dueRecurringCount} due recurring | ||
| </Link> | ||
| ) : null} | ||
| <span className="inline-flex items-center gap-2 rounded-full bg-white/5 px-3 py-1.5 text-[11px] font-bold uppercase tracking-[0.12em] text-zinc-300"> | ||
| <span className="inline-flex items-center gap-2 rounded-full bg-[var(--fs-primary)]/5 px-3 py-1.5 text-[11px] font-bold uppercase tracking-[0.12em] text-[var(--fs-text-secondary)]"> | ||
| {formatMoney(item.netBalanceCents, item.currency)} | ||
| </span> | ||
| </div> | ||
|
|
@@ -175,13 +178,13 @@ export default async function DashboardPage() { | |
|
|
||
| <div className="grid grid-cols-1 gap-6 lg:grid-cols-3"> | ||
| <div className="lg:col-span-2"> | ||
| <GlassCard className="p-5 sm:p-8 border-white/5 bg-white/[0.01]"> | ||
| <GlassCard className="p-5 sm:p-8 border-[var(--fs-border)] bg-[var(--fs-surface)] shadow-[var(--fs-shadow-soft)]"> | ||
| <div className="flex items-center justify-between mb-8"> | ||
| <div> | ||
| <h2 className="text-sm font-black italic tracking-tight text-white uppercase"> | ||
| <h2 className="text-sm font-black italic tracking-tight text-[var(--fs-text-primary)] uppercase"> | ||
| Crews & Nodes | ||
| </h2> | ||
| <p className="text-[10px] font-bold tracking-widest text-zinc-600 uppercase mt-0.5"> | ||
| <p className="text-[10px] font-bold tracking-widest text-[var(--fs-text-secondary)] uppercase mt-0.5"> | ||
| Distributed spending groups | ||
| </p> | ||
| </div> | ||
|
|
@@ -198,18 +201,18 @@ export default async function DashboardPage() { | |
| <Link | ||
| href={`/dashboard/groups/${group.id}`} | ||
| key={group.id} | ||
| className="flex items-center justify-between p-4 rounded-xl border border-white/5 bg-white/5 hover:bg-white/10 transition-all cursor-pointer group" | ||
| className="flex items-center justify-between p-4 rounded-xl border border-[var(--fs-border)] bg-[var(--fs-primary)]/5 hover:bg-[var(--fs-primary)]/10 transition-all cursor-pointer group" | ||
| > | ||
| <div className="flex items-center gap-3"> | ||
| <div className="h-8 w-8 rounded-lg bg-zinc-900 flex items-center justify-center text-zinc-500 group-hover:text-purple-400 transition-colors"> | ||
| <div className="h-8 w-8 rounded-lg bg-[var(--fs-background)] flex items-center justify-center text-[var(--fs-text-muted)] group-hover:text-[var(--fs-primary)] transition-colors"> | ||
| <CreditCard size={14} /> | ||
| </div> | ||
| <span className="text-xs font-black tracking-tight text-white uppercase"> | ||
| <span className="text-xs font-black tracking-tight text-[var(--fs-text-primary)] uppercase"> | ||
| {group.name} | ||
| </span> | ||
| </div> | ||
| <div className="flex items-center gap-2"> | ||
| <span className="text-[10px] font-bold text-zinc-500 uppercase tracking-widest"> | ||
| <span className="text-[10px] font-bold text-[var(--fs-text-secondary)] uppercase tracking-widest"> | ||
| {group.currency} | ||
| </span> | ||
| <ArrowUpRight | ||
|
|
@@ -220,8 +223,8 @@ export default async function DashboardPage() { | |
| </Link> | ||
| ))} | ||
| {groups.length === 0 && ( | ||
| <div className="col-span-full py-12 text-center rounded-2xl border border-dashed border-white/5"> | ||
| <p className="text-[10px] font-black uppercase tracking-[0.2em] text-zinc-700"> | ||
| <div className="col-span-full py-12 text-center rounded-2xl border border-dashed border-[var(--fs-border)]"> | ||
| <p className="text-[10px] font-black uppercase tracking-[0.2em] text-[var(--fs-text-muted)]"> | ||
| NO_ACTIVE_GROUPS_LOCATED | ||
| </p> | ||
| </div> | ||
|
|
@@ -231,17 +234,17 @@ export default async function DashboardPage() { | |
| </div> | ||
|
|
||
| <div className="flex flex-col gap-6"> | ||
| <div className="relative overflow-hidden rounded-2xl sm:rounded-[2.5rem] border border-white/5 bg-gradient-to-br from-purple-600/20 to-indigo-600/20 p-5 sm:p-8 flex flex-col justify-between gap-4 sm:aspect-square"> | ||
| <div className="relative overflow-hidden rounded-2xl sm:rounded-[2.5rem] border border-[var(--fs-border)] bg-gradient-to-br from-[var(--fs-primary)]/20 to-[var(--fs-primary)]/10 p-5 sm:p-8 flex flex-col justify-between gap-4 sm:aspect-square"> | ||
| <div className="absolute top-0 right-0 p-8"> | ||
| <TrendingUp size={48} className="text-purple-500/20" /> | ||
| </div> | ||
| <h3 className="text-2xl sm:text-3xl font-black italic tracking-tighter text-white leading-none"> | ||
| <h3 className="text-2xl sm:text-3xl font-black italic tracking-tighter text-[var(--fs-text-primary)] leading-none"> | ||
| OPTIMIZE <br /> FLOW. | ||
| </h3> | ||
| <p className="text-xs font-bold text-purple-200/50 uppercase tracking-widest leading-relaxed"> | ||
| <p className="text-xs font-bold text-[var(--fs-primary)]/60 uppercase tracking-widest leading-relaxed"> | ||
| Review the groups with pending settlements and due recurring bills first. | ||
| </p> | ||
| <button className="w-full h-12 rounded-2xl bg-white text-[10px] font-black uppercase tracking-widest text-[#030303] hover:bg-purple-400 transition-all"> | ||
| <button className="w-full h-12 rounded-2xl bg-[var(--fs-primary)] text-[10px] font-black uppercase tracking-widest text-white hover:bg-[var(--fs-primary)]/80 transition-all"> | ||
| Review attention queue | ||
| </button> | ||
| </div> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,7 @@ | |
| --fs-section-inline-wide: clamp(1.25rem, 3vw, 3rem); | ||
| --fs-card-surface: color-mix(in srgb, var(--fs-surface) 90%, transparent); | ||
| --fs-card-surface-strong: color-mix(in srgb, var(--fs-surface) 96%, transparent); | ||
| --fs-card-border-strong: color-mix(in srgb, var(--fs-border) 78%, white 22%); | ||
| --fs-card-border-strong: color-mix(in srgb, var(--fs-border) 78%, var(--fs-surface) 22%); | ||
| } | ||
|
|
||
| html { | ||
|
|
@@ -43,7 +43,7 @@ | |
|
|
||
| .marketing-card:hover { | ||
| box-shadow: var(--fs-shadow-elevated); | ||
| @apply -translate-y-1 border-white/15; | ||
| @apply -translate-y-1 border-[var(--fs-border)] shadow-xl; | ||
| } | ||
|
|
||
| .glass-panel { | ||
|
|
@@ -102,12 +102,12 @@ | |
| @apply inline-flex items-center gap-2 rounded-full px-4 py-2 text-[0.65rem] font-bold uppercase tracking-[0.25em]; | ||
| border: 1px solid color-mix(in srgb, var(--fs-primary) 28%, var(--fs-border) 72%); | ||
| background-color: color-mix(in srgb, var(--fs-primary) 10%, var(--fs-surface) 90%); | ||
| color: color-mix(in srgb, white 84%, var(--fs-primary) 16%); | ||
| color: var(--fs-primary); | ||
| } | ||
|
|
||
| .neo-border { | ||
| border: 4px solid white; | ||
| box-shadow: 4px 4px 0px 0px rgba(255, 255, 255, 1); | ||
| border: 4px solid var(--fs-text-primary); | ||
| box-shadow: 4px 4px 0px 0px var(--fs-text-primary); | ||
| } | ||
|
|
||
| .neo-pop-hover { | ||
|
|
@@ -144,9 +144,10 @@ | |
|
|
||
| .grid-bg { | ||
| background-image: | ||
| linear-gradient(to right, rgba(255, 255, 255, 0.05) 1px, transparent 1px), | ||
| linear-gradient(to bottom, rgba(255, 255, 255, 0.05) 1px, transparent 1px); | ||
| linear-gradient(to right, var(--fs-border) 1px, transparent 1px), | ||
| linear-gradient(to bottom, var(--fs-border) 1px, transparent 1px); | ||
|
Comment on lines
+147
to
+148
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Line 150 applies element-level opacity, so any text/content inside a 🔧 Proposed fix .grid-bg {
background-image:
- linear-gradient(to right, var(--fs-border) 1px, transparent 1px),
- linear-gradient(to bottom, var(--fs-border) 1px, transparent 1px);
+ linear-gradient(to right, color-mix(in srgb, var(--fs-border) 15%, transparent) 1px, transparent 1px),
+ linear-gradient(to bottom, color-mix(in srgb, var(--fs-border) 15%, transparent) 1px, transparent 1px);
background-size: 40px 40px;
- opacity: 0.15;
}Also applies to: 150-150 🤖 Prompt for AI Agents |
||
| background-size: 40px 40px; | ||
| opacity: 0.15; | ||
| } | ||
|
|
||
| .glitch-text { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add health check before running migrations.
There's no wait between starting Docker Compose services and running migrations. The database may not be ready to accept connections immediately after
docker compose up -dreturns, which could cause intermittent CI failures.🛠️ Proposed fix: Add a wait-for-healthy step
- name: Start Database Services run: docker compose up -d + - name: Wait for Database + run: | + timeout 60 bash -c 'until docker compose exec -T postgres pg_isready; do sleep 1; done' + - name: Migrate DatabaseAlternatively, if your
docker-compose.ymlhas a healthcheck defined, you can use:🧰 Tools
🪛 Checkov (3.2.519)
[medium] 67-68: Basic Auth Credentials
(CKV_SECRET_4)
🤖 Prompt for AI Agents