diff --git a/package.json b/package.json index 24c859d7..5367c3b8 100644 --- a/package.json +++ b/package.json @@ -58,8 +58,6 @@ "@nivo/bar": "^0.87.0", "@oclif/core": "^4.0.12", "@oclif/plugin-warn-if-update-available": "^3.1.11", - "@sentry/nestjs": "^8.30.0", - "@sentry/profiling-node": "^8.30.0", "@tanstack/react-query": "^5.48.0", "axios": "^1.7.7", "class-transformer": "^0.5.1", diff --git a/packages/app/src/api/platformServer/weeklyReport.platformApi.ts b/packages/app/src/api/platformServer/weeklyReport.platformApi.ts index e0d78db6..a2ddbc18 100644 --- a/packages/app/src/api/platformServer/weeklyReport.platformApi.ts +++ b/packages/app/src/api/platformServer/weeklyReport.platformApi.ts @@ -19,13 +19,11 @@ export const useGetAiWeeklyReport = (startOfWeek: string, email?: string) => { export const useGenerateAiWeeklyReport = () => { const queryClient = useQueryClient() - let startOfWeek: string const mutationFn = (body: { email: string startOfWeek: string weeklyReport: CodeClimbers.WeeklyScores }) => { - startOfWeek = body.startOfWeek return platformApiRequest({ url: `${PLATFORM_API_URL}/ai-weekly-report`, method: 'POST', @@ -34,9 +32,9 @@ export const useGenerateAiWeeklyReport = () => { } return useMutation({ mutationFn, - onSuccess: () => { + onSuccess: (_, variables) => { queryClient.invalidateQueries({ - queryKey: weeklyReportKeys.aiWeeklyReports(startOfWeek), + queryKey: weeklyReportKeys.aiWeeklyReports(variables.startOfWeek), }) }, }) diff --git a/packages/app/src/components/Home/Source/SourceTimeChart.tsx b/packages/app/src/components/Home/Source/SourceTimeChart.tsx index ddbfcd74..bede495c 100644 --- a/packages/app/src/components/Home/Source/SourceTimeChart.tsx +++ b/packages/app/src/components/Home/Source/SourceTimeChart.tsx @@ -41,7 +41,7 @@ export const SourceTimeChart = ({ /> - + {time} diff --git a/packages/app/src/components/common/CodeSnippit/CodeSnippit.tsx b/packages/app/src/components/common/CodeSnippit/CodeSnippit.tsx index abce81ac..3288b8d2 100644 --- a/packages/app/src/components/common/CodeSnippit/CodeSnippit.tsx +++ b/packages/app/src/components/common/CodeSnippit/CodeSnippit.tsx @@ -28,22 +28,40 @@ export const CodeSnippit = ({ code, onCopy }: CodeSnippitProps) => { } }, []) - const copyToClipboard = () => { + const copyToClipboard = async () => { if (errored || copied) return if (onCopy) onCopy() + if (navigator.clipboard && window.isSecureContext) { + await navigator.clipboard.writeText(code) + setCopied(true) + handleTimer(() => { + setCopied(false) + }) + } else { + const textArea = document.createElement('textarea') + textArea.value = code - if (navigator.clipboard) { - navigator.clipboard.writeText(code)?.then(() => { + textArea.style.position = 'absolute' + textArea.style.left = '-999999px' + + document.body.prepend(textArea) + textArea.select() + + try { + document.execCommand('copy') setCopied(true) handleTimer(() => { setCopied(false) }) - }) - } else { - setErrored(true) - handleTimer(() => { - setErrored(false) - }) + } catch (error) { + setErrored(true) + handleTimer(() => { + setErrored(false) + }) + console.error(error) + } finally { + textArea.remove() + } } } diff --git a/packages/app/src/services/feature.service.ts b/packages/app/src/services/feature.service.ts index 768b693a..03e6b102 100644 --- a/packages/app/src/services/feature.service.ts +++ b/packages/app/src/services/feature.service.ts @@ -8,25 +8,20 @@ export const isFeatureEnabled = ( state: FeatureState, ): boolean => { const enabledFeatures = JSON.parse( - localStorage.getItem('enabled-features') || '{}', + localStorage.getItem(`enabled-features-${feature}`) || '', ) - return enabledFeatures[feature] === state + return enabledFeatures === state } export const setFeatureEnabled = (feature: FeatureKey, state: FeatureState) => { - const enabledFeatures = JSON.parse( - localStorage.getItem('enabled-features') || '{}', - ) - const newEnabledFeatures = { ...enabledFeatures, [feature]: state } - localStorage.setItem('enabled-features', JSON.stringify(newEnabledFeatures)) - posthog.capture('$set', { - $set: { 'enabled-features': newEnabledFeatures }, - }) -} - -export const getFeaturePreferences = (): Record => { - const enabledFeatures = JSON.parse( - localStorage.getItem('enabled-features') || '{}', - ) - return enabledFeatures + let stringState = '' + try { + stringState = JSON.stringify(state) + localStorage.setItem(`enabled-features-${feature}`, stringState) + posthog.capture('$set', { + $set: { [`enabled-features-${feature}`]: state }, + }) + } catch (error) { + console.error(`Error setting feature enabled-features-${feature}`, error) + } } diff --git a/packages/server/commands/log/error.ts b/packages/server/commands/log/error.ts new file mode 100644 index 00000000..03fec6f9 --- /dev/null +++ b/packages/server/commands/log/error.ts @@ -0,0 +1,26 @@ +// oclif command to get the latest 50 lines of error logs from the log file +import { Command, Flags } from '@oclif/core' +import path from 'node:path' +import fs from 'node:fs' +import { CODE_CLIMBER_META_DIR, ERROR_LOG_NAME } from '../../utils/node.util' + +export default class Log extends Command { + static description = 'Get the latest 50 lines of error logs' + + static flags = { + lines: Flags.string({ + char: 'l', + description: 'Number of lines to get', + required: false, + }), + } + + async run() { + const { flags } = await this.parse(Log) + const lines = flags.lines || 50 + this.log(`Getting latest ${lines} lines of error logs...`) + const errorLogPath = path.join(CODE_CLIMBER_META_DIR, ERROR_LOG_NAME) + const errorLogContent = fs.readFileSync(errorLogPath, 'utf8') + this.log(errorLogContent.split('\n').slice(-lines).join('\n')) + } +} diff --git a/packages/server/commands/log/out.ts b/packages/server/commands/log/out.ts new file mode 100644 index 00000000..37c303b1 --- /dev/null +++ b/packages/server/commands/log/out.ts @@ -0,0 +1,26 @@ +// oclif command to get the latest 50 lines of error logs from the log file +import { Command, Flags } from '@oclif/core' +import path from 'node:path' +import fs from 'node:fs' +import { CODE_CLIMBER_META_DIR, LOG_NAME } from '../../utils/node.util' + +export default class LogOut extends Command { + static description = 'Get the latest 50 lines of error logs' + + static flags = { + lines: Flags.string({ + char: 'l', + description: 'Number of lines to get', + required: false, + }), + } + + async run() { + const { flags } = await this.parse(LogOut) + const lines = flags.lines || 50 + this.log(`Getting latest ${lines} lines of logs...`) + const logPath = path.join(CODE_CLIMBER_META_DIR, LOG_NAME) + const logContent = fs.readFileSync(logPath, 'utf8') + this.log(logContent.split('\n').slice(-lines).join('\n')) + } +} diff --git a/packages/server/package.json b/packages/server/package.json index 43f997e6..7909bc85 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -44,8 +44,6 @@ "@nestjs/schedule": "^4.1.1", "@nestjs/serve-static": "^4.0.2", "@oclif/core": "^4.0.17", - "@sentry/nestjs": "^8.25.0", - "@sentry/profiling-node": "^8.25.0", "axios": "^1.7.7", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", diff --git a/packages/server/src/app.module.ts b/packages/server/src/app.module.ts index 57f094be..118d907e 100644 --- a/packages/server/src/app.module.ts +++ b/packages/server/src/app.module.ts @@ -5,13 +5,11 @@ import { AllExceptionsFilter } from './filters/allExceptions.filter' import { RequestLoggerMiddleware } from './common/infrastructure/http/middleware/requestlogger.middleware' import { ServeStaticModule } from '@nestjs/serve-static' import { DbModule } from './v1/database/knex' -import { SentryModule } from '@sentry/nestjs/setup' import { APP_DIST_PATH } from '../utils/node.util' import { ScheduledTaskModule } from './common/scheduler.module' @Module({ imports: [ - SentryModule.forRoot(), DbModule, V1Module, ScheduledTaskModule, diff --git a/packages/server/src/main.ts b/packages/server/src/main.ts index 3942f966..2607670b 100644 --- a/packages/server/src/main.ts +++ b/packages/server/src/main.ts @@ -1,5 +1,3 @@ -// Import this first! -import './sentry' import { NestFactory } from '@nestjs/core' import { AppModule } from './app.module' import { Logger, ValidationPipe } from '@nestjs/common' @@ -40,7 +38,7 @@ export const bootstrap = async () => { ] : [ 'https://codeclimbers.io', - 'chrome-extension://fdmoefklpgbjapealpjfailnmalbgpbe', + /chrome-extension.+$/, 'http://localhost:5173', /\.codeclimbers\.io$/, /\.web\.app$/, diff --git a/packages/server/src/sentry.ts b/packages/server/src/sentry.ts deleted file mode 100644 index c5e52e45..00000000 --- a/packages/server/src/sentry.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as Sentry from '@sentry/nestjs' -import { nodeProfilingIntegration } from '@sentry/profiling-node' -import { isProd } from '../utils/environment.util' - -if (isProd()) { - Sentry.init({ - dsn: 'https://e885e4c5eeed09d6229c7d7bfdc8d762@o4507772937043968.ingest.us.sentry.io/4507772946022400', - integrations: [nodeProfilingIntegration()], - // Performance Monitoring - tracesSampleRate: 0.1, // Capture 100% of the transactions - - // Set sampling rate for profiling - this is relative to tracesSampleRate - profilesSampleRate: 0.1, - }) -} diff --git a/packages/server/utils/node.util.ts b/packages/server/utils/node.util.ts index d71c6265..ba7f3ea9 100644 --- a/packages/server/utils/node.util.ts +++ b/packages/server/utils/node.util.ts @@ -20,6 +20,8 @@ interface INodeUtil { PROJECT_ROOT: string BIN_PATH: string START_ERR_LOG_MESSAGE: string + LOG_NAME: string + ERROR_LOG_NAME: string HOME_DIR: string CODE_CLIMBER_META_DIR: string DB_PATH: string @@ -35,8 +37,15 @@ abstract class BaseNodeUtil implements INodeUtil { : path.join(__dirname, '..', '..', '..') BIN_PATH = path.join(this.PROJECT_ROOT, 'bin') HOME_DIR = os.homedir() - - abstract START_ERR_LOG_MESSAGE: string + LOG_NAME = 'codeclimbers.log' + ERROR_LOG_NAME = 'codeclimbers_error.log' + START_ERR_LOG_MESSAGE = pc.red(` + It seems the server is having trouble starting. Run the command + + npx codeclimbers log:error -l 50 + + to investigate the issue further. You can also refer to https://github.com/CodeClimbersIO/cli/blob/release/docs/Troubleshooting.md or message us on our Discord + `) abstract CODE_CLIMBER_META_DIR: string abstract DB_PATH: string APP_DIST_PATH = path.join(this.PROJECT_ROOT, 'packages', 'app', 'dist') @@ -61,13 +70,6 @@ class DarwinNodeUtil extends BaseNodeUtil { this.CODE_CLIMBER_META_DIR, isTest() ? 'codeclimber.test.sqlite' : dbName, ) - START_ERR_LOG_MESSAGE = pc.red(` - It seems the server is having trouble starting. Run the command - - ${pc.white('cat ' + this.CODE_CLIMBER_META_DIR + '/codeclimbers_error.log')} - - to investigate the issue further. You can also refer to https://github.com/CodeClimbersIO/cli/blob/release/docs/Troubleshooting.md or message us on our Discord - `) NODE_PATH = (): string => { const result = execSync('which node').toString().trim() @@ -85,13 +87,8 @@ class WindowsNodeUtil extends BaseNodeUtil { this.CODE_CLIMBER_META_DIR, isTest() ? 'codeclimber.test.sqlite' : 'codeclimber.sqlite', ) - START_ERR_LOG_MESSAGE: string = pc.red(` - It seems the server is having trouble starting. Run the command in cmd (not powershell) - - ${pc.white('more ' + this.CODE_CLIMBER_META_DIR + '\\codeclimbers.err.log')} - - to investigate the issue further. You can also refer to https://github.com/CodeClimbersIO/cli/blob/release/docs/Troubleshooting.md or message us on our Discord - `) + LOG_NAME = 'codeclimbers.out.log' + ERROR_LOG_NAME = 'codeclimbers.err.log' NODE_PATH = (): string => { const result = execSync('where node').toString().trim() @@ -109,13 +106,7 @@ class LinuxNodeUtil extends BaseNodeUtil { this.CODE_CLIMBER_META_DIR, isTest() ? 'codeclimber.test.sqlite' : 'codeclimber.sqlite', ) - START_ERR_LOG_MESSAGE = pc.red(` - It seems the server is having trouble starting. Run the command - - ${pc.white('cat ' + this.CODE_CLIMBER_META_DIR + '/codeclimbers_error.log')} - - to investigate the issue further - `) + NODE_PATH = (): string => { const result = execSync('which node').toString().trim() return path.dirname(result) @@ -146,6 +137,8 @@ export const CODE_CLIMBER_META_DIR = nodeUtil.CODE_CLIMBER_META_DIR export const DB_PATH = nodeUtil.DB_PATH export const APP_DIST_PATH = nodeUtil.APP_DIST_PATH export const CODE_CLIMBER_INI_PATH = nodeUtil.CODE_CLIMBER_INI_PATH +export const LOG_NAME = nodeUtil.LOG_NAME +export const ERROR_LOG_NAME = nodeUtil.ERROR_LOG_NAME export const NODE_PATH = nodeUtil.NODE_PATH export const initDBDir = nodeUtil.initDBDir @@ -160,6 +153,8 @@ const logPaths = () => { Logger.debug(HOME_DIR, 'HOME_DIR') Logger.debug(APP_DIST_PATH, 'APP_DIST_PATH') Logger.debug(CODE_CLIMBER_INI_PATH, 'CODE_CLIMBER_INI_PATH') + Logger.debug(LOG_NAME, 'LOG_NAME') + Logger.debug(ERROR_LOG_NAME, 'ERROR_LOG_NAME') } logPaths()