Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions Backend/src/common/logger/winston.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { utilities as nestWinstonUtilities, WinstonModuleOptions } from 'nest-winston';
import * as winston from 'winston';
// winston-daily-rotate-file is a CommonJS module without a default export; tsconfig has no
// esModuleInterop, so the `import = require` form is needed for the class to be constructible.
// eslint-disable-next-line @typescript-eslint/no-require-imports
import DailyRotateFile = require('winston-daily-rotate-file');
import { getCorrelationId } from './correlation-id.store';

Expand Down
5 changes: 5 additions & 0 deletions Backend/src/config/env.validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ export const envValidationSchema = Joi.object({
),
CONTRACT_ID_GIST_REGISTRY: Joi.string().allow('').default(''),
STELLAR_SECRET_KEY: Joi.string().allow('').default(''),
// Funded public key used as the simulation source for read-only calls (no secret needed)
SOROBAN_READ_ACCOUNT: Joi.string()
.pattern(/^G[A-Z2-7]{55}$/)
.allow('')
.default(''),

// IPFS (Pinata) — optional; empty means mock CIDs in dev
PINATA_API_KEY: Joi.string().allow('').default(''),
Expand Down
36 changes: 36 additions & 0 deletions Backend/src/gists/gist.repository.integration-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -539,4 +539,40 @@ describe('GistRepository (integration)', () => {
// The hidden gist's cell should not inflate counts
});
});

describe('is_active filtering', () => {
const MARKER = `inactive-${Date.now()}`;
let gistId: string;

beforeAll(async () => {
const gist = await repository.create({
content: 'gist that will be deactivated',
lat: 9.0579,
lon: 7.4951,
location_cell: 's1t7d8c',
stellar_gist_id: MARKER,
});
gistId = gist.id;
});

afterAll(async () => {
await dataSource.query(`DELETE FROM gists WHERE stellar_gist_id = $1`, [MARKER]);
});

it('returns an active gist from findByGistId and findNearby', async () => {
expect((await repository.findByGistId(gistId))?.id).toBe(gistId);
const page = await repository.findNearby({ lat: 9.0579, lon: 7.4951, radiusMeters: 500, limit: 100 });
expect(page.data.some((g) => g.id === gistId)).toBe(true);
});

it('hides the gist everywhere once is_active is false', async () => {
const before = await repository.countNearby(9.0579, 7.4951, 500);
await repository.setGistActive(MARKER, false);

expect(await repository.findByGistId(gistId)).toBeNull();
const page = await repository.findNearby({ lat: 9.0579, lon: 7.4951, radiusMeters: 500, limit: 100 });
expect(page.data.some((g) => g.id === gistId)).toBe(false);
expect(await repository.countNearby(9.0579, 7.4951, 500)).toBe(before - 1);
});
});
});
6 changes: 5 additions & 1 deletion Backend/src/gists/gist.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export class GistRepository {

clauses.push(`g.expires_at > NOW()`);
clauses.push(`g.hidden = false`);
clauses.push(`g.is_active = true`);

if (cursor) {
const decoded = PaginationHelper.decodeCursor(cursor) ?? cursor;
Expand Down Expand Up @@ -145,6 +146,7 @@ export class GistRepository {
WHERE id = $1
AND expires_at > NOW()
AND hidden = false
AND is_active = true
LIMIT 1
`,
[id],
Expand Down Expand Up @@ -194,7 +196,8 @@ export class GistRepository {
ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography,
$3
) AND expires_at > NOW()
AND hidden = false`,
AND hidden = false
AND is_active = true`,
[lon, lat, radiusMeters],
);
return parseInt(row.count, 10);
Expand All @@ -212,6 +215,7 @@ export class GistRepository {
$3
) AND expires_at > NOW()
AND hidden = false
AND is_active = true
GROUP BY location_cell ORDER BY count DESC`,
[lon, lat, radiusMeters],
);
Expand Down
7 changes: 0 additions & 7 deletions Backend/src/gists/gists.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@ import { stripHtml } from '../common/utils/sanitize';

const DEFAULT_TTL_HOURS = 24;

interface CacheEntry {
data: Record<string, unknown>;
expiresAt: number;
}

export interface CountNearbyResult {
count: number;
radius: number;
Expand All @@ -29,8 +24,6 @@ export interface CountNearbyResult {
@Injectable()
export class GistsService {
private readonly logger = new Logger(GistsService.name);
private readonly contentCache = new Map<string, CacheEntry>();
private static readonly CACHE_TTL_MS = 5 * 60 * 1000;

constructor(
@InjectDataSource() private readonly dataSource: DataSource,
Expand Down
18 changes: 14 additions & 4 deletions Backend/src/soroban/soroban.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ export class SorobanService {

const simulation = await rpcServer.simulateTransaction(
new TransactionBuilder(
await rpcServer.getAccount(this.getSigner().publicKey()),
await rpcServer.getAccount(this.getReadAccountKey()),
{ fee: BASE_FEE, networkPassphrase: this.networkPassphrase },
)
.addOperation(contract.call('get_admin'))
Expand Down Expand Up @@ -560,6 +560,16 @@ export class SorobanService {
return this.contract;
}

/**
* Source account for read-only simulations. Simulation needs a funded public
* account but no signature, so reads use `SOROBAN_READ_ACCOUNT` when set and
* fall back to the signer's public key otherwise.
*/
private getReadAccountKey(): string {
const configured = this.config.get<string>('SOROBAN_READ_ACCOUNT');
return configured || this.getSigner().publicKey();
}

private getSigner(): Keypair {
if (!this.signer) {
throw new Error('STELLAR_SECRET_KEY is required for Soroban live mode');
Expand Down Expand Up @@ -633,7 +643,7 @@ export class SorobanService {

const simulation = await rpcServer.simulateTransaction(
new TransactionBuilder(
await rpcServer.getAccount(this.getSigner().publicKey()),
await rpcServer.getAccount(this.getReadAccountKey()),
{ fee: BASE_FEE, networkPassphrase: this.networkPassphrase },
)
.addOperation(
Expand Down Expand Up @@ -665,7 +675,7 @@ export class SorobanService {

const simulation = await rpcServer.simulateTransaction(
new TransactionBuilder(
await rpcServer.getAccount(this.getSigner().publicKey()),
await rpcServer.getAccount(this.getReadAccountKey()),
{ fee: BASE_FEE, networkPassphrase: this.networkPassphrase },
)
.addOperation(
Expand Down Expand Up @@ -697,7 +707,7 @@ export class SorobanService {

const simulation = await rpcServer.simulateTransaction(
new TransactionBuilder(
await rpcServer.getAccount(this.getSigner().publicKey()),
await rpcServer.getAccount(this.getReadAccountKey()),
{ fee: BASE_FEE, networkPassphrase: this.networkPassphrase },
)
.addOperation(
Expand Down
Loading