Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Java CI with Maven

on:
push:
branches: ["master", "main"]
pull_request:
branches: ["master", "main"]

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up JDK 25
uses: actions/setup-java@v4
with:
java-version: "25"
distribution: "temurin"
cache: maven

- name: Build and Test with Maven
run: mvn -B clean test
25 changes: 25 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Agent Instructions

## GitHub CLI (`gh`) Usage
When running `gh` commands in this project via an automated agent environment, ensure you bypass the default `GITHUB_TOKEN` environment variable. The agent environment may have an invalid `GITHUB_TOKEN` set, which `gh` prioritizes over valid keyring credentials, resulting in an `HTTP 401: Bad credentials` error.

**Workaround:** Prefix `gh` commands with `env -u GITHUB_TOKEN` to force the CLI to use the valid keyring authentication.

Example:
```bash
env -u GITHUB_TOKEN gh pr create --title "..." --body "..."
```

## Developer Guidelines & Code Architecture

### 1. Spring Boot & Java Requirements
- **Java Version**: The project is configured for **Java 17** (or newer) to align with Spring Boot 3.x requirements.
- **Jakarta EE / Servlets**: Always use `jakarta.servlet.*` package imports instead of the legacy `javax.servlet.*` packages.

### 2. Frontend Configuration & Webpack 5
- **Node & NPM Version**: Managed by `frontend-maven-plugin` (version `1.15.0`). The project targets Node `v22.11.0` and NPM `10.9.0` to ensure build stability.
- **Webpack Bundle**: The bundle compiles using Webpack 5 syntax and extracts styles using `mini-css-extract-plugin` rather than `extract-text-webpack-plugin`.
- **Uppy File Uploader**:
- Upgraded to Uppy v5.
- Do **not** call `uppy.run()`. This method was deprecated/removed. Simply instantiating Uppy with `new Uppy()` and using plugins compiles and registers events automatically.
- CSS styles must be imported from `@uppy/core/css/style.min.css` and `@uppy/dashboard/css/style.min.css` rather than the old `dist` structure or the legacy unified `uppy` css bundle.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ Then visit http://localhost:8080/test/ in your browser and try to upload a file
1. File `src/main/resources/public/index.html` contains the actual home page and loads the Uppy JavaScript file generated by the `uppy-file-upload` module.

* Module `uppy-file-upload` contains the Webpack and Uppy configuration to fetch and join all required JavaScript files. This is not a production ready setup!
1. File `uppy-file-upload/app/uppy-fileupload.js` contains the configuration parameters of the Uppy tus JS client.
1. File `uppy-file-upload/app/uppy-fileupload.js` contains the configuration parameters of the Uppy tus JS client (upgraded to Uppy v5, configured via ES module imports and compiled using Webpack 5).
2. Stylesheets are imported individually from `@uppy/core/css/style.min.css` and `@uppy/dashboard/css/style.min.css`.
13 changes: 9 additions & 4 deletions spring-boot-rest/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
<version>0.0.1-SNAPSHOT</version>

<properties>
<java.version>1.8</java.version>
<java.version>17</java.version>
</properties>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.18.RELEASE</version>
<version>3.3.0</version>
</parent>

