diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml new file mode 100644 index 0000000..ed724e4 --- /dev/null +++ b/.github/workflows/integration_tests.yml @@ -0,0 +1,33 @@ +name: Integration Tests +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test: + name: Run Integration Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + + - name: Install dependencies + run: npm install + working-directory: ./backend + + - name: Run Integration Tests + env: + MONGODB_URI: ${{ secrets.MONGODB_URI }} + JWT_SECRET: ${{ secrets.JWT_SECRET }} + run: npm run test:integration + working-directory: ./backend diff --git a/.github/workflows/package_size.yml b/.github/workflows/package_size.yml new file mode 100644 index 0000000..a0a6548 --- /dev/null +++ b/.github/workflows/package_size.yml @@ -0,0 +1,23 @@ +name: Package Size Report + +on: pull_request + +permissions: + pull-requests: write + contents: read + +jobs: + pkg-size-report: + name: Package Size Report + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 20 + + - name: Package size report + uses: pkg-size/action@v1.1.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4de7b91..fdb3630 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,11 +18,11 @@ jobs: id-token: write # to enable use of OIDC for npm provenance steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 'lts/*' - name: Install dependencies @@ -33,25 +33,3 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: npx semantic-release - - update-badge: - needs: release - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Get latest release version - id: get_version - run: | - VERSION=$(gh release list --limit 1 | cut -f1) - echo "::set-output name=version::$VERSION" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Create badge - uses: schneegans/dynamic-badges-action@v1.6.0 - with: - auth: ${{ secrets.GIST_SECRET }} - gistID: - filename: deepfocus-version.json - label: version - message: ${{ steps.get_version.outputs.version }} - color: blue \ No newline at end of file diff --git a/README.md b/README.md index 46aedaf..08352de 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@

