Initial commit

This commit is contained in:
Andre Henriques 2024-09-11 18:11:44 +01:00
commit b8e617ef6c
90 changed files with 11199 additions and 0 deletions

18
.editorconfig Normal file
View File

@ -0,0 +1,18 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2
[docker-compose.yml]
indent_size = 4

64
.env.example Normal file
View File

@ -0,0 +1,64 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_TIMEZONE=UTC
APP_URL=http://localhost
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
APP_MAINTENANCE_STORE=database
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"

11
.gitattributes vendored Normal file
View File

@ -0,0 +1,11 @@
* text=auto eol=lf
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore

21
.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
/.phpunit.cache
/node_modules
/public/build
/public/hot
/public/storage
/storage/*.key
/vendor
.env
.env.backup
.env.production
.phpactor.json
.phpunit.result.cache
Homestead.json
Homestead.yaml
auth.json
npm-debug.log
yarn-error.log
/.fleet
/.idea
/.vscode
url-ext.xpi

6
Dockerfile Normal file
View File

@ -0,0 +1,6 @@
# vi: ft=dockerfile
FROM docker.io/nginx
ADD nginx.proxy.conf /nginx.conf
CMD ["nginx", "-c", "/nginx.conf", "-g", "daemon off;"]

40
api/.gitignore vendored Normal file
View File

@ -0,0 +1,40 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Kotlin ###
.kotlin

View File

@ -0,0 +1,7 @@
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/applications
spring.datasource.username=applications
spring.datasource.password=applications
# spring.sql.init.schema-locations=classpath:schema.sql
# spring.sql.init.mode=always

49
api/build.gradle.kts Normal file
View File

@ -0,0 +1,49 @@
plugins {
id("org.springframework.boot") version "3.3.1"
id("io.spring.dependency-management") version "1.1.5"
kotlin("plugin.jpa") version "1.9.24"
kotlin("jvm") version "1.9.24"
kotlin("plugin.spring") version "1.9.24"
}
group = "com.andr3h3nriqu3s"
version = "0.0.1-SNAPSHOT"
java { toolchain { languageVersion = JavaLanguageVersion.of(17) } }
repositories { mavenCentral() }
dependencies {
implementation("org.postgresql:postgresql")
implementation("org.springframework.security:spring-security-crypto:6.0.3")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-mustache")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("org.hibernate.orm:hibernate-community-dialects")
developmentOnly("org.springframework.boot:spring-boot-devtools")
runtimeOnly("com.h2database:h2")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
implementation("org.bouncycastle:bcprov-jdk18on:1.76")
}
//kotlin { compilerOptions { freeCompilerArgs.addAll("-Xjsr305=strict") } compiler { jvm { target = JavaLanguageVersion.of(17) } } }
kotlin {
compilerOptions {
freeCompilerArgs.addAll("-Xjsr305=strict")
}
jvmToolchain(17)
}
tasks.withType<Test> { useJUnitPlatform() }
tasks.withType<Test> {
useJUnitPlatform()
}

BIN
api/data/testdb.mv.db Normal file

Binary file not shown.

337
api/data/testdb.trace.db Normal file
View File

@ -0,0 +1,337 @@
2024-07-06 21:04:26.959072+01:00 jdbc[3]: exception
org.h2.jdbc.JdbcSQLSyntaxErrorException: Column "t.id" not found [42122-224]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:514)
at org.h2.message.DbException.getJdbcSQLException(DbException.java:489)
at org.h2.message.DbException.get(DbException.java:223)
at org.h2.message.DbException.get(DbException.java:199)
at org.h2.jdbc.JdbcResultSet.getColumnIndex(JdbcResultSet.java:3492)
at org.h2.jdbc.JdbcResultSet.getString(JdbcResultSet.java:301)
at com.zaxxer.hikari.pool.HikariProxyResultSet.getString(HikariProxyResultSet.java)
at com.andr3h3nriqu3s.applications.SessionService.verifyToken$lambda$0(User.kt:186)
at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:94)
at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:61)
at org.springframework.jdbc.core.JdbcTemplate$1.doInPreparedStatement(JdbcTemplate.java:733)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:658)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:723)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:754)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:767)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:820)
at com.andr3h3nriqu3s.applications.SessionService.verifyToken(User.kt:181)
at com.andr3h3nriqu3s.applications.SessionService.verifyTokenThrow(User.kt:203)
at com.andr3h3nriqu3s.applications.ApplicationsController.submitText(ApplicationsController.kt:30)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at kotlin.reflect.jvm.internal.calls.CallerImpl$Method.callMethod(CallerImpl.kt:97)
at kotlin.reflect.jvm.internal.calls.CallerImpl$Method$Instance.call(CallerImpl.kt:113)
at kotlin.reflect.jvm.internal.KCallableImpl.callDefaultMethod$kotlin_reflection(KCallableImpl.kt:207)
at kotlin.reflect.jvm.internal.KCallableImpl.callBy(KCallableImpl.kt:112)
at org.springframework.web.method.support.InvocableHandlerMethod$KotlinDelegate.invokeFunction(InvocableHandlerMethod.java:334)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:252)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:389)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:904)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
at java.base/java.lang.Thread.run(Thread.java:840)
2024-07-06 21:25:03.965298+01:00 jdbc[3]: exception
org.h2.jdbc.JdbcSQLSyntaxErrorException: Column "u.id" not found [42122-224]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:514)
at org.h2.message.DbException.getJdbcSQLException(DbException.java:489)
at org.h2.message.DbException.get(DbException.java:223)
at org.h2.message.DbException.get(DbException.java:199)
at org.h2.jdbc.JdbcResultSet.getColumnIndex(JdbcResultSet.java:3492)
at org.h2.jdbc.JdbcResultSet.getString(JdbcResultSet.java:301)
at com.zaxxer.hikari.pool.HikariProxyResultSet.getString(HikariProxyResultSet.java)
at com.andr3h3nriqu3s.applications.SessionService.verifyToken$lambda$0(User.kt:186)
at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:94)
at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:61)
at org.springframework.jdbc.core.JdbcTemplate$1.doInPreparedStatement(JdbcTemplate.java:733)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:658)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:723)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:754)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:767)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:820)
at com.andr3h3nriqu3s.applications.SessionService.verifyToken(User.kt:181)
at com.andr3h3nriqu3s.applications.SessionService.verifyTokenThrow(User.kt:203)
at com.andr3h3nriqu3s.applications.ApplicationsController.submitText(ApplicationsController.kt:30)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at kotlin.reflect.jvm.internal.calls.CallerImpl$Method.callMethod(CallerImpl.kt:97)
at kotlin.reflect.jvm.internal.calls.CallerImpl$Method$Instance.call(CallerImpl.kt:113)
at kotlin.reflect.jvm.internal.KCallableImpl.callDefaultMethod$kotlin_reflection(KCallableImpl.kt:207)
at kotlin.reflect.jvm.internal.KCallableImpl.callBy(KCallableImpl.kt:112)
at org.springframework.web.method.support.InvocableHandlerMethod$KotlinDelegate.invokeFunction(InvocableHandlerMethod.java:334)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:252)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:389)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:904)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
at java.base/java.lang.Thread.run(Thread.java:840)
2024-07-06 21:30:12.489279+01:00 jdbc[3]: exception
org.h2.jdbc.JdbcSQLSyntaxErrorException: Column "password" not found [42122-224]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:514)
at org.h2.message.DbException.getJdbcSQLException(DbException.java:489)
at org.h2.message.DbException.get(DbException.java:223)
at org.h2.message.DbException.get(DbException.java:199)
at org.h2.jdbc.JdbcResultSet.getColumnIndex(JdbcResultSet.java:3492)
at org.h2.jdbc.JdbcResultSet.getString(JdbcResultSet.java:301)
at com.zaxxer.hikari.pool.HikariProxyResultSet.getString(HikariProxyResultSet.java)
at com.andr3h3nriqu3s.applications.SessionService.verifyToken$lambda$0(User.kt:189)
at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:94)
at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:61)
at org.springframework.jdbc.core.JdbcTemplate$1.doInPreparedStatement(JdbcTemplate.java:733)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:658)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:723)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:754)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:767)
at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:820)
at com.andr3h3nriqu3s.applications.SessionService.verifyToken(User.kt:181)
at com.andr3h3nriqu3s.applications.SessionService.verifyTokenThrow(User.kt:203)
at com.andr3h3nriqu3s.applications.ApplicationsController.submitText(ApplicationsController.kt:30)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at kotlin.reflect.jvm.internal.calls.CallerImpl$Method.callMethod(CallerImpl.kt:97)
at kotlin.reflect.jvm.internal.calls.CallerImpl$Method$Instance.call(CallerImpl.kt:113)
at kotlin.reflect.jvm.internal.KCallableImpl.callDefaultMethod$kotlin_reflection(KCallableImpl.kt:207)
at kotlin.reflect.jvm.internal.KCallableImpl.callBy(KCallableImpl.kt:112)
at org.springframework.web.method.support.InvocableHandlerMethod$KotlinDelegate.invokeFunction(InvocableHandlerMethod.java:334)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:252)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:389)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:904)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
at java.base/java.lang.Thread.run(Thread.java:840)
2024-07-06 21:58:05.248085+01:00 jdbc[3]: exception
org.h2.jdbc.JdbcSQLNonTransientConnectionException: Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-224]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:690)
at org.h2.message.DbException.getJdbcSQLException(DbException.java:489)
at org.h2.message.DbException.get(DbException.java:223)
at org.h2.message.DbException.get(DbException.java:199)
at org.h2.message.DbException.get(DbException.java:188)
at org.h2.jdbc.JdbcConnection.checkClosed(JdbcConnection.java:1425)
at org.h2.jdbc.JdbcConnection.getMetaData(JdbcConnection.java:334)
at com.zaxxer.hikari.pool.ProxyConnection.getMetaData(ProxyConnection.java:371)
at com.zaxxer.hikari.pool.HikariProxyConnection.getMetaData(HikariProxyConnection.java)
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$1.execute(JdbcEnvironmentInitiator.java:295)
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$1.execute(JdbcEnvironmentInitiator.java:291)
at org.hibernate.jdbc.WorkExecutor.executeReturningWork(WorkExecutor.java:58)
at org.hibernate.jdbc.AbstractReturningWork.accept(AbstractReturningWork.java:34)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcIsolationDelegate.delegateWork(JdbcIsolationDelegate.java:70)
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.getJdbcEnvironmentUsingJdbcMetadata(JdbcEnvironmentInitiator.java:290)
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:123)
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:77)
at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:130)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:263)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:238)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:215)
at org.hibernate.boot.model.relational.Database.<init>(Database.java:45)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.getDatabase(InFlightMetadataCollectorImpl.java:221)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.<init>(InFlightMetadataCollectorImpl.java:189)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:171)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:1431)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1502)
at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:75)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:390)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:409)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:396)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:366)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1835)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1784)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:205)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:952)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:624)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:335)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352)
at com.andr3h3nriqu3s.applications.ApplicationsApplicationKt.main(ApplicationsApplication.kt:13)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50)
2024-07-06 21:58:05.249168+01:00 jdbc[3]: exception
org.h2.jdbc.JdbcSQLNonTransientConnectionException: Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) [90121-224]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:690)
at org.h2.message.DbException.getJdbcSQLException(DbException.java:489)
at org.h2.message.DbException.get(DbException.java:223)
at org.h2.message.DbException.get(DbException.java:199)
at org.h2.message.DbException.get(DbException.java:188)
at org.h2.jdbc.JdbcConnection.checkClosed(JdbcConnection.java:1425)
at org.h2.jdbc.JdbcConnection.clearWarnings(JdbcConnection.java:660)
at com.zaxxer.hikari.pool.ProxyConnection.close(ProxyConnection.java:258)
at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.closeConnection(DatasourceConnectionProviderImpl.java:127)
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess.releaseConnection(JdbcEnvironmentInitiator.java:442)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcIsolationDelegate.delegateWork(JdbcIsolationDelegate.java:108)
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.getJdbcEnvironmentUsingJdbcMetadata(JdbcEnvironmentInitiator.java:290)
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:123)
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:77)
at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:130)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:263)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:238)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:215)
at org.hibernate.boot.model.relational.Database.<init>(Database.java:45)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.getDatabase(InFlightMetadataCollectorImpl.java:221)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.<init>(InFlightMetadataCollectorImpl.java:189)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:171)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:1431)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1502)
at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:75)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:390)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:409)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:396)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:366)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1835)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1784)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:205)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:952)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:624)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:335)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352)
at com.andr3h3nriqu3s.applications.ApplicationsApplicationKt.main(ApplicationsApplication.kt:13)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50)

BIN
api/gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

249
api/gradlew vendored Executable file
View File

@ -0,0 +1,249 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

92
api/gradlew.bat vendored Normal file
View File

@ -0,0 +1,92 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

1
api/settings.gradle.kts Normal file
View File

@ -0,0 +1 @@
rootProject.name = "applications"

0
api/sqlitesample.db Normal file
View File

View File

@ -0,0 +1,11 @@
package com.andr3h3nriqu3s.applications
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class ApplicationsApplication
fun main(args: Array<String>) {
runApplication<ApplicationsApplication>(*args)
}

View File

@ -0,0 +1,499 @@
package com.andr3h3nriqu3s.applications
import java.sql.ResultSet
import java.util.UUID
import kotlin.collections.setOf
import okhttp3.OkHttpClient
import okhttp3.Request
import org.springframework.http.MediaType
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.RowMapper
import org.springframework.stereotype.Service
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
data class Application(
var id: String,
var url: String,
var original_url: String?,
var unique_url: String?,
var title: String,
var user_id: String,
var extra_data: String,
var payrange: String,
var status: Int,
var flairs: List<Flair>
) {
companion object : RowMapper<Application> {
override public fun mapRow(rs: ResultSet, rowNum: Int): Application {
return Application(
rs.getString("id"),
rs.getString("url"),
rs.getString("original_url"),
rs.getString("unique_url"),
rs.getString("title"),
rs.getString("user_id"),
rs.getString("extra_data"),
rs.getString("payrange"),
rs.getInt("status"),
emptyList()
)
}
}
}
data class SubmitRequest(val text: String)
data class ListRequest(val status: Int?)
data class StatusRequest(val id: String, val status: Int)
data class FlairRequest(val id: String, val text: String)
data class UpdateUrl(val id: String, val url: String)
@RestController
@ControllerAdvice
@RequestMapping("/api/application")
class ApplicationsController(
val sessionService: SessionService,
val applicationService: ApplicationService,
val flairService: FlairService
) {
@PostMapping(path = ["/text"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun submitText(
@RequestBody submit: SubmitRequest,
@RequestHeader("token") token: String
): Int {
val user = sessionService.verifyTokenThrow(token)
var text = submit.text.replace("=\n", "")
var urls: List<String> = emptyList()
while (true) {
var index = text.indexOf("href")
if (index == -1) {
break
}
var new_url = StringBuilder()
var found_start = false
while (true) {
if (found_start) {
if (text[index] == '"') {
break
}
new_url.append(text[index])
} else if (text[index] == '"') {
found_start = true
}
index++
}
text = text.substring(index)
urls = urls.plus(new_url.toString().replace("&amp;", "&").replace("=3D", "="))
}
print("found: ")
print(urls.size)
print(" links\n")
urls = urls.filter { predicate -> predicate.contains("jobListing") }
print("found fileted: ")
print(urls.size)
print(" links\n")
urls = urls.toSet().toList()
print("removed duplicates: ")
print(urls.size)
print(" links\n")
var client = OkHttpClient()
var applications =
urls.map { elm ->
var request =
Request.Builder()
.url(elm)
.header(
"User-Agent",
"Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0"
)
.addHeader(
"Accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"
)
.addHeader(
"Cookie",
"gdId=aafdcb86-c37d-4bfd-8d84-4a07f11dbfc2; AWSALB=Qj3Rvldo90lFQhsChPHFXcGlVxdaZOEwr1ZP4d+whxBTlXCprKj+utUCz4DHXoC6fFrewDdFdRCPBbM0v2WQQdY4URUD4lYXd0GsBEWS/5AzZX0i9yk4C7Cfz5xA; AWSALBCORS=Qj3Rvldo90lFQhsChPHFXcGlVxdaZOEwr1ZP4d+whxBTlXCprKj+utUCz4DHXoC6fFrewDdFdRCPBbM0v2WQQdY4URUD4lYXd0GsBEWS/5AzZX0i9yk4C7Cfz5xA; indeedCtk=1i17t757dk2mo801; at=Sf_nQa0d6HxrGoYn305KELOfsWT0o-KrKdraenJrTJMqApEH4LO70x3i0oOafGWes13u8kOSq8PzPD1pZAxAVOEz0hrNnOhRnLGl3Ako4I-CYx_oCB_YhFe7M-do__yNC-ipCVlbzUujwQzCW83mKFLMHk9N12RSjhzsPEQi6Xw4n66wvXSttDChGCpY-BMSn862U-OQ9gnNzy3j214vctizTPC3g2Ig7Qysg3AY7lo-HPkHaq9fojbituLfbtgtpkCDtWAwR2-3XCWu6d2H_gk7ZWn6CH1FgLf640fd1Kep7K3kArceNq046PEalzHnJMLPSnvusS_jJatdk-x4k2rxF1FJvK0MLkPiKr39V8i7J27uOMwFLRD-49EoJUOZzTxQ1wb3lyt503NuwPhYolRGr3Xla2b6wyzYRSk2e-RHHElzvCn-FlWXaTQqEiZXKEjAGihlagUz8HFmY6bRf0FLCKvA26oVjT1vEoICmn32FOHr9M65YDllTbgHj3dDeTUczzxf_nrDSyide_UL08LsZIYJAtyueUSBqvqq49WlKemvhyKBMUloNP68A_MfT5suKPasvQu-n_ZvXahY7O5KbgtIR0QVnSimFC8yaVy8Sqjo5cq2f1VnZ6kxXj9frSK-v_jz1J7hobOAo8Mlfy_EfzBDV_3nywqjT1pKgzRRmHcNLnj_cn2rPga3NUjxpYzl485eevPDv9Gj62qAMHh1w5S6v1QkSBmgaSyKpBKYD1zD86JLJ6Upf0bVM8WcHv9u33GuKX0CgC_P-P3189AgLgtISr-2NuIaNTkKMxenagCvri6oGLj6OYMD6nO9JQ_jKUqS6ShRMhDD9K1bSUE42c2_ePhBDLWFyb3AZiJvVjcL1v1k1dO93jCn7XUQZkE7x7m1QeGC0f_5OK1IZeGuJn8Jsx-u2EvT7DNSV0LNY1hfIXauy9KCLFdTeoqDuGSumi81cg; uc=8013A8318C98C5171B57B9070099BAD115C8BBA5C5A6DB94DFFE7C86F37B699E2B54A4621F700A694011D5B5CA447CCC87B1BAC35D51A1AA5B5FF13CBDC5343400624A80C8E3C3A1966ED67EBC62D473CD7C57C9E6D138AD745B36C8625AD88EA13CE3458B51AD202B52198961B4D49007278A9385005B27B951F0F7091D61DCBBD54D21C8CFBCAD7AABEF4EA398F978; trs=direct:direct:direct:2024-06-25+07%3A59%3A23.686:undefined:undefined; cf_clearance=IZpDoUoDUUNidzYbSZRoIq6S93FXgL7hmz6.PdfEiUM-1720205316-1.0.1.1-tNL38ZNHbQw9XbL3J9g4oL4aHwiAoXXuYdOb4Xi8Z3B4yWKOuL31FrrPrqPRcxVUV7BNEdlqSMaNF3k8q.Tv0A; rl_session=RudderEncrypt%3AU2FsdGVkX19OL1VCjdlEuacjPBxOQ%2FsqFNPp5d9Dke5bKES7onFGAEFzkA4iuW3rfqQ8v2sfoX19gn7Zm3Qd83i6PWHRHESeelsza78DN%2B2U7IwWQEiyEeptsuZyTPhHxb5ALLCzUhgHcvnsh3GhQw%3D%3D; rl_user_id=RudderEncrypt%3AU2FsdGVkX19%2BQEGQCkE9Cg3Ikh5t8h65RqO0N24wvgA%3D; rl_trait=RudderEncrypt%3AU2FsdGVkX1%2B1EoAGj4RTTyGWbZRPkyuua9oOW4YQPaE%3D; rl_group_id=RudderEncrypt%3AU2FsdGVkX18STRtzoe4K%2FzJKt8zfNqQwMj7KTzQDhfQ%3D; rl_group_trait=RudderEncrypt%3AU2FsdGVkX1%2B5Kbrex9bI6O0Gr524R6IRASVZGgrWNco%3D; rl_anonymous_id=RudderEncrypt%3AU2FsdGVkX19X88n4GXRAtkOTDXcuHojO%2BetO8p7n37y2YLe6nWW5P87Stu9l86nJ4zGDs7e8D5MEUPW6KrCywQ%3D%3D; rl_page_init_referrer=RudderEncrypt%3AU2FsdGVkX19MgsL8FFX%2BSjRpwcg0nu4XxNDvIPdg2WGEivR2%2BsH1%2BqAvvlaEZmiQ; rl_page_init_referring_domain=RudderEncrypt%3AU2FsdGVkX18kgmzvlkMRtIo%2BzqUQ%2FVRfcuCvngKkmV8qAgqKAKraZOr9Qpqc6loH; _cfuvid=ezdGXFBaF3Eh.jiKZrHkmaV7GItIbZ4qPUy7gOyHgns-1720604124536-0.0.1.1-604800000; gdGPCset=true; _urc=293647244; AFSID=MTQ3YTQxNzEtZmYwMC00N2Y0LWJiOGMtNzVjMmRjZWM5MTQ4; gdsid=1720604123405:1720637554591:4466F8BAFD3D2D34B6058423B0F90392; JSESSIONID=BA9AA08792C5E981A69CF079E7A91B35; GSESSIONID=undefined; cass=0; asst=1720637634.1; __cf_bm=6b1QlZvq2zEE.Cqvcg0g2a0OiPObgL0IKFr.vZwZSwQ-1720637555-1.0.1.1-qaRfjwA.xRIpqzfJiksMvc_hWVvVnfGJ1YnRToNwK.m3Od0BXKUvOVnvhrpiR9bP311eCdI.EAYQZvKpwD7KHQ; bs=oiDddWzpeaDeES0KI7rnsQ:NDp0g4i9mXeF0vvvvYq2Ud_auNQosyTqd2NVCprviXG0nOGhPzqbVJa16DxpiFQkIluzbbDJqHLht66Dwrqn4yi7u_FvaGOojP_y91QEUZU:Hex7BRo2zXhuIdlGhiQR8uO5nGn2VCjpx6dF2aJPIxk"
)
.addHeader("Accept-Language", "en-US,en;q=0.7,pt;q=0.3")
.addHeader("Accept-Encoding", "gzip, deflate, br, zstd")
.addHeader("Connection", "keep-alive")
.build()
var new_url: String =
client.newCall(request).execute().use { response ->
print("response:")
print(response)
print("\n")
response.request.url.toString()
}
var unique: String? = new_url!!.split("?")[0]
var original_url: String? = elm
if (new_url == elm) {
original_url = null
unique = null
}
Thread.sleep(2000)
print("Got new url!\n")
Application(
UUID.randomUUID().toString(),
new_url,
original_url,
unique,
"New Aplication",
user.id,
"",
"",
0,
emptyList()
)
}
applications =
applications.filter { elm -> applicationService.createApplication(user, elm) }
print("created new: ")
print(applications.size)
print(" links\n")
return applications.size
}
@PostMapping(path = ["/text/flair"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun textFlair(
@RequestBody info: FlairRequest,
@RequestHeader("token") token: String
): Int {
val user = sessionService.verifyTokenThrow(token)
val application = applicationService.findApplicationById(user, info.id)
if (application == null) {
throw NotFound()
}
val flairs = flairService.listUser(user)
var count = 0
for (flair: Flair in flairs) {
val regex =
Regex(
".*" + flair.expr + ".*",
setOf(RegexOption.IGNORE_CASE, RegexOption.DOT_MATCHES_ALL)
)
if (regex.matches(info.text)) {
count += 1
flairService.linkFlair(application, flair)
}
}
return count
}
@PostMapping(path = ["/list"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun list(
@RequestBody info: ListRequest,
@RequestHeader("token") token: String
): List<Application> {
val user = sessionService.verifyTokenThrow(token)
return applicationService.findAll(user, info)
}
@GetMapping(path = ["/active"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun active(@RequestHeader("token") token: String): Application? {
val user = sessionService.verifyTokenThrow(token)
val possibleApplications = applicationService.findAll(user, ListRequest(1))
if (possibleApplications.size == 0) {
return null
}
return applicationService.findApplicationById(user, possibleApplications[0].id)
}
@PutMapping(path = ["/status"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun status(
@RequestBody info: StatusRequest,
@RequestHeader("token") token: String,
): Application {
val user = sessionService.verifyTokenThrow(token)
var application = applicationService.findApplicationById(user, info.id)
if (application == null) {
throw NotFound()
}
application.status = info.status
applicationService.update(application)
return application
}
@PutMapping(path = ["/update"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun update(
@RequestBody info: Application,
@RequestHeader("token") token: String
): Application {
val user = sessionService.verifyTokenThrow(token)
var application = applicationService.findApplicationById(user, info.id)
if (application == null) {
throw NotFound()
}
applicationService.update(info)
return info
}
@PostMapping(path = ["/update/url"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun updateUrl(
@RequestBody info: UpdateUrl,
@RequestHeader("token") token: String
): Application {
val user = sessionService.verifyTokenThrow(token)
var application = applicationService.findApplicationById(user, info.id)
if (application == null) {
throw NotFound()
}
if (application.unique_url != null) {
throw BadRequest()
}
application.original_url = application.url
application.url = info.url
application.unique_url = info.url.split("?")[0]
var maybe_exists =
applicationService.findApplicationByUrl(
user,
application.url,
application.unique_url
)
if (maybe_exists != null) {
applicationService.delete(application)
if (maybe_exists.status == 0 && application.status == 1) {
maybe_exists.status = 1
applicationService.update(maybe_exists)
}
maybe_exists.flairs = flairService.listFromLinkApplicationId(maybe_exists.id)
return maybe_exists
}
applicationService.update(application)
application.flairs = flairService.listFromLinkApplicationId(application.id)
return application
}
@PostMapping(path = ["/reset/url/{id}"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun updateUrl(
@PathVariable id: String,
@RequestHeader("token") token: String
): Application {
val user = sessionService.verifyTokenThrow(token)
var application = applicationService.findApplicationById(user, id)
if (application == null) {
throw NotFound()
}
if (application.unique_url == null) {
throw BadRequest()
}
application.url = application.original_url!!
application.original_url = null
application.unique_url = null
applicationService.update(application)
application.flairs = flairService.listFromLinkApplicationId(application.id)
return application
}
@DeleteMapping(path = ["/flair/{id}/{flairid}"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun delete(
@PathVariable id: String,
@PathVariable flairid: String,
@RequestHeader("token") token: String
): Application {
val user = sessionService.verifyTokenThrow(token)
val application = applicationService.findApplicationById(user, id)
if (application == null) {
throw NotFound()
}
flairService.unlinkFlair(id, flairid)
return applicationService.findApplicationById(user, id)!!
}
@DeleteMapping(path = ["/{id}"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun delete(
@PathVariable id: String,
@RequestHeader("token") token: String
): Application {
val user = sessionService.verifyTokenThrow(token)
val application = applicationService.findApplicationById(user, id)
if (application == null) {
throw NotFound()
}
applicationService.delete(application)
return application
}
}
@Service
class ApplicationService(val db: JdbcTemplate, val flairService: FlairService) {
public fun findApplicationByUrl(user: UserDb, url: String, unique_url: String?): Application? {
if (unique_url != null) {
val unique =
db.query(
"select * from applications where unique_url=? and user_id=?",
arrayOf(unique_url, user.id),
Application
)
.toList()
if (unique.size != 0) {
return unique[0]
}
}
val applications =
db.query(
"select * from applications where url=? and user_id=?",
arrayOf(url, user.id),
Application
)
.toList()
if (applications.size == 0) {
return null
}
return applications[0]
}
public fun findApplicationById(user: UserDb, id: String): Application? {
var applications =
db.query(
"select * from applications where id=? and user_id=?",
arrayOf(id, user.id),
Application
)
.toList()
if (applications.size == 0) {
return null
}
var application = applications[0]
application.flairs = flairService.listFromLinkApplicationId(application.id)
return application
}
public fun createApplication(user: UserDb, application: Application): Boolean {
if (this.findApplicationByUrl(user, application.url, application.unique_url) != null) {
return false
}
db.update(
"insert into applications (id, url, original_url, unique_url, title, user_id, extra_data, payrange, status) values (?, ?, ?, ?, ?, ?, ?, ?, ?);",
application.id,
application.url,
application.original_url,
application.unique_url,
application.title,
application.user_id,
application.extra_data,
application.payrange,
application.status,
)
return true
}
public fun findAll(user: UserDb, info: ListRequest): List<Application> {
if (info.status == null) {
return db.query(
"select * from applications where user_id=? order by title asc;",
arrayOf(user.id),
Application
)
.toList()
}
return db.query(
"select * from applications where user_id=? and status=? order by title asc;",
arrayOf(user.id, info.status),
Application,
)
.toList()
}
public fun update(application: Application): Application {
db.update(
"update applications set url=?, original_url=?, unique_url=?, title=?, user_id=?, extra_data=?, payrange=?, status=? where id=?",
application.url,
application.original_url,
application.unique_url,
application.title,
application.user_id,
application.extra_data,
application.payrange,
application.status,
application.id,
)
return application
}
public fun delete(application: Application) {
db.update(
"delete from applications where id=?",
application.id,
)
}
}

View File

@ -0,0 +1,18 @@
package com.andr3h3nriqu3s.applications
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.http.MediaType
@RestController
@RequestMapping("/api/utils")
class BaseInfo {
@GetMapping(path = [ "/version" ], produces = [ MediaType.APPLICATION_JSON_VALUE ])
fun version(): Version {
return Version(1)
}
}
data class Version(val version: Int)

View File

@ -0,0 +1,71 @@
package com.andr3h3nriqu3s.appliations
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer
import org.springframework.web.servlet.config.annotation.EnableWebMvc
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer
import org.springframework.web.servlet.config.annotation.CorsRegistry
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer
import org.springframework.web.servlet.mvc.Controller
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver
import org.springframework.web.method.HandlerTypePredicate
import org.springframework.web.bind.annotation.RestController
import org.springframework.http.MediaType
import org.springframework.stereotype.Component
import org.springframework.web.servlet.ModelAndView
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import com.andr3h3nriqu3s.applications.NoToken
import com.andr3h3nriqu3s.applications.NotAuth
import com.andr3h3nriqu3s.applications.UserNotFound
import com.andr3h3nriqu3s.applications.NotFound
import com.andr3h3nriqu3s.applications.BadRequest
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override public fun configureContentNegotiation(configurer: ContentNegotiationConfigurer){
// configurer.defaultContentType( MediaType.APPLICATION_JSON );
configurer.mediaType("json", MediaType.APPLICATION_JSON)
}
override public fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("*").allowedOrigins("*")
}
}
@Component
public class RestResponseStatusExceptionResolver : AbstractHandlerExceptionResolver() {
override protected fun doResolveException(
requeset: HttpServletRequest,
response: HttpServletResponse,
handler: Any?,
ex: Exception
): ModelAndView? {
try {
if (ex is NoToken || ex is NotAuth || ex is UserNotFound) {
response.sendError(HttpServletResponse.SC_FORBIDDEN)
return ModelAndView()
}
if (ex is NotFound) {
response.sendError(HttpServletResponse.SC_NOT_FOUND)
return ModelAndView()
}
if (ex is BadRequest) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST)
return ModelAndView()
}
} catch (handlerException: Exception) {
print("Faield to handle exception ")
print(handlerException)
print("\n")
}
return null;
}
}

View File

@ -0,0 +1,7 @@
package com.andr3h3nriqu3s.applications
public class NotAuth : Exception("Not Authenticated")
public class NoToken : Exception("Invalid Token")
public class UserNotFound : Exception("User Not Found")
public class NotFound : Exception("Not Found")
public class BadRequest : Exception("Bad Request")

View File

@ -0,0 +1,209 @@
package com.andr3h3nriqu3s.applications
import java.sql.ResultSet
import java.util.UUID
import org.springframework.http.MediaType
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.RowMapper
import org.springframework.stereotype.Service
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
data class CreateFlair(
var color: String,
var name: String,
var expr: String,
var description: String?
)
@RestController
@ControllerAdvice
@RequestMapping("/api/flair")
class FlairController(val sessionService: SessionService, val flairService: FlairService) {
@PutMapping(path = ["/"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun create(
@RequestBody flair: CreateFlair,
@RequestHeader("token") token: String
): Flair {
val user = sessionService.verifyTokenThrow(token)
return flairService.createFlair(user, flair)
}
@PutMapping(path = ["/update"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun create(@RequestBody flair: Flair, @RequestHeader("token") token: String): Flair {
val user = sessionService.verifyTokenThrow(token)
var old_flair = flairService.getById(user, flair.id)
if (old_flair == null) {
throw NotFound()
}
if (old_flair.user_id != user.id) {
throw NotAuth()
}
flair.user_id = old_flair.user_id
return flairService.updateFlair(flair)
}
@GetMapping(path = ["/"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun list(@RequestHeader("token") token: String): List<Flair> {
val user = sessionService.verifyTokenThrow(token)
return flairService.listUser(user)
}
@DeleteMapping(path = ["/{id}"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun delete(@PathVariable id: String, @RequestHeader("token") token: String): Flair {
val user = sessionService.verifyTokenThrow(token)
return flairService.deleteById(user, id)
}
}
data class Flair(
var id: String,
var user_id: String,
var color: String,
var name: String,
var expr: String,
var description: String,
) {
companion object : RowMapper<Flair> {
override public fun mapRow(rs: ResultSet, rowNum: Int): Flair {
return Flair(
rs.getString("id"),
rs.getString("user_id"),
rs.getString("color"),
rs.getString("name"),
rs.getString("expr"),
rs.getString("description"),
)
}
}
}
data class FlairLink(var id: String, var application_id: String, var flair_id: String)
@Service
public class FlairService(val db: JdbcTemplate) {
public fun linkFlair(application: Application, flair: Flair): FlairLink {
val links =
db.query(
"select * from flair_link where application_id=? and flair_id=?",
arrayOf(application.id, flair.id)
) { request, _ ->
FlairLink(
request.getString("id"),
request.getString("application_id"),
request.getString("flair_id")
)
}
if (links.size > 0) {
return links[0]
}
val link = FlairLink(UUID.randomUUID().toString(), application.id, flair.id)
db.update(
"insert into flair_link (id, application_id, flair_id) values (?, ?, ?)",
link.id,
link.application_id,
link.flair_id
)
return link
}
public fun unlinkFlair(applicationId: String, flairId: String) {
db.update(
"delete from flair_link where flair_id=? and application_id=?",
flairId,
applicationId,
)
}
public fun listFromLinkApplicationId(id: String): List<Flair> =
db.query(
"select f.id, f.user_id, f.color, f.name, f.expr, f.description from flair_link as fl inner join flair as f on f.id = fl.flair_id where application_id=?;",
arrayOf(id),
Flair
)
.toList()
public fun listUser(user: UserDb): List<Flair> =
db.query("select * from flair where user_id=?;", arrayOf(user.id), Flair).toList()
public fun getById(user: UserDb, id: String): Flair? {
val items =
db.query(
"select * from flair where user_id=? and id=?;",
arrayOf(user.id, id),
Flair
)
.toList()
if (items.size == 0) {
return null
}
return items[0]
}
public fun deleteById(user: UserDb, id: String): Flair {
val flair = this.getById(user, id)
if (flair == null) {
throw NotFound()
}
db.update("delete from flair where id=?", id)
return flair
}
public fun updateFlair(flair: Flair): Flair {
db.update(
"update flair set user_id=?, color=?, name=?, expr=?, description=? where id=?;",
flair.user_id,
flair.color,
flair.name,
flair.expr,
flair.description,
flair.id,
)
return flair
}
public fun createFlair(user: UserDb, flair: CreateFlair): Flair {
val id = UUID.randomUUID().toString()
var description = ""
if (flair.description != null) {
description = flair.description!!
}
var new_flair = Flair(id, user.id, flair.color, flair.name, flair.expr, description)
db.update(
"insert into flair (id, user_id, color, name, expr, description) values (?, ?, ?, ?, ?, ?)",
new_flair.id,
new_flair.user_id,
new_flair.color,
new_flair.name,
new_flair.expr,
new_flair.description
)
return new_flair
}
}

View File

@ -0,0 +1,225 @@
package com.andr3h3nriqu3s.applications
import java.util.UUID
import kotlin.io.encoding.Base64
import kotlin.random.Random
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder
import org.springframework.stereotype.Service
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
enum class UserLevel(val level: Int) {
NORMAL(1),
ADMIN(255)
}
data class UserDb(
var id: String,
var username: String,
var email: String,
var password: String,
var level: Int
) {
fun toSafe(): User {
return User(this.id, this.username, this.email, this.level)
}
fun checkLevel(level: Int): Boolean {
return (level and this.level) == level
}
fun checkLevelThrow(level: Int) {
if (!this.checkLevel(level)) {
throw NotAuth()
}
}
}
data class UserCreateRequest(val email: String, val password: String, val username: String)
data class LoginUserRequest(val email: String, val password: String)
data class LoggedInUser(val token: String, val user: User)
data class User(val id: String, var username: String, var email: String, var level: Int)
@RestController
@ControllerAdvice
@RequestMapping("/api/user")
@kotlin.io.encoding.ExperimentalEncodingApi
class UserController(
val userService: UserService,
val sessionService: SessionService,
val sessionServiceCreator: SessionServiceCreator,
) {
@PostMapping(path = ["/login"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun login(@RequestBody user: LoginUserRequest): LoggedInUser {
val userdb = userService.login(user.email, user.password)
val session = sessionServiceCreator.createSession(userdb)
return LoggedInUser(session.token, userdb.toSafe())
}
@GetMapping(path = ["/list"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun list(@RequestHeader("token") token: String): List<User> {
sessionService.verifyTokenThrow(token).checkLevelThrow(UserLevel.ADMIN.ordinal)
return userService.findUsers().map { elm -> elm.toSafe() }
}
@PostMapping(path = ["/register"], produces = [MediaType.APPLICATION_JSON_VALUE])
public fun register(@RequestBody user: UserCreateRequest): LoggedInUser {
var new_user = userService.createUser(user)
val session = sessionServiceCreator.createSession(new_user)
return LoggedInUser(session.token, new_user.toSafe())
}
}
@Service
class UserService(val db: JdbcTemplate) {
fun login(email: String, passwd: String): UserDb {
var user = this.findUserByEmail(email)
if (user == null) {
throw UserNotFound()
}
var arg2SpringSecurity = Argon2PasswordEncoder(16, 32, 1, 60000, 10)
// TODO make this safe
if (!arg2SpringSecurity.matches(passwd, user.password)) {
throw UserNotFound()
}
return user
}
fun findUserByEmail(email: String): UserDb? {
var users =
db
.query("select * from users where email=?", arrayOf(email)) { response, _ ->
UserDb(
response.getString("id"),
response.getString("username"),
response.getString("email"),
response.getString("passwd"),
response.getInt("level")
)
}
.toList()
if (users.size == 0) {
return null
}
return users[0]
}
fun createUser(user: UserCreateRequest): UserDb {
var user_check = findUserByEmail(user.email)
if (user_check != null) {
throw Exception("User already exists")
}
val id = UUID.randomUUID().toString()
var arg2SpringSecurity = Argon2PasswordEncoder(16, 32, 1, 60000, 10)
val hashPassword = arg2SpringSecurity.encode(user.password)
var new_user = UserDb(id, user.username, user.email, hashPassword, UserLevel.NORMAL.ordinal)
db.update(
"insert into users (id, username, email, passwd, level) values (?, ?, ?, ?, ?)",
new_user.id,
user.username,
user.email,
new_user.password,
new_user.level,
)
return new_user
}
fun findUsers(): List<UserDb> =
db.query("select * from users") { response, _ ->
UserDb(
response.getString("id"),
response.getString("username"),
response.getString("email"),
response.getString("passwd"),
response.getInt("level"),
)
}
}
data class Session(val token: String, val user_id: String)
@Service
class SessionService(val db: JdbcTemplate) {
fun verifyToken(token: String): UserDb? {
var users =
db
.query(
"select * from tokens as t inner join users as u on id = user_id where token = ?",
arrayOf(token)
) { response, _ ->
UserDb(
response.getString("id"),
response.getString("username"),
response.getString("email"),
response.getString("passwd"),
response.getInt("level"),
)
}
.toList()
if (users.size == 0) {
return null
}
return users[0]
}
fun verifyTokenThrow(token: String): UserDb {
val new_user = this.verifyToken(token)
if (new_user == null) {
throw NoToken()
}
return new_user
}
}
@Service
@kotlin.io.encoding.ExperimentalEncodingApi
class SessionServiceCreator(val db: JdbcTemplate) {
fun createSession(user: UserDb): Session {
val session = Session(this.generateToken(), user.id)
db.update(
"insert into tokens (token, user_id) values (?,?);",
session.token,
session.user_id
)
return session
}
fun generateToken(): String {
var b = ByteArray(60)
Random.nextBytes(b)
return Base64.encode(b)
}
}

View File

@ -0,0 +1 @@
spring.application.name=Applications

View File

@ -0,0 +1,38 @@
CREATE TABLE IF NOT EXISTS users (
id VARCHAR PRIMARY KEY,
username VARCHAR NOT NULL,
email VARCHAR NOT NULL,
passwd VARCHAR NOT NULL,
level INT NOT NULL
);
CREATE TABLE IF NOT EXISTS tokens (
token VARCHAR PRIMARY KEY,
user_id VARCHAR
);
create table if not exists applications (
id text primary key,
url text not null,
original_url text,
unique_url text,
title text,
user_id text,
extra_data text,
status integer
);
create table if not exists flair (
id text primary key,
user_id text not null,
color text default '#ff0000',
name text default 'New Flair',
expr text default 'flair'
description text default '',
);
create table if not exists flair_link (
id text primary key,
application_id text not null,
flair_id text not null
);

View File

@ -0,0 +1,13 @@
package com.andr3h3nriqu3s.applications
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
@SpringBootTest
class ApplicationsApplicationTests {
@Test
fun contextLoads() {
}
}

0
api/test.db Normal file
View File

View File

@ -0,0 +1,66 @@
/*browser.browserAction.onClicked.addListener(function (e) {
console.log("This is a nice test!", e);
});*/
browser.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
if (message.type !== "MY_GET_URL") return;
let windowList = (await browser.storage.local.get("windows")).windows ?? [];
if (windowList.length !== 1) {
browser.tabs.sendMessage(sender.tab.id, {
type: "MY_GET_URL_R",
error: "Invalid number of pages marked as target",
data: windowList,
});
return;
}
const tab = await browser.tabs.get(windowList[0]);
browser.tabs.sendMessage(sender.tab.id, {
type: "MY_GET_URL_R",
url: tab.url,
all_data: tab,
});
});
browser.runtime.onInstalled.addListener(async () => {
console.log(browser);
await browser.storage.local.set({
windows: [],
});
// Clear the menus from the prev install
browser.menus.removeAll();
browser.menus.create({
id: "mark-page",
title: "Mark Page As the Glassdoor target",
contexts: ["all"],
});
browser.menus.create({
id: "mark-page-clear",
title: "Clear Marked Pages",
contexts: ["all"],
});
browser.menus.onClicked.addListener(async function (e, tab) {
let windowList =
(await browser.storage.local.get("windows")).windows ?? [];
if (e.menuItemId === "mark-page") {
console.log("marking page", tab);
if (windowList.includes(tab.id)) {
windowList = windowList.filter((a) => a !== tab.id);
} else {
windowList.push(tab.id);
}
} else if (e.menuItemId === "mark-page-clear") {
windowList = [];
console.log("clear");
}
await browser.storage.local.set({
windows: windowList,
});
});
});