<dependencies>
Expand All @@ -35,12 +35,17 @@
<dependency>
<groupId>me.desair.tus</groupId>
<artifactId>tus-java-server</artifactId>
<version>1.0.0-2.1</version>
<version>1.0.0-3.1</version>
</dependency>
<dependency>
<groupId>io.tus.java.client</groupId>
<artifactId>tus-java-client</artifactId>
<version>0.4.0</version>
<version>0.5.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public TusFileUploadService tusFileUploadService() {
return new TusFileUploadService()
.withStoragePath(tusDataPath)
.withDownloadFeature()
.withUploadURI(servletContextPath + "/api/upload")
.withUploadUri(servletContextPath + "/api/upload")
.withThreadLocalCache(true);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import me.desair.tus.server.TusFileUploadService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,14 @@
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
public class WebMvcConfig implements WebMvcConfigurer {

private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/public/" };

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
super.addResourceHandlers(registry);
if (!registry.hasMappingForPattern("/webjars/**")) {
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
Expand Down
2 changes: 1 addition & 1 deletion spring-boot-rest/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
spring.profiles.active=dev
server.port=8080
server.context-path=/test
server.servlet.context-path=/test
tus.server.data.directory=${java.io.tmpdir}/tus
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package me.desair.spring.tus;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import me.desair.tus.server.TusFileUploadService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest
@AutoConfigureMockMvc
public class FileUploadControllerTest {

@Autowired
private TusFileUploadService tusFileUploadService;

@Autowired
private MockMvc mockMvc;

@Test
public void contextLoads() {
assertNotNull(tusFileUploadService, "TusFileUploadService should be instantiated");
}

@Test
public void testUploadEndpointOptions() throws Exception {
// TUS OPTIONS request should return 204 No Content
mockMvc.perform(options("/api/upload")
.header("Tus-Resumable", "1.0.0"))
.andExpect(status().isNoContent());
}

@Test
public void testUploadEndpointPostWithoutHeaders() throws Exception {
// A standard POST request without Tus headers should be rejected by the Tus service (status 412)
// because the required Tus-Resumable header is missing.
mockMvc.perform(post("/api/upload"))
.andExpect(status().isPreconditionFailed());
}
}
4 changes: 2 additions & 2 deletions uppy-file-upload/app/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
var fileUploader = require('./uppy-fileupload');
fileUploader.uppy().run();
import { uppy } from './uppy-fileupload';
uppy();
61 changes: 27 additions & 34 deletions uppy-file-upload/app/uppy-fileupload.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,31 @@
module.exports = {
uppy: function() {
require("uppy/dist/uppy.min.css")
const Uppy = require('@uppy/core')
const Dashboard = require('@uppy/dashboard')
const Tus = require('@uppy/tus')
import '@uppy/core/css/style.min.css';
import '@uppy/dashboard/css/style.min.css';
import Uppy from '@uppy/core';
import Dashboard from '@uppy/dashboard';
import Tus from '@uppy/tus';

const uppy = Uppy({
debug: true,
autoProceed: false,
// restrictions: {
// maxFileSize: 1000000,
// maxNumberOfFiles: 3,
// minNumberOfFiles: 2,
// allowedFileTypes: ['image/*', 'video/*']
// }
})
.use(Dashboard, {
inline: true,
target: '.DashboardContainer',
replaceTargetContent: true,
note: 'The best way to test this is with a file of several hundred MB (e.g. a Linux distro DVD image)',
maxHeight: 450,
metaFields: [
{ id: 'license', name: 'License', placeholder: 'specify license' },
{ id: 'caption', name: 'Caption', placeholder: 'describe what the image is about' }
]
})
.use(Tus, { endpoint: 'http://localhost:8080/test/api/upload' })
export function uppy() {
const uppyInstance = new Uppy({
debug: true,
autoProceed: false,
})
.use(Dashboard, {
inline: true,
target: '.DashboardContainer',
replaceTargetContent: true,
note: 'The best way to test this is with a file of several hundred MB (e.g. a Linux distro DVD image)',
maxHeight: 450,
metaFields: [
{ id: 'license', name: 'License', placeholder: 'specify license' },
{ id: 'caption', name: 'Caption', placeholder: 'describe what the image is about' }
]
})
.use(Tus, { endpoint: 'http://localhost:8080/test/api/upload' });

uppy.on('upload-success', function(file, upload) {
console.log("Upload " + file.name + " completed with URL " + upload.url);
console.log("Developer: Now pass URL " + upload.url + " to the backend or dynmically add it to an existing form!");
});
uppyInstance.on('upload-success', function(file, upload) {
console.log("Upload " + file.name + " completed with URL " + upload.url);
console.log("Developer: Now pass URL " + upload.url + " to the backend or dynamically add it to an existing form!");
});

return uppy;
}
return uppyInstance;
}
15 changes: 9 additions & 6 deletions uppy-file-upload/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@
"description": "",
"main": "./app",
"scripts": {
"build": "webpack -p"
"build": "webpack --mode production"
},
"author": "",
"license": "ISC",
"dependencies": {
"uppy": "1.0.0"
"@uppy/core": "^5.2.0",
"@uppy/dashboard": "^5.1.1",
"@uppy/tus": "^5.1.1"
},
"devDependencies": {
"webpack": "3.10.0",
"extract-text-webpack-plugin": "3.0.2",
"css-loader": "0.28.8",
"style-loader": "0.19.1"
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4",
"mini-css-extract-plugin": "^2.9.0",
"css-loader": "^7.1.1",
"style-loader": "^4.0.0"
}
}
6 changes: 3 additions & 3 deletions uppy-file-upload/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>0.0.27</version>
<version>1.15.0</version>

<executions>

Expand All @@ -22,8 +22,8 @@
<goal>install-node-and-npm</goal>
</goals>
<configuration>
<nodeVersion>v8.9.3</nodeVersion>
<npmVersion>5.5.1</npmVersion>
<nodeVersion>v22.11.0</nodeVersion>
<npmVersion>10.9.0</npmVersion>
</configuration>
</execution>

Expand Down
24 changes: 7 additions & 17 deletions uppy-file-upload/webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,18 @@
var packageJSON = require('./package.json');
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var path = require('path');
var webpack = require('webpack');
const packageJSON = require('./package.json');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');

const PATHS = {
build: path.join(__dirname, 'target', 'classes', 'META-INF', 'resources', 'webjars', packageJSON.name, packageJSON.version)
};

const loaders = {
css: {
loader: 'css-loader'
}
}

module.exports = {
entry: './app/index.js',
module: {
loaders: [
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [loaders.css]
})
use: [MiniCssExtractPlugin.loader, 'css-loader']
}
]
},
Expand All @@ -31,5 +21,5 @@ module.exports = {
publicPath: '/assets/',
filename: 'app-bundle.js'
},
plugins: [new ExtractTextPlugin('[name].css')]
plugins: [new MiniCssExtractPlugin({ filename: '[name].css' })]
};
Loading