[![Release](https://github.com/timeowilliams/deepFocus/actions/workflows/release.yml/badge.svg)](https://github.com/timeowilliams/deepFocus/actions/workflows/release.yml) +[![Integration Tests](https://github.com/timeowilliams/deepFocus/actions/workflows/integration_tests.yml/badge.svg)](https://github.com/timeowilliams/deepFocus/actions/workflows/integration_tests.yml) [![Version](https://img.shields.io/npm/v/project.svg)](https://www.npmjs.com/package/project) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) @@ -50,7 +51,7 @@ pnpm install ## Usage - +Note, for running this app locally, you may run into issues with active-window. Try running `npm install --ignore-scripts` to fix this. More info [here](https://github.com/sindresorhus/active-window/issues/10). ## Goals @@ -92,4 +93,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file [Timeo Williams] - [@timeowilliams](https://twitter.com/timeowilliams) - timeo.williams@gmail.com -Project Link: [https://github.com/timeowilliams/deepFocus](https://github.com/timeowilliams/deepFocus) \ No newline at end of file +Project Link: [https://github.com/timeowilliams/deepFocus](https://github.com/timeowilliams/deepFocus) diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..aee0489 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,9 @@ +# Node modules +node_modules/ + +# Build outputs +dist/ +out/ + +# Environment variables +.env \ No newline at end of file diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..9d387f0 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,65 @@ +## Getting Started + +### Prerequisites + +- Node.js LTS >=v20.12.2 +- npm v10.5.0 or pnpm +- MongoDB instance + +### Installation + +#### Backend + +1. Navigate to the `backend` directory: + + ```bash + cd backend + ``` + +2. Install dependencies: + + ```bash + pnpm install + ``` + +3. Create a `.env` file with your MongoDB URI: + + ```env + MONGO_URI=your_mongodb_uri + JWT_SECRET=your_jwt_secret + ``` + +4. Start the backend server: + ```bash + pnpm start + ``` + +#### Frontend + +1. Navigate to the `frontend` directory: + + ```bash + cd frontend + ``` + +2. Install dependencies: + + ```bash + pnpm install + ``` + +3. Start the Electron app: + ```bash + pnpm start + ``` + +## Goals + +- [x] Allow all users to download on any machine through an Electron JS app +- [x] Migrate to TypeScript and implement SolidJS +- [x] Set up CI/CD pipeline and automatic releases +- [x] Implement changelog using conventional commits +- [ ] Allow users to enter session goals and customize productive/unproductive sites +- [ ] Migrate from electron-storage to SQLite for improved data handling +- [ ] Add integration and automated tests +- [ ] Implement user authentication and cloud-based data persistence using MongoDB and JWT diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 0000000..dd29543 --- /dev/null +++ b/backend/package.json @@ -0,0 +1,45 @@ +{ + "name": "deepfocus-backend", + "version": "1.0.0", + "type": "module", + "main": "src/server.ts", + "scripts": { + "start": "tsx src/server.ts", + "dev": "tsx watch .", + "test": "echo \"Error: no test specified\" && exit 1", + "format": "prettier --write \"**/*.+(js|ts|jsx|tsx|json|css|md|yml|yaml|html)\"", + "format:check": "prettier --check \"**/*.+(js|ts|jsx|tsx|json|css|md|yml|yaml|html)\"", + "build": "copyfiles -u 1 src/**/* dist", + "test:integration": "tsx node_modules/mocha/bin/mocha src/tests/integration/**/*.test.ts --exit" + }, + "dependencies": { + "bcrypt": "^5.1.1", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.17.1", + "format": "^0.2.2", + "jsonwebtoken": "^8.5.1", + "mongoose": "^8.5.4", + "tsx": "^4.18.0" + }, + "devDependencies": { + "@types/bcrypt": "^5.0.2", + "@types/chai": "^4.3.17", + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.6", + "@types/mocha": "^10.0.7", + "@types/supertest": "^6.0.2", + "chai": "^5.1.1", + "crypto": "^1.0.1", + "mocha": "^10.7.3", + "prettier": "^3.3.3", + "supertest": "^7.0.0", + "ts-node": "^9.1.1", + "typescript": "^4.2.4" + }, + "engines": { + "node": ">=20.0.0" + } +} diff --git a/backend/src/connectToDB.ts b/backend/src/connectToDB.ts new file mode 100644 index 0000000..564a748 --- /dev/null +++ b/backend/src/connectToDB.ts @@ -0,0 +1,31 @@ +import mongoose from 'mongoose' +import dotenv from 'dotenv' + +dotenv.config() +const uri = process.env.MONGODB_URI || '' + +if (!uri) { + throw new Error('MONGODB_URI is not defined in the environment variables') +} + +async function connectToDB(): Promise { + try { + // Connect to the MongoDB server using Mongoose + await mongoose.connect(uri) + console.log('Connected successfully to MongoDB server') + return mongoose + } catch (error) { + console.error('Error connecting to the database:', error) + throw error + } +} +async function closeDBConnection(): Promise { + try { + await mongoose.connection.close() + console.log('Disconnected successfully from MongoDB server') + } catch (error) { + console.error('Error closing the database connection:', error) + } +} + +export { connectToDB, closeDBConnection } diff --git a/backend/src/models/User.ts b/backend/src/models/User.ts new file mode 100644 index 0000000..d1b670b --- /dev/null +++ b/backend/src/models/User.ts @@ -0,0 +1,32 @@ +import mongoose, { Document, Schema } from 'mongoose' + +// Define the User interface +interface IUser extends Document { + username: string + password: string + firstName: string + lastName: string + country: string + language: string +} + +// Create the User schema +const UserSchema: Schema = new Schema({ + username: { + type: String, + required: true, + unique: true, + match: [/.+@.+\..+/, 'Please fill a valid email address'] // Email validation + }, + password: { type: String, required: true }, + firstName: { type: String, required: true }, + lastName: { type: String, required: true }, + country: { type: String, required: true }, + language: { type: String, required: true } +}) + +// Create the User model +const User = mongoose.model('User', UserSchema) + +export default User +export { IUser } diff --git a/backend/src/server.ts b/backend/src/server.ts new file mode 100644 index 0000000..ae953a7 --- /dev/null +++ b/backend/src/server.ts @@ -0,0 +1,125 @@ +import express from 'express' +import jwt from 'jsonwebtoken' +import cors from 'cors' +import bcrypt from 'bcrypt' +import dotenv from 'dotenv' +import { connectToDB, closeDBConnection } from './connectToDB' +import User from './models/User' + +export const app = express() +const port = process.env.PORT || 5000 + +dotenv.config() +const jwtSecret = process.env.JWT_SECRET + +if (!jwtSecret) { + console.error('JWT_SECRET is not set. Please set it as an environment variable.') + process.exit(1) +} + +app.use(express.json()) + +app.use( + cors({ + origin: '*', + credentials: true + }) +) + +// Health check endpoint +app.get('/api/v1/health', (req, res) => { + res.status(200).send('Server is healthy') +}) + +app.get('/api/v1/test-db', async (req, res) => { + await connectToDB() + res.status(200).send('DB is connected') +}) + +// Authentication routes +app.post('/api/v1/auth/signup', async (req, res) => { + try { + const { username, password, firstName, lastName, country, language } = req.body + console.log('Incoming signup request for username:', username) + await connectToDB() + const existingUser = await User.findOne({ username }) + + if (existingUser) { + return res.status(400).send('Username already exists') + } + + const hashedPassword = await bcrypt.hash(password, 10) + const newUser = new User({ + username, + password: hashedPassword, + firstName, + lastName, + country, + language + }) + await newUser.save() + + const token = jwt.sign({ username: newUser.username }, jwtSecret, { + expiresIn: '2h' + }) + res.json({ token }) + } catch (error) { + console.error('Signup error:', error) + res.status(500).send('Server error') + } finally { + await closeDBConnection() + } +}) + +app.post('/api/v1/auth/login', async (req, res) => { + try { + const { username, password } = req.body + console.log('Incoming login request for username:', username) + await connectToDB() + const user = await User.findOne({ username }) + + if (user && (await bcrypt.compare(password, user.password))) { + const token = jwt.sign( + { + username: user.username, + firstName: user.firstName, + lastName: user.lastName, + country: user.country, + language: user.language + }, + jwtSecret, + { expiresIn: '2h' } + ) + res.json({ token }) + } else { + res.status(400).send('Invalid credentials') + } + } catch (error) { + console.error('Login error:', error) + res.status(500).send('Server error') + } finally { + await closeDBConnection() + } +}) + +// Add the delete user route +app.delete('/api/v1/auth/delete', async (req, res) => { + const { username } = req.body + + try { + await connectToDB() + const user = await User.findOneAndDelete({ username }) + if (!user) { + return res.status(404).send('User not found') + } + res.status(200).send('User deleted successfully') + } catch (error) { + res.status(500).send('Internal server error') + } finally { + await closeDBConnection() + } +}) + +app.listen(port, function () { + console.log(`Server is running on port ${port}`) +}) diff --git a/backend/src/tests/integration/auth.test.ts b/backend/src/tests/integration/auth.test.ts new file mode 100644 index 0000000..a3d1a53 --- /dev/null +++ b/backend/src/tests/integration/auth.test.ts @@ -0,0 +1,89 @@ +/* eslint-env mocha */ +import request from 'supertest' +import { expect } from 'chai' +import { app } from '../../server' +import { Server } from 'http' +describe('Authentication Endpoints', () => { + let server: Server + + before((done) => { + server = app.listen(5001, done) // Start the server on a different port for testing + }) + + after((done) => { + server.close(done) + }) + + describe('POST /api/v1/auth/signup', () => { + it('should create a new user and return a token, then delete the user', async () => { + const res = await request(server).post('/api/v1/auth/signup').send({ + username: 'testuser1@gmail.com', + password: 'testpassword', + firstName: 'Test', + lastName: 'User', + country: 'Testland', + language: 'English' + }) + + expect(res.status).to.equal(200) + expect(res.body).to.have.property('token') + + // Delete the user after creation + await request(server).delete('/api/v1/auth/delete').send({ + username: 'testuser1@gmail.com' + }) + }) + + it('should return 400 if username already exists', async () => { + await request(server).post('/api/v1/auth/signup').send({ + username: 'testuser@gmail.com', + password: 'testpassword', + firstName: 'Test', + lastName: 'User', + country: 'Testland', + language: 'English' + }) + + const res = await request(server).post('/api/v1/auth/signup').send({ + username: 'testuser@gmail.com', + password: 'testpassword', + firstName: 'Test', + lastName: 'User', + country: 'Testland', + language: 'English' + }) + + expect(res.status).to.equal(400) + expect(res.text).to.equal('Username already exists') + }) + }) + + describe('POST /api/v1/auth/login', () => { + it('should login an existing user and return a token', async () => { + await request(server).post('/api/v1/auth/signup').send({ + username: 'testuser2@gmail.com', + password: 'testpassword', + firstName: 'Test', + lastName: 'User', + country: 'Testland', + language: 'English' + }) + + const res = await request(server) + .post('/api/v1/auth/login') + .send({ username: 'testuser2@gmail.com', password: 'testpassword' }) + + expect(res.status).to.equal(200) + expect(res.body).to.have.property('token') + }) + + it('should return 400 for invalid credentials', async () => { + const res = await request(server) + .post('/api/v1/auth/login') + .send({ username: 'nonexistentuser', password: 'wrongpassword' }) + + expect(res.status).to.equal(400) + expect(res.text).to.equal('Invalid credentials') + }) + }) +}) diff --git a/backend/tsconfig.json b/backend/tsconfig.json new file mode 100644 index 0000000..2467f4b --- /dev/null +++ b/backend/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "CommonJS", + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} diff --git a/electron-builder.yml b/electron-builder.yml index b726222..8ffa9df 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -9,6 +9,8 @@ files: extraResources: - from: '.env' to: '.env' + - from: 'src/main/emailTemplates' + to: 'emailTemplates' asar: true asarUnpack: - 'resources/**' diff --git a/package-lock.json b/package-lock.json index 4ca5d36..b93b2fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "dotenv": "^16.4.5", "electron-store": "^10.0.0", "electron-updater": "^6.1.7", - "get-windows": "^9.1.1", + "get-windows": "^9.2.0", "node-schedule": "^2.1.1", "resend": "^3.4.0", "url": "^0.11.3" @@ -1761,8 +1761,8 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", @@ -1778,8 +1778,8 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "debug": "^4.3.4" }, @@ -1791,8 +1791,8 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -1805,8 +1805,8 @@ "version": "7.0.5", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "agent-base": "^7.0.2", "debug": "4" @@ -1819,15 +1819,15 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "devOptional": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/@npmcli/fs": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "semver": "^7.3.5" }, @@ -3215,8 +3215,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "devOptional": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/acorn": { "version": "8.12.1", @@ -3271,8 +3271,8 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -4025,11 +4025,11 @@ } }, "node_modules/cacache": { - "version": "18.0.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.3.tgz", - "integrity": "sha512-qXCd4rh6I07cnDqh8V48/94Tc/WSfj+o3Gn6NZ0aZovS255bUx8O13uKxRFd2eWG0xgsco7+YItQNPaa5E85hg==", - "devOptional": true, + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", + "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", "license": "ISC", + "optional": true, "dependencies": { "@npmcli/fs": "^3.1.0", "fs-minipass": "^3.0.0", @@ -4052,8 +4052,8 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -4073,8 +4073,8 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "devOptional": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/cacheable-lookup": { "version": "5.0.4", @@ -4217,8 +4217,8 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "devOptional": true, "license": "MIT", + "optional": true, "engines": { "node": ">=6" } @@ -6193,8 +6193,8 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", - "devOptional": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "optional": true }, "node_modules/extract-zip": { "version": "2.0.1", @@ -6569,8 +6569,8 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "minipass": "^7.0.3" }, @@ -6699,9 +6699,9 @@ } }, "node_modules/get-windows": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/get-windows/-/get-windows-9.1.1.tgz", - "integrity": "sha512-Pof/LBFfrrQqVDaWKYsZOouGiW97e/iGVhmlpu0Oy2AOyC/IYum9cz/NtkPi7LaixWsu61XdFmu1FeZWAeFXMw==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/get-windows/-/get-windows-9.2.0.tgz", + "integrity": "sha512-Gnz+EV2aZB/9r2JOJsM4rHlzJoR74IV/v05QAaZ5beRJznYcRnmNSnqcY2Ylvm7XA1Ls1N3vSp1fO8WG9P+bYg==", "hasInstallScript": true, "license": "MIT", "engines": { @@ -6712,8 +6712,8 @@ }, "optionalDependencies": { "@mapbox/node-pre-gyp": "^1.0.11", - "node-addon-api": "^8.0.0", - "node-gyp": "^10.1.0" + "node-addon-api": "^8.1.0", + "node-gyp": "^10.2.0" }, "peerDependencies": { "node-gyp": "^10.1.0" @@ -7296,8 +7296,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "devOptional": true, "license": "MIT", + "optional": true, "engines": { "node": ">=8" } @@ -7368,8 +7368,8 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" @@ -7447,8 +7447,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "devOptional": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/is-number": { "version": "7.0.0", @@ -7742,8 +7742,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "devOptional": true, - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/jsesc": { "version": "2.5.2", @@ -8168,8 +8168,8 @@ "version": "13.0.1", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "@npmcli/agent": "^2.0.0", "cacache": "^18.0.0", @@ -8188,16 +8188,6 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/make-fetch-happen/node_modules/proc-log": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", - "devOptional": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/marked": { "version": "12.0.2", "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", @@ -8424,8 +8414,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "minipass": "^7.0.3" }, @@ -8437,8 +8427,8 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", @@ -8455,8 +8445,8 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "minipass": "^3.0.0" }, @@ -8468,8 +8458,8 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -8481,15 +8471,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "devOptional": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/minipass-pipeline": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "minipass": "^3.0.0" }, @@ -8501,8 +8491,8 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -8514,15 +8504,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "devOptional": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/minipass-sized": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "minipass": "^3.0.0" }, @@ -8534,8 +8524,8 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -8547,8 +8537,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "devOptional": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/minizlib": { "version": "2.1.2", @@ -8645,8 +8635,8 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "devOptional": true, "license": "MIT", + "optional": true, "engines": { "node": ">= 0.6" } @@ -8713,11 +8703,11 @@ } }, "node_modules/node-gyp": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.1.0.tgz", - "integrity": "sha512-B4J5M1cABxPc5PwfjhbV5hoy2DP9p8lFXASnEN6hugXOa61416tnTZ29x9sSwAd0o99XNIcpvDDy1swAExsVKA==", - "devOptional": true, + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.2.0.tgz", + "integrity": "sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==", "license": "MIT", + "optional": true, "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", @@ -8725,9 +8715,9 @@ "graceful-fs": "^4.2.6", "make-fetch-happen": "^13.0.0", "nopt": "^7.0.0", - "proc-log": "^3.0.0", + "proc-log": "^4.1.0", "semver": "^7.3.5", - "tar": "^6.1.2", + "tar": "^6.2.1", "which": "^4.0.0" }, "bin": { @@ -8741,8 +8731,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", - "devOptional": true, "license": "ISC", + "optional": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -8751,8 +8741,8 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -8772,8 +8762,8 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", - "devOptional": true, "license": "ISC", + "optional": true, "engines": { "node": ">=16" } @@ -8782,8 +8772,8 @@ "version": "7.2.1", "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "abbrev": "^2.0.0" }, @@ -8798,8 +8788,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "isexe": "^3.1.1" }, @@ -8835,8 +8825,8 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "abbrev": "1" }, @@ -11680,8 +11670,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "aggregate-error": "^3.0.0" }, @@ -12086,11 +12076,11 @@ } }, "node_modules/proc-log": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", - "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", - "devOptional": true, + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", "license": "ISC", + "optional": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -13125,8 +13115,8 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "devOptional": true, "license": "MIT", + "optional": true, "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" @@ -13136,8 +13126,8 @@ "version": "2.8.3", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" @@ -13151,8 +13141,8 @@ "version": "8.0.4", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "agent-base": "^7.1.1", "debug": "^4.3.4", @@ -13166,8 +13156,8 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "debug": "^4.3.4" }, @@ -13296,15 +13286,15 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "devOptional": true, - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "optional": true }, "node_modules/ssri": { "version": "10.0.6", "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "minipass": "^7.0.3" }, @@ -14068,8 +14058,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "unique-slug": "^4.0.0" }, @@ -14081,8 +14071,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "imurmurhash": "^0.1.4" }, diff --git a/package.json b/package.json index 9fe62f0..7d015cf 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "deepfocus", "version": "1.1.0", - "description": "An Electron application with Solid and TypeScript", + "description": "DeepFocus is a productivity tool that helps you stay focused and productive by tracking your time spent on your computer.", "main": "./out/main/index.js", - "author": "example.com", - "homepage": "https://electron-vite.org", + "author": "Timeo Williams", + "homepage": "https://deepFocus.cc", "type": "module", "scripts": { "format": "prettier --write .", @@ -13,9 +13,9 @@ "typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false", "typecheck": "npm run typecheck:node && npm run typecheck:web", "start": "electron-vite preview", - "dev": "electron-vite dev", + "dev": "electron-vite dev -- --inspect ", "build": "NODE_ENV=production npm run typecheck && electron-vite build", - "postinstall": "electron-builder install-app-deps", + "postinstall": "electron-builder install-app-deps && npx electron-rebuild", "build:unpack": "npm run build && electron-builder --dir", "build:win": "npm run build && electron-builder --win", "build:mac": "NODE_ENV=production electron-vite build && electron-builder --mac", @@ -28,7 +28,7 @@ "dotenv": "^16.4.5", "electron-store": "^10.0.0", "electron-updater": "^6.1.7", - "get-windows": "^9.1.1", + "get-windows": "^9.2.0", "node-schedule": "^2.1.1", "resend": "^3.4.0", "url": "^0.11.3" diff --git a/src/main/emailService.ts b/src/main/emailService.ts index de5451f..636f2d7 100644 --- a/src/main/emailService.ts +++ b/src/main/emailService.ts @@ -1,7 +1,10 @@ import { Resend } from 'resend' import * as schedule from 'node-schedule' import { TypedStore } from './types' -import { capitalizeFirstLetter } from './productivityUtils' +import { formatUrl } from './productivityUtils' +import fs from 'fs' +import path from 'path' +import { app } from 'electron' interface TopSite { url: string @@ -121,58 +124,37 @@ export class EmailService { .sort((a, b) => b.timeSpent - a.timeSpent) .slice(0, 3) .map((tracker) => ({ - url: this.formatUrl(tracker.url), + url: formatUrl(tracker.url), timeSpent: Math.round(tracker.timeSpent / (1000 * 60)) // Convert ms to minutes })) return topSites } - private formatUrl(input: string): string { - // Regular expression to check if the input looks like a URL - const urlPattern = /^(https?:\/\/)?([^\s$.?#].[^\s]*)$/i - - if (urlPattern.test(input)) { - // If it looks like a URL, try to create a URL object - try { - const url = new URL(input.startsWith('http') ? input : `http://${input}`) - const { hostname } = url - const parts = hostname.split('.').filter((part) => part !== 'www') - - if (parts.length > 2) { - // There is a subdomain, so format it as Subdomain.Domain - const subdomain = parts.slice(0, -2).join('.') // Everything before the domain and TLD - const domain = parts.slice(-2).join('.') // The domain and TLD - return `${capitalizeFirstLetter(subdomain)}.${capitalizeFirstLetter(domain)}` - } else { - // No subdomain, just return the domain - const domain = parts.join('.') // The domain and TLD - return capitalizeFirstLetter(domain) - } - } catch (error) { - console.error('Error formatting URL:', error) - return input - } - } else { - // If the input is not a valid URL, return it as is - return input - } - } - public async testEmailSend(): Promise { console.log('Testing email send...') // Use placeholder data for testing - const testDeepWorkHours = 5 - const testTopSites: TopSite[] = [ - { url: 'example.com', timeSpent: 120 }, - { url: 'github.com', timeSpent: 90 }, - { url: 'stackoverflow.com', timeSpent: 60 }, - { url: 'docs.google.com', timeSpent: 45 }, - { url: 'chat.openai.com', timeSpent: 30 } - ] - - const emailBody = this.composeEmailBody(testDeepWorkHours, testTopSites) + // const testDeepWorkHours = 5 + // const testTopSites: TopSite[] = [ + // { url: 'example.com', timeSpent: 120 }, + // { url: 'github.com', timeSpent: 90 }, + // { url: 'stackoverflow.com', timeSpent: 60 }, + // { url: 'docs.google.com', timeSpent: 45 }, + // { url: 'chat.openai.com', timeSpent: 30 } + // ] + + // const emailBody = this.composeEmailBody(testDeepWorkHours, testTopSites) + // const emailBody = fs.readFileSync(path.join(__dirname, 'emailTemplates/welcome.html'), 'utf8') + console.log('app.getAppPath() ', app.getAppPath()) + console.log( + 'path.join(app.getAppPath(), "emailTemplates/welcome.html") ', + path.join(app.getAppPath(), '/src/main/emailTemplates/welcome.html') + ) + const emailBody = fs.readFileSync( + path.join(app.getAppPath(), '/src/main/emailTemplates/welcome.html'), + 'utf8' + ) try { const data = await this.resend.emails.send({ diff --git a/src/main/emailTemplates/welcome.html b/src/main/emailTemplates/welcome.html new file mode 100644 index 0000000..82608a8 --- /dev/null +++ b/src/main/emailTemplates/welcome.html @@ -0,0 +1,1012 @@ + + + + + + + + + New Message + + + + + + + +
+ + + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+

+ October 1st, 2021 +

+
+
+
+
+ + + + +
+ + + + +
+ + + + +
+ + + + + + + +
+

+ Dear *|FNAME|*, +

+
+

+ Thanks for taking the leap & installing deepFocus.

It's + been a nice year together. You showed yourself as a skilled + professional.
Now it's time to move on.
We decided + to start a new direction -- Mobile Apps for external + IT companies -- which we want you to run. Your duties + will be:  +

+
    +
  • + you will be a team leader of your own unit; +
  • +
  • + interviewing new developers; +
  • +
  • + monitoring performance of the entire unit; +
  • +
  • + communication with clients after they placed an order to + better understand what they need; +
  • +
  • + if you like, you can continue to work on Mobile Apps + development on your own, too. +
  • +
+

+ We hope you will take our offer as we trust in + you. 
Please, give us your answer by October 4th.

Regards,
Timeo + Williams. +

+
+
+
+
+ + + + +
+ + + + +
+ + + + + + +
+ + + + +
+ +
+
+ + + + + +
+ + + + +
+ +
+
+ + + + + +
+ + + + + + + +
+

+ Timeo Williams +

+

+ CEO of deepFocus +

+

+ deepFocus.cc +

+
+ + + + + + +
+
+ +
+
+
+
+ + diff --git a/src/main/index.ts b/src/main/index.ts index 154de2d..3bcae5c 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -2,7 +2,6 @@ import { app, shell, BrowserWindow, ipcMain } from 'electron' import path, { join } from 'path' import fs from 'fs' import dotenv from 'dotenv' -// import icon from '../../resources/icon.png?asset' import { electronApp, optimizer, is } from '@electron-toolkit/utils' const { activeWindow } = await import('get-windows') import { EmailService } from './emailService' @@ -24,7 +23,6 @@ if (app.isPackaged) { console.log('Env file path:', envPath) console.log('Env file exists:', fs.existsSync(envPath)) if (fs.existsSync(envPath)) { - console.log('Env file contents:', fs.readFileSync(envPath, 'utf8')) dotenv.config({ path: envPath }) } else { console.error('Env file not found in production build') @@ -43,7 +41,6 @@ const emailService = new EmailService(process.env.EMAIL || '', store) export let currentSiteTimeTrackers: SiteTimeTracker[] = [] function saveSiteTimeTrackers(): void { store.set('siteTimeTrackers', currentSiteTimeTrackers) - // console.log('Saved site time trackers:', currentSiteTimeTrackers) } async function loadSiteTimeTrackers(): Promise { diff --git a/src/main/productivityUtils.ts b/src/main/productivityUtils.ts index 9b8a602..98b3ff1 100644 --- a/src/main/productivityUtils.ts +++ b/src/main/productivityUtils.ts @@ -33,6 +33,37 @@ function isProductiveUrl(url: string): boolean { return !unproductiveSites.some((site) => domain.includes(site.toLowerCase())) } +export function formatUrl(input: string): string { + // Regular expression to check if the input looks like a URL + const urlPattern = /^(https?:\/\/)?([^\s$.?#].[^\s]*)$/i + + if (urlPattern.test(input)) { + // If it looks like a URL, try to create a URL object + try { + const url = new URL(input.startsWith('http') ? input : `http://${input}`) + const { hostname } = url + const parts = hostname.split('.').filter((part) => part !== 'www') + + if (parts.length > 2) { + // There is a subdomain, so format it as Subdomain.Domain + const subdomain = parts.slice(0, -2).join('.') // Everything before the domain and TLD + const domain = parts.slice(-2).join('.') // The domain and TLD + return `${capitalizeFirstLetter(subdomain)}.${capitalizeFirstLetter(domain)}` + } else { + // No subdomain, just return the domain + const domain = parts.join('.') // The domain and TLD + return capitalizeFirstLetter(domain) + } + } catch (error) { + console.error('Error formatting URL:', error) + return input + } + } else { + // If the input is not a valid URL, return it as is + return input + } +} + export function formatTime(milliseconds: number): string { const seconds = Math.floor(milliseconds / 1000) const minutes = Math.floor(seconds / 60) diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 1e964bc..0f46568 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -10,7 +10,7 @@ const App: Component = () => { const getLatestRelease = (): string => 'v1.2.6' // Call this function when you want to test the email send - //const testEmailSend = (): void => window?.electron.ipcRenderer.send('test-email-send') + const testEmailSend = (): void => window?.electron.ipcRenderer.send('test-email-send') return ( <> @@ -21,11 +21,11 @@ const App: Component = () => {

Latest Release: {getLatestRelease()}

- {/* {/*