cleanup and finished project pages

This commit is contained in:
Jake Potrebic 2021-02-09 20:40:34 -08:00
parent 9b8e45ca08
commit fb1efe97e5
No known key found for this signature in database
GPG Key ID: 7C58557EC9C421F8
55 changed files with 280 additions and 427 deletions

View File

@ -37,7 +37,6 @@ hangar:
auth-url: "http://localhost:8000"
base-url: "http://localhost:8080"
plugin-upload-dir: "/uploads"
use-webpack: true
sponsors:
- name: Beer
@ -53,7 +52,7 @@ hangar:
channels:
max-name-len: 15
name-regex: "^[a-zA-Z0-9]+$"
color-default: 7
color-default: cyan
name-default: "Release"
pages:
@ -61,9 +60,10 @@ hangar:
name: "Home"
message: "Welcome to your new project!"
min-len: 15
max-len: 32000
page:
max-len: 75000
max-len: 75000
name-regex: "^[a-zA-Z0-9-_ ]+$"
max-name-len: 25
min-name-len: 3
projects:
max-name-len: 25
@ -81,6 +81,11 @@ hangar:
unsafe-download-max-age: 10
name-regex: "^[a-zA-Z0-9-_]{3,}$"
orgs:
create-limit: 5
dummy-email-domain: "org.papermc.io"
enabled: true
users:
max-tagline-len: 100
author-page-size: 25
@ -115,7 +120,5 @@ hangar:
avatar-url: "http://localhost:8000/avatar/%s?size=120x120" # only comment in if you run auth locally
key: changeme
timeout: 10000
breaker:
max-failures: 5
timeout: 10s
reset: 5m
safe-download-hosts:
- "github.com"

View File

@ -15,10 +15,11 @@
<v-spacer></v-spacer>
<v-menu v-if="$util.isLoggedIn()" bottom offset-y transition="slide-y-transition">
<v-menu v-if="$util.isLoggedIn()" bottom offset-y transition="slide-y-transition" open-on-hover>
<template #activator="{ on, attrs }">
<v-btn v-bind="attrs" color="primary" v-on="on">
{{ $t('nav.createNew') }}
<v-icon right>mdi-chevron-down</v-icon>
</v-btn>
</template>
<Dropdown :controls="newControls" />

View File

