Merge pull request #17 from JBB0807/save-backend-b

Working save and deploy
This commit is contained in:
JB Balahadia 2025-05-08 01:26:04 -07:00 committed by GitHub
commit 771f1e680c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 1235 additions and 86008 deletions

View file

@ -4,11 +4,67 @@ const axios = require("axios");
const bcrypt = require("bcrypt");
require("dotenv").config();
const DB_ASSIGNMENT_SERVICE_URL = process.env.DB_ASSIGNMENT_SERVICE_URL;
const DEPLOY_API_URL = process.env.DEPLOY_API_URL || "http://localhost:3600";
studentRouter.post("/save", async (req, res) => {
//get the app name and code and save the latest jupyter file in s3 bucket
const { appName, code } = req.body;
studentRouter.post("/save", (req, res) => {});
const notebook = {
cells: [
{
cell_type: "code",
execution_count: null,
metadata: {
language: "python"
},
outputs: [],
source: code.split('\n').map(line => line + '\n')
}
],
metadata: {
kernelspec: {
display_name: "Python 3",
language: "python",
name: "python3"
},
language_info: {
name: "python",
version: "3.x"
}
},
nbformat: 4,
nbformat_minor: 5
};
studentRouter.post("/deploy", (req, res) => {});
// Convert the notebook object to a JSON string and then to base64
const jsonString = JSON.stringify(notebook, null, 2);
const base64 = Buffer.from(jsonString, 'utf-8').toString('base64');
const notebookName = `${Date.now()}-notebook.ipynb`;
console.log("DEPLOY_API_URL:", DEPLOY_API_URL);
await fetch(`${DEPLOY_API_URL}/${appName}/upload`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ notebookName: notebookName, fileContentBase64: base64 })
})
.then((response) => {
if (!response.ok) throw new Error("Failed to save notebook");
return response.json();
})
.then((data) => {
console.log("Notebook saved successfully:", data);
res.status(200).json(data);
})
.catch((error) => {
console.error("Error saving notebook:", error.message);
res.status(500).json({ error: error.message });
});
});
studentRouter.post("/deploy", (req, res) => {
});
studentRouter.get("/assignment/:qrnum", (req, res) => {
const qrnum = req.params.qrnum;
@ -68,4 +124,18 @@ studentRouter.post("/verify", async (req, res) => {
}
});
// post restart from deployment service /appname/restart endpoint
studentRouter.post("/restart", async (req, res) => {
const { appName } = req.body;
console.log("Received request to restart app:", appName);
try {
const response = await axios.post(`${DEPLOY_API_URL}/${appName}/restart`);
console.log("Restart response:", response.data);
res.status(response.status).json(response.data);
} catch (error) {
console.error("Error restarting app:", error.message);
res.status(error.response?.status || 500).json({ error: error.message });
}
});
module.exports = studentRouter;

View file

@ -1,4 +1,4 @@
require('dotenv').config();
require("dotenv").config();
const cors = require("cors");
const passport = require("passport");
const session = require("express-session");
@ -13,7 +13,7 @@ const s3 = new AWS.S3({
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: process.env.AWS_REGION,
s3ForcePathStyle: true
s3ForcePathStyle: true,
});
const BUCKET = process.env.COMMON_BUCKET;
@ -51,18 +51,25 @@ app.get("/", (req, res) => {
res.send("OK");
});
app.get("/notebook/save/:appname", async (req, res) => {
});
app.get("/notebook/:appName", async (req, res) => {
try {
const { appName } = req.params;
const prefix = `${appName}/notebooks/`;
const list = await s3.listObjectsV2({ Bucket: BUCKET, Prefix: prefix }).promise();
const list = await s3
.listObjectsV2({ Bucket: BUCKET, Prefix: prefix })
.promise();
if (!list.Contents || list.Contents.length === 0) {
return res.status(404).json({ error: "Notebook not found" });
}
const latest = list.Contents.reduce((prev, curr) =>
prev.LastModified > curr.LastModified ? prev : curr
);
const data = await s3.getObject({ Bucket: BUCKET, Key: latest.Key }).promise();
const data = await s3
.getObject({ Bucket: BUCKET, Key: latest.Key })
.promise();
res.send(data.Body.toString("utf-8"));
} catch (error) {
console.error("Failed to load notebook:", error);
@ -71,4 +78,6 @@ app.get("/notebook/:appName", async (req, res) => {
});
const port = process.env.NODE_PORT || 8080;
app.listen({ port: port, host: '::', ipv6Only: false }, () => console.log(`Listening on ${port}...`));
app.listen({ port: port, host: "::", ipv6Only: false }, () =>
console.log(`Listening on ${port}...`)
);

View file

@ -7,7 +7,7 @@ RUN npm ci --only=production
COPY src ./src
COPY snakeapi_service ./snakeapi_service
ENV FLY_ACCESS_TOKEN="FlyV1 fm2_lJPECAAAAAAACJJHxBByW6wRXXxQ17OD8xlRRR5cwrVodHRwczovL2FwaS5mbHkuaW8vdjGUAJLOAA//nh8Lk7lodHRwczovL2FwaS5mbHkuaW8vYWFhL3YxxDwQBQg0Vif1OLMYOJOtNVokX+9SIVL2E8QoNub0JDBE4wNh97aUAPiiNvpAAMhM/eO7SWUVAx5rcDTBjf7ETtdnvXtcHaqOnK2HmNSV9K9UVy5Or3Sd+0+kxqDoWRXGE0y5pdb8+HNqwMcryszYvAv8HVcoKFgF4qd7GmzniNvZETOkrbsjMsU1+mVXTMQgh7H9z6IcGVjJozV92cDsSn91USqxOmBdwFQAkFGwPV0=,fm2_lJPETtdnvXtcHaqOnK2HmNSV9K9UVy5Or3Sd+0+kxqDoWRXGE0y5pdb8+HNqwMcryszYvAv8HVcoKFgF4qd7GmzniNvZETOkrbsjMsU1+mVXTMQQqnJP464DwxC6D4e3p9THZMO5aHR0cHM6Ly9hcGkuZmx5LmlvL2FhYS92MZgEks5oESI9zwAAAAEkCUBbF84AD2FZCpHOAA9hWQzEEL6gO8olFxMOq1uFxP1yJavEIBDKb7RuqVr/sFQniKl0S2HMM6+AQJH3940ly0mufbYx"
ENV FLY_ACCESS_TOKEN="FlyV1 fm2_lJPECAAAAAAACJJHxBAjRF69RAjf3FXXuVT+M3bcwrVodHRwczovL2FwaS5mbHkuaW8vdjGUAJLOAA//nh8Lk7lodHRwczovL2FwaS5mbHkuaW8vYWFhL3YxxDxmIdNTu/DGjUSyYxuC5W7Rio4bNT5w6c1Ihi+ZJnjcmEutbt5KuyFcCo1C0CFPEhrP4hY5SEvXN58GHUDEToWZ0GwI5ndmIsZnhWSG8TBixbuFTaBb8lTBU5lNOvm2l4rX1i6dfId7S9Ko6qXpOzl9oYngy0zw+g2MwXuQrH6/XELBdEy/KThVeTEjt8QgBzOo/Eae+DsrATm6WjVv9f5a4iS/s7WtYHydZZr3z9M=,fm2_lJPEToWZ0GwI5ndmIsZnhWSG8TBixbuFTaBb8lTBU5lNOvm2l4rX1i6dfId7S9Ko6qXpOzl9oYngy0zw+g2MwXuQrH6/XELBdEy/KThVeTEjt8QQNZaUoOrVdOnk6Vo/DkeMGsO5aHR0cHM6Ly9hcGkuZmx5LmlvL2FhYS92MZgEks5oGwzFzwAAAAEkEyrjF84AD2FZCpHOAA9hWQzEEASQrBHkPDFO3LlZDaxRRIjEIEW1ki/syKHnhFamHgze8PFeunPOAmNmh57hslV04lL7"
EXPOSE 3006
CMD ["node", "src/index.js"]

File diff suppressed because it is too large Load diff

View file

@ -220,6 +220,28 @@ app.post("/:appName/upload", async (req, res) => {
}
});
// restart a Fly app
app.post("/:appName/restart", async (req, res) => {
const { appName } = req.params;
try {
const fly = createFlyClient();
const { data: machines } = await fly.get(`/apps/${appName}/machines`);
if (!machines || !Array.isArray(machines) || machines.length === 0) {
return res.status(404).json({ error: "No machines found for this app" });
}
const results = await Promise.all(
machines.map(machine =>
fly.post(`/apps/${appName}/machines/${machine.id}/restart`)
)
);
res.json({ status: "restarted", app: appName, count: results.length });
} catch (err) {
console.error("Restart error:", err.response?.data || err.message);
res.status(500).json({ error: err.response?.data || err.message });
}
});
// Delete a Fly app
app.post("/:appName/delete", async (req, res) => {
const { appName } = req.params;

View file

@ -0,0 +1,22 @@
# General
.DS_Store
.vscode
# Node
node_modules
# SvelteKit
.output
.svelte-kit
/build
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
# Netlify
.netlify

View file

@ -0,0 +1,18 @@
# See https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/
name: Fly Deploy
on:
push:
branches:
- main
jobs:
deploy:
name: Deploy app
runs-on: ubuntu-latest
concurrency: deploy-group # optional: ensure only one action runs at a time
steps:
- uses: actions/checkout@v4
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy --remote-only
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}

View file

@ -0,0 +1,47 @@
# syntax = docker/dockerfile:1
# Adjust NODE_VERSION as desired
ARG NODE_VERSION=22.13.0
FROM node:${NODE_VERSION}-slim AS base
LABEL fly_launch_runtime="SvelteKit"
# SvelteKit app lives here
WORKDIR /app
# Set production environment
ENV NODE_ENV="production"
ENV PORT="3005"
# Throw-away build stage to reduce size of final image
FROM base AS build
# Install packages needed to build node modules
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential node-gyp pkg-config python-is-python3
# Install node modules
COPY .npmrc package-lock.json package.json ./
RUN npm ci --include=dev
# Copy application code
COPY . .
# Build application
RUN npm run build
# Remove development dependencies
RUN npm prune --omit=dev
# Final stage for app image
FROM base
# Copy built application
COPY --from=build /app/build /app/build
COPY --from=build /app/node_modules /app/node_modules
COPY --from=build /app/package.json /app
# Start the server by default, this can be overwritten at runtime
EXPOSE 3005
CMD [ "node", "./build/index.js" ]

View file

@ -0,0 +1,20 @@
app = "gameboard-service-aged-glitter-8141"
primary_region = "sea"
[env]
PORT = "3005"
[build]
[http_service]
internal_port = 3005
force_https = true
auto_stop_machines = "stop"
auto_start_machines = true
min_machines_running = 0
processes = ["app"]
[[vm]]
memory = "1gb"
cpu_kind = "shared"
cpus = 1

File diff suppressed because it is too large Load diff

View file

@ -15,11 +15,13 @@
"test:unit": "vitest run"
},
"devDependencies": {
"@flydotio/dockerfile": "^0.7.10",
"@iconify-json/heroicons": "^1.1.11",
"@iconify-json/heroicons-solid": "^1.1.7",
"@neoconfetti/svelte": "^1.0.0",
"@playwright/test": "^1.28.1",
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/adapter-node": "^1.3.1",
"@sveltejs/adapter-static": "^2.0.3",
"@sveltejs/kit": "^1.30.3",
"@tailwindcss/forms": "^0.5.4",

View file

@ -0,0 +1,12 @@
import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
const response = await resolve(event);
response.headers.delete('X-Frame-Options');
response.headers.set('X-Frame-Options', 'ALLOWALL');
response.headers.set('Content-Security-Policy', "frame-ancestors *");
return response;
};

View file

@ -1,10 +1,11 @@
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
// import adapter from '@sveltejs/adapter-auto';
import adapter from "@sveltejs/adapter-static";
// import adapter from '@sveltejs/adapter-node';
import adapter from '@sveltejs/adapter-node';
// import adapter from "@sveltejs/adapter-static";
import { vitePreprocess } from "@sveltejs/kit/vite";
import { vitePreprocess } from '@sveltejs/kit/vite';
/** @type {import('@sveltejs/kit').Config} */
const config = {
@ -13,12 +14,8 @@ const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter({
// These are the defaults, see https://kit.svelte.dev/docs/adapter-static
pages: "build",
assets: "build",
fallback: undefined,
precompress: false,
strict: true
// This option specifies the output directory for the build
out: 'build'
})
}
};

View file

@ -6,7 +6,7 @@ mkdir -p "${NOTEBOOK_DIR}"
# fetch latest notebook
echo "Syncing notebooks from S3 bucket..."
aws --endpoint-url "$AWS_ENDPOINT_URL_S3" --region "$AWS_REGION" \
s3 sync "s3://$BUCKET_NAME/$INSTANCE_PREFIX/notebooks/" "${NOTEBOOK_DIR}/"
s3 sync "s3://$COMMON_BUCKET/$INSTANCE_PREFIX/notebooks/" "${NOTEBOOK_DIR}/"
# convert to Python script for dynamic import
echo "Finding the latest notebook..."