Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3ad3306
feat: add endpoint to list users posts
mrjvs Apr 12, 2026
158e6cc
feat: make user pages use the new user post list endpoint
mrjvs Apr 12, 2026
fcca537
fix: revert total post count calculation
mrjvs Apr 12, 2026
8ce7de1
feat: add endpoints to list followers and following
mrjvs Apr 12, 2026
dd75420
fix: fixed pid 0 being added to followers
mrjvs Apr 12, 2026
49f07c5
feat: make frontend use the new follower endpoints
mrjvs Apr 12, 2026
436095a
fix: increase limit of follower endpoints
mrjvs Apr 12, 2026
1633674
fix: fixed friends not showing up with a name
mrjvs Apr 12, 2026
60f54da
feat: add follow/unfollow endpoints to miiverse-api
mrjvs Apr 12, 2026
4a12a69
feat: make juxtaposition-ui use new follow/unfollow endpoints
mrjvs Apr 12, 2026
ee66669
fix: fixed community queries
mrjvs Apr 12, 2026
e46dc2d
fix: fixed empty body requests breaking
mrjvs Apr 12, 2026
56cb776
chore: remove old database.js functions
mrjvs Apr 12, 2026
a54c032
feat: add endpoint to get current user
mrjvs Apr 14, 2026
4cafa10
feat: add new self endpoint to the request state of juxtaposition-ui
mrjvs Apr 14, 2026
ec71113
feat: use new self state to determine ban messages
mrjvs Apr 14, 2026
881579f
fix: add accessLevel to self response
mrjvs Apr 14, 2026
c46a684
feat: implement new self endpoint into frontend pages
mrjvs Apr 14, 2026
b3d7038
Handle auth-access middleware better (now enforced in a guard)
mrjvs Apr 15, 2026
8796e75
Merge branch 'feat/port-post-feeds-datasource' into feat/port-user-da…
mrjvs Apr 19, 2026
57be7d6
Merge branch 'dev' into feat/port-user-datasource
mrjvs Apr 26, 2026
fd5bdae
Merge branch 'dev' into feat/port-user-datasource
mrjvs Apr 27, 2026
3c13fa0
chore: fix merge conflicts
mrjvs Apr 27, 2026
28690c7
chore: fix more merge conflicts (github merge editor broke it)
mrjvs Apr 27, 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
17 changes: 0 additions & 17 deletions apps/juxtaposition-ui/src/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,21 +113,6 @@ async function getUserContent(pid) {
return CONTENT.findOne({ pid: pid });
}

async function getFollowingUsers(content) {
verifyConnected();
return SETTINGS.find({
pid: content.following_users,
...notBanned()
});
}

async function getFollowedUsers(content) {
verifyConnected();
return SETTINGS.find({
pid: content.followed_users
});
}