@ -8,7 +8,7 @@
<v-card-text>
<v-form ref="modalForm" v-model="validForm">
<v-text-field v-model.trim="form.name" filled :rules="[$util.$vc.require('Page name')]" :label="$t('page.new.name')" />
<v-select v-model="form.parent" :items="pages" filled clearable :label="$t('page.new.parent')" item-text="name" item-value="id" />
<v-select v-model="form.parent" :items="pageRoots" filled clearable :label="$t('page.new.parent')" item-text="name" item-value="id" />
</v-form>
</v-card-text>
<v-card-actions class="justify-end">
@ -38,6 +38,21 @@ export default class NewPageModal extends HangarFormModal {
@Prop({ type: Array as PropType<HangarProjectPage[]>, required: true })
pages!: HangarProjectPage[];
get pageRoots(): HangarProjectPage[] {
return this.flatDeep(this.pages);
}
flatDeep(pages: HangarProjectPage[]): HangarProjectPage[] {
let ps: HangarProjectPage[] = [];
for (const page of pages) {
if (page.children.length > 0) {
ps = ps.concat(this.flatDeep(page.children));
}
ps.push(page);
}
return ps;
}
createPage() {
this.loading = true;
this.$api
@ -47,7 +62,7 @@ export default class NewPageModal extends HangarFormModal {
})
.then((slug) => {
this.dialog = false;
this.$nuxt.refresh(); // Might be needed to refresh the HangarProject w/the new page? idk - jake
this.$nuxt.refresh();
this.$router.push(`/${this.$route.params.author}/${this.$route.params.slug}/pages/${slug}`);
})
.catch(this.$util.handleRequestError)

View File

@ -155,8 +155,12 @@ const msgs: LocaleMessageObject = {
title: 'Create a new page',
error: {
minLength: 'Page name was too short',
invalidName: 'Page names must be between 3 and 25 alphanumeric (+ underscore & hyphen) characters',
duplicateName: 'A page with that name already exists',
name: {
maxLength: 'Page name too long',
minLength: 'Page name too short',
invalidChars: 'Page name contained invalid characters',
},
},
name: 'Page Name',
parent: 'Parent Page (optional)',

View File

@ -145,7 +145,6 @@ export default class ProjectPage extends Vue {
}
async asyncData({ $api, params, $util }: Context) {
console.log('asyncData ProjectPage');
const project = await $api
.requestInternal<HangarProject>(`projects/project/${params.author}/${params.slug}`, false)
.catch($util.handlePageRequestError);

View File

@ -1,7 +1,7 @@
<template>
<div>
<v-row>
<v-col v-if="page.contents" cols="12" md="8">
<v-col v-if="page.contents" cols="12" md="9">
<MarkdownEditor
v-if="canEdit"
ref="editor"
@ -13,7 +13,7 @@
/>
<Markdown v-else :raw="page.contents" />
</v-col>
<v-col cols="12" md="4">
<v-col cols="12" md="3">
<ProjectPageList :project="project" />
</v-col>
</v-row>
@ -37,12 +37,7 @@ import ProjectPageList from '~/components/projects/ProjectPageList.vue';
},
})
export default class VueProjectPage extends DocPageMixin {
async asyncData({ $api, params, $util, beforeNuxtRender }: Context) {
if (process.server) {
beforeNuxtRender(({ nuxtState }) => {
console.log(nuxtState);
});
}
async asyncData({ $api, params, $util }: Context) {
const page = await $api
.requestInternal<ProjectPage>(`pages/page/${params.author}/${params.slug}/${params.pathMatch}`, false)
.catch<any>($util.handlePageRequestError);

View File

@ -234,7 +234,6 @@
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator';
import { Context } from '@nuxt/types';
import { HangarValidationException } from 'hangar-api';
import { ProjectOwner } from 'hangar-internal';
import StepperStepContent from '~/components/steppers/StepperStepContent.vue';
import { RootState } from '~/store';
@ -326,12 +325,7 @@ export default class NewPage extends Vue {
})
.catch((err) => {
this.projectError = true;
if (err.response?.data?.isHangarValidationException) {
const validationError: HangarValidationException = err.response.data;
for (const fieldError of validationError.fieldErrors) {
this.$util.error(fieldError.errorMsg);
}
}
this.$util.handleRequestError(err, 'Unable to create project');
})
.finally(() => {
this.projectLoading = false;

View File

@ -1,7 +1,7 @@
import { Context } from '@nuxt/types';
import { Inject } from '@nuxt/types/app';
import { AxiosError } from 'axios';
import { HangarException } from 'hangar-api';
import { HangarApiException, HangarValidationException } from 'hangar-api';
import { HangarUser } from 'hangar-internal';
import { NamedPermission } from '~/types/enums';
import { RootState } from '~/store';
@ -19,12 +19,18 @@ function handleRequestError(err: AxiosError, error: Context['error']) {
});
console.log(err);
} else if (err.response) {
if (err.response.data.isHangarApiException || err.response.data.isHangarValidationException) {
const data: HangarException = err.response.data;
if (err.response.data.isHangarApiException) {
const data: HangarApiException = err.response.data;
error({
statusCode: data.httpError.statusCode,
message: data.message,
});
} else if (err.response.data.isHangarValidationException) {
const data: HangarValidationException = err.response.data;
error({
statusCode: data.httpError.statusCode,
message: data.fieldErrors.map((f) => f.errorMsg).join(', '),
});
} else {
error({
statusCode: err.response.status,
@ -132,13 +138,29 @@ const createUtil = ({ store, error }: Context) => {
console.log(err);
} else if (err.response) {
// TODO check is msg is a i18n key and use that instead
if (err.response.data.isHangarValidationException || err.response.data.isHangarApiException) {
const data: HangarException = err.response.data;
if (err.response.data.isHangarApiException) {
const data: HangarApiException = err.response.data;
store.dispatch('snackbar/SHOW_NOTIF', {
message: msg ? `${msg}: ${data.message}` : data.message,
color: 'error',
timeout: 3000,
} as NotifPayload);
} else if (err.response.data.isHangarValidationException) {
const data: HangarValidationException = err.response.data;
for (const fieldError of data.fieldErrors) {
store.dispatch('snackbar/SHOW_NOTIF', {
message: fieldError.errorMsg,
color: 'error',
timeout: 3000,
} as NotifPayload);
}
if (msg) {
store.dispatch('snackbar/SHOW_NOTIF', {
message: msg,
color: 'error',
timeout: 3000,
} as NotifPayload);
}
} else {
store.dispatch('snackbar/SHOW_NOTIF', {
message: msg ? `${msg}: ${err.response.statusText}` : err.response.statusText,

View File

@ -4,7 +4,7 @@
[#-- @ftlvariable name="mapper" type="com.fasterxml.jackson.databind.ObjectMapper" --]
[#-- @ftlvariable name="_csrf" type="org.springframework.security.web.csrf.CsrfToken" --]
[#-- @ftlvariable name="cu" type="io.papermc.hangar.db.modelold.UsersTable" --]
[#-- @ftlvariable name="markdownService" type="io.papermc.hangar.serviceold.MarkdownService" --]
[#-- @ftlvariable name="markdownService" type="io.papermc.hangar.service.internal.MarkdownService" --]
[#-- @ftlvariable name="utils" type="io.papermc.hangar.util.TemplateHelper" --]
[#-- @ftlvariable name="@helper" type="freemarker.template.TemplateHashModel" --]
[#-- @ftlvariable name="alerts" type="java.util.Map" --]

View File

@ -9,6 +9,7 @@ public class CacheConfig {
public static final String AUTHORS_CACHE = "AUTHORS_CACHE";
public static final String STAFF_CACHE = "STAFF_CACHE";
// TODO dont need these caches anymore
public static final String PENDING_VERSION_CACHE = "PENDING_VERSION_CACHE";
public static final String NEW_VERSION_CACHE = "NEW_VERSION_CACHE";

View File

@ -8,6 +8,7 @@ import org.springframework.context.support.ReloadableResourceBundleMessageSource
@Configuration
public class UtilsConfig {
// TODO remove cause its now client side stuff
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();

View File

@ -1,17 +1,19 @@
package io.papermc.hangar.config.hangar;
import io.papermc.hangar.model.common.Color;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import javax.validation.constraints.Size;
@Component
@ConfigurationProperties(prefix = "hangar.channels")
public class ChannelsConfig {
@Size(min = 1)
private int maxNameLen = 15;
private String nameRegex = "^[a-zA-Z0-9]+$";
@Value("#{T(io.papermc.hangar.model.common.Color).getById(${hangar.channels.color-default})}")
private Color colorDefault = Color.getById(7);
private Color colorDefault = Color.CYAN;
@Size(min = 1, max = 15)
private String nameDefault = "Release";
public int getMaxNameLen() {

View File

@ -44,7 +44,7 @@ public class HangarConfig {
@NestedConfigurationProperty
public UserConfig user;
@NestedConfigurationProperty
public OrgConfig org;
public OrganizationsConfig org;
@NestedConfigurationProperty
public ApiConfig api;
@NestedConfigurationProperty
@ -86,7 +86,7 @@ public class HangarConfig {
}
@Autowired
public HangarConfig(FakeUserConfig fakeUser, HomepageConfig homepage, ChannelsConfig channels, PagesConfig pages, ProjectsConfig projects, UserConfig user, OrgConfig org, ApiConfig api, SsoConfig sso, HangarSecurityConfig security, QueueConfig queue) {
public HangarConfig(FakeUserConfig fakeUser, HomepageConfig homepage, ChannelsConfig channels, PagesConfig pages, ProjectsConfig projects, UserConfig user, OrganizationsConfig org, ApiConfig api, SsoConfig sso, HangarSecurityConfig security, QueueConfig queue) {
this.fakeUser = fakeUser;
this.homepage = homepage;
this.channels = channels;
@ -208,7 +208,7 @@ public class HangarConfig {
return user;
}
public OrgConfig getOrg() {
public OrganizationsConfig getOrg() {
return org;
}

View File

@ -42,46 +42,6 @@ public class HangarSecurityConfig {
private String avatarUrl = "http://localhost:8000/avatar/%s?size=120x120";
private String key = "changeme";
private long timeout = 10000;
@NestedConfigurationProperty
public SecurityApiConfig.ApiBreakerConfig breaker;
@Autowired
public SecurityApiConfig(SecurityApiConfig.ApiBreakerConfig breaker) {
this.breaker = breaker;
}
@Component
@ConfigurationProperties(prefix = "hangar.security.api.breaker")
public static class ApiBreakerConfig {
private int maxFailures = 5;
private String timeout = "10s";
private String reset = "5m";
public int getMaxFailures() {
return maxFailures;
}
public void setMaxFailures(int maxFailures) {
this.maxFailures = maxFailures;
}
public String getTimeout() {
return timeout;
}
public void setTimeout(String timeout) {
this.timeout = timeout;
}
public String getReset() {
return reset;
}
public void setReset(String reset) {
this.reset = reset;
}
}
public String getUrl() {
return url;
@ -114,14 +74,6 @@ public class HangarSecurityConfig {
public void setTimeout(long timeout) {
this.timeout = timeout;
}
public SecurityApiConfig.ApiBreakerConfig getBreaker() {
return breaker;
}
public void setBreaker(SecurityApiConfig.ApiBreakerConfig breaker) {
this.breaker = breaker;
}
}
public Duration getTokenExpiry() {

View File

@ -1,13 +1,17 @@
package io.papermc.hangar.config.hangar;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
@Component
@ConfigurationProperties(prefix = "hangar.homepage")
public class HomepageConfig {
@DurationUnit(ChronoUnit.MINUTES)
private Duration updateInterval = Duration.ofMinutes(10);
public Duration getUpdateInterval() {

View File

@ -5,7 +5,7 @@ import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "hangar.orgs")
public class OrgConfig {
public class OrganizationsConfig {
private boolean enabled = true;
private String dummyEmailDomain = "org.papermc.io";
private int createLimit = 5;

View File

@ -1,25 +1,29 @@
package io.papermc.hangar.config.hangar;
import io.papermc.hangar.exceptions.HangarApiException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import java.util.regex.Pattern;
@Component
@ConfigurationProperties(prefix = "hangar.pages")
public class PagesConfig {
@NestedConfigurationProperty
public Home home;
private String nameRegex = "^[a-zA-Z0-9-_ ]+$";
private int minNameLen = 3;
private int maxNameLen = 25;
private int minLen = 15;
private int maxLen = 32000;
@NestedConfigurationProperty
public Page page;
@Autowired
public PagesConfig(Home home, Page page) {
public PagesConfig(Home home) {
this.home = home;
this.page = page;
}
@Component
@ -45,20 +49,6 @@ public class PagesConfig {
}
}
@Component
@ConfigurationProperties(prefix = "hangar.pages.page")
public static class Page {
private int maxLen = 75000;
public int getMaxLen() {
return maxLen;
}
public void setMaxLen(int maxLen) {
this.maxLen = maxLen;
}
}
public Home getHome() {
return home;
}
@ -67,6 +57,30 @@ public class PagesConfig {
this.home = home;
}
public String getNameRegex() {
return nameRegex;
}
public void setNameRegex(String nameRegex) {
this.nameRegex = nameRegex;
}
public int getMinNameLen() {
return minNameLen;
}
public void setMinNameLen(int minNameLen) {
this.minNameLen = minNameLen;
}
public int getMaxNameLen() {
return maxNameLen;
}
public void setMaxNameLen(int maxNameLen) {
this.maxNameLen = maxNameLen;
}
public int getMinLen() {
return minLen;
}
@ -83,11 +97,13 @@ public class PagesConfig {
this.maxLen = maxLen;
}
public Page getPage() {
return page;
}
public void setPage(Page page) {
this.page = page;
public void testPageName(String name) {
if (name.length() > maxNameLen) {
throw new HangarApiException(HttpStatus.BAD_REQUEST, "page.new.error.name.maxLength");
} else if (name.length() < minNameLen) {
throw new HangarApiException(HttpStatus.BAD_REQUEST, "page.new.error.name.minLength");
} else if (!Pattern.compile(nameRegex).matcher(name).matches()) {
throw new HangarApiException(HttpStatus.BAD_REQUEST, "page.new.error.name.invalidChars");
}
}
}

View File

@ -9,7 +9,7 @@ import java.util.List;
@ConfigurationProperties(prefix = "hangar.users")
public class UserConfig {
private int maxTaglineLen = 100;
@Deprecated
@Deprecated(forRemoval = true)
private int authorPageSize = 25;
private List<String> staffRoles = List.of("Hangar_Admin", "Hangar_Mod");

View File

@ -4,6 +4,7 @@ import io.papermc.hangar.controller.HangarController;
import io.papermc.hangar.controller.api.v1.interfaces.IProjectsController;
import io.papermc.hangar.exceptions.InternalHangarException;
import io.papermc.hangar.model.api.PaginatedResult;
import io.papermc.hangar.model.api.project.DayProjectStats;
import io.papermc.hangar.model.api.project.Project;
import io.papermc.hangar.model.api.project.ProjectMember;
import io.papermc.hangar.model.api.requests.RequestPagination;
@ -19,7 +20,9 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.servlet.view.RedirectView;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
@Controller
public class ProjectsController extends HangarController implements IProjectsController {
@ -48,6 +51,12 @@ public class ProjectsController extends HangarController implements IProjectsCon
return ResponseEntity.ok(projectsApiService.getProjects(q, categories, tags, owner, sort, orderWithRelevance, pagination));
}
@Override
public ResponseEntity<Map<String, DayProjectStats>> getProjectStats(String author, String slug, @NotNull OffsetDateTime fromDate, @NotNull OffsetDateTime toDate) {
return ResponseEntity.ok(projectsApiService.getProjectStats(author, slug, fromDate, toDate));
}
// TODO move to internal project api
@GetMapping(value = "/project/{author}/{name}/icon")
public Object getProjectIcon(@PathVariable String author, @PathVariable String name) {
try {

View File

@ -1,6 +1,7 @@
package io.papermc.hangar.controller.api.v1.interfaces;
import io.papermc.hangar.model.api.PaginatedResult;
import io.papermc.hangar.model.api.project.DayProjectStats;
import io.papermc.hangar.model.api.project.Project;
import io.papermc.hangar.model.api.project.ProjectMember;
import io.papermc.hangar.model.api.requests.RequestPagination;
@ -16,14 +17,15 @@ import io.swagger.annotations.Authorization;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
@Anyone
@Api(tags = "Projects", produces = MediaType.APPLICATION_JSON_VALUE)
@ -43,7 +45,6 @@ public interface IProjectsController {
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
})
@GetMapping( "/projects/{author}/{slug}")
// @PreAuthorize("@authenticationService.handleApiRequest(T(io.papermc.hangar.model.common.Permission).ViewPublicInfo, T(io.papermc.hangar.controller.extras.ApiScope).ofProject(#author, #slug))")
ResponseEntity<Project> getProject(@ApiParam("The author of the project to return") @PathVariable String author,
@ApiParam("The slug of the project to return") @PathVariable String slug);
@ -60,7 +61,6 @@ public interface IProjectsController {
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
})
@GetMapping("/projects/{author}/{slug}/members")
@PreAuthorize("@authenticationService.handleApiRequest(T(io.papermc.hangar.model.common.Permission).ViewPublicInfo, T(io.papermc.hangar.controller.extras.ApiScope).ofProject(#author, #slug))")
ResponseEntity<PaginatedResult<ProjectMember>> getProjectMembers(
@ApiParam("The author of the project to return members for") @PathVariable("author") String author,
@ApiParam("The slug of the project to return") @PathVariable("slug") String slug,
@ -89,4 +89,23 @@ public interface IProjectsController {
@ApiParam("If how relevant the project is to the given query should be used when sorting the projects") @RequestParam(defaultValue = "true") boolean relevance,
@ApiParam("Pagination information") @NotNull RequestPagination pagination
);
@ApiOperation(
value = "Returns the stats for a project",
nickname = "showProjectStats",
notes = "Returns the stats(downloads, views) for a project per day for a certain date range. Requires the `is_subject_member` permission.",
authorizations = @Authorization("Session"),
tags = "Projects"
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
})
@GetMapping("/projects/{author}/{slug}/stats")
ResponseEntity<Map<String, DayProjectStats>> getProjectStats(@ApiParam("The author of the project to return the stats for") @PathVariable String author,
@ApiParam("The slug of the project to return") @PathVariable String slug,
@NotNull @ApiParam(value = "The first date to include in the result", required = true) @RequestParam OffsetDateTime fromDate,
@NotNull @ApiParam(value = "The last date to include in the result", required = true) @RequestParam OffsetDateTime toDate
);
}

View File

@ -1,8 +1,8 @@
package io.papermc.hangar.controller.internal;
import io.papermc.hangar.controller.HangarController;
import io.papermc.hangar.controller.api.RefreshResponse;
import io.papermc.hangar.exceptions.HangarException;
import io.papermc.hangar.model.api.auth.RefreshResponse;
import io.papermc.hangar.model.db.UserTable;
import io.papermc.hangar.model.internal.sso.AuthUser;
import io.papermc.hangar.model.internal.sso.URLWithNonce;

View File

@ -11,8 +11,8 @@ import io.papermc.hangar.security.annotations.permission.PermissionRequired;
import io.papermc.hangar.security.annotations.unlocked.Unlocked;
import io.papermc.hangar.security.annotations.visibility.Type;
import io.papermc.hangar.security.annotations.visibility.VisibilityRequired;
import io.papermc.hangar.service.internal.MarkdownService;
import io.papermc.hangar.service.internal.projects.ProjectPageService;
import io.papermc.hangar.serviceold.MarkdownService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;

View File

@ -7,7 +7,7 @@ import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateModelException;
import io.papermc.hangar.config.hangar.HangarConfig;
import io.papermc.hangar.db.modelold.UsersTable;
import io.papermc.hangar.serviceold.MarkdownService;
import io.papermc.hangar.service.internal.MarkdownService;
import io.papermc.hangar.serviceold.UserService;
import io.papermc.hangar.util.TemplateHelper;
import org.springframework.beans.factory.annotation.Autowired;

View File

@ -14,7 +14,7 @@ import io.papermc.hangar.modelold.viewhelpers.ProjectPage;
import io.papermc.hangar.modelold.viewhelpers.ScopedProjectData;
import io.papermc.hangar.securityold.annotations.ProjectPermission;
import io.papermc.hangar.securityold.annotations.UserLock;
import io.papermc.hangar.serviceold.MarkdownService;
import io.papermc.hangar.service.internal.MarkdownService;
import io.papermc.hangar.serviceold.StatsService;
import io.papermc.hangar.serviceold.UserActionLogService;
import io.papermc.hangar.serviceold.project.PagesFactory;

View File

@ -1,6 +1,6 @@
package io.papermc.hangar.controllerold;
import io.papermc.hangar.serviceold.PaypalService;
import io.papermc.hangar.service.internal.PaypalService;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;

View File

@ -1,71 +0,0 @@
package io.papermc.hangar.controllerold.api;
import io.papermc.hangar.modelold.generated.Project;
import io.papermc.hangar.modelold.generated.ProjectStatsDay;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import io.swagger.annotations.Authorization;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.Map;
@Api(value = "projects", tags = "Projects")
@RequestMapping({"/api", "/api/v1"})
public interface ProjectsApi {
@ApiOperation(
value = "Returns info on a specific project",
nickname = "showProjectById",
notes = "Returns info on a specific project. Requires the `view_public_info` permission.",
response = Project.class,
authorizations = {
@Authorization(value = "Session")
},
tags = "Projects",
hidden = true
)
@ApiResponses({
@ApiResponse(code = 200, message = "Ok", response = Project.class),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
})
@GetMapping(value = "/projects/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Project> showProject(
@ApiParam(value = "The id of the project to return", required = true) @PathVariable("id") long id
);
@ApiOperation(
value = "Returns the stats for a project",
nickname = "showProjectStats",
notes = "Returns the stats(downloads, views) for a project per day for a certain date range. Requires the `is_subject_member` permission.",
response = ProjectStatsDay.class,
responseContainer = "Map",
authorizations = {
@Authorization(value = "Session")
},
tags = "Projects"
)
@ApiResponses(
value = {
@ApiResponse(code = 200, message = "Ok", response = ProjectStatsDay.class, responseContainer = "Map"),
@ApiResponse(code = 401, message = "Api session missing, invalid or expired"),
@ApiResponse(code = 403, message = "Not enough permissions to use this endpoint")
})
@GetMapping(value = "/projects/{author}/{slug}/stats", produces = MediaType.APPLICATION_JSON_VALUE)
ResponseEntity<Map<String, ProjectStatsDay>> showProjectStats(
@ApiParam(value = "The author of the project to return the stats for", required = true) @PathVariable("author") String author,
@ApiParam(value = "The slug of the project to return", required = true) @PathVariable("slug") String slug,
@NotNull @ApiParam(value = "The first date to include in the result", required = true) @Valid @RequestParam(value = "fromDate", required = true) String fromDate,
@NotNull @ApiParam(value = "The last date to include in the result", required = true) @Valid @RequestParam(value = "toDate", required = true) String toDate
);
}

View File

@ -1,80 +0,0 @@
package io.papermc.hangar.controllerold.api;
import io.papermc.hangar.config.hangar.HangarConfig;
import io.papermc.hangar.exceptions.HangarApiException;
import io.papermc.hangar.model.common.Permission;
import io.papermc.hangar.modelold.ApiAuthInfo;
import io.papermc.hangar.modelold.generated.Project;
import io.papermc.hangar.modelold.generated.ProjectStatsDay;
import io.papermc.hangar.serviceold.apiold.ProjectApiService;
import io.papermc.hangar.util.ApiUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.time.LocalDate;
import java.util.Map;
@Controller
public class ProjectsApiController implements ProjectsApi {
private static final Logger log = LoggerFactory.getLogger(ProjectsApiController.class);
private final HangarConfig hangarConfig;
private final ApiAuthInfo apiAuthInfo;
private final ProjectApiService projectApiService;
@Autowired
public ProjectsApiController(HangarConfig hangarConfig, ApiAuthInfo apiAuthInfo, ProjectApiService projectApiService) {
this.hangarConfig = hangarConfig;
this.apiAuthInfo = apiAuthInfo;
this.projectApiService = projectApiService;
}
// @Override
// @PreAuthorize("@authenticationService.authApiRequest(T(io.papermc.hangar.model.common.Permission).ViewPublicInfo, T(io.papermc.hangar.controller.extras.ApiScope).ofProject(#author, #slug))")
// public ResponseEntity<List<ProjectMember>> showMembers(String author, String slug, Long inLimit, Long inOffset) {
// long limit = ApiUtil.limitOrDefault(inLimit, hangarConfig.getProjects().getInitLoad());
// long offset = ApiUtil.offsetOrZero(inOffset);
// List<ProjectMember> projectMembers = projectApiService.getProjectMembers(author, slug, limit, offset);
// if (projectMembers == null || projectMembers.isEmpty()) { // TODO this will also happen when the offset is too high
// log.error("Couldn't find a project for that author/slug");
// throw new HangarApiException(HttpStatus.NOT_FOUND, "Couldn't find a project for: " + author + "/" + slug);
// }
// return ResponseEntity.ok(projectMembers);
// }
@Override
@PreAuthorize("@authenticationService.authApiRequest(T(io.papermc.hangar.model.common.Permission).ViewPublicInfo, T(io.papermc.hangar.controller.extras.ApiScope).ofProject(#id))")
public ResponseEntity<Project> showProject(long id) {
boolean seeHidden = apiAuthInfo.getGlobalPerms().has(Permission.SeeHidden);
Project project = projectApiService.getProject(id, seeHidden, apiAuthInfo.getUserId());
if (project == null) {
log.error("Couldn't find a project for that id");
throw new HangarApiException(HttpStatus.NOT_FOUND, "Couldn't find a project for id: " + id);
}
return ResponseEntity.ok(project);
}
@Override
@PreAuthorize("@authenticationService.authApiRequest(T(io.papermc.hangar.model.common.Permission).IsProjectMember, T(io.papermc.hangar.controller.extras.ApiScope).ofProject(#author, #slug))")
public ResponseEntity<Map<String, ProjectStatsDay>> showProjectStats(String author, String slug, @NotNull @Valid String fromDate, @NotNull @Valid String toDate) {
LocalDate from = ApiUtil.parseDate(fromDate);
LocalDate to = ApiUtil.parseDate(toDate);
if (from.isAfter(to)) {
throw new HangarApiException(HttpStatus.BAD_REQUEST, "From date is after to date");
}
Map<String, ProjectStatsDay> projectStats = projectApiService.getProjectStats(author, slug, from, to);
if (projectStats == null || projectStats.isEmpty()) {
log.error("Couldn't find a project for that author/slug");
throw new HangarApiException(HttpStatus.NOT_FOUND, "Couldn't find a project for: " + author + "/" + slug);
}
return ResponseEntity.ok(projectStats);
}
}

View File

@ -6,10 +6,12 @@ import org.jdbi.v3.sqlobject.config.RegisterConstructorMapper;
import org.jdbi.v3.sqlobject.customizer.BindBean;
import org.jdbi.v3.sqlobject.customizer.Timestamped;
import org.jdbi.v3.sqlobject.statement.GetGeneratedKeys;
import org.jdbi.v3.sqlobject.statement.SqlBatch;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.jdbi.v3.sqlobject.statement.SqlUpdate;
import org.springframework.stereotype.Repository;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -25,6 +27,9 @@ public interface ProjectPagesDAO {
@SqlUpdate("UPDATE project_pages SET contents = :contents WHERE id = :id")
void update(@BindBean ProjectPageTable projectPageTable);
@SqlBatch("UPDATE project_pages SET parent_id = :parentId WHERE id = :id")
void updateParents(@BindBean Collection<ProjectPageTable> projectPageTables);
@SqlUpdate("DELETE FROM project_pages WHERE id = :id")
void delete(@BindBean ProjectPageTable projectPageTable);
@ -32,8 +37,11 @@ public interface ProjectPagesDAO {
@SqlQuery("SELECT * FROM project_pages WHERE project_id = :projectId AND parent_id IS NULL")
Map<Long, ProjectPageTable> getRootPages(long projectId);
@SqlQuery("SELECT slug FROM project_pages WHERE project_id = :projectId AND parent_id = :parentId")
List<String> getChildPages(long projectId, long parentId);
@SqlQuery("SELECT * FROM project_pages WHERE project_id = :projectId AND parent_id = :parentId")
List<ProjectPageTable> getChildPages(long projectId, long parentId);
@SqlQuery("SELECT * FROM project_pages WHERE project_id = :projectId AND parent_id = :parentId AND slug = :slug")
ProjectPageTable getChildPage(long projectId, long parentId, String slug);
@SqlQuery("SELECT pp.* FROM project_pages pp" +
" WHERE pp.project_id = :projectId" +

View File

@ -1,6 +1,7 @@
package io.papermc.hangar.db.dao.v1;
import io.papermc.hangar.db.mappers.PromotedVersionMapper;
import io.papermc.hangar.model.api.project.DayProjectStats;
import io.papermc.hangar.model.api.project.Project;
import io.papermc.hangar.model.api.project.ProjectMember;
import org.jdbi.v3.sqlobject.config.KeyColumn;
@ -14,7 +15,9 @@ import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.jdbi.v3.stringtemplate4.UseStringTemplateEngine;
import org.springframework.stereotype.Repository;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Map;
@Repository
@RegisterConstructorMapper(Project.class)
@ -110,4 +113,18 @@ public interface ProjectsApiDAO {
" WHERE p.slug = :slug AND p.owner_name = :author " +
" GROUP BY u.name")
long getProjectMembersCount(String author, String slug);
@KeyColumn("dateKey")
@RegisterConstructorMapper(DayProjectStats.class)
@SqlQuery("SELECT cast(dates.day AS date) dateKey, coalesce(sum(pvd.downloads), 0) AS downloads, coalesce(pv.views, 0) AS views" +
" FROM projects p," +
" (SELECT generate_series(:fromDate::date, :toDate::date, INTERVAL '1 DAY') as day) dates " +
" LEFT JOIN project_versions_downloads pvd ON dates.day = pvd.day" +
" LEFT JOIN project_views pv ON dates.day = pv.day AND pvd.project_id = pv.project_id" +
" WHERE " +
" p.owner_name = :author AND " +
" p.slug = :slug AND" +
" (pvd IS NULL OR pvd.project_id = p.id)" +
" GROUP BY pv.views, dates.day")
Map<String, DayProjectStats> getProjectStats(String author, String slug, OffsetDateTime fromDate, OffsetDateTime toDate);
}

View File

@ -1,4 +1,4 @@
package io.papermc.hangar.controller.api;
package io.papermc.hangar.model.api.auth;
public class RefreshResponse {

View File

@ -0,0 +1,28 @@
package io.papermc.hangar.model.api.project;
public class DayProjectStats {
private final long views;
private final long downloads;
public DayProjectStats(long views, long downloads) {
this.views = views;
this.downloads = downloads;
}
public long getViews() {
return views;
}
public long getDownloads() {
return downloads;
}
@Override
public String toString() {
return "DayProjectStats{" +
"views=" + views +
", downloads=" + downloads +
'}';
}
}

View File

@ -14,7 +14,7 @@ public class ProjectPageTable extends Table implements Named {
private final String slug;
private String contents;
private final boolean deletable;
private final Long parentId;
private Long parentId;
@JdbiConstructor
public ProjectPageTable(OffsetDateTime createdAt, long id, long projectId, String name, String slug, String contents, boolean deletable, Long parentId) {
@ -67,6 +67,10 @@ public class ProjectPageTable extends Table implements Named {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
@Override
public String toString() {
return "ProjectPageTable{" +

View File

@ -1,12 +1,16 @@
package io.papermc.hangar.model.internal.api.requests.projects;
import com.fasterxml.jackson.annotation.JsonCreator;
import io.papermc.hangar.controller.validations.Validate;
import javax.validation.constraints.NotBlank;
public class NewProjectPage {
@NotBlank
@Validate(SpEL = "#root.length() ge @hangarConfig.pages.minNameLen", message = "page.new.error.name.minLength")
@Validate(SpEL = "#root.length() le @hangarConfig.pages.maxNameLen", message = "page.new.error.name.maxLength")
@Validate(SpEL = "#root matches @hangarConfig.pages.nameRegex", message = "page.new.error.name.invalidChars")
private final String name;
private final Long parentId;

View File

@ -1,7 +1,7 @@
package io.papermc.hangar.model.internal.sso;
import io.papermc.hangar.model.common.roles.GlobalRole;
import io.papermc.hangar.util.SsoUtil;
import io.papermc.hangar.util.StringUtils;
import java.util.List;
import java.util.Map;
@ -71,7 +71,7 @@ public class SsoSyncData {
Integer.parseInt(payload.get("external_id")),
payload.get("email"),
payload.get("username"),
SsoUtil.parsePythonNullable(payload.get("name")),
StringUtils.parsePythonNullable(payload.get("name")),
payload.get("add_groups").split(","),
payload.get("remove_groups").split(","),
Boolean.parseBoolean(payload.get("admin")),

View File

@ -7,7 +7,7 @@ import io.papermc.hangar.db.modelold.ProjectsTable;
import io.papermc.hangar.db.modelold.UserProjectRolesTable;
import io.papermc.hangar.db.modelold.UsersTable;
import io.papermc.hangar.model.common.projects.Visibility;
import io.papermc.hangar.serviceold.MarkdownService;
import io.papermc.hangar.service.internal.MarkdownService;
import java.util.List;
import java.util.Map;

View File

@ -37,7 +37,7 @@ public class UnlockedVoter implements AccessDecisionVoter<MethodInvocation> {
if (((HangarAuthenticationToken) authentication).getPrincipal().isLocked()) {
throw new HangarApiException(HttpStatus.UNAUTHORIZED, "error.user.locked");
}
return ACCESS_ABSTAIN;
return ACCESS_GRANTED;
}
private UnlockedAttribute findUnlockedAttribute(Collection<ConfigAttribute> attributes) {

View File

@ -61,6 +61,8 @@ public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
decisionVoters.add(roleVoter);
decisionVoters.add(new AuthenticatedVoter());
decisionVoters.addAll(accessDecisionVoters);
return new UnanimousBased(decisionVoters);
UnanimousBased accessDecisionManager = new UnanimousBased(decisionVoters);
accessDecisionManager.setAllowIfAllAbstainDecisions(true);
return accessDecisionManager;
}
}

View File

@ -4,11 +4,11 @@ import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import io.papermc.hangar.controller.api.RefreshResponse;
import io.papermc.hangar.controller.utils.CookieUtils;
import io.papermc.hangar.db.dao.HangarDao;
import io.papermc.hangar.db.dao.internal.table.auth.UserRefreshTokenDAO;
import io.papermc.hangar.exceptions.HangarApiException;
import io.papermc.hangar.model.api.auth.RefreshResponse;
import io.papermc.hangar.model.common.Permission;
import io.papermc.hangar.model.db.UserTable;
import io.papermc.hangar.model.db.auth.UserRefreshToken;

View File

@ -4,6 +4,7 @@ import io.papermc.hangar.db.dao.HangarDao;
import io.papermc.hangar.db.dao.v1.ProjectsApiDAO;
import io.papermc.hangar.model.api.PaginatedResult;
import io.papermc.hangar.model.api.Pagination;
import io.papermc.hangar.model.api.project.DayProjectStats;
import io.papermc.hangar.model.api.project.Project;
import io.papermc.hangar.model.api.project.ProjectMember;
import io.papermc.hangar.model.api.requests.RequestPagination;
@ -12,12 +13,13 @@ import io.papermc.hangar.model.common.projects.Category;
import io.papermc.hangar.modelold.generated.ProjectSortingStrategy;
import io.papermc.hangar.modelold.generated.Tag;
import io.papermc.hangar.service.HangarService;
import io.papermc.hangar.util.ApiUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@ -26,8 +28,8 @@ public class ProjectsApiService extends HangarService {
private final ProjectsApiDAO projectsApiDAO;
@Autowired
public ProjectsApiService(HangarDao<ProjectsApiDAO> projectsDAO) {
this.projectsApiDAO = projectsDAO.get();
public ProjectsApiService(HangarDao<ProjectsApiDAO> projectsApiDAO) {
this.projectsApiDAO = projectsApiDAO.get();
}
public Project getProject(String author, String slug) {
@ -40,6 +42,10 @@ public class ProjectsApiService extends HangarService {
return new PaginatedResult<>(new Pagination(projectsApiDAO.getProjectMembersCount(author, slug), requestPagination), projectMembers);
}
public Map<String, DayProjectStats> getProjectStats(String author, String slug, OffsetDateTime fromDate, OffsetDateTime toDate) {
return projectsApiDAO.getProjectStats(author, slug, fromDate, toDate);
}
public PaginatedResult<Project> getProjects(String query, List<Category> categories, List<String> tags, String owner, ProjectSortingStrategy sort, boolean orderWithRelevance, RequestPagination pagination) {
List<Tag> parsedTags = new ArrayList<>();
if (tags == null) {
@ -51,7 +57,7 @@ public class ProjectsApiService extends HangarService {
}
boolean seeHidden = getHangarPrincipal().getGlobalPermissions().has(Permission.SeeHidden);
String ordering = ApiUtil.strategyOrDefault(sort).getSql();
String ordering = sort.getSql();
if (orderWithRelevance && query != null && !query.isEmpty()) {
String relevance = "ts_rank(p.search_words, websearch_to_tsquery_postfix('english', :query)) DESC";
if(query.endsWith(" ")) {
@ -61,7 +67,7 @@ public class ProjectsApiService extends HangarService {
// 1483056000 is the Ore epoch TODO change to hangar epoch
// 86400 seconds to days
// 604800 seconds to weeks
switch(ApiUtil.strategyOrDefault(sort)){
switch(sort){
case STARS: orderingFirstHalf = "p.starts * "; break;
case DOWNLOADS: orderingFirstHalf ="(p.downloads / 100) * "; break;
case VIEWS: orderingFirstHalf ="(p.views / 200) *"; break;

View File

@ -1,4 +1,4 @@
package io.papermc.hangar.serviceold;
package io.papermc.hangar.service.internal;
import com.vladsch.flexmark.ast.MailLink;
import com.vladsch.flexmark.ext.admonition.AdmonitionExtension;

View File

@ -1,5 +1,6 @@
package io.papermc.hangar.serviceold;
package io.papermc.hangar.service.internal;
import io.papermc.hangar.serviceold.HangarService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

View File

@ -15,6 +15,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Service
@ -32,11 +33,13 @@ public class ProjectPageService extends HangarService {
throw new HangarApiException(HttpStatus.BAD_REQUEST, "page.new.error.minLength");
}
if (!hangarConfig.projects.getNameMatcher().test(name)) {
throw new HangarApiException(HttpStatus.BAD_REQUEST, "page.new.error.invalidName");
if (contents.length() > hangarConfig.pages.getMaxLen()) {
throw new HangarApiException(HttpStatus.BAD_REQUEST, "page.new.error.maxLength");
}
if (parentId != null && projectPagesDAO.getChildPages(projectId, parentId).contains(slug)) {
hangarConfig.pages.testPageName(name);
if (parentId != null && projectPagesDAO.getChildPage(projectId, parentId, slug) != null) {
throw new HangarApiException(HttpStatus.BAD_REQUEST, "page.new.error.duplicateName");
}
@ -126,6 +129,13 @@ public class ProjectPageService extends HangarService {
if (!pageTable.isDeletable()) {
throw new HangarApiException(HttpStatus.BAD_REQUEST, "Page is not deletable");
}
List<ProjectPageTable> children = projectPagesDAO.getChildPages(projectId, pageId);
if (!children.isEmpty()) {
for (ProjectPageTable child : children) {
child.setParentId(pageTable.getParentId());
}
projectPagesDAO.updateParents(children);
}
// Log must come first otherwise db error
userActionLogService.projectPage(LoggedActionType.PROJECT_PAGE_DELETED.with(ProjectPageContext.of(projectId, pageId)), "", pageTable.getContents());
projectPagesDAO.delete(pageTable);

View File

@ -2,10 +2,7 @@ package io.papermc.hangar.serviceold.apiold;
import io.papermc.hangar.db.dao.HangarDao;
import io.papermc.hangar.db.daoold.api.UsersApiDao;
import io.papermc.hangar.modelold.generated.ProjectCompact;
import io.papermc.hangar.modelold.generated.ProjectSortingStrategy;
import io.papermc.hangar.modelold.generated.User;
import io.papermc.hangar.util.ApiUtil;
import org.springframework.stereotype.Service;
import java.util.List;
@ -30,20 +27,4 @@ public class UserApiService {
public long getUsersCount(String q) {
return usersApiDao.get().usersQueryCount(q);
}
public List<ProjectCompact> getWatchedProjects(String user, boolean seeHidden, Long userId, ProjectSortingStrategy strategy, long limit, long offset) {
return usersApiDao.get().watchersQuery(user, seeHidden, userId, ApiUtil.strategyOrDefault(strategy).getSql(), limit, offset);
}
public long getWatchedProjectsCount(String user, boolean seeHidden, Long userId) {
return usersApiDao.get().watchersQuery(user, seeHidden, userId, ProjectSortingStrategy.Default.getSql(), null, 0).size();
}
public List<ProjectCompact> getStarredProjects(String user, boolean seeHidden, Long userId, ProjectSortingStrategy strategy, long limit, long offset) {
return usersApiDao.get().starredQuery(user, seeHidden, userId, ApiUtil.strategyOrDefault(strategy).getSql(), limit, offset);
}
public long getStarredProjectsCount(String user, boolean seeHidden, Long userId) {
return usersApiDao.get().starredQuery(user, seeHidden, userId, ProjectSortingStrategy.Default.getSql(), null, 0).size();
}
}

View File

@ -1,20 +0,0 @@
package io.papermc.hangar.serviceold.sso;
public class UrlWithNonce {
private final String url;
private final String nonce;
public UrlWithNonce(String url, String nonce) {
this.url = url;
this.nonce = nonce;
}
public String getUrl() {
return url;
}
public String getNonce() {
return nonce;
}
}

View File

@ -1,6 +0,0 @@
package io.papermc.hangar.template;
public class HangarDirectives {
}

View File

@ -7,6 +7,7 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.util.HashMap;
import java.util.Map;
@Deprecated(forRemoval = true)
public class AlertUtil {
public enum AlertType {

View File

@ -1,43 +0,0 @@
package io.papermc.hangar.util;
import io.papermc.hangar.db.modelold.UsersTable;
import io.papermc.hangar.modelold.generated.ProjectSortingStrategy;
import org.jetbrains.annotations.Nullable;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;
public class ApiUtil {
private ApiUtil() { }
@Deprecated
public static long limitOrDefault(Long limit, long defaultValue) {
if (limit != null && limit < 1) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Limit should be greater than 0");
return Math.min(limit == null ? defaultValue : limit, defaultValue);
}
@Deprecated
public static long offsetOrZero(Long offset) {
return Math.max(offset == null ? 0 : offset, 0);
}
public static Long userIdOrNull(UsersTable usersTable) {
return usersTable == null ? null : usersTable.getId();
}
public static LocalDate parseDate(String date) {
try {
return LocalDate.parse(date);
} catch (DateTimeParseException e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Badly formatted date " + date);
}
}
@Deprecated
public static ProjectSortingStrategy strategyOrDefault(@Nullable ProjectSortingStrategy strategy) {
return strategy == null ? ProjectSortingStrategy.Default : strategy;
}
}

View File

@ -7,6 +7,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Deprecated
public class ListUtils {
private ListUtils() { }

View File

@ -1,10 +1,6 @@
package io.papermc.hangar.util;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.HttpStatus;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.HandlerMapping;
@ -12,10 +8,8 @@ import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
public class RequestUtil {
@ -38,28 +32,8 @@ public class RequestUtil {
}
}
public static <T> Optional<T> requirePathParams(List<String> requestedParams, Function<String[], T> tFunction) {
Map<String, String> pathParams = getCurrentPathParams();
if (pathParams.keySet().containsAll(requestedParams)) {
return Optional.ofNullable(tFunction.apply(requestedParams.toArray(new String[0])));
}
return Optional.empty();
}
public static Map<String, String> getCurrentPathParams() {
return getPathParams(getCurrentRequest());
}
@Deprecated
public static Map<String, String> getPathParams(HttpServletRequest request) {
return Optional.ofNullable((Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE)).orElse(new HashMap<>());
}
@NotNull
public static HttpServletRequest getCurrentRequest() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (!(requestAttributes instanceof ServletRequestAttributes)) {
throw new IllegalStateException("Cannot get http servlet request out of HTTP context");
}
return (HttpServletRequest) requestAttributes;
}
}

View File

@ -1,7 +0,0 @@
package io.papermc.hangar.util;
public class SsoUtil {
public static String parsePythonNullable(String input) {
return input.equals("None") ? null : input;
}
}

View File

@ -163,4 +163,8 @@ public class StringUtils {
}
});
}
public static String parsePythonNullable(String input) {
return input.equals("None") ? null : input;
}
}

View File

@ -16,6 +16,7 @@ import java.time.format.FormatStyle;
import java.util.Map;
@Component
@Deprecated
public class TemplateHelper {
private final ObjectMapper mapper;

View File

@ -45,7 +45,6 @@ hangar:
dev: true
auth-url: "http://localhost:8000"
base-url: "http://localhost:8080"
use-webpack: true
ga-code: "UA-38006759-9"
announcements:
@ -70,7 +69,7 @@ hangar:
channels:
max-name-len: 15
name-regex: "^[a-zA-Z0-9]+$"
color-default: 7
color-default: cyan
name-default: "Release"
pages:
@ -78,9 +77,10 @@ hangar:
name: "Home"
message: "Welcome to your new project!"
min-len: 15
max-len: 32000
page:
max-len: 75000
max-len: 75000
name-regex: "^[a-zA-Z0-9-_ ]+$"
max-name-len: 25
min-name-len: 3
projects:
max-name-len: 25
@ -98,6 +98,11 @@ hangar:
unsafe-download-max-age: 10
name-regex: "^[a-zA-Z0-9-_]{3,}$"
orgs:
create-limit: 5
dummy-email-domain: "org.papermc.io"
enabled: true
users:
max-tagline-len: 100
author-page-size: 25
@ -133,9 +138,5 @@ hangar:
avatar-url: "https://paper.readthedocs.io/en/latest/_images/papermc_logomark_500.png"
key: changeme
timeout: 10000
breaker:
max-failures: 5
timeout: 10s
reset: 5m
safe-download-hosts:
- "github.com"

View File

@ -125,7 +125,7 @@ CREATE TABLE project_pages
parent_id bigint
CONSTRAINT project_pages_parent_id_fkey
REFERENCES project_pages
ON DELETE SET NULL
ON DELETE RESTRICT
);
CREATE INDEX page_slug_idx

View File

@ -1,4 +1,4 @@
package io.papermc.hangar.serviceold;
package io.papermc.hangar.service.internal;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;