15
extensions/definitions.js Normal file
View File

@ -0,0 +1,15 @@
browser.runtime.onMessage.addListener(function (message) {
if (message.type === "MY_GET_URL_R") {
window.postMessage(message);
}
});
window.addEventListener("message", function (e) {
if (e.data.type === "MY_GET_URL") {
browser.runtime.sendMessage({ type: "MY_GET_URL" });
} else if (e.data.type === "HAS_EXTENSION_Q") {
console.log("Got request for ext");
window.postMessage({ type: "HAS_EXTENSION" });
}
//console.log("here2", e);
});

29
extensions/manifest.json Normal file
View File

@ -0,0 +1,29 @@
{
"manifest_version": 2,
"name": "Url Extractor Extension",
"version": "1.0",
"description": "Allow my webpage to extract urls from other webpages",
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["definitions.js"],
"all_frames": true
}
],
"permissions": ["activeTab", "menus", "tabs", "storage"],
"background": {
"scripts": ["background-script.js"],
"persistent": false,
"type": "module"
},
"applications": {
"gecko": {
"id": "me@andr3h3nriqu3s.com"
}
}
}

49
nginx.proxy.conf Normal file
View File

@ -0,0 +1,49 @@
events {
worker_connections 2024;
}
http {
proxy_read_timeout 600;
proxy_connect_timeout 600;
proxy_send_timeout 600;
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 8001;
client_max_body_size 5G;
location / {
proxy_http_version 1.1;
proxy_pass http://localhost:5173;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
location /api {
proxy_http_version 1.1;
proxy_pass http://localhost:8080;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
location /glassdoor {
rewrite ^/glassdoor(.*)$ $1 break;
proxy_http_version 1.1;
proxy_pass https://glassdoor.com;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
}

21
site/.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
node_modules
# Output
.output
.vercel
/.svelte-kit
/build
# OS
.DS_Store
Thumbs.db
# Env
.env
.env.*
!.env.example
!.env.test
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

1
site/.npmrc Normal file
View File

@ -0,0 +1 @@
engine-strict=true

4
site/.prettierignore Normal file
View File

@ -0,0 +1,4 @@
# Package Managers
package-lock.json
pnpm-lock.yaml
yarn.lock

8
site/.prettierrc Normal file
View File

@ -0,0 +1,8 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

38
site/README.md Normal file
View File

@ -0,0 +1,38 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm create svelte@latest
# create a new project in my-app
npm create svelte@latest my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.

33
site/eslint.config.js Normal file
View File

@ -0,0 +1,33 @@
import js from '@eslint/js';
import ts from 'typescript-eslint';
import svelte from 'eslint-plugin-svelte';
import prettier from 'eslint-config-prettier';
import globals from 'globals';
/** @type {import('eslint').Linter.FlatConfig[]} */
export default [
js.configs.recommended,
...ts.configs.recommended,
...svelte.configs['flat/recommended'],
prettier,
...svelte.configs['flat/prettier'],
{
languageOptions: {
globals: {
...globals.browser,
...globals.node
}
}
},
{
files: ['**/*.svelte'],
languageOptions: {
parserOptions: {
parser: ts.parser
}
}
},
{
ignores: ['build/', '.svelte-kit/', 'dist/']
}
];

4654
site/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

36
site/package.json Normal file
View File

@ -0,0 +1,36 @@
{
"name": "site",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .",
"format": "prettier --write ."
},
"devDependencies": {
"@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0",
"@types/eslint": "^8.56.7",
"autoprefixer": "^10.4.19",
"eslint": "^9.0.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.36.0",
"globals": "^15.0.0",
"postcss": "^8.4.39",
"prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2",
"svelte": "^5.0.0-next.1",
"svelte-check": "^3.6.0",
"tailwindcss": "^3.4.4",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"typescript-eslint": "^8.0.0-alpha.20",
"vite": "^5.0.3"
},
"type": "module"
}

2541
site/pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

6
site/postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

74
site/src/app.css Normal file
View File

@ -0,0 +1,74 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@font-face {
font-family: 'JetBrainsMono';
src: url('/fonts/JetBrainsMono-VariableFont_wght.ttf');
}
@layer components {
.grad-back {
@apply bg-gradient-to-t from-violet-700 via-blue-400 to-violet-700;
background-size: 100vw 400vh;
animation: grad-back 100s linear infinite;
}
@keyframes grad-back {
from {
background-position: 0 0;
}
to {
background-position: 0 400vh;
}
}
.font-JetBrainsMono {
font-family: 'JetBrainsMono';
}
h1 {
@apply text-purple-500 font-bold font-JetBrainsMono text-xl;
}
dialog {
@apply p-3 rounded-md;
}
dialog::backdrop {
@apply bg-secudanry opacity-75 fixed top-0 right-0 bottom-0 left-0;
}
.flabel {
@apply block text-purple-500;
}
.finput {
@apply rounded-lg w-full p-2 drop-shadow-lg border-gray-300 border;
}
.finput[type='color'] {
@apply p-0;
}
.btns {
@apply flex justify-center py-2 gap-2;
}
.btn-primary {
@apply rounded-lg text-white bg-violet-500 p-2;
}
.btn-danger {
@apply rounded-lg bg-danger p-2 text-white;
}
.btn-confirm {
@apply rounded-lg bg-blue-400 text-white p-2;
}
.card {
@apply bg-white rounded-lg drop-shadow-lg;
}
}

13
site/src/app.d.ts vendored Normal file
View File

@ -0,0 +1,13 @@
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface PageState {}
// interface Platform {}
}
}
export {};

