mirror of
https://github.com/System-End/scraps.git
synced 2026-04-19 22:05:09 +00:00
add fraud check and airlock
This commit is contained in:
parent
c46fd4d92e
commit
e737cf7648
4 changed files with 85 additions and 4 deletions
|
|
@ -41,7 +41,7 @@ interface HackatimeStatsResponse {
|
|||
// Cache of email -> hackatime user to avoid repeated lookups
|
||||
const hackatimeUserCache = new Map<string, HackatimeUser>()
|
||||
|
||||
async function getHackatimeUser(email: string): Promise<HackatimeUser | null> {
|
||||
export async function getHackatimeUser(email: string): Promise<HackatimeUser | null> {
|
||||
const cached = hackatimeUserCache.get(email)
|
||||
if (cached !== undefined) return cached
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { projectActivityTable } from '../schemas/activity'
|
|||
import { getUserFromSession } from '../lib/auth'
|
||||
import { calculateScrapsFromHours, getUserScrapsBalance } from '../lib/scraps'
|
||||
import { payoutPendingScraps, getNextPayoutDate } from '../lib/scraps-payout'
|
||||
import { syncSingleProject } from '../lib/hackatime-sync'
|
||||
import { syncSingleProject, getHackatimeUser } from '../lib/hackatime-sync'
|
||||
import { notifyProjectReview } from '../lib/slack'
|
||||
import { config } from '../config'
|
||||
import { computeEffectiveHours, getProjectShippedDates, hasProjectBeenShipped, computeEffectiveHoursForProject } from '../lib/effective-hours'
|
||||
|
|
@ -480,6 +480,7 @@ admin.get('/reviews/:id', async ({ params, headers }) => {
|
|||
.select({
|
||||
id: usersTable.id,
|
||||
username: usersTable.username,
|
||||
email: usersTable.email,
|
||||
avatar: usersTable.avatar,
|
||||
internalNotes: usersTable.internalNotes
|
||||
})
|
||||
|
|
@ -501,6 +502,15 @@ admin.get('/reviews/:id', async ({ params, headers }) => {
|
|||
.where(inArray(usersTable.id, reviewerIds))
|
||||
}
|
||||
|
||||
// Look up Hackatime user ID
|
||||
let hackatimeUserId: number | null = null
|
||||
if (projectUser[0]?.email) {
|
||||
console.log('[ADMIN] Looking up hackatime user for:', projectUser[0].email)
|
||||
const htUser = await getHackatimeUser(projectUser[0].email)
|
||||
console.log('[ADMIN] Hackatime user result:', htUser)
|
||||
if (htUser) hackatimeUserId = htUser.user_id
|
||||
}
|
||||
|
||||
const isAdmin = user.role === 'admin'
|
||||
// Hide pending_admin_approval from non-admin reviewers
|
||||
const maskedProject = (!isAdmin && project[0].status === 'pending_admin_approval')
|
||||
|
|
@ -514,9 +524,11 @@ admin.get('/reviews/:id', async ({ params, headers }) => {
|
|||
|
||||
return {
|
||||
project: maskedProject,
|
||||
hackatimeUserId,
|
||||
user: projectUser[0] ? {
|
||||
id: projectUser[0].id,
|
||||
username: projectUser[0].username,
|
||||
email: projectUser[0].email,
|
||||
avatar: projectUser[0].avatar,
|
||||
internalNotes: projectUser[0].internalNotes
|
||||
} : null,
|
||||
|
|
@ -869,6 +881,7 @@ admin.get('/second-pass/:id', async ({ params, headers }) => {
|
|||
.select({
|
||||
id: usersTable.id,
|
||||
username: usersTable.username,
|
||||
email: usersTable.email,
|
||||
avatar: usersTable.avatar,
|
||||
internalNotes: usersTable.internalNotes
|
||||
})
|
||||
|
|
@ -890,14 +903,23 @@ admin.get('/second-pass/:id', async ({ params, headers }) => {
|
|||
.where(inArray(usersTable.id, reviewerIds))
|
||||
}
|
||||
|
||||
// Look up Hackatime user ID
|
||||
let hackatimeUserId: number | null = null
|
||||
if (projectUser[0]?.email) {
|
||||
const htUser = await getHackatimeUser(projectUser[0].email)
|
||||
if (htUser) hackatimeUserId = htUser.user_id
|
||||
}
|
||||
|
||||
// Calculate effective hours and overlapping projects
|
||||
const effectiveHoursData = await computeEffectiveHoursForProject(project[0])
|
||||
|
||||
return {
|
||||
project: project[0],
|
||||
hackatimeUserId,
|
||||
user: projectUser[0] ? {
|
||||
id: projectUser[0].id,
|
||||
username: projectUser[0].username,
|
||||
email: projectUser[0].email,
|
||||
avatar: projectUser[0].avatar,
|
||||
internalNotes: projectUser[0].internalNotes
|
||||
} : null,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@
|
|||
Bot,
|
||||
Loader,
|
||||
ArrowLeft,
|
||||
MessageSquare
|
||||
MessageSquare,
|
||||
ShieldAlert,
|
||||
Clipboard,
|
||||
Lock
|
||||
} from '@lucide/svelte';
|
||||
import ProjectPlaceholder from '$lib/components/ProjectPlaceholder.svelte';
|
||||
import { getUser } from '$lib/auth-client';
|
||||
|
|
@ -87,6 +90,7 @@
|
|||
let projectUser = $state<ProjectUser | null>(null);
|
||||
let reviews = $state<Review[]>([]);
|
||||
let overlappingProjects = $state<OverlappingProject[]>([]);
|
||||
let hackatimeUserId = $state<number | null>(null);
|
||||
let loading = $state(true);
|
||||
let submitting = $state(false);
|
||||
let savingNotes = $state(false);
|
||||
|
|
@ -140,6 +144,7 @@
|
|||
projectUser = data.user;
|
||||
reviews = data.reviews || [];
|
||||
overlappingProjects = data.overlappingProjects || [];
|
||||
hackatimeUserId = data.hackatimeUserId ?? null;
|
||||
userInternalNotes = data.user?.internalNotes || '';
|
||||
|
||||
// Check if project is deleted
|
||||
|
|
@ -535,6 +540,31 @@
|
|||
<span>{syncing ? 'syncing...' : 'sync hours'}</span>
|
||||
</button>
|
||||
{/if}
|
||||
{#if hackatimeUserId}
|
||||
<a
|
||||
href="https://joe.fraud.hackclub.com/profile/{hackatimeUserId}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex cursor-pointer items-center gap-2 rounded-full border-4 border-black px-4 py-2 font-bold transition-all duration-200 hover:border-dashed"
|
||||
>
|
||||
<ShieldAlert size={18} />
|
||||
<span>fraud check</span>
|
||||
</a>
|
||||
{/if}
|
||||
{#if project.githubUrl}
|
||||
<button
|
||||
onclick={async () => {
|
||||
if (project?.githubUrl) {
|
||||
await navigator.clipboard.writeText(project.githubUrl);
|
||||
window.open('https://airlock.hackclub.com/', '_blank');
|
||||
}
|
||||
}}
|
||||
class="inline-flex cursor-pointer items-center gap-2 rounded-full border-4 border-black px-4 py-2 font-bold transition-all duration-200 hover:border-dashed"
|
||||
>
|
||||
<Lock size={18} />
|
||||
<span>airlock</span>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- User Info (clickable) -->
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@
|
|||
ArrowLeft,
|
||||
RefreshCw,
|
||||
Bot,
|
||||
MessageSquare
|
||||
MessageSquare,
|
||||
ShieldAlert,
|
||||
Lock
|
||||
} from '@lucide/svelte';
|
||||
import ProjectPlaceholder from '$lib/components/ProjectPlaceholder.svelte';
|
||||
import { getUser } from '$lib/auth-client';
|
||||
|
|
@ -82,6 +84,7 @@
|
|||
let projectUser = $state<ProjectUser | null>(null);
|
||||
let reviews = $state<Review[]>([]);
|
||||
let overlappingProjects = $state<OverlappingProject[]>([]);
|
||||
let hackatimeUserId = $state<number | null>(null);
|
||||
let loading = $state(true);
|
||||
let submitting = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
|
@ -129,6 +132,7 @@
|
|||
projectUser = data.user;
|
||||
reviews = data.reviews || [];
|
||||
overlappingProjects = data.overlappingProjects || [];
|
||||
hackatimeUserId = data.hackatimeUserId ?? null;
|
||||
if (data.project?.hoursOverride != null) {
|
||||
hoursOverride = data.project.hoursOverride;
|
||||
}
|
||||
|
|
@ -412,6 +416,31 @@
|
|||
<span>try it out</span>
|
||||
</span>
|
||||
{/if}
|
||||
{#if hackatimeUserId}
|
||||
<a
|
||||
href="https://joe.fraud.hackclub.com/profile/{hackatimeUserId}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="inline-flex cursor-pointer items-center gap-2 rounded-full border-4 border-black px-4 py-2 font-bold transition-all duration-200 hover:border-dashed"
|
||||
>
|
||||
<ShieldAlert size={18} />
|
||||
<span>fraud check</span>
|
||||
</a>
|
||||
{/if}
|
||||
{#if project.githubUrl}
|
||||
<button
|
||||
onclick={async () => {
|
||||
if (project?.githubUrl) {
|
||||
await navigator.clipboard.writeText(project.githubUrl);
|
||||
window.open('https://airlock.hackclub.com/', '_blank');
|
||||
}
|
||||
}}
|
||||
class="inline-flex cursor-pointer items-center gap-2 rounded-full border-4 border-black px-4 py-2 font-bold transition-all duration-200 hover:border-dashed"
|
||||
>
|
||||
<Lock size={18} />
|
||||
<span>airlock</span>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- User Info (clickable) -->
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue