diff --git a/packages/nc-gui/components/feed/Changelog/Item.vue b/packages/nc-gui/components/feed/Changelog/Item.vue index e26e5b453d..71ca929981 100644 --- a/packages/nc-gui/components/feed/Changelog/Item.vue +++ b/packages/nc-gui/components/feed/Changelog/Item.vue @@ -126,6 +126,10 @@ const renderedText = computedAsync(async () => { img { @apply !rounded-lg; } + + h1 { + @apply !text-3xl !font-bold; + } } .aside { diff --git a/packages/nc-gui/components/feed/Recents/Card.vue b/packages/nc-gui/components/feed/Recents/Card.vue index f8b7681eab..07d748572a 100644 --- a/packages/nc-gui/components/feed/Recents/Card.vue +++ b/packages/nc-gui/components/feed/Recents/Card.vue @@ -53,8 +53,13 @@ const renderedText = computedAsync(async () => { - diff --git a/packages/nc-gui/components/feed/Youtube/Player.vue b/packages/nc-gui/components/feed/Youtube/Player.vue index d71b9545d8..748caac7cf 100644 --- a/packages/nc-gui/components/feed/Youtube/Player.vue +++ b/packages/nc-gui/components/feed/Youtube/Player.vue @@ -14,7 +14,7 @@ const { - + diff --git a/packages/nocodb/src/controllers/utils.controller.ts b/packages/nocodb/src/controllers/utils.controller.ts index 05fb63ec71..36ff243553 100644 --- a/packages/nocodb/src/controllers/utils.controller.ts +++ b/packages/nocodb/src/controllers/utils.controller.ts @@ -7,6 +7,7 @@ import { HttpCode, Post, Req, + Request, UseGuards, } from '@nestjs/common'; import { ProjectRoles, validateAndExtractSSLProp } from 'nocodb-sdk'; @@ -166,4 +167,16 @@ export class UtilsController { // todo: refactor return (await this.utilsService.aggregatedMetaInfo()) as any; } + + @UseGuards(PublicApiLimiterGuard) + @Get('/api/v1/feed') + async feed(@Request() req: NcRequest) { + return await this.utilsService.feed(req); + } + + @UseGuards(PublicApiLimiterGuard) + @Get('/api/v1/new-feed') + async newFeed(@Request() req: NcRequest) { + return await this.utilsService.getLatestFeed(req); + } } diff --git a/packages/nocodb/src/services/utils.service.ts b/packages/nocodb/src/services/utils.service.ts index 76785ac8c3..4c9b826f5e 100644 --- a/packages/nocodb/src/services/utils.service.ts +++ b/packages/nocodb/src/services/utils.service.ts @@ -5,7 +5,7 @@ import { compareVersions, validate } from 'compare-versions'; import { ViewTypes } from 'nocodb-sdk'; import { ConfigService } from '@nestjs/config'; import { useAgent } from 'request-filtering-agent'; -import type { AppConfig } from '~/interface/config'; +import type { AppConfig, NcRequest } from '~/interface/config'; import { NC_APP_SETTINGS, NC_ATTACHMENT_FIELD_SIZE } from '~/constants'; import SqlMgrv2 from '~/db/sql-mgr/v2/SqlMgrv2'; import { NcError } from '~/helpers/catchError'; @@ -13,7 +13,7 @@ import { Base, Store, User } from '~/models'; import Noco from '~/Noco'; import NcConnectionMgrv2 from '~/utils/common/NcConnectionMgrv2'; import getInstance from '~/utils/getInstance'; -import { MetaTable, RootScopes } from '~/utils/globals'; +import { CacheScope, MetaTable, RootScopes } from '~/utils/globals'; import { jdbcToXcConfig } from '~/utils/nc-config/helpers'; import { packageVersion } from '~/utils/packageVersion'; import { @@ -21,6 +21,8 @@ import { defaultLimitConfig, } from '~/helpers/extractLimitAndOffset'; import { DriverClient } from '~/utils/nc-config'; +import NocoCache from '~/cache/NocoCache'; +import { getCircularReplacer } from '~/utils'; const versionCache = { releaseVersion: null, @@ -385,7 +387,6 @@ export class UtilsService { if (result.status === 'fulfilled') { return result.value; } - console.log(result.reason); return null; }); }; @@ -460,6 +461,7 @@ export class UtilsService { disableEmailAuth: this.configService.get('auth.disableEmailAuth', { infer: true, }), + feedEnabled: process.env.NC_DISABLE_PRODUCT_FEED !== 'true', mainSubDomain: this.configService.get('mainSubDomain', { infer: true }), dashboardPath: this.configService.get('dashboardPath', { infer: true }), inviteOnlySignup: settings.invite_only_signup, @@ -471,4 +473,99 @@ export class UtilsService { return result; } + + async feed(req: NcRequest) { + const { + type = 'all', + page = '1', + per_page = '10', + } = req.query as { + type: 'github' | 'youtube' | 'all' | 'twitter' | 'cloud'; + page: string; + per_page: string; + }; + + const cacheKey = `${CacheScope.PRODUCT_FEED}:${type}:${page}:${per_page}`; + + const cachedData = await NocoCache.get(cacheKey, 'json'); + + if (cachedData) { + try { + return JSON.parse(cachedData); + } catch (e) { + console.log(e); + } + } + + let response; + + try { + response = await axios.get('https://nocodb.com/api/v1/social/feed', { + params: { + per_page, + page, + type, + }, + }); + } catch (e) { + console.log(e); + return []; + } + + // The feed includes the attachments, which has the presigned URL + // So the cache should match the presigned URL cache + await NocoCache.setExpiring( + cacheKey, + JSON.stringify(response.data, getCircularReplacer), + isNaN(parseInt(process.env.NC_ATTACHMENT_EXPIRE_SECONDS)) + ? 2 * 60 * 60 + : parseInt(process.env.NC_ATTACHMENT_EXPIRE_SECONDS), + ); + + return response.data; + } + + async getLatestFeed(req: NcRequest) { + const { last_published_at } = req.query as { + last_published_at: string; + }; + + if (!last_published_at) { + return 0; + } + + const utils = { + found: false, + page: 1, + missedItems: 0, + }; + while (!utils.found) { + const feed = await this.feed({ + query: { + type: 'all', + page: utils.page.toString(), + per_page: '100', + }, + } as unknown as NcRequest); + + if (!feed || !feed?.length) { + break; + } + + for (const item of feed) { + if (item['Published Time'] === last_published_at) { + utils.found = true; + break; + } + + utils.missedItems++; + } + + if (!utils.found) { + utils.page++; + } + } + + return utils.missedItems; + } } diff --git a/packages/nocodb/src/utils/globals.ts b/packages/nocodb/src/utils/globals.ts index c0d0f1e1bb..d6e2b2e960 100644 --- a/packages/nocodb/src/utils/globals.ts +++ b/packages/nocodb/src/utils/globals.ts @@ -187,6 +187,7 @@ export enum CacheScope { INTEGRATION = 'integration', COL_BUTTON = 'colButton', CMD_PALETTE = 'cmdPalette', + PRODUCT_FEED = 'productFeed', } export enum CacheGetType {