Update CI/CD workflows (#66)

**BREAKING CHANGES:**
* Schema change: put user social logins to a `socialLogins` map
* Original users would migrate to new schema without problem (because
sharing the same email)
* Docker images are moved to a new account
[linuxodyssey](https://hub.docker.com/u/linuxodyssey)
* Images are separate to different repo: frontend, backend, analytics,
quest-base
  * Tags are only for versioning.

Major improvements:
* Deploy to production on releasing tags
* Deploy to staging on main branch pushes
* build docker images with versioning tags: `latest`, `main`, `v0.1.0`,
`pr-63`, etc.
* Add basic auth for admin dashboard. Specify username and password by
env.

Minor improvements:
* Upgrade code review bot to use GPT 4
* Centralize all user creation in `models/userManager.js`
* Update docker get image method, based on QUEST_PREFIX to generate the
image name
This commit is contained in:
Lin, Hung-Shin authored and GitHub committed 2023-11-19 15:10:23 +08:00
commit 9bdaa95bad
28 files changed
+364 -178

No files matched your search

+57
View File
@@ -0,0 +1,57 @@
name: Build Docker Image
on:
workflow_call:
inputs:
imageName:
type: string
required: true
description: 'Name of the image to build'
dockerfilePath:
type: string
required: true
description: 'Path to the Dockerfile'
target:
type: string
required: false
description: 'Target to build'
secrets:
DOCKERHUB_USERNAME:
required: true
DOCKERHUB_TOKEN:
required: true
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ inputs.imageName }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build Image
uses: docker/build-push-action@v2
with:
context: .
file: ${{ inputs.dockerfilePath }}
target: ${{ inputs.target }}
push: true
tags: ${{ steps.meta.outputs.tags }}
+36
View File
@@ -0,0 +1,36 @@
name: Build
on:
workflow_call:
jobs:
build-backend:
uses: ./.github/workflows/build-docker-image.yml
with:
imageName: 'linuxodyssey/backend'
dockerfilePath: './server/Dockerfile'
target: prod
secrets: inherit
build-frontend:
uses: ./.github/workflows/build-docker-image.yml
with:
imageName: 'linuxodyssey/frontend'
dockerfilePath: './app/Dockerfile'
target: prod
secrets: inherit
build-analytics:
uses: ./.github/workflows/build-docker-image.yml
with:
imageName: 'linuxodyssey/analytics'
dockerfilePath: './packages/analytics/Dockerfile'
target: prod
secrets: inherit
build-base:
uses: ./.github/workflows/build-docker-image.yml
with:
imageName: 'linuxodyssey/quest-base'
dockerfilePath: './quests/Dockerfile'
secrets: inherit
+45
View File
@@ -0,0 +1,45 @@
name: CI Checks
on:
workflow_call:
jobs:
ci:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20
- name: Install dependencies
run: yarn install
- name: Lint
run: yarn lint
- name: Test
run: yarn test
e2e:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Build Test Environment, Stage 1
run: docker-compose -f docker-compose.testing.yml build base backend app-test cypress
- name: Build Test Quests
run: docker-compose -f docker-compose.testing.yml run --rm backend yarn build:quests
- name: Run Cypress Tests
env:
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
GITHUB_CLIENT_ID: ${{ secrets.GITHUB_CLIENT_ID }}
GITHUB_CLIENT_SECRET: ${{ secrets.GITHUB_CLIENT_SECRET }}
run: docker-compose -f docker-compose.testing.yml run cypress
-110
View File
@@ -1,110 +0,0 @@
name: CI/CD
on:
push:
jobs:
ci:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20
- name: Install dependencies
run: yarn install
- name: Lint
run: yarn lint
- name: Test
run: yarn test
e2e:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Build Test Environment, Stage 1
run: docker-compose -f docker-compose.testing.yml build base backend app-test
- name: Build Test Quests
run: docker-compose -f docker-compose.testing.yml run --rm backend yarn build:quests
- name: Run Cypress Tests
env:
GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }}
GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }}
GITHUB_CLIENT_ID: ${{ secrets.GITHUB_CLIENT_ID }}
GITHUB_CLIENT_SECRET: ${{ secrets.GITHUB_CLIENT_SECRET }}
run: docker-compose -f docker-compose.testing.yml run cypress
build-and-push:
needs:
- ci
- e2e
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build backend
uses: docker/build-push-action@v2
with:
context: .
file: ./server/Dockerfile
target: prod
push: true
tags: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/linux-odyssey:backend
- name: Build app
uses: docker/build-push-action@v2
with:
context: .
file: ./app/Dockerfile
target: prod
push: true
tags: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/linux-odyssey:app
- name: Build analytics
uses: docker/build-push-action@v2
with:
context: .
file: ./packages/analytics/Dockerfile
target: prod
push: true
tags: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/linux-odyssey:analytics
- name: Build base image
uses: docker/build-push-action@v2
with:
context: .
file: ./quests/Dockerfile
push: true
tags: docker.io/${{ secrets.DOCKERHUB_USERNAME }}/linux-odyssey:base
deploy:
needs: build-and-push
runs-on: ubuntu-latest
steps:
- name: SSH into server and update service
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
port: ${{ secrets.SERVER_PORT }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
+32
View File
@@ -0,0 +1,32 @@
name: Production Deployment
on:
push:
tags:
- 'v*'
concurrency:
group: production
cancel-in-progress: true
jobs:
ci-checks:
uses: ./.github/workflows/ci-checks.yml
build:
needs: ci-checks
uses: ./.github/workflows/build.yml
secrets: inherit
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: SSH into server and update service
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.GCP_SSH_HOST }}
port: ${{ secrets.GCP_SSH_PORT }}
username: ${{ secrets.GCP_SSH_USER }}
key: ${{ secrets.GCP_SSH_PRIVATE_KEY }}
@@ -1,11 +1,17 @@
name: Code Review GPT name: Pull Request Workflow
on: on:
pull_request: pull_request:
branches: [main]
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
run_code_review: ci-checks:
uses: ./.github/workflows/ci-checks.yml
code_review:
permissions: permissions:
pull-requests: write pull-requests: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -18,5 +24,10 @@ jobs:
uses: mattzcarey/code-review-gpt@v0.1.4-alpha uses: mattzcarey/code-review-gpt@v0.1.4-alpha
with: with:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
MODEL: 'gpt-3.5-turbo' MODEL: 'gpt-4'
GITHUB_TOKEN: ${{ github.token }} GITHUB_TOKEN: ${{ github.token }}
build:
needs: ci-checks
uses: ./.github/workflows/build.yml
secrets: inherit
+32
View File
@@ -0,0 +1,32 @@
name: Staging Deployment
on:
push:
branches:
- main
concurrency:
group: staging
cancel-in-progress: true
jobs:
ci-checks:
uses: ./.github/workflows/ci-checks.yml
build:
needs: ci-checks
uses: ./.github/workflows/build.yml
secrets: inherit
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: SSH into server and update service
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
port: ${{ secrets.SERVER_PORT }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
+7 -4
View File
@@ -1,7 +1,7 @@
version: '3' version: '3'
services: services:
app: app:
image: lancatlin/linux-odyssey:app image: linuxodyssey/frontend
depends_on: depends_on:
- backend - backend
restart: always restart: always
@@ -13,8 +13,9 @@ services:
internal: internal:
ports: ports:
- 8080:80 - 8080:80
backend: backend:
image: lancatlin/linux-odyssey:backend image: linuxodyssey/backend
depends_on: depends_on:
- db - db
restart: always restart: always
@@ -36,7 +37,7 @@ services:
- TRUSTED_PROXIES=${TRUSTED_PROXIES} - TRUSTED_PROXIES=${TRUSTED_PROXIES}
analytics: analytics:
image: lancatlin/linux-odyssey:analytics image: linuxodyssey/analytics
build: build:
context: . context: .
dockerfile: packages/analytics/Dockerfile dockerfile: packages/analytics/Dockerfile
@@ -50,6 +51,8 @@ services:
- '3001:3001' - '3001:3001'
environment: environment:
- MONGO_URL=mongodb://${MONGO_USER}:${MONGO_PASSWORD}@db:27017/${MONGO_DB} - MONGO_URL=mongodb://${MONGO_USER}:${MONGO_PASSWORD}@db:27017/${MONGO_DB}
- ADMIN_USERNAME=${ADMIN_USERNAME}
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
db: db:
image: mongo image: mongo
@@ -76,7 +79,7 @@ services:
- BUG_REPORT_URL=${BUG_REPORT_URL} - BUG_REPORT_URL=${BUG_REPORT_URL}
base: base:
image: lancatlin/linux-odyssey:base image: linuxodyssey/quest-base
build: build:
context: . context: .
dockerfile: ./quests/Dockerfile dockerfile: ./quests/Dockerfile
+5 -3
View File
@@ -16,7 +16,7 @@ services:
- app-test - app-test
app-test: app-test:
image: lancatlin/linux-odyssey:app image: linuxodyssey/app
build: build:
context: . context: .
dockerfile: app/Dockerfile dockerfile: app/Dockerfile
@@ -29,7 +29,7 @@ services:
- backend - backend
backend: backend:
image: lancatlin/linux-odyssey:backend image: linuxodyssey/backend
build: build:
context: . context: .
dockerfile: server/Dockerfile dockerfile: server/Dockerfile
@@ -69,9 +69,11 @@ services:
- MONGO_USER=user - MONGO_USER=user
- MONGO_PASS=user-password - MONGO_PASS=user-password
- MONGO_DB=odyssey-test - MONGO_DB=odyssey-test
ports:
- 27017:27017
base: base:
image: lancatlin/linux-odyssey:base image: linuxodyssey/quest-base
build: build:
context: . context: .
dockerfile: ./quests/Dockerfile dockerfile: ./quests/Dockerfile
+4 -1
View File
@@ -14,6 +14,7 @@ services:
volumes: volumes:
- ./app:/app/app - ./app:/app/app
- ./packages:/app/packages - ./packages:/app/packages
backend: backend:
depends_on: depends_on:
- db - db
@@ -62,6 +63,8 @@ services:
- '3001:3001' - '3001:3001'
environment: environment:
- MONGO_URL=mongodb://db:27017/odyssey-test - MONGO_URL=mongodb://db:27017/odyssey-test
- USERNAME=user
- PASSWORD=user
volumes: volumes:
- ./packages/analytics:/app/packages/analytics - ./packages/analytics:/app/packages/analytics
- ./packages/models:/app/packages/models - ./packages/models:/app/packages/models
@@ -85,7 +88,7 @@ services:
- SWAGGER_FILE=/tmp/swagger.yaml - SWAGGER_FILE=/tmp/swagger.yaml
base: base:
image: lancatlin/linux-odyssey:base image: linuxodyssey/quest-base
build: build:
context: . context: .
dockerfile: ./quests/Dockerfile dockerfile: ./quests/Dockerfile
+1
View File
@@ -12,6 +12,7 @@
"dependencies": { "dependencies": {
"@linux-odyssey/models": "*", "@linux-odyssey/models": "*",
"express": "^4.18.2", "express": "^4.18.2",
"express-basic-auth": "^1.2.1",
"express-handlebars": "^7.1.2" "express-handlebars": "^7.1.2"
}, },
"devDependencies": { "devDependencies": {
+9
View File
@@ -1,7 +1,16 @@
function getOrFailed(key) {
if (!process.env[key]) {
throw new Error(`Missing required environment variable: ${key}`)
}
return process.env[key]
}
const config = { const config = {
host: process.env.HOST || 'localhost', host: process.env.HOST || 'localhost',
port: process.env.PORT || 3001, port: process.env.PORT || 3001,
db: process.env.MONGO_URL || 'mongodb://localhost:27017/odyssey-test', db: process.env.MONGO_URL || 'mongodb://localhost:27017/odyssey-test',
username: getOrFailed('ADMIN_USERNAME'),
password: getOrFailed('ADMIN_PASSWORD'),
} }
config.baseUrl = process.env.BASE_URL || `http://${config.host}:${config.port}` config.baseUrl = process.env.BASE_URL || `http://${config.host}:${config.port}`
+8
View File
@@ -1,6 +1,7 @@
import connectDB from '@linux-odyssey/models' import connectDB from '@linux-odyssey/models'
import express from 'express' import express from 'express'
import { engine } from 'express-handlebars' import { engine } from 'express-handlebars'
import basicAuth from 'express-basic-auth'
import config from './config.js' import config from './config.js'
import router from './router.js' import router from './router.js'
@@ -13,6 +14,13 @@ async function main() {
app.set('view engine', 'handlebars') app.set('view engine', 'handlebars')
app.set('views', './views') app.set('views', './views')
app.use(
basicAuth({
users: { [config.username]: config.password },
challenge: true,
realm: 'Linux Odyssey Admin Dashboard',
})
)
app.use(router) app.use(router)
app.use(express.static('./public')) app.use(express.static('./public'))
+1 -2
View File
@@ -3,8 +3,7 @@ import { Session, User } from '@linux-odyssey/models'
function loginMethods(user) { function loginMethods(user) {
const methods = [] const methods = []
if (user.hashedPassword) methods.push('password') if (user.hashedPassword) methods.push('password')
if (user.google?.id) methods.push('google') if (user.socialLogins) methods.push(...Object.keys(user.socialLogins))
if (user.github?.id) methods.push('github')
return methods return methods
} }
+20 -7
View File
@@ -1,5 +1,20 @@
import { model, Schema } from 'mongoose' import { model, Schema } from 'mongoose'
const socialLoginSchema = new Schema({
provider: {
type: String,
required: true,
},
id: {
type: String,
required: true,
},
displayName: {
type: String,
required: false,
},
})
const User = model( const User = model(
'User', 'User',
new Schema( new Schema(
@@ -15,13 +30,11 @@ const User = model(
required: true, required: true,
}, },
hashedPassword: String, hashedPassword: String,
google: { socialLogins: {
id: String, type: Map,
displayName: String, of: socialLoginSchema,
}, default: {},
github: { required: true,
id: String,
displayName: String,
}, },
}, },
{ {
+1 -1
View File
@@ -1,3 +1,3 @@
FROM lancatlin/linux-odyssey:base FROM linuxodyssey/quest-base
COPY home ${HOME} COPY home ${HOME}
+1 -1
View File
@@ -1,3 +1,3 @@
FROM lancatlin/linux-odyssey:base FROM linuxodyssey/quest-base
COPY home ${HOME} COPY home ${HOME}
+8 -6
View File
@@ -1,5 +1,4 @@
import { matchedData } from 'express-validator' import { matchedData } from 'express-validator'
import { User } from '@linux-odyssey/models'
import { genJWT } from '../../utils/auth.js' import { genJWT } from '../../utils/auth.js'
import { createUser } from '../../models/userManager.js' import { createUser } from '../../models/userManager.js'
import { asyncHandler } from '../../middleware/error.js' import { asyncHandler } from '../../middleware/error.js'
@@ -17,7 +16,7 @@ export const issueToken = asyncHandler(async (req, res) => {
export const register = asyncHandler(async (req, res, next) => { export const register = asyncHandler(async (req, res, next) => {
const { username, password, email } = matchedData(req) const { username, password, email } = matchedData(req)
const user = await createUser(username, email, password) const user = await createUser(username, email, { password })
req.login(user, (err) => { req.login(user, (err) => {
if (err) { if (err) {
next(err) next(err)
@@ -50,7 +49,7 @@ export function checkSession(req, res) {
}) })
} }
export function socialLogin(req, res) { export function socialLoginHandler(req, res) {
const { newUser } = req.user const { newUser } = req.user
if (newUser) { if (newUser) {
req.session.newUser = newUser req.session.newUser = newUser
@@ -71,10 +70,13 @@ export const registerFromSession = asyncHandler(async (req, res, next) => {
return return
} }
newUser.username = username const { email, socialLogin } = newUser
const user = new User(newUser)
await user.save() if (!socialLogin) {
throw new Error('Socail Login not found')
}
const user = await createUser(username, email, { socialLogin })
req.login(user, (err) => { req.login(user, (err) => {
if (err) { if (err) {
+3 -3
View File
@@ -7,8 +7,8 @@ import {
register, register,
checkSession, checkSession,
logout, logout,
socialLogin,
registerFromSession, registerFromSession,
socialLoginHandler,
} from '../controllers/authController.js' } from '../controllers/authController.js'
import { import {
checkUsernameValidators, checkUsernameValidators,
@@ -49,7 +49,7 @@ if (enabledMethods.google) {
'/google/callback', '/google/callback',
authenticateRateLimit, authenticateRateLimit,
passport.authenticate('google'), passport.authenticate('google'),
socialLogin socialLoginHandler
) )
} }
@@ -59,7 +59,7 @@ if (enabledMethods.github) {
'/github/callback', '/github/callback',
authenticateRateLimit, authenticateRateLimit,
passport.authenticate('github'), passport.authenticate('github'),
socialLogin socialLoginHandler
) )
} }
+1 -1
View File
@@ -4,7 +4,7 @@ import config from '../config.js'
import oauthVerify from './oauthVerify.js' import oauthVerify from './oauthVerify.js'
function verify(accessToken, refreshToken, profile, cb) { function verify(accessToken, refreshToken, profile, cb) {
oauthVerify('github', profile, { 'github.id': profile.id }, cb) oauthVerify('github', profile, { 'socialLogins.github.id': profile.id }, cb)
} }
const enabled = config.github.clientID && config.github.clientSecret const enabled = config.github.clientID && config.github.clientSecret
+1 -1
View File
@@ -4,7 +4,7 @@ import config from '../config.js'
import oauthVerify from './oauthVerify.js' import oauthVerify from './oauthVerify.js'
function verify(issuer, profile, cb) { function verify(issuer, profile, cb) {
oauthVerify('google', profile, { 'google.id': profile.id }, cb) oauthVerify('google', profile, { 'socialLogins.google.id': profile.id }, cb)
} }
const enabled = config.google.clientID && config.google.clientSecret const enabled = config.google.clientID && config.google.clientSecret
+21 -16
View File
@@ -2,26 +2,31 @@ import { User } from '@linux-odyssey/models'
export default async function oauthVerify(provider, profile, query, cb) { export default async function oauthVerify(provider, profile, query, cb) {
process.nextTick(async () => { process.nextTick(async () => {
const email = profile.emails[0]?.value try {
if (!email) { const email = profile.emails[0]?.value
return cb(new Error('No email found')) if (!email) {
} return cb(new Error('No email found'))
let user = await User.findOne(query)
if (!user) {
user = await User.findOne({ email })
if (!user) {
const newUser = { email }
newUser[provider] = profile
return cb(null, { newUser })
} }
user[provider] = {
const socialLogin = {
provider,
id: profile.id, id: profile.id,
displayName: profile.displayName, displayName: profile.displayName,
} }
await user.save() let user = await User.findOne(query)
} if (!user) {
user = await User.findOne({ email })
if (!user) {
const newUser = { email, socialLogin }
return cb(null, { newUser })
}
user.socialLogins.set(provider, socialLogin)
await user.save()
}
return cb(null, user) return cb(null, user)
} catch (err) {
return cb(err)
}
}) })
} }
+11 -7
View File
@@ -26,14 +26,18 @@ if (enabledMethods.github) {
passport.serializeUser((user, done) => { passport.serializeUser((user, done) => {
process.nextTick(async () => { process.nextTick(async () => {
if (!(await UserProfile.exists({ user: user._id }))) { try {
UserProfile.create({ user: user._id }) if (user._id && !(await UserProfile.exists({ user: user._id }))) {
await UserProfile.create({ user: user._id })
}
done(null, {
_id: user._id,
username: user.username,
email: user.email,
})
} catch (err) {
done(err)
} }
done(null, {
_id: user._id,
username: user.username,
email: user.email,
})
}) })
}) })
+10 -3
View File
@@ -24,7 +24,6 @@ const config = {
port: get('PORT', 3000), port: get('PORT', 3000),
db: get('MONGO_URL', 'mongodb://localhost:27017/odyssey-test'), db: get('MONGO_URL', 'mongodb://localhost:27017/odyssey-test'),
secret: get('SECRET_KEY', ''), secret: get('SECRET_KEY', ''),
dockerNetwork: get('DOCKER_NETWORK', 'linux-odyssey-players'),
isProduction: process.env.NODE_ENV === 'production', isProduction: process.env.NODE_ENV === 'production',
hostPwd: get('HOST_PWD', ''), hostPwd: get('HOST_PWD', ''),
containerExpiry: get('EXPIRY', 1000 * 60 * 60), containerExpiry: get('EXPIRY', 1000 * 60 * 60),
@@ -40,9 +39,13 @@ const config = {
trustedProxies: getTrustProxies('TRUSTED_PROXIES'), trustedProxies: getTrustProxies('TRUSTED_PROXIES'),
surveyUrl: getUrl('SURVEY_URL', ''), surveyUrl: getUrl('SURVEY_URL', ''),
bugReportUrl: getUrl('BUG_REPORT_URL', ''), bugReportUrl: getUrl('BUG_REPORT_URL', ''),
dockerImage: get('DOCKER_IMAGE', 'lancatlin/linux-odyssey'),
mountQuest: get('MOUNT_QUEST', ''), docker: {
network: get('DOCKER_NETWORK', 'linux-odyssey-players'),
defaultImage: get('QUEST_IMAGE', 'linuxodyssey/quest-base'),
imagePrefix: get('DOCKER_PREFIX', 'linuxodyssey/quest-'),
mountQuest: get('MOUNT_QUEST', ''),
},
} }
config.baseUrl = get('BASE_URL', `http://${config.host}:${config.port}`) config.baseUrl = get('BASE_URL', `http://${config.host}:${config.port}`)
@@ -52,4 +55,8 @@ config.testing = {
password: get('TESTING_PASSWORD', ''), password: get('TESTING_PASSWORD', ''),
} }
export function getQuestImage(id) {
return `${config.docker.imagePrefix}${id}`
}
export default config export default config
+5 -5
View File
@@ -1,5 +1,5 @@
import Docker from 'dockerode' import Docker from 'dockerode'
import config from '../config.js' import config, { getQuestImage } from '../config.js'
const engine = new Docker() const engine = new Docker()
@@ -11,17 +11,17 @@ const containerOptions = {
OpenStdin: true, OpenStdin: true,
StdinOnce: false, StdinOnce: false,
HostConfig: { HostConfig: {
NetworkMode: config.dockerNetwork, NetworkMode: config.docker.network,
}, },
} }
// const network = engine.getNetwork(config.dockerNetwork) // const network = engine.getNetwork(config.dockerNetwork)
export function createContainer(name, tag) { export function createContainer(name, questId) {
const option = { const option = {
...containerOptions, ...containerOptions,
name, name,
Image: `${config.dockerImage}:${tag}`, Image: getQuestImage(questId),
} }
if (!config.isProduction && config.hostPwd && config.mountQuest) { if (!config.isProduction && config.hostPwd && config.mountQuest) {
option.HostConfig.Bind = [ option.HostConfig.Bind = [
@@ -87,7 +87,7 @@ export function buildQuestImage(questPath, questId) {
src: ['Dockerfile', 'home'], src: ['Dockerfile', 'home'],
}, },
{ {
t: `${config.dockerImage}:${questId}`, t: getQuestImage(questId),
networkmode: 'none', networkmode: 'none',
memory: 10 * 1e6, memory: 10 * 1e6,
}, },
+10 -2
View File
@@ -1,16 +1,24 @@
import { User, UserProfile } from '@linux-odyssey/models' import { User, UserProfile } from '@linux-odyssey/models'
import { hashPassword } from '../utils/auth.js' import { hashPassword } from '../utils/auth.js'
export async function createUser(username, email, password) { export async function createUser(username, email, { password, socialLogin }) {
const user = new User({ const user = new User({
username, username,
email, email,
}) })
user.hashedPassword = await hashPassword(password) if (password) {
user.hashedPassword = await hashPassword(password)
} else if (socialLogin) {
user.socialLogins.set(socialLogin.provider, socialLogin)
} else {
throw new Error('No password or social login provided')
}
await user.save() await user.save()
const userProfile = new UserProfile({ const userProfile = new UserProfile({
user, user,
}) })
await userProfile.save() await userProfile.save()
return user
} }
+1 -1
View File
@@ -17,6 +17,6 @@ export default async function setupTest() {
return return
} }
await createUser(username, email, password) await createUser(username, email, { password })
console.log('Created testing account') console.log('Created testing account')
} }
+19
View File
@@ -1593,6 +1593,13 @@ base64url@3.x.x:
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
basic-auth@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==
dependencies:
safe-buffer "5.1.2"
bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2: bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
@@ -2850,6 +2857,13 @@ expect@^29.6.1:
jest-message-util "^29.6.1" jest-message-util "^29.6.1"
jest-util "^29.6.1" jest-util "^29.6.1"
express-basic-auth@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/express-basic-auth/-/express-basic-auth-1.2.1.tgz#d31241c03a915dd55db7e5285573049cfcc36381"
integrity sha512-L6YQ1wQ/mNjVLAmK3AG1RK6VkokA1BIY6wmiH304Xtt/cLTps40EusZsU1Uop+v9lTDPxdtzbFmdXfFO3KEnwA==
dependencies:
basic-auth "^2.0.1"
express-handlebars@^7.1.2: express-handlebars@^7.1.2:
version "7.1.2" version "7.1.2"
resolved "https://registry.yarnpkg.com/express-handlebars/-/express-handlebars-7.1.2.tgz#2471673d11af46f496cba4098a705f0217232fda" resolved "https://registry.yarnpkg.com/express-handlebars/-/express-handlebars-7.1.2.tgz#2471673d11af46f496cba4098a705f0217232fda"
@@ -5581,6 +5595,11 @@ rxjs@^7.5.1:
dependencies: dependencies:
tslib "^2.1.0" tslib "^2.1.0"
safe-buffer@5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
version "5.2.1" version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"