18
site/src/app.html Normal file
View File

@ -0,0 +1,18 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
/>
%sveltekit.head%
</head>
<body class="grad-back" data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View File

@ -0,0 +1,91 @@
import type { Flair } from './FlairStore.svelte';
import { post } from './utils';
export const ApplicationStatus = Object.freeze({
ToApply: 0,
WorkingOnIt: 1,
Ignore: 2,
ApplyedButSaidNo: 3,
Applyed: 4,
Expired: 5
});
export const ApplicationStatusMaping: Record<
(typeof ApplicationStatus)[keyof typeof ApplicationStatus],
string
> = Object.freeze({
0: 'To Apply',
1: 'Working On It',
2: 'Ignore',
3: 'Applyed But Said No',
4: 'Applyed',
5: 'Expired'
});
export type Application = {
id: string;
url: string;
original_url: string | null;
unique_url: string | null;
title: string;
user_id: string;
extra_data: string;
payrange: string;
status: number;
flairs: Flair[];
};
function createApplicationStore() {
let applications: Application[] = $state([]);
let applyed: Application[] = $state([]);
let dragApplication: Application | undefined = $state(undefined);
return {
/**
* @throws {Error}
*/
async loadApplications(force = false) {
if (!force && applications.length > 1) {
return;
}
applications = await post('application/list', { status: 0 });
},
/**
* @throws {Error}
*/
async loadAplyed(force = false) {
if (!force && applications.length > 1) {
return;
}
applyed = await post('application/list', { status: ApplicationStatus.Applyed });
},
clear() {
applications = [];
},
dragStart(application: Application) {
dragApplication = application;
},
dragEnd() {
dragApplication = undefined;
},
get dragging() {
return dragApplication;
},
get applications() {
return applications;
},
get applyed() {
return applyed;
}
};
}
export const applicationStore = createApplicationStore();

