Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
13d4ed2
feat(sidebar): add collapsible sidebar with smooth animation
NiallJoeMaher Jan 7, 2026
568f8ba
feat(layout): integrate collapsible sidebar into app layout
NiallJoeMaher Jan 7, 2026
aeb4c4f
feat(config): add sidebar navigation items and social link
NiallJoeMaher Jan 7, 2026
d669068
refactor(pages): update feed, home, and articles layout styling
NiallJoeMaher Jan 7, 2026
5c2331b
feat(settings): update settings page styling
NiallJoeMaher Jan 7, 2026
8f607f2
refactor(theme): simplify theme provider and remove unused nav code
NiallJoeMaher Jan 7, 2026
a992efc
refactor(ui): update link component and trending posts loading styles
NiallJoeMaher Jan 7, 2026
ee36ad9
chore(assets): add LinkedIn icon and Codú logo
NiallJoeMaher Jan 7, 2026
fedf644
chore(deps): add new script for sources
NiallJoeMaher Jan 7, 2026
3249f11
chore(db): update migration and add seed sources script
NiallJoeMaher Jan 7, 2026
55f764f
refactor(ui): add aria-labels and update layout components
NiallJoeMaher Jan 8, 2026
29d3aff
fix(e2e): update test expectations to match current UI
NiallJoeMaher Jan 8, 2026
9a66a21
feat(api): add getUserLinkBySlug endpoint and fix myScheduled query
NiallJoeMaher Jan 8, 2026
1d030d8
feat(editor): add new PostEditor component and E2E test infrastructure
NiallJoeMaher Jan 8, 2026
9878e30
feat: add link post support and UI improvements
NiallJoeMaher Jan 8, 2026
6f3e8bc
chore: misc updates and cleanup
NiallJoeMaher Jan 8, 2026
2a4f3ef
feat(e2e): enhance publish flow tests with improved wait conditions a…
NiallJoeMaher Jan 8, 2026
e50a838
style: standardize formatting and clean up whitespace across multiple…
NiallJoeMaher Jan 8, 2026
7715fcf
feat(editor): enhance CreateContent component with improved data hand…
NiallJoeMaher Jan 8, 2026
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
Prev Previous commit
Next Next commit
feat(api): add getUserLinkBySlug endpoint and fix myScheduled query
  - Add getUserLinkBySlug endpoint for fetching user link posts by slug
  - Fix myScheduled to filter by status="scheduled" instead of "published"
  - Remove unused gt import from drizzle-orm
  • Loading branch information
NiallJoeMaher committed Jan 8, 2026
commit 9a66a214ff31c1f66792c24eb520ea3b7fd809c9
122 changes: 118 additions & 4 deletions server/api/router/content.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { createTRPCRouter, publicProcedure, protectedProcedure } from "../trpc";
import {
GetUnifiedFeedSchema,
Expand Down Expand Up @@ -28,7 +29,7 @@ import {
user,
comments,
} from "@/server/db/schema";
import { and, eq, desc, lt, lte, gt, sql, isNotNull, count } from "drizzle-orm";
import { and, eq, desc, lt, lte, sql, isNotNull, count } from "drizzle-orm";
import { increment } from "./utils";
import crypto from "crypto";

Expand Down Expand Up @@ -1078,12 +1079,11 @@ export const contentRouter = createTRPCRouter({
}));
}),

// My Scheduled - get user's scheduled ARTICLE content (publishedAt > now)
// My Scheduled - get user's scheduled ARTICLE content
myScheduled: protectedProcedure
.input(MyScheduledContentSchema)
.query(async ({ ctx, input }) => {
const userId = ctx.session.user.id;
const now = new Date().toISOString();

const results = await ctx.db
.select({
Expand All @@ -1103,7 +1103,6 @@ export const contentRouter = createTRPCRouter({
eq(posts.authorId, userId),
eq(posts.type, "article"),
eq(posts.status, "scheduled"),
gt(posts.publishedAt, now),
),
)
.orderBy(desc(posts.publishedAt))
Expand Down Expand Up @@ -1175,4 +1174,119 @@ export const contentRouter = createTRPCRouter({

return updated;
}),

// Get user-created link post by username and slug
getUserLinkBySlug: publicProcedure
.input(GetContentBySlugSchema.extend({ username: z.string() }))
.query(async ({ ctx, input }) => {
const userId = ctx.session?.user?.id;

// Find the user
const userResult = await ctx.db
.select({ id: user.id })
.from(user)
.where(eq(user.username, input.username))
.limit(1);

if (userResult.length === 0) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}

const authorId = userResult[0].id;

// Find the link post
const linkPostResults = await ctx.db
.select({
id: posts.id,
type: posts.type,
title: posts.title,
body: posts.body,
excerpt: posts.excerpt,
externalUrl: posts.externalUrl,
coverImage: posts.coverImage,
slug: posts.slug,
publishedAt: posts.publishedAt,
upvotes: posts.upvotesCount,
downvotes: posts.downvotesCount,
showComments: posts.showComments,
createdAt: posts.createdAt,
updatedAt: posts.updatedAt,
// Author info
authorId: user.id,
authorName: user.name,
authorUsername: user.username,
authorImage: user.image,
authorBio: user.bio,
})
.from(posts)
.leftJoin(user, eq(posts.authorId, user.id))
.where(
and(
eq(posts.slug, input.slug),
eq(posts.authorId, authorId),
eq(posts.type, "link"),
eq(posts.status, "published"),
lte(posts.publishedAt, new Date().toISOString()),
),
)
.limit(1);

if (linkPostResults.length === 0) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Link post not found",
});
}

const linkPost = linkPostResults[0];

// Get user vote if logged in
let userVote: "up" | "down" | null = null;
let isBookmarked = false;

if (userId) {
const [voteResult, bookmarkResult] = await Promise.all([
ctx.db
.select({ voteType: post_votes.voteType })
.from(post_votes)
.where(
and(
eq(post_votes.postId, linkPost.id),
eq(post_votes.userId, userId),
),
)
.limit(1),
ctx.db
.select({ id: bookmarks.id })
.from(bookmarks)
.where(
and(
eq(bookmarks.postId, linkPost.id),
eq(bookmarks.userId, userId),
),
)
.limit(1),
]);

userVote = voteResult[0]?.voteType ?? null;
isBookmarked = bookmarkResult.length > 0;
}

return {
...linkPost,
type: toFrontendType(linkPost.type),
userVote,
isBookmarked,
author: {
id: linkPost.authorId,
name: linkPost.authorName,
username: linkPost.authorUsername,
image: linkPost.authorImage,
bio: linkPost.authorBio,
},
};
}),
});