124 lines
3.9 KiB
TypeScript
124 lines
3.9 KiB
TypeScript
import { ConvexError, v } from 'convex/values';
|
|
|
|
import type { Doc, Id } from './_generated/dataModel';
|
|
import { internalMutation, internalQuery, query } from './_generated/server';
|
|
import { getOwnedSpoon, getRequiredUserId } from './model';
|
|
|
|
const syncStatus = v.union(
|
|
v.literal('up_to_date'),
|
|
v.literal('behind'),
|
|
v.literal('ahead'),
|
|
v.literal('diverged'),
|
|
v.literal('unknown'),
|
|
);
|
|
|
|
export const deriveSyncStatus = ({
|
|
upstreamAheadBy,
|
|
forkAheadBy,
|
|
}: {
|
|
upstreamAheadBy: number;
|
|
forkAheadBy: number;
|
|
}): Doc<'spoonRepositoryStates'>['status'] => {
|
|
if (upstreamAheadBy === 0 && forkAheadBy === 0) return 'up_to_date';
|
|
if (upstreamAheadBy > 0 && forkAheadBy === 0) return 'behind';
|
|
if (upstreamAheadBy === 0 && forkAheadBy > 0) return 'ahead';
|
|
if (upstreamAheadBy > 0 && forkAheadBy > 0) return 'diverged';
|
|
return 'unknown';
|
|
};
|
|
|
|
export const getForSpoon = query({
|
|
args: { spoonId: v.id('spoons') },
|
|
handler: async (ctx, { spoonId }) => {
|
|
const ownerId = await getRequiredUserId(ctx);
|
|
await getOwnedSpoon(ctx, spoonId, ownerId);
|
|
return await ctx.db
|
|
.query('spoonRepositoryStates')
|
|
.withIndex('by_spoon', (q) => q.eq('spoonId', spoonId))
|
|
.first();
|
|
},
|
|
});
|
|
|
|
export const listForOwner = query({
|
|
args: {},
|
|
handler: async (ctx) => {
|
|
const ownerId = await getRequiredUserId(ctx);
|
|
return await ctx.db
|
|
.query('spoonRepositoryStates')
|
|
.withIndex('by_owner', (q) => q.eq('ownerId', ownerId))
|
|
.collect();
|
|
},
|
|
});
|
|
|
|
export const getInternal = internalQuery({
|
|
args: { spoonId: v.id('spoons'), ownerId: v.id('users') },
|
|
handler: async (ctx, { spoonId, ownerId }) => {
|
|
const state = await ctx.db
|
|
.query('spoonRepositoryStates')
|
|
.withIndex('by_spoon', (q) => q.eq('spoonId', spoonId))
|
|
.first();
|
|
if (state && state.ownerId !== ownerId) {
|
|
throw new ConvexError('Repository state ownership mismatch.');
|
|
}
|
|
return state;
|
|
},
|
|
});
|
|
|
|
export const upsert = internalMutation({
|
|
args: {
|
|
spoonId: v.id('spoons'),
|
|
ownerId: v.id('users'),
|
|
upstreamFullName: v.string(),
|
|
forkFullName: v.string(),
|
|
upstreamDefaultBranch: v.string(),
|
|
forkDefaultBranch: v.string(),
|
|
upstreamHeadSha: v.optional(v.string()),
|
|
forkHeadSha: v.optional(v.string()),
|
|
mergeBaseSha: v.optional(v.string()),
|
|
upstreamAheadBy: v.number(),
|
|
forkAheadBy: v.number(),
|
|
status: syncStatus,
|
|
openForkPullRequestCount: v.number(),
|
|
openUpstreamPullRequestCount: v.number(),
|
|
lastCommitAt: v.optional(v.number()),
|
|
rawCompareUrl: v.optional(v.string()),
|
|
},
|
|
handler: async (ctx, args): Promise<Id<'spoonRepositoryStates'>> => {
|
|
const now = Date.now();
|
|
const existing = await ctx.db
|
|
.query('spoonRepositoryStates')
|
|
.withIndex('by_spoon', (q) => q.eq('spoonId', args.spoonId))
|
|
.first();
|
|
const patch = {
|
|
ownerId: args.ownerId,
|
|
upstreamFullName: args.upstreamFullName,
|
|
forkFullName: args.forkFullName,
|
|
upstreamDefaultBranch: args.upstreamDefaultBranch,
|
|
forkDefaultBranch: args.forkDefaultBranch,
|
|
upstreamHeadSha: args.upstreamHeadSha,
|
|
forkHeadSha: args.forkHeadSha,
|
|
mergeBaseSha: args.mergeBaseSha,
|
|
upstreamAheadBy: args.upstreamAheadBy,
|
|
forkAheadBy: args.forkAheadBy,
|
|
status: args.status,
|
|
openForkPullRequestCount: args.openForkPullRequestCount,
|
|
openUpstreamPullRequestCount: args.openUpstreamPullRequestCount,
|
|
lastCommitAt: args.lastCommitAt,
|
|
rawCompareUrl: args.rawCompareUrl,
|
|
refreshedAt: now,
|
|
updatedAt: now,
|
|
};
|
|
if (existing) {
|
|
if (existing.ownerId !== args.ownerId) {
|
|
throw new ConvexError('Repository state ownership mismatch.');
|
|
}
|
|
await ctx.db.patch(existing._id, patch);
|
|
return existing._id;
|
|
}
|
|
return await ctx.db.insert('spoonRepositoryStates', {
|
|
spoonId: args.spoonId,
|
|
...patch,
|
|
createdAt: now,
|
|
});
|
|
},
|
|
});
|