View File

@ -0,0 +1,29 @@
import { get } from './utils';
export type Flair = {
id: string;
user_id: string;
color: string;
name: string;
expr: string;
description: string;
};
function createFlairStore() {
let flairList: Flair[] = $state([]);
return {
get flairs() {
return flairList;
},
async loadItems(force?: boolean) {
if (flairList.length > 0 && !force) {
return;
}
flairList = await get('flair/');
}
};
}
export const flairStore = createFlairStore();

View File

@ -0,0 +1,16 @@
<script lang="ts">
import { onMount, type Snippet } from 'svelte';
import { userStore } from './UserStore.svelte';
let ready = $state(false);
const { children }: { children: Snippet } = $props();
onMount(() => {
ready = userStore.checkLogin();
});
</script>
{#if ready}
{@render children()}
{/if}

View File

@ -0,0 +1,72 @@
import { goto } from '$app/navigation';
export type User = {
token: string;
user: {
username: string;
email: string;
level: number;
};
};
function createUserStore() {
let user: User | undefined = $state();
const local_user = localStorage.getItem('user');
if (local_user) {
try {
user = JSON.parse(local_user);
} catch {
localStorage.removeItem('user');
}
}
return {
checkLogin(redirect = true) {
console.log('test1');
if (user !== undefined) {
return true;
}
console.log('test2');
if (redirect) {
goto('/login');
}
console.log('test3');
return false;
},
get isLoggedIn() {
console.log(user);
return user !== undefined;
},
get token() {
if (user === undefined) {
throw new Error('User not logged in');
}
return user.token;
},
set user(new_user: User | undefined) {
user = new_user;
if (new_user === undefined) {
localStorage.removeItem('user');
} else {
localStorage.setItem('user', JSON.stringify(new_user));
}
},
get user(): User['user'] {
if (user === undefined) {
throw new Error('User not logged in');
}
return user.user;
}
};
}
export const userStore = createUserStore();

1
site/src/lib/index.ts Normal file
View File

@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.

60
site/src/lib/utils.ts Normal file
View File

@ -0,0 +1,60 @@
import { goto } from '$app/navigation';
import { userStore } from './UserStore.svelte';
export function preventDefault<T extends Event, Return>(f: (e: T) => Return): (e: T) => Return {
return (e: T): Return => {
e.preventDefault();
return f(e);
};
}
export async function request(
method: 'POST' | 'PUT' | 'GET' | 'DELETE',
url: string,
body?: unknown
) {
const headers = new Headers();
headers.append('response-type', 'application/json');
if (body) {
headers.append('content-type', 'application/json');
}
if (userStore.isLoggedIn) {
headers.append('token', userStore.token);
}
const r = await fetch(`/api/${url}`, {
method,
headers,
body: JSON.stringify(body)
});
if (r.status === 401) {
userStore.user = undefined;
goto('/login');
return;
}
if (r.status !== 200) {
throw r;
}
return r.json();
}
export async function get(url: string) {
return request('GET', url);
}
export async function deleteR(url: string) {
return request('DELETE', url);
}
export async function post(url: string, body: unknown) {
return request('POST', url, body);
}
export async function put(url: string, body: unknown) {
return request('PUT', url, body);
}

View File

@ -0,0 +1,5 @@
<script>
import '../app.css';
</script>
<slot />

View File

@ -0,0 +1,4 @@
export const prerender = true;
export const ssr = false;
export const csr = true;
export const trailingSlash = 'always';

View File

@ -0,0 +1,20 @@
<script lang="ts">
import NavBar from './NavBar.svelte';
import HasUser from '$lib/HasUser.svelte';
import ApplicationsList from './ApplicationsList.svelte';
import WorkArea from './work-area/WorkArea.svelte';
import AppliyedList from './AppliyedList.svelte';
</script>
<HasUser>
<div class="flex flex-col h-[100vh]">
<NavBar />
<div class="w-full px-4 grow h-full gap-3 flex flex-col">
<div class="flex h-3/5 gap-3">
<ApplicationsList />
<WorkArea />
</div>
<AppliyedList />
</div>
</div>
</HasUser>

View File

@ -0,0 +1,50 @@
<script lang="ts">
import { applicationStore } from '$lib/ApplicationsStore.svelte';
import { onMount } from 'svelte';
let filter = $state('');
onMount(() => {
applicationStore.loadApplications();
});
</script>
<div class="w-2/12 card p-3 flex flex-col">
<h1>To Apply</h1>
<div class="flex">
<input placeholder="Filter" class="p-2 flex-grow" bind:value={filter} />
<div>
{applicationStore.applications.length}
</div>
</div>
<div class="overflow-auto flex-grow p-2">
{#each applicationStore.applications.filter((i) => {
if (!filter) {
return true;
}
const f = new RegExp(filter, 'ig');
return i.title.match(f);
}) as item}
<div
class="card p-2 my-2 bg-slate-100"
draggable="true"
ondragstart={() => applicationStore.dragStart(item)}
ondragend={() => {
window.requestAnimationFrame(() => {
applicationStore.dragEnd();
});
}}
role="none"
>
<div class:animate-pulse={applicationStore.dragging?.id === item.id}>
<h2 class="text-lg text-blue-500">
{item.title}
</h2>
<a href={item.url} class="text-violet-600 overflow-hidden whitespace-nowrap block">
{item.url}
</a>
</div>
</div>
{/each}
</div>
</div>

