diff --git a/.env b/.env new file mode 100644 index 0000000..ff5df31 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +NODE_ENV=development +MONGO_URI=mongodb://localhost/mean-dev \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b1079aa --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,23 @@ +name: Release + +on: + push: + branches: + - master + +permissions: + contents: write + pull-requests: write + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - name: Run Release Please + uses: google-github-actions/release-please-action@v4 + with: + release-type: node + package-name: your-package-name + bump-minor-pre-major: true + bump-patch-for-minor-pre-major: true \ No newline at end of file diff --git a/.github/workflows/tag-latest.yml b/.github/workflows/tag-latest.yml deleted file mode 100644 index 9ab9561..0000000 --- a/.github/workflows/tag-latest.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: tag - -on: - release: - types: [published] - -jobs: - tag: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@master - - name: Run latest-tag - uses: EndBug/latest-tag@v1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b5bf455..7166616 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [ '16', '18', '20', '22' ] + node: [ '20', '22', '24' ] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 diff --git a/.gitignore b/.gitignore index 7ee3cca..7398fac 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,5 @@ local.properties *.ntvs* *.njsproj *.sln +.env.* +.rebooted \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index d0ff5d9..da14437 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,22 @@ -FROM node:20.11.0-alpine +FROM node:22.20.0-alpine + +RUN apk add --no-cache git WORKDIR /home/iris # Install iris packages COPY package.json /home/iris/package.json COPY package-lock.json /home/iris/package-lock.json -RUN npm install - -# Manually trigger bower. Why doesnt this work via npm install? COPY .bowerrc /home/iris/.bowerrc COPY bower.json /home/iris/bower.json +COPY gruntfile.js /home/iris/gruntfile.js + +RUN npm install RUN npm run bower # Make everything available for start COPY . /home/iris +RUN npm run build # Set development environment as default ENV NODE_ENV development diff --git a/LICENSE.md b/LICENSE.md index b739cd5..7f39528 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 2017-2025 Andrii Yermolenko +Copyright (c) 2017-2026 Andrii Yermolenko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/bower.json b/bower.json index 1708b6a..0db4137 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,6 @@ { "name": "iris", "dependencies": { - "bootstrap": "~3", "angular": "~1.3", "angular-resource": "~1.3", "angular-animate": "~1.3", @@ -11,8 +10,11 @@ "angular-ui-router": "~0.2", "angular-file-upload": "1.1.5", "angular-messages": "~1.3.17", - "owasp-password-strength-test": "~1.3.0", - "lodash": "^4.17.4" + "bootstrap": "~3", + "dom-to-image": "^2.6.0", + "file-saver": "^2.0.2", + "lodash": "^4.17.4", + "owasp-password-strength-test": "~1.3.0" }, "resolutions": { "angular": "~1.3" diff --git a/config/assets/default.js b/config/assets/default.js index 9995baf..bc0d2e1 100644 --- a/config/assets/default.js +++ b/config/assets/default.js @@ -18,6 +18,8 @@ module.exports = { 'public/lib/angular-file-upload/angular-file-upload.min.js', 'public/lib/owasp-password-strength-test/owasp-password-strength-test.js', 'public/lib/lodash/dist/lodash.min.js', + 'public/lib/dom-to-image/dist/dom-to-image.min.js', + 'public/lib/file-saver/dist/FileSaver.min.js' ], tests: ['public/lib/angular-mocks/angular-mocks.js'] }, diff --git a/config/env/default.js b/config/env/default.js index bcb2af3..2642927 100644 --- a/config/env/default.js +++ b/config/env/default.js @@ -1,13 +1,18 @@ module.exports = { app: { - title: 'IRIS', - description: 'Full-Stack JavaScript with MongoDB, Express, AngularJS, and Node.js', + title: 'Homio', + description: 'Grocery store', keywords: 'mongodb, express, angularjs, node.js, mongoose, passport', googleAnalyticsTrackingID: process.env.GOOGLE_ANALYTICS_TRACKING_ID || 'GOOGLE_ANALYTICS_TRACKING_ID' }, db: { uri: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean', options: { + maxPoolSize: 5, + minPoolSize: 1, + maxIdleTimeMS: 30000, + serverSelectionTimeoutMS: 5000, + socketTimeoutMS: 45000, // user: '', // pass: '', }, @@ -15,7 +20,7 @@ module.exports = { debug: process.env.MONGODB_DEBUG || false }, port: process.env.PORT || 3000, - templateEngine: 'swig', + templateEngine: 'pug', // Session Cookie settings sessionCookie: { // session expiration is set by default to 24 hours diff --git a/config/lib/app.js b/config/lib/app.js index 55d0604..555ef78 100644 --- a/config/lib/app.js +++ b/config/lib/app.js @@ -21,7 +21,7 @@ module.exports.loadModels = mongoose.loadModels; module.exports.start = async function start() { const db = await mongoose.connect(); const app = await express.init(db); - app.listen(config.port, () => { + app.listen(config.port, '0.0.0.0', () => { // Logging initialization console.log('--'); console.log(chalk.green(config.app.title)); diff --git a/config/lib/express.js b/config/lib/express.js index b87e419..88df9d3 100644 --- a/config/lib/express.js +++ b/config/lib/express.js @@ -10,11 +10,11 @@ const compress = require('compression'); const methodOverride = require('method-override'); const cookieParser = require('cookie-parser'); const helmet = require('helmet'); -const consolidate = require('@ladjs/consolidate'); const config = require('../config'); const logger = require('./logger'); const configureSocketIO = require('./socket.io'); +const { version } = require('../../package'); /** * Initialize local variables @@ -34,6 +34,7 @@ module.exports.initLocalVariables = (app) => { app.locals.livereload = config.livereload; app.locals.logo = config.logo; app.locals.favicon = config.favicon; + app.locals.version = version; // Passing the request url to environment locals app.use((req, res, next) => { @@ -91,11 +92,11 @@ module.exports.initMiddleware = (app) => { * Configure view engine */ module.exports.initViewEngine = (app) => { - // Set swig as the template engine - app.engine('server.view.html', consolidate[config.templateEngine]); + // Set Pug as the template engine + app.engine('pug', require('pug').__express); // Set views path and view engine - app.set('view engine', 'server.view.html'); + app.set('view engine', 'pug'); app.set('views', './'); }; diff --git a/fly.toml b/fly.toml new file mode 100644 index 0000000..3c4c524 --- /dev/null +++ b/fly.toml @@ -0,0 +1,28 @@ +app = "homio" +primary_region = "fra" + +kill_signal = "SIGINT" +kill_timeout = 5 + +[build] +dockerfile = "Dockerfile" + +[env] +NODE_ENV = "production" +PORT = "3000" + +[[services]] +internal_port = 3000 + +[[services.ports]] +port = 80 +handlers = ["http"] + +[[services.ports]] +port = 443 +handlers = ["tls", "http"] + +[[services.tcp_checks]] +interval = "10s" +timeout = "2s" +grace_period = "10s" diff --git a/gruntfile.js b/gruntfile.js index 1a0eb07..0eb1949 100644 --- a/gruntfile.js +++ b/gruntfile.js @@ -72,7 +72,7 @@ module.exports = (grunt) => { } }, concurrent: { - debug: ['node-inspect', 'watch'], + debug: ['nodemon', 'watch'], options: { logConcurrentOutput: true } @@ -138,6 +138,40 @@ module.exports = (grunt) => { } } }, + nodemon: { + dev: { + script: 'server.js', + options: { + nodeArgs: ['--inspect', '--env-file=.env.local'], + env: { + PORT: '3000' + }, + // omit this property if you aren't serving HTML files and + // don't want to open a browser tab on start + callback: function (nodemon) { + nodemon.on('log', function (event) { + console.log(event.colour); + }); + + // opens browser on initial server start + nodemon.on('config:update', function () { + // Delay before server listens on port + setTimeout(function() { + require('open')('http://localhost:3000'); + }, 1000); + }); + + // refreshes browser when server reboots + nodemon.on('restart', function () { + // Delay before server listens on port + setTimeout(function() { + require('fs').writeFileSync('.rebooted', 'rebooted'); + }, 1000); + }); + } + } + } + }, }); // Load NPM tasks @@ -145,7 +179,7 @@ module.exports = (grunt) => { // grunt.loadNpmTasks('grunt-protractor-coverage'); // Make sure upload directory exists - grunt.task.registerTask('mkdir:upload', 'Task that makes sure upload directory exists.', () => { + grunt.task.registerTask('mkdir:upload', 'Task that makes sure upload directory exists.', function() { // Get the callback const done = this.async(); grunt.file.mkdir(path.normalize(__dirname + '/modules/users/client/img/profile/uploads')); @@ -192,11 +226,11 @@ module.exports = (grunt) => { // Lint CSS and JavaScript files. grunt.registerTask('lint', ['sass', 'jshint', 'eslint', 'csslint']); // Lint project files and minify them into two production files. - grunt.registerTask('build', ['env:dev', 'lint', 'uglify', 'cssmin']); + grunt.registerTask('build', ['env:dev', 'uglify', 'cssmin']); // Run the project in development mode - grunt.registerTask('default', ['env:dev', 'lint', 'mkdir:upload', 'copy:localConfig', 'server']); + grunt.registerTask('default', ['env:dev', 'mkdir:upload', 'copy:localConfig', 'server']); // Run the project in debug mode - grunt.registerTask('debug', ['env:dev', 'lint', 'mkdir:upload', 'copy:localConfig', 'concurrent:debug']); + grunt.registerTask('debug', ['env:dev', 'mkdir:upload', 'copy:localConfig', 'concurrent:debug']); // Run the project in production mode grunt.registerTask('prod', ['build', 'env:prod', 'mkdir:upload', 'copy:localConfig', 'server']); }; diff --git a/modules/core/client/app/init.js b/modules/core/client/app/init.js index e220074..e66d007 100644 --- a/modules/core/client/app/init.js +++ b/modules/core/client/app/init.js @@ -13,13 +13,12 @@ angular.module(ApplicationConfiguration.applicationModuleName).config(['$locatio ]); angular.module(ApplicationConfiguration.applicationModuleName).run(function ($rootScope, $state, Authentication) { - // Check authentication before changing state $rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) { if (toState.data && toState.data.roles && toState.data.roles.length > 0) { var allowed = false; toState.data.roles.forEach(function (role) { - if (Authentication.user.roles !== undefined && Authentication.user.roles.indexOf(role) !== -1) { + if (Authentication.user && Authentication.user.roles !== undefined && Authentication.user.roles.indexOf(role) !== -1) { allowed = true; return true; } @@ -27,7 +26,7 @@ angular.module(ApplicationConfiguration.applicationModuleName).run(function ($ro if (!allowed) { event.preventDefault(); - if (Authentication.user !== undefined && typeof Authentication.user === 'object') { + if (Authentication.user && typeof Authentication.user === 'object') { $state.go('forbidden'); } else { $state.go('authentication.signin').then(function () { diff --git a/modules/core/client/css/core.css b/modules/core/client/css/core.css index be5846a..9c86160 100644 --- a/modules/core/client/css/core.css +++ b/modules/core/client/css/core.css @@ -1,6 +1,11 @@ .content { margin-top: 50px; } + +.content-footer { + min-height: calc(100vh - 67px); +} + .undecorated-link:hover { text-decoration: none; } @@ -69,3 +74,54 @@ a:hover .header-profile-image { .hero-widget var { display: block; height: 64px; font-size: 64px; line-height: 64px; font-style: normal; } .hero-widget label { font-size: 17px; } .hero-widget .options { margin-top: 10px; } + +.footer { + height: 17px; + text-align: center; +} + +.footer a { + color: #333; + font-weight: bold; + font-size: 12px; + font-family: Calibri, serif; +} + +.footer-version { + color: #333; + font-weight: bold; + font-size: 12px; + font-family: Calibri, serif; +} + +.alert { + position: fixed; + top: 80px; + left: 50%; + transform: translateX(-50%) translateY(0); + + z-index: 9999; + + padding: 10px 16px; + min-width: 220px; + + background: #ffffff; + border: 1px solid #38b2ac; + color: #065f5b; + + border-radius: 6px; + box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15); + + text-align: center; + font-size: 14px; + font-weight: 500; + + opacity: 1; + transition: opacity 0.25s ease, transform 0.25s ease; +} + +.alert.ng-hide { + opacity: 0; + transform: translateX(-50%) translateY(-10px); + pointer-events: none; +} diff --git a/modules/core/client/img/brand/favicon.ico b/modules/core/client/img/brand/favicon.ico index 756ec7e..80a09c8 100644 Binary files a/modules/core/client/img/brand/favicon.ico and b/modules/core/client/img/brand/favicon.ico differ diff --git a/modules/core/client/img/brand/logo.png b/modules/core/client/img/brand/logo.png index d28cc87..a368f52 100644 Binary files a/modules/core/client/img/brand/logo.png and b/modules/core/client/img/brand/logo.png differ diff --git a/modules/core/client/views/confirm.client.view.html b/modules/core/client/views/confirm.client.view.html index 99af783..903fb49 100644 --- a/modules/core/client/views/confirm.client.view.html +++ b/modules/core/client/views/confirm.client.view.html @@ -1,5 +1,5 @@