async function getNotifications(pid, limit, offset) {
verifyConnected();
return NOTIFICATION.find({
Expand Down Expand Up @@ -192,8 +177,6 @@ export const database = {
getPostByID,
getDuplicatePosts,
getEndPoint,
getFollowingUsers,
getFollowedUsers,
getUsersSettings,
getUserSettings,
getUserSettingsFuzzySearch,
Expand Down
66 changes: 25 additions & 41 deletions apps/juxtaposition-ui/src/middleware/checkBan.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import moment from 'moment';
import { database as db } from '@/database';
import { config } from '@/config';
import { humanDate, humanFromNow } from '@/util';
import { WebLoginView } from '@/services/juxt-web/views/web/loginView';
Expand All @@ -8,10 +6,10 @@ import { PortalFatalErrorView } from '@/services/juxt-web/views/portal/errorView
import type { RequestHandler } from 'express';

export const checkBan: RequestHandler = async (request, response, next) => {
// Initialize access levels so the template engine can always access them
response.locals.tester = false;
response.locals.moderator = false;
response.locals.developer = false;
// Set access levels
response.locals.tester = request.self?.permissions.tester ?? null;
response.locals.moderator = request.self?.permissions.moderator ?? null;
response.locals.developer = request.self?.permissions.developer ?? null;

if (!request.user) {
if (request.guest_access || request.path === '/login') {
Expand All @@ -21,11 +19,6 @@ export const checkBan: RequestHandler = async (request, response, next) => {
}
}

// Set access levels
response.locals.tester = request.user.accessLevel >= 1 && request.user.accessLevel <= 3;
response.locals.moderator = request.user.accessLevel == 2 || request.user.accessLevel == 3;
response.locals.developer = request.user.accessLevel == 3;

// Check if user has access to the environment
let accessAllowed = false;
switch (config.serverEnvironment) {
Expand All @@ -52,33 +45,29 @@ export const checkBan: RequestHandler = async (request, response, next) => {
ctr: <CtrFatalErrorView code={banCode} message={banMessage} />
});
}
const userSettings = await db.getUserSettings(request.pid);
if (userSettings && moment(userSettings.ban_lift_date) <= moment() && userSettings.account_status !== 3) {
userSettings.account_status = 0;
await userSettings.save();
}
// This includes ban checks for both Juxt specifically and the account server, ideally this should be squashed
// assuming we support more gradual bans on PNID's
if (userSettings && (userSettings.account_status < 0 || userSettings.account_status > 1 || request.user.accessLevel < 0)) {
let banMessage = '';
let banCode = 5980020;
switch (userSettings.account_status) {
case 2:
banMessage = `${request.user.username} has been banned. The ban ends ${humanFromNow(userSettings.ban_lift_date)} (at ${humanDate(userSettings.ban_lift_date)}).`;
banCode = 5980010;
break;
case 3:
banMessage = `${request.user.username} has been permanently banned.`;
banCode = 5980011;
break;
default:
banMessage = `${request.user.username} has been banned.`;

if (request.self?.banState) {
const banState = request.self.banState;
const endDateMessage = banState.endDate ? ` The ban ends ${humanFromNow(banState.endDate)} (at ${humanDate(banState.endDate)}).` : '';

let banCode = 5980020; // fallback
let banMessage = `${request.self.username} has been banned.${endDateMessage}`; // fallback
if (banState.code === 'temp_ban') {
banMessage = `${request.self.username} has been banned.${endDateMessage}`;
banCode = 5980010;
} else if (banState.code === 'perma_ban') {
banMessage = `${request.user.username} has been permanently banned.${endDateMessage}`;
banCode = 5980011;
} else if (banState.code === 'network_ban') {
banMessage = `${request.user.username} has been permanently banned.${endDateMessage}`;
banMessage += `\n\nThis ban restricts all parts of Pretendo Network.`;
banCode = 5980020;
}
if (request.user.accessLevel < 0) {
banMessage += '\n\nThis ban restricts all parts of Pretendo Network.';
} else if (userSettings.ban_reason) {
banMessage += `\n\nReason: ${userSettings.ban_reason}.`;

if (banState.reason) {
banMessage += `\n\nReason: ${banState.reason}.`;
}

banMessage += `\n\nIf you have any questions, please contact the moderators on the Pretendo Network Forum (https://preten.do/ban-appeal/).`;

return response.jsxForDirectory({
Expand All @@ -88,10 +77,5 @@ export const checkBan: RequestHandler = async (request, response, next) => {
});
}

if (userSettings) {
userSettings.last_active = new Date();
await userSettings.save();
}

next();
};
10 changes: 10 additions & 0 deletions apps/juxtaposition-ui/src/middleware/consoleAuth.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createInternalApiClient } from '@/api/client';
import { config } from '@/config';
import { getLanguage } from '@/i18n';
import { logger } from '@/logger';
Expand Down Expand Up @@ -60,6 +61,15 @@ export const consoleAuth: RequestHandler = async (request, response, next) => {
}
}

request.self = null;
if (request.user) {
const selfResult = await createInternalApiClient(request.tokens).self.get({ throwOnError: false });
if (selfResult.error) {
logger.error(selfResult.error, 'Failed to get self from access token');
}
request.self = selfResult.error ? null : selfResult.data ?? null;
}

const mayBypassAuthChecks = request.user?.accessLevel === 3 || config.disableConsoleChecks;

// This section includes checks if a user is a developer and adds exceptions for these cases
Expand Down
10 changes: 10 additions & 0 deletions apps/juxtaposition-ui/src/middleware/webAuth.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createInternalApiClient } from '@/api/client';
import { config } from '@/config';
import { logger } from '@/logger';
import { getUserAccountData, getUserDataFromToken } from '@/util';
Expand Down Expand Up @@ -34,6 +35,15 @@ export const webAuth: RequestHandler = async (request, response, next) => {

request.tokens = { oauthToken: request.cookies.access_token };

request.self = null;
if (request.user) {
const selfResult = await createInternalApiClient(request.tokens).self.get({ throwOnError: false });
if (selfResult.error) {
logger.error(selfResult.error, 'Failed to get self from access token');
}
request.self = selfResult.error ? null : selfResult.data ?? null;
}

// Handle guest access pages
if (!request.pid) {
if (!requestOkForGuest(request)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,7 @@ adminRouter.get('/posts', async function (req, res) {

// `any` is needed because database.js is not typed yet
const rawReports: HydratedReportDocument[] = await database.getAllOpenReports() as any;
const userContent = await database.getUserContent(auth().pid);
if (!userContent) {
throw new Error('User content is null');
}
const userContent = auth().self.content;

const postIds = rawReports.map(obj => obj.post_id);
const nonRemovedPosts = await POST.find(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,7 @@ communitiesRouter.get('/:communityID/related', async function (req, res) {
})
});

const userSettings = await database.getUserSettings(auth().pid);
const userContent = await database.getUserContent(auth().pid);
if (!userContent || !userSettings) {
if (!auth().self.hasDoneOnboarding) {
return res.redirect('/404');
}
const { data: community } = await req.api.communities.get({ id: params.communityID });
Expand Down Expand Up @@ -172,9 +170,8 @@ communitiesRouter.get('/:communityID/:type', async function (req, res) {
})
});

const userSettings = hasAuth() ? await database.getUserSettings(auth().pid) : null;
const userContent = hasAuth() ? await database.getUserContent(auth().pid) : null;
if (hasAuth() && (!userContent || !userSettings)) {
const self = hasAuth() ? auth().self : null;
if (self && !self.hasDoneOnboarding) {
return res.redirect('/404');
}
const { data: community } = await req.api.communities.get({ id: params.communityID });
Expand All @@ -189,8 +186,8 @@ communitiesRouter.get('/:communityID/:type', async function (req, res) {
throw new Error('Community stats could not be found');
}

const canPost = userSettings !== null && isPostingAllowed(community, userSettings, null, auth().user);
const isUserFollowing = userContent !== null && userContent.followed_communities.includes(community.olive_community_id);
const canPost = !!self && isPostingAllowed(community, self, null);
const isUserFollowing = !!self && self.content.followed_communities.includes(community.olive_community_id);

const { data: subCommunitiesList } = await req.api.communities.list({ category: 'sub', limit: 90, parent_id: community.olive_community_id });
const subCommunities = subCommunitiesList.items;
Expand All @@ -210,7 +207,7 @@ communitiesRouter.get('/:communityID/:type', async function (req, res) {
const postListProps: PostListViewProps = {
nextLink: `/titles/${params.communityID}/${params.type}/more?offset=${posts.length}&pjax=true`,
posts,
userContent
userContent: self?.content ?? null
};

if (query.pjax) {
Expand Down Expand Up @@ -249,7 +246,7 @@ communitiesRouter.get('/:communityID/:type', async function (req, res) {
});

communitiesRouter.get('/:communityID/:type/more', async function (req, res) {
const { query, params, auth } = parseReq(req, {
const { query, params, auth, hasAuth } = parseReq(req, {
params: z.object({
communityID: z.string(),
type: z.string()
Expand All @@ -260,7 +257,7 @@ communitiesRouter.get('/:communityID/:type/more', async function (req, res) {
});

const offset = query.offset;
const userContent = await database.getUserContent(auth().pid);
const userContent = hasAuth() ? auth().self.content : null;
const { data: community } = await req.api.communities.get({ id: params.communityID });
if (!community || !userContent) {
return res.redirect('/404');
Expand Down
25 changes: 12 additions & 13 deletions apps/juxtaposition-ui/src/services/juxt-web/routes/console/feed.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import express from 'express';
import { z } from 'zod';
import { database } from '@/database';
import { config } from '@/config';
import { parseReq } from '@/services/juxt-web/routes/routeUtils';
import { WebGlobalFeedView, WebPeopleFeedView, WebPersonalFeedView } from '@/services/juxt-web/views/web/feed';
Expand All @@ -13,12 +12,12 @@ import { PortalPostListView } from '@/services/juxt-web/views/portal/postList';
export const feedRouter = express.Router();

feedRouter.get('/', async function (req, res) {
const { auth, query } = parseReq(req, {
const { auth, hasAuth, query } = parseReq(req, {
query: z.object({
pjax: z.stringbool().optional()
})
});
const userContent = await database.getUserContent(auth().pid);
const userContent = hasAuth() ? auth().self.content : null;
if (!userContent) {
return res.redirect('/404');
}
Expand All @@ -43,12 +42,12 @@ feedRouter.get('/', async function (req, res) {
});

feedRouter.get('/people', async function (req, res) {
const { auth, query } = parseReq(req, {
const { auth, hasAuth, query } = parseReq(req, {
query: z.object({
pjax: z.stringbool().optional()
})
});
const userContent = await database.getUserContent(auth().pid);
const userContent = hasAuth() ? auth().self.content : null;
if (!userContent) {
return res.redirect('/404');
}
Expand All @@ -73,12 +72,12 @@ feedRouter.get('/people', async function (req, res) {
});

feedRouter.get('/all', async function (req, res) {
const { auth, query } = parseReq(req, {
const { auth, hasAuth, query } = parseReq(req, {
query: z.object({
pjax: z.stringbool().optional()
})
});
const userContent = await database.getUserContent(auth().pid);
const userContent = hasAuth() ? auth().self.content : null;
if (!userContent) {
return res.redirect('/404');
}
Expand All @@ -103,13 +102,13 @@ feedRouter.get('/all', async function (req, res) {
});

feedRouter.get('/more', async function (req, res) {
const { auth, query } = parseReq(req, {
const { auth, hasAuth, query } = parseReq(req, {
query: z.object({
pjax: z.stringbool().optional(),
offset: z.coerce.number().nonnegative().default(0)
})
});
const userContent = await database.getUserContent(auth().pid);
const userContent = hasAuth() ? auth().self.content : null;
if (!userContent) {
return res.redirect('/404');
}
Expand All @@ -130,13 +129,13 @@ feedRouter.get('/more', async function (req, res) {
});

feedRouter.get('/people/more', async function (req, res) {
const { auth, query } = parseReq(req, {
const { auth, hasAuth, query } = parseReq(req, {
query: z.object({
pjax: z.stringbool().optional(),
offset: z.coerce.number().nonnegative().default(0)
})
});
const userContent = await database.getUserContent(auth().pid);
const userContent = hasAuth() ? auth().self.content : null;
if (!userContent) {
return res.redirect('/404');
}
Expand All @@ -157,13 +156,13 @@ feedRouter.get('/people/more', async function (req, res) {
});

feedRouter.get('/all/more', async function (req, res) {
const { auth, query } = parseReq(req, {
const { auth, hasAuth, query } = parseReq(req, {
query: z.object({
pjax: z.stringbool().optional(),
offset: z.coerce.number().nonnegative().default(0)
})
});
const userContent = await database.getUserContent(auth().pid);
const userContent = hasAuth() ? auth().self.content : null;
if (!userContent) {
return res.redirect('/404');
}
Expand Down
Loading