View File

@ -0,0 +1,36 @@
<script lang="ts">
import { applicationStore } from '$lib/ApplicationsStore.svelte';
import { onMount } from 'svelte';
onMount(() => {
applicationStore.loadAplyed();
});
</script>
<div class="card p-3 rounded-lg flex flex-col">
<h1>Applied</h1>
<div class="overflow-auto flex-grow">
{#each applicationStore.applyed as item}
<div
class="card p-2 my-2 bg-slate-100"
draggable="true"
ondragstart={() => applicationStore.dragStart(item)}
ondragend={() => {
window.requestAnimationFrame(() => {
applicationStore.dragEnd();
});
}}
role="none"
>
<div class:animate-pulse={applicationStore.dragging?.id === item.id}>
<h2 class="text-lg text-blue-500">
{item.title}
</h2>
<a href={item.url} class="text-violet-600 overflow-hidden whitespace-nowrap block">
{item.url}
</a>
</div>
</div>
{/each}
</div>
</div>

View File

@ -0,0 +1,29 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { userStore } from '$lib/UserStore.svelte';
</script>
<div class="p-7">
<div class="card p-2 rounded-xl flex">
<button class="text-secudanry hover:text-primary px-2" onclick={() => goto('/')}> Home </button>
<button class="text-secudanry hover:text-primary px-2" onclick={() => goto('/submit')}>
Submit text
</button>
<button class="text-secudanry hover:text-primary px-2" onclick={() => goto('/flair')}>
Flair
</button>
<div class="flex-grow"></div>
<div class="text-secudanry px-2">
{userStore.user.username}
</div>
<button
class="text-secudanry hover:text-primary px-2"
onclick={() => {
userStore.user = undefined;
goto('/login');
}}
>
Logout
</button>
</div>
</div>

View File

@ -0,0 +1,132 @@
<script lang="ts">
import { onMount } from 'svelte';
let id: string | undefined | null;
onMount(() => {
const url = new URLSearchParams(window.location.search);
id = url.get('id');
loadData();
});
const application = undefined;
async function loadData() {}
</script>
<svelte:head>
<title>CV</title>
</svelte:head>
<div class="flex items-center w-full flex-col">
<div class="max-w-[210mm] px-5 py-16">
<div class="w-full flex">
<h1 class="dark:text-white text-black text-5xl">Andre Henriques</h1>
<div class="flex-grow"></div>
<div class="text-right">
<ul>
<li class="px-1">
<a class="text-white underline" href="mailto:contact@andr3h3nriqu3s.com"
>contact@andr3h3nriqu3s.com</a
>
</li>
<li class="px-1">
<a class="text-white underline" href="tel:+4407823391342">+44 0 782339 1342</a>
</li>
</ul>
</div>
</div>
<div class="p-5"></div>
<div class="w-full flex py-10">
<div class="text-white">
I am a dedicated and versatile programmer with for year of professional experience. <br />
During those years mainly worked with Typescript, JavaScript, React, Svelte. <br />
I thrive in high paced new environments, and I am always striving to learn new skills and technologies.
<br />
I also am knowable in various other technologies and am always learning new ones. <br />
</div>
</div>
{#if application}
<div class="p-5"></div>
<div>TODO: Application Information</div>
{/if}
<div class="p-5"></div>
<div class="dark:text-white text-black">
<h2 class="py-2 text-3xl">Work Expericence</h2>
<div>
<div>
<h1>Planum Solucoes, Lda</h1>
<div class="ml-5">
<h2>4 year - May 2020 - Present</h2>
<h3>Working with:</h3>
<ul class="pl-5 list-disc">
<li>Python</li>
<li>Jenkins</li>
<li>GitLab CI</li>
<li>Ansible</li>
<li>Docker</li>
</ul>
<h3>Associated Software Developer / DevOps Engineer:</h3>
<ul class="pl-5 list-disc">
<li>Developed web-based tools for the DevOps team to use</li>
<li>
Updated Jenkins pipelines that the team uses to manage one of the most important
pipelines that the team manages.
</li>
<li>
Created new scripts that were used to clean up multiple terabytes of unused data
that led to improvements in the performance of the other scripts running on the same
server as well as the performance of the backup system
</li>
</ul>
</div>
</div>
<div>
<h1>Sky UK</h1>
<div class="ml-5">
<h2>1 year - July 2022 - June 2023</h2>
<h3>Working with:</h3>
<ul class="pl-5 list-disc">
<li>Python</li>
<li>Jenkins</li>
<li>GitLab CI</li>
<li>Ansible</li>
<li>Docker</li>
</ul>
<h3>Associated Software Developer / DevOps Engineer:</h3>
<ul class="pl-5 list-disc">
<li>Developed web-based tools for the DevOps team to use</li>
<li>
Updated Jenkins pipelines that the team uses to manage one of the most important
pipelines that the team manages.
</li>
<li>
Created new scripts that were used to clean up multiple terabytes of unused data
that led to improvements in the performance of the other scripts running on the same
server as well as the performance of the backup system
</li>
</ul>
</div>
</div>
</div>
</div>
<div class="p-5"></div>
<div class="dark:text-white text-black">
<h2 class="py-2 text-3xl">Education</h2>
<div>
<div>
<h1>University of Surrey</h1>
<div class="ml-5">
<h2>July 2020 - June 2024</h2>
</div>
</div>
</div>
</div>
<!--div class="p-5"></div>
<div>TODO: Previous projetcs</div -->
<!-- div class="p-5"></div>
<div>TODO: Info form</div -->
</div>
</div>

View File

@ -0,0 +1,116 @@
<script lang="ts">
import { flairStore, type Flair as FlairModel } from '$lib/FlairStore.svelte';
import { deleteR, preventDefault, put } from '$lib/utils';
import { onMount } from 'svelte';
import NavBar from '../NavBar.svelte';
import Flair from './Flair.svelte';
let flair: FlairModel = $state({} as unknown as FlairModel);
async function create() {
try {
await put('flair/', flair);
flair = {} as unknown as FlairModel;
flairStore.loadItems(true);
} catch (e) {
// TODO: Show to user
console.log(e);
}
}
async function remove(id: string) {
try {
await deleteR(`flair/${id}`);
flairStore.loadItems(true);
} catch (e) {
// TODO: Show to user
console.log(e);
}
}
let edit: FlairModel | undefined = $state();
onMount(() => {
flairStore.loadItems();
});
async function update() {
try {
await put('flair/update', edit);
edit = undefined;
flairStore.loadItems(true);
} catch (e) {
// TODO: Show to user
console.log(e);
}
}
</script>
<NavBar />
<div class="p-2 flex flex-col gap-2">
<!-- Create -->
<div class="card p-2 rounded-lg">
<form class="flex flex-wrap gap-2" onsubmit={preventDefault(create)}>
<fieldset>
<label class="flabel" for="color">Color</label>
<input required type="color" id="color" class="finput" bind:value={flair.color} />
</fieldset>
<fieldset class="flex-grow">
<label class="flabel" for="name">Name</label>
<input required id="name" class="w-full finput" bind:value={flair.name} />
</fieldset>
<fieldset class="flex-grow">
<label class="flabel" for="expr">Expr</label>
<input required id="expr" class="w-full finput" bind:value={flair.expr} />
</fieldset>
<div class="btns w-full">
<button class="btn-confirm">Create</button>
</div>
</form>
</div>
<!-- List -->
<div class="card rounded-lg p-2">
<h1>Flairs</h1>
{#each flairStore.flairs.filter((a) => !flair.name || ` ${flair.name} `.match(new RegExp(a.expr, 'i'))) as item}
<div class="p-1 flex items-center gap-2">
<button
onclick={() => {
edit = item;
}}
>
<Flair {item} />
</button>
<div class="inline text-white">
Expr: {item.expr}
</div>
<div class="flex-grow"></div>
<button class="btn-danger" onclick={preventDefault(() => remove(item.id))}> Remove </button>
</div>
{#if item.id == edit?.id}
<form onsubmit={preventDefault(update)} class="shadow-inner p-2 rounded-xl">
<fieldset>
<label class="flabel" for="color">Color</label>
<input required type="color" id="color" bind:value={edit.color} />
</fieldset>
<fieldset>
<label class="flabel" for="name">Name</label>
<input required id="name" class="finput w-full" bind:value={edit.name} />
</fieldset>
<fieldset>
<label class="flabel" for="expr">Expr</label>
<input required id="expr" class="finput w-full" bind:value={edit.expr} />
</fieldset>
<fieldset>
<label class="flabel" for="description">Description</label>
<textarea required id="description" class="finput w-full" bind:value={edit.description}
></textarea>
</fieldset>
<div class="btns w-full">
<button class="btn-confirm">Save</button>
</div>
</form>
{/if}
{/each}
</div>
</div>

View File

@ -0,0 +1,42 @@
<script lang="ts">
import type { Application } from '$lib/ApplicationsStore.svelte';
import type { Flair } from '$lib/FlairStore.svelte';
import { deleteR, preventDefault } from '$lib/utils';
let {
item,
allowDelete,
updateApplication,
application
}: {
application?: Application;
item: Flair;
allowDelete?: boolean;
updateApplication?: (item: Application) => void;
} = $props();
async function remove() {
if (!allowDelete || !application || !updateApplication) return;
try {
updateApplication(await deleteR(`application/flair/${application.id}/${item.id}`));
} catch (e) {
// Show User
console.log(e);
}
}
let onTop = $state(false);
</script>
<button
style="background: {item.color}"
class="inline px-2 py-1 rounded-lg relative"
onclick={preventDefault(remove)}
onmouseenter={() => (onTop = true)}
onmouseleave={() => (onTop = false)}
>
{item.name}
{#if onTop && allowDelete}
<span style="background: {item.color}" class="bi bi-trash-fill text-danger absolute right-0"
></span>
{/if}
</button>

View File

@ -0,0 +1,57 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { userStore } from '$lib/UserStore.svelte';
import { post, preventDefault } from '$lib/utils';
import { onMount } from 'svelte';
onMount(() => {
if (userStore.isLoggedIn) {
goto('/');
}
});
let userData = $state({
email: '',
password: ''
});
async function register() {
console.log('this is a requeset');
try {
const r = await post('user/login', userData);
userStore.user = r;
goto('/');
} catch (e) {
// TODO messages
console.log(e);
return;
}
userData = {
email: '',
password: ''
};
}
</script>
<div class="w-full h-lvh flex justify-center items-center">
<form class="card p-3 min-w-[300px]" onsubmit={preventDefault(register)}>
<h1 class="text-center">Login</h1>
<fieldset class="py-2">
<label class="flabel" for="email">Email</label>
<input required class="finput" id="email" type="email" bind:value={userData.email} />
</fieldset>
<fieldset class="py-2">
<label class="flabel" for="password">Password</label>
<input required class="finput" id="password" type="password" bind:value={userData.password} />
</fieldset>
<div class="btns">
<button type="submit" class="btn-confirm"> Login </button>
</div>
<div class="btns">
<button class="text-secudanry" type="button" onclick={() => goto('/register')}>
Register
</button>
</div>
</form>
</div>

View File

@ -0,0 +1,61 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { userStore } from '$lib/UserStore.svelte';
import { post, preventDefault } from '$lib/utils';
import { onMount } from 'svelte';
onMount(() => {
if (userStore.isLoggedIn) {
goto('/');
}
});
let userData = $state({
username: '',
email: '',
password: ''
});
async function register() {
console.log('this is a requeset');
try {
const r = await post('user/register', userData);
userStore.user = r;
goto('/');
} catch (e) {
// TODO messages
console.log(e);
return;
}
userData = {
username: '',
email: '',
password: ''
};
}
</script>
<div class="w-full h-lvh flex justify-center items-center">
<form class="bg-third rounded-lg p-3 min-w-[300px]" onsubmit={preventDefault(register)}>
<h1 class="text-center">Register</h1>
<fieldset class="py-2">
<label class="flabel" for="user">User</label>
<input required class="finput" id="user" bind:value={userData.username} />
</fieldset>
<fieldset class="py-2">
<label class="flabel" for="email">Email</label>
<input required class="finput" id="email" type="email" bind:value={userData.email} />
</fieldset>
<fieldset class="py-2">
<label class="flabel" for="password">Password</label>
<input required class="finput" id="password" type="password" bind:value={userData.password} />
</fieldset>
<div class="btns">
<button type="submit" class="btn-confirm"> Register </button>
</div>
<div class="btns">
<button class="text-secudanry" type="button" onclick={() => goto('/login')}> Login </button>
</div>
</form>
</div>

View File

@ -0,0 +1,34 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { applicationStore } from '$lib/ApplicationsStore.svelte';
import { post, preventDefault } from '$lib/utils';
import NavBar from '../NavBar.svelte';
const text = $state({
text: ''
});
async function submit() {
try {
await post('application/text', text);
applicationStore.loadApplications(true);
goto('/');
} catch (e) {
console.log(e);
}
}
</script>
<NavBar />
<div class="flex justify-center h-4/6">
<form onsubmit={preventDefault(submit)} class="card p-5 rounded-lg w-2/5">
<fieldset>
<label for="text" class="flabel">Text</label>
<textarea id="text" class="finput h-96" bind:value={text.text}></textarea>
</fieldset>
<div class="btns">
<button class="btn-confirm"> Submit </button>
</div>
</form>
</div>

View File

@ -0,0 +1,40 @@
<script lang="ts">
import { post, preventDefault } from '$lib/utils';
let {
dialog = $bindable(),
onreload,
id
}: { dialog: HTMLDialogElement; onreload: () => void; id: string } = $props();
let data = $state({
text: ''
});
async function submit() {
try {
await post('application/text/flair', {
id,
...data
});
data.text = '';
dialog.close();
onreload();
} catch (e) {
// Show message to the user
console.log(e);
}
}
</script>
<dialog class="card" bind:this={dialog}>
<form onsubmit={preventDefault(submit)}>
<fieldset>
<label class="flabel" for="text">Text</label>
<textarea class="finput min-w-96 min-h-96" id="text" bind:value={data.text}></textarea>
</fieldset>
<div class="btns">
<button type="submit" class="btn-confirm">Extract</button>
</div>
</form>
</dialog>

View File

@ -0,0 +1,96 @@
<script lang="ts">
import type { Application } from '$lib/ApplicationsStore.svelte';
import { post, preventDefault } from '$lib/utils';
let {
dialog = $bindable(),
openWindow = $bindable(),
onreload,
id
}: {
dialog: HTMLDialogElement;
onreload: (item: Application) => void;
id: string;
openWindow?: Window | null;
} = $props();
const data = $state({
url: ''
});
let form: HTMLFormElement | undefined = $state();
async function submit() {
try {
const newItem = await post('application/update/url', {
id,
...data
});
data.url = '';
dialog.close();
onreload(newItem);
} catch (e) {
// Show message to the user
console.log(e);
}
}
let hasExtension = $state(false);
$effect(() => {
function onMessage(e: MessageEvent) {
if (e.data.type === 'MY_GET_URL_R') {
if (e.data.error) {
console.log(e.data);
if (e.data.data.length === 0) {
console.log('TODO inform user to mark page');
} else {
console.log('TODO inform use to unmark page');
}
return;
}
data.url = e.data.url ?? '';
window.requestAnimationFrame(() => {
if (!form?.reportValidity()) {
return;
}
submit();
});
} else if (e.data.type === 'HAS_EXTENSION') {
hasExtension = true;
console.log('got ext');
}
if (!hasExtension) {
window.postMessage({ type: 'HAS_EXTENSION_Q' });
}
}
console.log('setting up');
window.addEventListener('message', onMessage);
return () => {
window.removeEventListener('message', onMessage);
};
});
function askForUrl() {
console.log('sending');
window.postMessage({ type: 'MY_GET_URL' });
}
</script>
<dialog class="card" bind:this={dialog}>
<form bind:this={form} onsubmit={preventDefault(submit)}>
<fieldset>
<label class="flabel" for="text">Url</label>
<textarea class="finput min-w-96 min-h-96" id="text" bind:value={data.url}></textarea>
</fieldset>
<div class="btns">
<button type="submit" class="btn-confirm">Update</button>
{#if hasExtension}
<button type="button" class="btn-confirm" onclick={askForUrl}>Get Url</button>
{/if}
</div>
</form>
</dialog>

View File

@ -0,0 +1,377 @@
<script lang="ts">
import {
ApplicationStatus,
ApplicationStatusMaping,
applicationStore,
type Application
} from '$lib/ApplicationsStore.svelte';
import { put, preventDefault, post, get, deleteR } from '$lib/utils';
import { onMount } from 'svelte';
import ExtractTextDialog from './ExtractTextDialog.svelte';
import Flair from '../flair/Flair.svelte';
import NewUrlDialog from './NewUrlDialog.svelte';
import { userStore } from '$lib/UserStore.svelte';
let activeItem: Application | undefined = $state();
let extractTokens: HTMLDialogElement;
let changeUrl: HTMLDialogElement;
let preparingToDrop = $state(false);
async function activate(item?: Application) {
if (!item) {
return;
}
if (activeItem && activeItem.id !== item.id && activeItem.status === 1) {
// Deactivate active item
try {
await put('application/status', {
id: activeItem.id,
status: 0
});
} catch (e) {
// Show User
console.log('info data', e);
return;
}
}
if (item.status === 0) {
openWindow(item.url);
}
try {
await put('application/status', {
id: item.id,
status: ApplicationStatus.WorkingOnIt
});
activeItem = await get('application/active');
if (!activeItem?.unique_url) {
window.requestAnimationFrame(() => {
changeUrl.showModal();
});
}
} catch (e) {
// Show User
console.log('info data', e);
}
applicationStore.loadApplications(true);
}
function openWindow(url: string) {
if (!url.startsWith('https://')) {
url = `https://${url}`;
}
window.open(
url,
'new window',
`location,height=${window.screen.height},width=${window.screen.width},scrollbars,status,toolbar,menubar,popup`
);
}
async function loadActive() {
try {
activeItem = await get('application/active');
if (activeItem?.unique_url === null) {
changeUrl.showModal();
}
} catch (e) {
// Show User
console.log('info data', e);
}
}
onMount(() => {
userStore.checkLogin();
loadActive();
});
async function moveStatus(status: number) {
if (!activeItem) return;
// Deactivate active item
try {
await put('application/status', {
id: activeItem.id,
status: status
});
} catch (e) {
// Show User
console.log('info data', e);
return;
}
applicationStore.loadApplications(true);
activeItem = undefined;
//openedWindow?.close();
//openedWindow = undefined;
}
async function remove() {
if (!activeItem) return;
// Deactivate active item
try {
await deleteR(`application/${activeItem.id}`);
} catch (e) {
// Show User
console.log('info data', e);
return;
}
applicationStore.loadApplications(true);
activeItem = undefined;
//openedWindow?.close();
//openedWindow = undefined;
}
async function save() {
try {
await put('application/update', activeItem);
} catch (e) {
// Show User
console.log('info data', e);
}
}
async function resetUrl() {
try {
activeItem = await post(`application/reset/url/${activeItem?.id}`, {});
} catch (e) {
// Show User
console.log('info data', e);
}
}
let drag = $state(true);
const statusMapping: string = $derived(
(ApplicationStatusMaping[
activeItem?.status as (typeof ApplicationStatus)[keyof typeof ApplicationStatus]
] as string) ?? ''
);
</script>
<div class="flex flex-col w-full gap-2 min-w-0" role="none">
{#if activeItem && (!applicationStore.dragging || applicationStore.dragging?.id === activeItem.id)}
<div
draggable={drag}
ondrag={() => {
applicationStore.dragStart(activeItem);
}}
ondragend={() => {
window.requestAnimationFrame(() => {
applicationStore.dragEnd();
});
}}
role="none"
class="flex flex-col p-2 h-full gap-2 card min-w-0 flex-grow"
>
<div class="w-full">
{#if activeItem.status != 1}
<div class="bg-danger text-white p-2 rounded-lg text-lg font-bold">
{statusMapping}
</div>
{/if}
<fieldset>
<label class="flabel" for="title">Title</label>
<input class="finput" id="title" bind:value={activeItem.title} onchange={save} />
</fieldset>
<fieldset>
<label class="flabel" for="payrange">Pay Range</label>
<input class="finput" id="payrange" bind:value={activeItem.payrange} onchange={save} />
</fieldset>
<fieldset
draggable="false"
onmouseenter={() => (drag = false)}
onmouseleave={() => (drag = true)}
class="max-w-full min-w-0 overflow-hidden"
>
<div class="flabel">Url</div>
<div class="finput bg-white w-full break-keep">
{activeItem.url}
</div>
</fieldset>
{#if activeItem.unique_url}
<fieldset
draggable="false"
onmouseenter={() => (drag = false)}
onmouseleave={() => (drag = true)}
>
<div class="flabel">Unique Url</div>
<div class="finput bg-white">
{activeItem.unique_url}
</div>
</fieldset>
{/if}
<div>
<div class="flabel">Tags</div>
<div class="flex gap-2 flex-wrap">
{#each activeItem.flairs as item}
<Flair
{item}
allowDelete
application={activeItem}
updateApplication={(item) => (activeItem = item)}
/>
{/each}
</div>
</div>
<fieldset>
<label class="flabel" for="extra">Extra Info</label>
<textarea class="finput" id="extra" bind:value={activeItem.extra_data} onchange={save}
></textarea>
</fieldset>
</div>
<div class="flex btns">
<button
class="btn-confirm"
onclick={() => {
openWindow(activeItem!.url);
}}
>
Open
</button>
<button
class="btn-primary"
onclick={() => window.open(`/cv?id=${activeItem!.id}`, '_blank')}
>
Open CV
</button>
<button class="btn-primary" onclick={() => extractTokens.showModal()}>
Extract Flair
</button>
{#if activeItem.original_url == null}
<button class="btn-primary" onclick={() => changeUrl.showModal()}> Update Url </button>
{/if}
{#if activeItem.original_url != null}
<button class="btn-danger" onclick={resetUrl}> Reset Url </button>
{/if}
</div>
</div>
{#if applicationStore.dragging}
<div class="flex w-full flex-grow rounded-lg p-1 gap-2">
<!-- Ignore -->
<div
class="grid place-items-center w-full card p-2 rounded-lg"
role="none"
ondragover={preventDefault(() => {})}
ondragenter={preventDefault(() => {})}
ondrop={() => {
moveStatus(ApplicationStatus.Ignore);
}}
>
<span
class="bi bi-trash-fill text-7xl absolute"
class:animate-bounce={applicationStore.dragging}
></span>
<span class="bi bi-trash-fill text-7xl opacity-0"></span>
<div class="text-xl">Drop Application Ignore it.</div>
</div>
<!-- Expired -->
<div
class="grid place-items-center w-full card p-2 rounded-lg"
role="none"
ondragover={preventDefault(() => {})}
ondragenter={preventDefault(() => {})}
ondrop={() => {
moveStatus(ApplicationStatus.Expired);
}}
>
<span
class="bi bi-clock-fill text-7xl text-orange-500 absolute"
class:animate-bounce={applicationStore.dragging}
></span>
<span class="bi bi-clock-fill text-7xl text-orange-500 opacity-0"></span>
<div class="text-orange-500 text-xl">Mark as expired.</div>
</div>
<!-- Repeated -->
<div
class="grid place-items-center w-full card p-2 rounded-lg"
role="none"
ondragover={preventDefault(() => {})}
ondragenter={preventDefault(() => {})}
ondrop={() => {
remove();
}}
>
<span
class="bi bi-trash-fill text-7xl text-danger absolute"
class:animate-bounce={applicationStore.dragging}
></span>
<span class="bi bi-trash-fill text-7xl text-danger opacity-0"></span>
<div class="text-danger text-xl">This is repeated</div>
</div>
<!-- Applyed -->
<div
class="grid place-items-center w-full card p-2 rounded-lg"
role="none"
ondragover={preventDefault(() => {})}
ondragenter={preventDefault(() => {})}
ondrop={async () => {
await moveStatus(ApplicationStatus.Applyed);
applicationStore.loadAplyed(true);
}}
>
<span
class="bi bi-server text-7xl text-confirm absolute"
class:animate-bounce={applicationStore.dragging}
></span>
<span class="bi bi-server text-7xl text-confirm opacity-0"></span>
<div class="text-confirm text-xl">Apply</div>
</div>
<!-- Rejected -->
<div
class="grid place-items-center w-full card p-2 rounded-lg"
role="none"
ondragover={preventDefault(() => {})}
ondragenter={preventDefault(() => {})}
ondrop={() => {
moveStatus(ApplicationStatus.ApplyedButSaidNo);
}}
>
<span
class="bi bi-fire text-danger text-7xl absolute"
class:animate-bounce={applicationStore.dragging}
></span>
<span class="bi bi-fire text-7xl text-danger opacity-0"></span>
<div class="text-danger text-xl">I was rejeted :(.</div>
</div>
</div>
{/if}
{:else}
<div
class="p-2 h-full w-full gap-2 flex-grow card grid place-items-center"
ondragover={(e) => {
e.preventDefault();
}}
ondragenter={preventDefault(() => {})}
ondrop={() => {
activate(applicationStore.dragging);
}}
role="none"
>
<div class="text-center relative">
<span
class="bi bi-layers-fill text-7xl text-white absolute"
class:animate-bounce={applicationStore.dragging}
></span>
<span class="bi bi-layers-fill text-7xl text-white opacity-0"></span>
<div class="text-white text-xl">Drop Application To Apply To.</div>
</div>
</div>
{/if}
</div>
<ExtractTextDialog id={activeItem?.id ?? ''} bind:dialog={extractTokens} onreload={loadActive} />
{#if !activeItem?.original_url}
<NewUrlDialog
id={activeItem?.id ?? ''}
bind:dialog={changeUrl}
onreload={(item) => (activeItem = item)}
/>
{/if}

BIN
site/static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

93
site/static/fonts/OFL.txt Normal file
View File

@ -0,0 +1,93 @@
Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@ -0,0 +1,79 @@
JetBrains Mono Variable Font
============================
This download contains JetBrains Mono as both variable fonts and static fonts.
JetBrains Mono is a variable font with this axis:
wght
This means all the styles are contained in these files:
JetBrainsMono-VariableFont_wght.ttf
JetBrainsMono-Italic-VariableFont_wght.ttf
If your app fully supports variable fonts, you can now pick intermediate styles
that arent available as static fonts. Not all apps support variable fonts, and
in those cases you can use the static font files for JetBrains Mono:
static/JetBrainsMono-Thin.ttf
static/JetBrainsMono-ExtraLight.ttf
static/JetBrainsMono-Light.ttf
static/JetBrainsMono-Regular.ttf
static/JetBrainsMono-Medium.ttf
static/JetBrainsMono-SemiBold.ttf
static/JetBrainsMono-Bold.ttf
static/JetBrainsMono-ExtraBold.ttf
static/JetBrainsMono-ThinItalic.ttf
static/JetBrainsMono-ExtraLightItalic.ttf
static/JetBrainsMono-LightItalic.ttf
static/JetBrainsMono-Italic.ttf
static/JetBrainsMono-MediumItalic.ttf
static/JetBrainsMono-SemiBoldItalic.ttf
static/JetBrainsMono-BoldItalic.ttf
static/JetBrainsMono-ExtraBoldItalic.ttf
Get started
-----------
1. Install the font files you want to use
2. Use your app's font picker to view the font family and all the
available styles
Learn more about variable fonts
-------------------------------
https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
https://variablefonts.typenetwork.com
https://medium.com/variable-fonts
In desktop apps
https://theblog.adobe.com/can-variable-fonts-illustrator-cc
https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
Online
https://developers.google.com/fonts/docs/getting_started
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
Installing fonts
MacOS: https://support.apple.com/en-us/HT201749
Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
Android Apps
https://developers.google.com/fonts/docs/android
https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
License
-------
Please read the full license text (OFL.txt) to understand the permissions,
restrictions and requirements for usage, redistribution, and modification.
You can use them in your products & projects print or digital,
commercial or otherwise.
This isn't legal advice, please consider consulting a lawyer and see the full
license for all details.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

18
site/svelte.config.js Normal file
View File

@ -0,0 +1,18 @@
import adapter from '@sveltejs/adapter-auto';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
// 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.
adapter: adapter()
}
};
export default config;

16
site/tailwind.config.js Normal file
View File

@ -0,0 +1,16 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{html,js,svelte,ts}'],
theme: {
extend: {
colors: {
primary: '#FFCCC9',
secudanry: '#011627',
third: '#757780',
danger: '#F71735',
confirm: '#41EAD4'
}
}
},
plugins: []
};

19
site/tsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler"
}
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
// except $lib which is handled by https://kit.svelte.dev/docs/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

6
site/vite.config.ts Normal file
View File

@ -0,0 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit()]
});