Compare commits

..

809 Commits

Author SHA1 Message Date
Jai A
96782b52cd Initial draft 2024-11-08 17:42:41 -08:00
Jai A
aae00e1312 Fix forge versions being reversed 2024-11-08 16:26:02 -08:00
he3als
24e90f0a54 feat(marketing): miami, overallocation card, misc fixes (#2926)
* fix(marketing): make faq headings medium

* feat(marketing): add card for overallocation

* feat(marketing): add miami location

* fix(marketing): 'login' -> 'sign in' consistency

* feat: plan query string support + simplify buttons
2024-11-08 23:52:32 +00:00
Jai A
f5208a85b0 Add rev.iq back to ads.txt 2024-11-07 19:08:12 -08:00
Jai A
8db4b3f83b Fix modrinth plus subscribing 2024-11-07 19:01:53 -08:00
Erb3
33ad04d036 feat(frontend): show date of user join (#2901)
* feat(frontend): show date of user join

Shows and formats the date when the user joined, on hover. Can add `cursor-help` if wanted.

Resolves #2243

* chore(frontend): lint
2024-11-08 02:25:02 +00:00
Jai A
4bcdb3f495 Fix lint + docker build 2024-11-07 18:21:01 -08:00
Jai A
70979172b0 fix daedalus neoforge manifest misformatting 2024-11-07 18:13:57 -08:00
Jai A
493b9a3975 Re-add inmobi 2024-11-06 13:25:39 -07:00
Jai A
5a21a67d46 Remove inmobi (temp) 2024-11-06 11:27:35 -07:00
Evan Song
ff72c906ba fix: correct uri encode paths in fs module (#2922)
Signed-off-by: Evan Song <theevansong@gmail.com>
2024-11-05 23:35:43 +00:00
Evan Song
907b1f67ed fix: multipart mrpack by gently holding ofetch's hand and saying "we got this, actually" and we ignore all the bullshit that comes with it (#2921)
* fix: multipart mrpack

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: use native fetch

Signed-off-by: Evan Song <theevansong@gmail.com>

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
2024-11-05 23:21:41 +00:00
Evan Song
72cbe7f905 fix: unnecessary refetch of server modules on first mount (#2918)
Signed-off-by: Evan Song <theevansong@gmail.com>
2024-11-05 22:53:14 +00:00
Evan Song
deb16aa7ab pyro: multipart mrpack uploads (#2917)
* chore: impl in pyroservers

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: disable install btn if loading

Signed-off-by: Evan Song <theevansong@gmail.com>

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
2024-11-05 22:24:13 +00:00
Evan Song
d0efa44c9e refactor(terminal): rewrite terminal virtualization (#2916)
Signed-off-by: Evan Song <theevansong@gmail.com>
2024-11-05 20:32:11 +00:00
he3als
d321843c02 feat(servers): full custom plan capacity checks & more (#2911)
* feat(servers): improve plan button logic

* feat(servers): custom plan capacity checks

* feat(servers): custom plan dynamic ram values

* feat(servers): add custom plan selector back

* fix(servers): final fixes
2024-11-05 02:07:40 +00:00
Jai A
2b44b145cb Remove direct clean.io script 2024-11-04 17:58:27 -07:00
nullptr
9f5606889e fix: remove plan selector (#2909) 2024-11-04 16:27:49 -08:00
Evan Song
417f2a8b91 fix: do not prepend modrinth.com to backup download url (#2908)
Signed-off-by: Evan Song <theevansong@gmail.com>
2024-11-04 23:42:32 +00:00
Jai A
d3a7bf967e kill ads loop after 10s 2024-11-03 23:50:55 -07:00
Jai A
db145cd8ad More ads test 2024-11-03 20:35:04 -07:00
Jai A
7d614f7ac5 Remove inmobi for US traffic (test) 2024-11-03 15:27:41 -08:00
Prospector
c7a2a3e29b Fix navigation tab highlights for certain project types 2024-11-03 10:45:43 -08:00
nullptr
3b966c03ee fix(servers): faqs not opening on nav (#2897)
* feat(servers): allow scrolling faq into view via hashes

* fix(servers): hide webkit details marker for faq

* fix(servers): faqs not opening on nav

---------

Signed-off-by: nullptr <62841684+not-nullptr@users.noreply.github.com>
2024-11-03 17:43:23 +00:00
Erb3
66d943d391 fix(frontend): alt-text for flags (#2894)
Fixes #2397
2024-11-03 17:10:09 +00:00
nullptr
f5f876e458 fix(servers): hide webkit details marker for faq (#2895)
* feat(servers): allow scrolling faq into view via hashes

* fix(servers): hide webkit details marker for faq

---------

Signed-off-by: nullptr <62841684+not-nullptr@users.noreply.github.com>
2024-11-03 17:09:29 +00:00
Erb3
27c3439120 fix(frontend): remove double dollar sign in chart (#2896)
Resolves #2400 by removing the SVG suffix.
2024-11-03 17:09:17 +00:00
nullptr
f212d04261 feat(servers): allow scrolling faq into view via hashes (#2893)
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2024-11-03 16:35:24 +00:00
Erb3
06f01aa85c fix(frontend): generate auth URL with provider (#2891)
Resolves an issue where the frontend generated URLs without provider property when there is a frontend redirect uri. Without the provider, labrinth defaults to GitHub.

Fixes #2884
2024-11-03 16:10:24 +00:00
nullptr
5f48dc08a9 fix(servers): standalone versions not showing up in loader page (#2890) 2024-11-03 16:00:06 +00:00
Prospector
e81e056758 Fix mistake in Servers marketing 2024-11-03 02:21:17 -08:00
Conrad Ludgate
2d95ff0830 chore: massage dependencies and features to remove openssl/native-tls (#2859)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2024-11-03 09:48:35 +00:00
Evan Song
81d921d625 Pyro: Initial Marketing Fixes (#2885)
* fix: typo

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: specify specs on reliable hosting card

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: clarify locations

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: clarify SFTP and networking features

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust copy

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: pushj faq section

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: address review

Signed-off-by: Evan Song <theevansong@gmail.com>

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
2024-11-03 01:05:47 -08:00
FelixBrakel
e988513ed7 Typo: Change millibit to megabyte (#2406)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2024-11-03 09:42:57 +00:00
June
783d4f82d9 removed 1rem top margin for project description (#2523)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2024-11-03 09:32:29 +00:00
Accieo
b5011f458f Add minecraft as loader to create version api docs (#2877)
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2024-11-03 09:32:18 +00:00
June
5d0e4366d2 set right margin to 0 on svgs inside a square notification button (#2871)
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2024-11-03 09:30:59 +00:00
June
8643dc02dd Consistent alignment for 'project settings page confirmation' buttons (#2524)
* buttons in .button-group now aligned to flex start

* revert global style change for .button-group

* manual fix for merge

* scoped .button-group styles

* /tags whitespace revert

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
2024-11-03 09:29:44 +00:00
Jai A
7c4dcb2817 Fix game version filters 2024-11-02 23:18:31 -07:00
Jai A
6b64fdafcb Fix loaders/gvs not showing 2024-11-02 22:45:50 -07:00
Elizabeth
185dd47668 Pyro Integration (#2503)
* fix

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix

Signed-off-by: Evan Song <theevansong@gmail.com>

* refactor(fileitem): optimize

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore(fileitem): fixed width timestamp

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(fileitem): allow editing json5/jsonc

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: motd pt 1, auto backups scaffolding, editing navbar changes

* feat: fancy sidebar animations

* fix: files

* fix: files pt2

* fix: faulty name validation disallowing spaces in file names

Signed-off-by: Evan Song <theevansong@gmail.com>

* refactor: fileitem props

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: upload files not refreshing files list

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(imgviewer): handle invalid/empty images

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: return of the sticky files header

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: prevent servericon from shrinking

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: wtf were we thinking with this anyway

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: further mobile optimization

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: propagate margin

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: truncation fixes

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: track navbar with sentinel

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(files): a11y

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: improve inspector styles

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: console preformance improvements, decrease blur

* feat(mobile): new server header

* fix: linting

* fix: useless z indeces

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust file filter names

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat(files): true breadcrumbs

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(marketing): make custom responsive

* fix(marketing): mobile file manager card

* feat: trackable navtabs

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: oh no

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: smartly truncate

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(terminal): z-indexes

* fix: autofocus more inputs

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: color

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust copy

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: backup modal usability improvements

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: padding

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: title

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(content): update banner mobile support

* fix: server listing icons

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: ignore clicks in server listing for labels

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat(mobile): backup card

* fix(backups): make plural conditional

* fix: debounce file item selectitem

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: lint

Signed-off-by: Evan Song <theevansong@gmail.com>

* stuff

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: temp sidebar fix until i can be smart

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: explictly set button type in file modals

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: properly sort backups

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: add getautobackup method to pyroservers

Signed-off-by: Evan Song <theevansong@gmail.com>

* choer: update autobackup params

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: update autobackup methods (REALLY GUYS)

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: implement autobackups

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: implement backup-while-running preference

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: make server labels a component

* feat: implement 'All details' modal

* fix(mobile): server manage page

* feat(files): mobile compatible

* fix(info labels): wrap

* chore(inspector): clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(backup settings): swap + and -

* fix(manage): new -> plans instead of modal

* feat: more small mobile fixes

* fix(auto backup modal): manual input validation

* fix(file browse navbar): home margin

* feat(purchase modal): mobile support

* fix(marketing): faded line alignments

* feat: add servers to mobile nav

* feat(network): dns record fixes

* feat: make all settings work on mobile

* fix(loader settings): modpack mobile

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat(marketing): add 'Manage your servers' button

* fix(marketing): only check servers if logged in

* fix(network): allocation edit & delete button

* fix(backups): use UiServersTeleportOverflowMenu

* chore: linting

* chore: but here comes the sentence case

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat(marketing): make buttons consistent

* lint

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(loader): prevent multiline version names in dropdown

Signed-off-by: Evan Song <theevansong@gmail.com>

* lint

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: copy

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: sentence case

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: linting

* chore: rename dumbass preference key

Signed-off-by: Evan Song <theevansong@gmail.com>

* refactor: rewrite power action buttons

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: robust download logic

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(loader mobile): modpack dropdown width

* fix: sentence case

* fix(save & 'working on it'): look good on mobile

* fix(TeleportDropdown): width

* fix(inspecting error): mobile

* fix: show action button dropdown when installing

* fix(navtabs): temp fix for mobile scrolling issue

* fix(install error): mobile compatible

* chore: just remove tracking

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: cleanup

* fix: broken svg clr in checkbox when using experimental styles

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust vanilla icon

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust loader props

Signed-off-by: Evan Song <theevansong@gmail.com>

* revert changes to serversidebar

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: server properties flicker

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(backups): plural

* fix: cases where the telepoverflow would clash with viewport edge

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat(backups): auto-backups label

* fix(network): titlecase

* feat(fileitem): new rename icon

* fix(properties): wiki proper noun

* fix: disable motd for the time being

* chore: adjust wording for power conifmration

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: "external" to billing

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: icon

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: add EULA checkbox

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* me and bro deciding which case rules to enforce

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat(sftp): copy address & username, launch tooltip

* feat(files): better move

* chore: attempt to mitigate excessive stack depth type

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(loader): prevent versions 1.2.4 and below

* feat(dns table): placeholder improvements

* feat(pyroServer): error handling

* fix: intrinsic size on loader icon

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust wording

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: sentence case

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust wording

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: types

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: "implemented" key in preference

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat(connection lost): redesign

* feat(connection error): make icon orange

* fix: cleanup

* chore(connection lost): redesign pt 2

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: OOOOHHH MY GOD

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: implement capacity api on marketing

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: update createdat backup type

Signed-off-by: Evan Song <theevansong@gmail.com>

* refactor: all of backups

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: update backup types

Signed-off-by: Evan Song <theevansong@gmail.com>

* refactor: backups pt 2

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: comically small icons

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: align designs

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: hide ram graph if ram as bytes enabled

Signed-off-by: Evan Song <theevansong@gmail.com>

* base add content page

* Fix conflict

* feat(content): mobile-compatible header, sticky

* fix(marketing): md instead of sm for custom

* fix: compiler macro warning

Signed-off-by: Evan Song <theevansong@gmail.com>

* again

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: loader type error

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: default uptime seconds prop

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: hydration errors on server listing

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: move custom URL to general

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: indiviudally checkj capacities

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: falsey

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: missing prop

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: Derive On That Thang

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: adjust gap

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: add default name for backups

* fix: the backup number should PROBABLY be computed lol

* fix(backups): truncate text, mobile fixes

* fix(loader): modpack mobile fix

* feat(plans): add vcpus

* fix(backup modal): blank by default, maxlength

* fix(subdomain): separate length & valid chars

* feat: mrpack installs functionality (untested), forbidden handling, backups grammar

* feat(content): make responsive on mobile

* fix: disable plan buttons separately

* fix(backup modal): update name max length

* fix(purchase): wrapping on eula, eula link

* fix: move skeleton

* fix(server mobile header): truncation

* fix(server header): proper alignment

* Finish content page fixes

* fix: who up rinthing

Signed-off-by: Evan Song <theevansong@gmail.com>

* wip

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(staging & email banner): z-index

* feat: make eula tickbox more visible

* fix: move "powered by pyro" below buttons on hero

* fix: oops sorry ellie, also updated the main screenshot

* feat: update content screenshot

* fix: content page card should hide image on lg

* feat: hide total storage for now

* fix: terminal card now uses terminal icon

* fix(marketing): make medium plan card border solid

* feat: modloader card, move pyro BACK below buttons, beta release pill

* fix: spinning logo should be behind hero

* feat: surgically remove the hero's massive forehead

* feat(marketing): mobile UI screenshot

* fix(hero): z-index goes over mobile nav

* fix: consistent borders, files breakpoints

* chore: update turbo

* chore: adjust hero sizing

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: mention region restrictions

* chore: double check if we are at capcity

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: measure twice cut once

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: bro cut twice and measured once 💀

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(marketing): login first

* fix: out of capacity text when logged out

* fix(slider): reset some values for frontend

* feat: wip hero section

Signed-off-by: Evan Song <theevansong@gmail.com>

* New navigation to support the new products (#2879)

* Nav

* oops extra file

* feat: mrpack uploading with existing modpack, fix: choose modpack duplicate

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: update features section

Signed-off-by: Evan Song <theevansong@gmail.com>

* Nav adjustments

* fix: server manager empty state clashing with loading state

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: query param hard

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: do not count uptime if crashed

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: grammar

Signed-off-by: Evan Song <theevansong@gmail.com>

* hide hero img on lg breakpoints

* Make plugins a plug

* chore: prep for buffered text selection terminal

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix: marketing responsive stuff, n fixes

* fix hoverable prop

* fix: edit mod spacing

* fix: type error for display name in dropdown

Signed-off-by: Evan Song <theevansong@gmail.com>

* feat: custom plans

* fix: no more console.log

* fix: properly linked prop label

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix(install hero mobile): padding

* fix: prevent x overflow on servers page

Signed-off-by: Evan Song <theevansong@gmail.com>

* fix lint oh ym fucking god yal

Signed-off-by: Evan Song <theevansong@gmail.com>

* Migrate modpack install to search

* fix(custom plan): warning icon variable

* fix: loading probally and modal loader things

* fix(marketing): login icon colours

* fix(marketing): responsiveness

* fix(marketing): responsiveness v2

* fix: sync button for icon tm

* fix(marketing): responsiveness v3

* fix: hero image

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: clean

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: switch to cdn links

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: switch to cdn links

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: switch to cdn links

Signed-off-by: Evan Song <theevansong@gmail.com>

* chore: switch to cdn links

Signed-off-by: Evan Song <theevansong@gmail.com>

* Remove prod override

---------

Signed-off-by: Evan Song <theevansong@gmail.com>
Co-authored-by: Evan Song <theevansong@gmail.com>
Co-authored-by: TheWander02 <48934424+thewander02@users.noreply.github.com>
Co-authored-by: he3als <65787561+he3als@users.noreply.github.com>
Co-authored-by: Evan Song <52982404+ferothefox@users.noreply.github.com>
Co-authored-by: Lio <git@lio.cat>
Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: not-nullptr <needhelpwithrift@gmail.com>
Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
Co-authored-by: Prospector <prospectordev@gmail.com>
Co-authored-by: sticks <tanner@teamhydra.dev>
2024-11-02 21:14:00 -07:00
Jai A
f165665a35 Fix primary filter 2024-11-02 01:44:05 -07:00
Jai A
ad38749f98 fix lint 2024-10-31 15:24:13 -07:00
Jai A
7825dd64ca Sync app consent with inmobi consent 2024-10-31 15:14:41 -07:00
Jai A
f6af620643 Add inmobi to app iframe 2024-10-30 15:30:24 -07:00
Skye
b5aeef7ebf Allow Bearer prefix on authorization tokens (#2854)
Signed-off-by: Skye <me@skye.vg>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-10-27 19:04:17 +00:00
June
f5a201dd94 add top margin to author actions section (#2861) 2024-10-27 11:27:06 -07:00
Erb3
72dab12033 fix(docs): correct favicon url (#2843)
Starlight defaults to favicon.svg, however a favicon.ico was added to the repo.

Changes:

- Format astro config according to Prettier
- Properly set favicon
2024-10-24 13:24:13 -07:00
Jai A
ce4b4ba41d Fix daedalus run 2024-10-23 13:41:22 -07:00
Jai A
945206153d ads.txt update 2024-10-22 12:10:54 -07:00
Geometrically
9f977d082b Merge pull request #2840 from modrinth/daedalus-add
Add daedalus to monorepo
2024-10-21 15:33:58 -07:00
Jai A
4a2ec0c40c Merge remote-tracking branch 'origin/daedalus-add' into daedalus-add 2024-10-21 13:22:30 -07:00
Jai A
70bf61a645 Fix fmt 2024-10-21 13:21:56 -07:00
Geometrically
674f4b1095 Merge branch 'main' into daedalus-add 2024-10-21 12:40:17 -07:00
Jai A
3304070034 Add custom parameter for app/web traffic 2024-10-21 12:34:46 -07:00
Geometrically
19ca5f08c6 Merge branch 'main' into daedalus-add 2024-10-21 12:16:41 -07:00
Jai A
5605910ac8 Build spec package 2024-10-21 12:11:14 -07:00
Erb3
b1eda435a5 fix: update old docs links (#2845)
- Markdown guide link to support article
- API version deprecated error to new docs
- Modpack permissions error to support article
- Labrinth README links to new docs
- App WebView2 link to support article
2024-10-21 12:08:06 -07:00
Jai A
e88ca8430e rust update 2024-10-19 20:11:18 -07:00
Jai A
d39db02a73 Fix docker build 2024-10-19 20:04:21 -07:00
Jai A
86a64ca929 Update readmes 2024-10-19 19:46:40 -07:00
Jai A
5ea0f7d4c7 Fix docker build again 2024-10-19 19:42:52 -07:00
Jai A
b3fa2fa6d2 Fix docker build 2024-10-19 19:38:36 -07:00
Jai A
999dc640bc fix package 2024-10-19 15:44:37 -07:00
Jai A
9be9658ffb Add daedalus 2024-10-19 15:14:30 -07:00
Jai A
595d5362f6 Merge remote-tracking branch 'daedalus/monorepo-migration' into daedalus-add 2024-10-19 14:59:12 -07:00
Jai A
f212fcf892 Start monorepo migration 2024-10-19 14:40:58 -07:00
Geometrically
8c1c5572c0 Merge pull request #2517 from modrinth/labrinth-add
Add labrinth to monorepo
2024-10-19 12:42:38 -07:00
Jai A
31d151638a Temp disable labrinth test in CI 2024-10-19 12:39:46 -07:00
Jai A
fb6b41630c Fix compose script 2024-10-19 10:07:22 -07:00
Jai A
6ab806cbde Merge remote-tracking branch 'origin/labrinth-add' into labrinth-add 2024-10-18 21:28:01 -07:00
Jai A
c0267f7746 Remove all features 2024-10-18 21:14:25 -07:00
Geometrically
a54b6dc7b9 Merge branch 'main' into labrinth-add 2024-10-18 19:45:43 -07:00
Jai A
9ec43ebe70 Fix lint 2024-10-18 19:45:00 -07:00
Geometrically
486cd68bf7 New docs site (#2521)
* initial docs

* more docs work

* Update readme + add license

* update frontend GH action
2024-10-18 17:36:20 -07:00
Jai A
98c050e7e9 Fix lint again 2024-10-18 17:33:15 -07:00
Jai A
25fcee984b Add container labels 2024-10-18 17:00:36 -07:00
Jai A
86922c4547 Fix lint not working 2024-10-18 16:54:21 -07:00
Jai A
7bbdfd25cd [skip ci] remove ds store 2024-10-18 16:28:40 -07:00
Jai A
b8ad22a6fb Fix checks 2024-10-18 16:23:32 -07:00
Jai A
8dd955563e Fix clippy errors + lint, use turbo CI 2024-10-18 16:07:35 -07:00
Jai A
663ab83b08 Fix build deps 2024-10-18 15:47:23 -07:00
Jai A
c143929b69 Move rust checks to own file 2024-10-18 15:43:30 -07:00
Jai A
1b73d248b3 bump rustc 2024-10-16 16:12:23 -07:00
Jai A
cc22a92daf Update dockerfile 2024-10-16 16:07:24 -07:00
Jai A
39f0408929 Fix contact path 2024-10-16 16:04:45 -07:00
Jai A
e66f46a464 Fix check config 2024-10-16 16:02:55 -07:00
Jai A
9243296197 Fix again 2024-10-16 16:01:50 -07:00
Jai A
26ce83f8f1 Fix docker path 2024-10-16 15:59:29 -07:00
Jai A
907ef38189 make checks work 2024-10-16 15:57:44 -07:00
Jai A
a7d4001b00 Merge remote-tracking branch 'labrinth/mono-repo-migrate' 2024-10-16 14:15:45 -07:00
Jai A
e3a3379615 move to monorepo dir 2024-10-16 14:11:42 -07:00
Jai A
356a06e694 add signal for app vs web 2024-10-15 23:47:51 -07:00
Geometrically
fce516a76f Remove ads muting (#2511) 2024-10-15 23:43:49 -07:00
Jai A
42ade0fbd1 make script non-async 2024-10-15 10:56:35 -07:00
Jai A
ba07f5dad4 Add clean.io direct 2024-10-15 10:52:00 -07:00
Jai A
cc89e0f3f1 remove ad cookie (main) 2024-10-14 23:49:27 -07:00
Jai A
0e14d3f9c1 update ads.txt 2024-10-14 23:47:42 -07:00
Geometrically
ff7975773e Prorations (#975)
* Prorations

* Fix pyro integration

* set server uuid on creation

* fix comp

* Fix new charge date, pyro suspend reason

* Update server creation endpoint
2024-10-14 13:30:04 -07:00
Norbiros
6716e2277d fix(theseus): Files drag & drop (#2499)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-10-12 13:27:07 -07:00
Mysticdrew
f986dc5d11 Remove extra "not" (#2506)
Signed-off-by: Mysticdrew <drewhaas@gmail.com>
2024-10-10 15:27:43 -07:00
Geometrically
570a4096f9 Update billing with backend changes (#2505) 2024-10-09 21:11:49 -07:00
Geometrically
c88bfbb5f0 Move charges to DB + fix subscription recurring payments (#971)
* Move charges to DB + fix subscription recurring payments

* Finish most + pyro integration

* Finish billing

* Run prepare

* Fix intervals

* Fix clippy

* Remove unused test
2024-10-09 21:11:30 -07:00
Jai A
d302795512 Fix ad init not working on no tauri invoke 2024-10-09 14:04:33 -07:00
Jai A
115acce80c Add hashing to ads 2024-10-09 13:54:35 -07:00
Geometrically
a8731b0ca2 Fix unfollowing projects (#2496) 2024-10-08 15:19:46 -07:00
nekk
fd596bf418 type in ErrorModal.vue (#2492)
Signed-off-by: nekk <108535017+iam-nekk@users.noreply.github.com>
2024-10-06 21:11:39 +00:00
Geometrically
ef7cfffeb6 Add support for Optima (#2489) 2024-10-04 13:35:39 -07:00
Geometrically
28b6bf8603 GDPR export route (#969)
* GDPR export route

* make users able to access
2024-09-27 12:43:17 -07:00
Geometrically
f7d1cd2a4f Pause subscription renewals (#968) 2024-09-24 13:02:19 -07:00
Geometrically
edb7e5f323 Integrate with Aditude API for payouts (#965)
* Integrate with Aditude API for payouts

* Update expiry

* Fix tests
2024-09-12 14:52:17 -07:00
Geometrically
5b5599128a Optimize user-generated images for reduced bandwidth (#961)
* Optimize user-generated images for reduced bandwidth

* run prepare

* Finish compression
2024-09-07 17:44:49 -07:00
Geometrically
cb0f03ca9c Slack webhooks (#959)
* Slack webhooks

* Fix automod rejecting audio and locale packs

* Run prepare
2024-09-06 23:42:54 -07:00
Geometrically
2e35f3608b Remove name field (and remove existing data) (#935)
* Remove name field (and remove existing data)

* run prep, fmt

* fix dummy data
2024-09-06 20:16:28 -07:00
Jai A
679ffbcce7 Fix Fabric Loader 0.16.0, old forge versions 2024-08-21 18:42:02 -07:00
Geometrically
637a923e84 Fix empty subscriptions (#954) 2024-08-18 11:35:39 -07:00
Jai A
1f8d569b79 Merge remote-tracking branch 'origin/master' 2024-08-17 13:28:20 -07:00
Jai A
93ae24e707 Fix forge format version 1 2024-08-17 13:28:00 -07:00
Geometrically
7dd340f0b6 Fix subscriptions edge case (#952)
* Fix subscriptions edge case

* prep
2024-08-15 02:03:46 -07:00
Geometrically
1d0d8d7fbe Payments/subscriptions support (#943)
* [wip] Payments/subscriptions support

* finish

* working payment flow

* finish subscriptions, lint, clippy, etc

* docker compose
2024-08-14 17:14:52 -07:00
Norbiros
6de8d2684a fix: Disable Run Meta on forks (#19)
* fix: Disable `Run Meta` on forks

* fix: Also don't run `docker` action
2024-07-31 13:14:39 -07:00
Norbiros
9763a43943 fix: Correctly replace linux natives for LWJGL 3.3.1 (#20) 2024-07-31 13:14:30 -07:00
PixelBedrock
60edbcd5f0 Allow user to select GitHub account (#922)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-07-09 15:12:15 -07:00
Jai A
0ae1e40d79 fix typo 2024-06-28 17:03:54 -07:00
Jai A
34d931573c try with bookworm 2024-06-28 17:02:30 -07:00
Jai A
721365578a update action to use master branch 2024-06-28 16:59:06 -07:00
Jai A
7be02318e0 add openssl 2024-06-28 16:55:54 -07:00
Jai A
88db79188c use rustls 2024-06-28 16:50:47 -07:00
Jai A
8a0329b23d don't run in detached mode 2024-06-28 16:48:03 -07:00
Jai A
02ebe59d2f add dispatch support for meta 2024-06-28 16:45:06 -07:00
Jai A
6e8e053b88 fix newline 2024-06-28 16:40:12 -07:00
Jai A
fc3056b0e0 add rust log env 2024-06-28 15:53:59 -07:00
Geometrically
4274a8ed68 Fix forge install issues (#18)
* Fix forge install issues

* remove mac garb
2024-06-28 15:44:17 -07:00
Geometrically
8b16cd1b36 Daedalus Rewrite + Code Cleanup (#16)
* [wip] rewrite daedalus, vanilla, fabric, and quilt

* finish forge + neo

* fix docker

* fix neoforge 1.21+

* update concurrency limit

* finish

* remove mac garb
2024-06-25 15:47:27 -07:00
Geometrically
5148e27448 Fix neoforge check, make forge validator more lenient (#928) 2024-06-14 13:16:05 -07:00
Geometrically
608e55c01f Fix duplicate file names (#927)
* Fix duplicate file names

* Fix checks
2024-06-12 21:40:24 -07:00
Geometrically
b8963d272a Update validators again (#925)
* Update validators again

* fix tests + clippy
2024-06-12 14:38:35 -07:00
Geometrically
beaaed6613 Use row level locking for payouts (#926) 2024-06-12 14:19:15 -07:00
Sean O'Connor
6bbd8c9b16 Update link to LICENSE in COPYING.md (#865)
The link to the license was outdated and resulted in a broken path. The correct filename is "LICENSE.txt".

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-06-12 13:41:40 -07:00
Geometrically
872ffa02ce fix version update route perf (#923)
* fix version update route perf

* fix tests
2024-06-12 09:58:01 -07:00
Geometrically
b933202694 Fix quilt validator (#916) 2024-05-08 20:28:40 -07:00
Geometrically
49cf0c8a9a Fix user deletion (#907)
* Fix user deletion

* run prep+fmt

* Update validators
2024-04-22 17:46:56 -07:00
Geometrically
83ccf4928f Fix mod msg status (#896)
* Fix mod msg status

* Fix validators
2024-04-22 13:09:35 -07:00
Emma Alexia
28b0d34bff Fix search query parameter validation (#863)
* Fix search query parameter validation

* fix compile

* make it actually work

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-04-22 11:40:31 -07:00
Geometrically
0a0837ea02 update ratelimiter (#897)
* update ratelimiter

* Switch to old scheduler
2024-03-27 15:56:29 -07:00
Geometrically
a0aa350a08 Fix cache stampede issues + generalize cache (#884)
* caching changes

* fix cache stampede issues

* Use pub/sub for better DB fetches

* remove pubsub

* remove debugs

* Fix caches not working

* fix search indexing removal
2024-03-26 21:15:50 -07:00
Geometrically
decfcb6c27 Fix issue with moderator identities being revealed (#892)
* Fix issue with moderator identities being revealed

* Fix on multiple threads route

* Fix thread notifs

* Fix failing test

* fix thread messages returning nothing
2024-03-19 17:25:49 -07:00
Geometrically
730913bec4 Fix issue in specifying dependencies (#891) 2024-03-18 13:56:06 -07:00
Geometrically
f8f037196e Fix payouts desync (#890)
* Fix payouts desync

* fix tests + user payouts req
2024-03-12 10:52:40 -07:00
Geometrically
e2ffeab8fa fix views analytics (#885)
* fix views analytics

* update ip stripping

* update clickhouse tables

* fix broken queries

* Fix panics

* fix download undercounting

* fix packerator failing sometimes

* run prep
2024-03-02 14:04:46 -07:00
Geometrically
04d834187b Automatic moderation (#875)
* Automatic moderation

* finish

* modpack fixes

* fix unknown license msg

* fix moderation issues
2024-02-21 16:24:21 -07:00
Geometrically
33b2a94d90 Fix version creation taking forever (#878)
* Fix version creation taking forever

* run fmt + prep

* fix tests?
2024-02-05 12:24:12 -07:00
Geometrically
ce3b024fea Fix gift cards (#877)
* Fix gift card cashout

* Fix mutex locks
2024-02-05 12:02:07 -07:00
Geometrically
a02aa7586b Fix version files updates route (#876)
* Fix version updates files route

* run fmt + prep

* actually work

* update query perf

* fix
2024-02-04 20:19:46 -07:00
Geometrically
d5107f2ef6 Fix unlisted showing (#873)
* Fix projects showing draft

* fix build

* run fmt
2024-01-27 19:11:00 -05:00
Geometrically
5b63b0b398 jemalloc (#861)
* jemalloc

* featurizeP

---------

Co-authored-by: Wyatt Verchere <wverchere@gmail.com>
2024-01-27 18:13:52 -05:00
Geometrically
fc577241bd Update deps (#859) 2024-01-14 12:36:11 -05:00
Geometrically
bb8a0e596c Fix settings fail (#856)
* Fix settings fail

* Revert settings equality checks
2024-01-13 21:09:28 -05:00
Geometrically
2a63b703f9 Fix additional categories not showing up correctly (#855) 2024-01-13 20:19:45 -05:00
Geometrically
bfeff78164 Update search queries (#854)
* Update search queries

* Bump accepted payload limit

* fixes

* push test changes

* fmt clippy prepare

---------

Co-authored-by: Wyatt Verchere <wverchere@gmail.com>
2024-01-13 18:20:28 -05:00
Wyatt Verchere
4826289020 hardcodes search facets (#852)
* hardcodes search facets

* disable search

* Uncomment

---------

Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-01-13 17:42:45 -05:00
Wyatt Verchere
0aebf37ef8 Index swapping when meilisearch reindex (#853)
* cycling indices

* removed printlns

* uses swap indices instead

* Bring back deletion

* Fix tests

* Fix version deletion

---------

Co-authored-by: Jai A <jaiagr+gpg@pm.me>
2024-01-13 17:40:30 -05:00
Geometrically
d1a09d0b95 Add assets and migrations to dockerfile (#851) 2024-01-12 15:00:37 -05:00
Geometrically
7b00003958 Org fixes (#850)
* Org fixes

* payouts bug

* Update dockerfile fix test

* Update to bookworm

* clippy
2024-01-12 14:19:39 -05:00
Wyatt Verchere
4483bb147c re-modularizes-search-facets (#842)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-01-12 11:05:46 -05:00
Wyatt Verchere
ef31c0c0da Cleans up and removes TODOs, adds tests (#844)
* removes version ordering from v2; simplifies now-unecessary three-level faceting

* resolved some todos

* test for game version updating

* merge fixes; display_categories fix
2024-01-11 21:11:27 -05:00
Wyatt Verchere
76c885f080 Fixes incorrect loader fields (#849)
* loader_fields fix

* tested, fixed

* added direct file check for invalid file_parts

* search fixes

* removed printlns

* Adds check for loaders

* removes println
2024-01-11 18:36:01 -05:00
Emma Alexia
f16e93bd3a Raise project limit on collections from 64 to 1024 (#847)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-01-11 10:27:38 -05:00
Geometrically
05d2a96900 Organization payouts (#848) 2024-01-11 10:27:15 -05:00
Julian Vennen
9c70c35669 Ignore spaces when parsing facets (#846) 2024-01-10 08:40:19 -08:00
Julian Vennen
9d54c41a2b Fix melisearch name replacements with operators other than : (#845)
* Fix melisearch name replacements with operators other than :

* Pass facet by refrence

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-01-09 13:16:36 -08:00
Wyatt Verchere
3464fbb2e8 fixes capitalization issue; modifies existing test to check for it (#841) 2024-01-09 15:42:17 -05:00
Wyatt Verchere
d51d6517be fixes modpacks losing version data on modification (#840) 2024-01-08 10:28:33 -05:00
Geometrically
5f6cc1281e Fix user revenue display (#839)
* Fix user revenue display

* fix rev display
2024-01-07 20:57:37 -05:00
Jai A
035fc69060 temp pause 2024-01-07 18:38:40 -05:00
Geometrically
34baf44534 Fix packs logic (#837)
* Fix packs logic

* Clippy
2024-01-07 18:36:42 -05:00
Wyatt Verchere
c3448033de Fixes missing plugin/datapack in search (#829)
* fixes datapack/plugin issue

* fixes level

* server side searching; org projects

* total hits

* total hits fixes

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2024-01-07 16:36:26 -05:00
Geometrically
aee9b6a951 Fix Plugin/Datapack creation and featuring (#836) 2024-01-07 15:08:28 -05:00
Wyatt Verchere
75e5bec962 test (#828) 2024-01-07 01:47:42 -05:00
Wyatt Verchere
59c269c8d0 Fixes game versions v2 display (#827)
* fixes game versions display

* reverses from version route
2024-01-07 01:04:13 -05:00
Wyatt Verchere
541022cdc3 Featured filtering patch (#825) 2024-01-06 22:31:34 -05:00
Wyatt Verchere
527521328f Fixes search not reading sidetypes (#823) 2024-01-06 16:36:26 -05:00
Geometrically
917b89e44f Fix org adding (#821) 2024-01-06 16:34:20 -05:00
Wyatt Verchere
87862f3e23 changes tests to a macro (#822) 2024-01-06 14:08:03 -05:00
Wyatt Verchere
10eed05d87 Fixes failing tests (#813)
* fixes failing  tests

* fmt clippy

* updated dockerfile

* fixes failing tests; adds important fix from extracts_versions PR

* assert_eq -> assert_status, giving better error messages

* fixed random failure bug

* fmt, clippy, etc
2024-01-05 11:20:56 -05:00
Geometrically
f5802fee31 Staging bug fixes (#819)
* Staging bug fixes

* Finish fixes

* fix tests

* Update migration

* Update migrations

* fix side types being added for ineligible loaders

* fix tests

* Fix tests

* Finish fixes

* Add slug display names
2024-01-04 16:24:33 -05:00
Geometrically
cf9c8cbb4f Fix deletion of private notes (#814) 2023-12-28 20:25:55 -05:00
Geometrically
f199ecf8e9 Final release fixes (#811) 2023-12-27 13:54:17 -05:00
Wyatt Verchere
3bdd551d40 fixes bug (#809) 2023-12-24 18:53:58 -05:00
Wyatt Verchere
4a7936a51d adds text, fixed problem (#808) 2023-12-21 18:50:25 -08:00
Wyatt Verchere
76e00c2432 Filtering refactoring (#806)
* switching computers

* fmt clippy sqlx prepare

* merge fixes
2023-12-21 16:36:30 -08:00
Wyatt Verchere
b46f3bf2c4 Live test fixes (#807)
* fixes

* changes
2023-12-21 15:34:42 -08:00
Wyatt Verchere
f7b4b782bf Organization ownership (#796)
* organization changes

* changes

* fixes failing test

* version changes

* removed printlns

* add_team_member comes pre-accepted

* no notification on force accept

* fixes tests

* merge fixes
2023-12-20 17:27:57 -05:00
Wyatt Verchere
60c535e861 Misc testing improvements (#805)
* made dummy data more consistent; not an option

* fixed variable dropping issue crashing actix (?)

* removed scopes specific tests, removed schedule tests

* team routes use api

* removed printlns, fmt clippy prepare
2023-12-20 14:46:53 -05:00
Wyatt Verchere
d59c522f7f Sanity checked all of V2 route conversions (#803)
* follows

* all v2 routes now either convert or have a comment

* added common structs, clippy

* merge fix

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-12-19 10:20:32 -08:00
Wyatt Verchere
9f798559cf country testing (#801)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-12-15 12:49:55 -07:00
Wyatt Verchere
f939e59463 Testing bug fixes (#788)
* fixes

* adds tests- fixes failures

* changes

* moved transaction commits/caches around

* collections nullable

* merge fixes

* sqlx prepare

* revs

* lf fixes

* made changes back

* added collections update

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-12-14 15:19:50 -07:00
Wyatt Verchere
50e89ad98b fix (#800) 2023-12-13 17:08:58 -07:00
Geometrically
c0c5978028 Bump meilisearch version (#799) 2023-12-12 21:04:40 -07:00
Emma Alexia
abbeed394e Fix project visibility in hash routes (#792)
* Fix project visibility in hash routes

* improve

* clippy

* CLIPPYYYYYYYYYYYYYY

* clippy, I hope you know that I hate you

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-12-12 20:45:32 -07:00
Wyatt Verchere
f5b8c15388 small add (#795)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-12-12 20:20:09 -07:00
Geometrically
f53b6b550f Fix high memory usage during search (#798)
* Fix high memory usage during search

* Fix settings lag

* Fix clippy + fmt
2023-12-12 20:11:56 -07:00
Wyatt Verchere
00e55b1874 get dependencies fix (#794) 2023-12-12 19:57:51 -07:00
Wyatt Verchere
90954dac49 Testing search prod (#791)
* testing push

* lowers it

* removed unwrap

* reduced to 500

* Really took down time

* reorders chunking

* rebuild docker

* reverted most changes

* cargo fmt

* reduced meilisearch limit

* added logs, removed deletion of index

* one client creation

* changes

* reverted gallery cahnge

* testing re-splitting again

* Remove chunking + index deletion

* Bring back chunking

* Update chunk size

---------

Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-12-11 20:01:15 -08:00
Wyatt Verchere
6217523cc8 Test permissions use api (#784)
* initial push

* fmt; clippy

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-12-11 18:24:49 -08:00
Wyatt Verchere
27ccd3dfa8 summary change to description (#793) 2023-12-11 18:53:30 -07:00
Wyatt Verchere
235f4f10ef Chunking searches (#787)
* new attempt

* revised searching CTEs

* prepare fix

* fix tests

* fixes

* restructured project_item to use queries

* search changes! fmt clippy prepare

* small changes
2023-12-08 23:14:17 -07:00
Geometrically
945e5a2dc3 Fix semi-colon causing query to fail (#785) 2023-12-04 21:18:50 -07:00
Wyatt Verchere
4b6a2685d0 fixed gallery mislabeled field (#783) 2023-12-04 20:44:48 -07:00
Geometrically
e76b6c3bde Optimize country analytics (#782) 2023-12-04 19:45:17 -07:00
Geometrically
4630d175d7 Optimize analytics queries (#781)
* Optimize analytics queries

* fix clippy
2023-12-04 18:49:51 -07:00
Wyatt Verchere
27055b96e3 fixes urls (#780) 2023-12-04 18:03:13 -07:00
Wyatt Verchere
0ef96c0bca fixed issue (#778) 2023-12-04 13:51:28 -08:00
Wyatt Verchere
b2be4a7d67 Search overhaul (#771)
* started work; switching context

* working!

* fmt clippy prepare

* fixes

* fixes

* revs

* merge fixes

* changed comments

* merge issues
2023-12-03 07:27:12 -07:00
Wyatt Verchere
a70df067bc Misc v3 linear tasks (#767)
* v3_reroute 404 error

* hash change

* fixed issue with error conversion

* added new model confirmation tests
+ title name change

* renaming, fields

* owner; test changes

* clippy prepare

* fmt

* merge fixes

* clippy

* working merge

* revs

* merge fixes
2023-12-01 20:15:00 -07:00
Wyatt Verchere
2d92b08404 V2 removal and _internal rerouting (#770)
* deleteed v3 exclusive routes

* moved routes around

* fixed linkage that movement broke

* initial merge errors

* fixes
2023-12-01 10:02:11 -08:00
Wyatt Verchere
4bbc57b0dc Links (#763) 2023-11-30 23:14:52 -08:00
Wyatt Verchere
756c14d988 fixed redis deserialization issue (#775) 2023-11-30 19:57:15 -08:00
Wyatt Verchere
b3b55210f7 Renamed default project type to unknown. (#774)
* small change

* plugins and datapacks now correctly return to project

* Adds to search
2023-11-30 17:17:21 -08:00
Wyatt Verchere
58093a9438 Modifies sql queries to use CTEs (#773)
* fixes huge slowodwn on version item

* changes!

* fixes, touch ups, indices

* clippy prepare
2023-11-30 11:10:56 -08:00
Wyatt Verchere
ed33dd2127 fixed issue (#772) 2023-11-30 10:30:30 -08:00
Geometrically
d4f9c97cca Payouts code (#765)
* push to rebase

* finish most

* finish most

* Finish impl

* Finish paypal

* run prep

* Fix comp err
2023-11-29 11:00:08 -07:00
Wyatt Verchere
f731c1080d Side types overhaul (#762)
* side types overhaul

* fixes, fmt clippy

* migration fix for v3 bug

* fixed migration issues

* more tested migration changes

* fmt, clippy

* bump cicd

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-11-28 11:36:59 -07:00
Wyatt Verchere
fd18185ef0 More staging fixes (#768)
* Fixes issues

* staging fixes

* passes tests

* fixes. fmt/clippy

* drops datapack/plugin extras

* fixed failing test

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-11-26 20:29:59 -07:00
Carter
0efbbed5e2 Add fields to OAuth (#769)
* Add url and description fields to OAuthClient
model

* Add OAuth client icon editing and deleting
endpoints

* updated query data

* fix missed queries

* sqlx prep

* update with tests builds
2023-11-25 21:48:51 -07:00
Wyatt Verchere
bad350e49b Plugins (#758)
* plugins; datapacks

* merge fixes/changes

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-11-25 18:11:38 -07:00
Wyatt Verchere
172b93d07f Tests v2 recreate (#760)
* added common project information; setup for v2 test change

* all tests now use with_test_environment

* progress, failing

* finished re-adding tests

* prepare

* cargo sqlx prepare -- --tests

* fmt; clippy; prepare

* sqlx prepare

* adds version_create fix and corresponding test

* merge fixes; rev

* fmt, clippy, prepare

* test cargo sqlx prepare
2023-11-25 14:42:39 -07:00
Wyatt Verchere
ade8c162cd Staging fixes (#766)
* fixes bugs

* fixed more things

* fixes version creation

* small change

* removed modpack
2023-11-24 11:42:06 -08:00
Wyatt Verchere
79e634316d Analytics permissions (#761)
* adds test; permissions fix

* clippy

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-11-21 10:02:07 -07:00
Wyatt Verchere
dfba6c7c91 Compiler improvements (#753)
* basic redis add

* toml; reverted unnecessary changes

* merge issues

* increased test connections

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-11-19 20:10:13 -07:00
Wyatt Verchere
e06a77af28 Adds code coverage (#757)
* coverage initial push

* compiles on PR

* adds db env variable

* fixed env variables being on the wrong action

* added more tests yml code

* refresh

* tried copying over tests.yml

* removed accidental tests

* shotgun attempts

* generated yml

* more tries

* shotgun again

* small mistakes

* repush

* repush

* Adds env variables to tarp

* removes unused actions and tests cfg attribute on main.rs

* only will work on push to master

* changed to 60%

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-11-17 17:58:15 -08:00
Wyatt Verchere
74973e73e6 Tests 3 restructure (#754)
* moved files

* moved files

* initial v3 additions

* moves req data

* tests passing, restructuring, remove v2

* fmt; clippy; prepare

* merge conflicts + issues

* merge conflict, fmt, clippy, prepare

* revs

* fixed failing test

* fixed tests
2023-11-16 11:36:03 -07:00
Geometrically
ac07ac5234 Fix date parsing for partial versions (#15) 2023-11-15 16:35:33 -07:00
Wyatt Verchere
f4880d0519 more games information, games route (#756)
* more games information, games route

* adds banner url
2023-11-14 11:01:31 -07:00
Wyatt Verchere
375f992a0c Adds ordering on loader fields enum (#755)
* now sorts on ordering, fmt clippy prepare

* fixed tests

* removed accidenetal printlns
2023-11-13 19:19:06 -07:00
Wyatt Verchere
ae1c5342f2 Search test + v3 (#731)
* search patch for accurate loader/gv filtering

* backup

* basic search test

* finished test

* incomplete commit; backing up

* Working multipat reroute backup

* working rough draft v3

* most tests passing

* works

* search v2 conversion

* added some tags.rs v2 conversions

* Worked through warnings, unwraps, prints

* refactors

* new search test

* version files changes fixes

* redesign to revs

* removed old caches

* removed games

* fmt clippy

* merge conflicts

* fmt, prepare

* moved v2 routes over to v3

* fixes; tests passing

* project type changes

* moved files over

* fmt, clippy, prepare, etc

* loaders to loader_fields, added tests

* fmt, clippy, prepare

* fixed sorting bug

* reversed back- wrong order for consistency

* fmt; clippy; prepare

---------

Co-authored-by: Jai A <jaiagr+gpg@pm.me>
2023-11-11 16:40:10 -08:00
Carter
97ccb7df94 Knossos Oauth 2 Flow Changes (#752)
* adjust type and response format

* Replace Found with Ok for handled redirects

* scope parse fix

* change apps query from body to query

* adjust tests for new response type

* remove unused imports

* Clippy fixes
2023-11-11 10:42:01 -07:00
Geometrically
a818199b5a fix download counts for ipv4 ips (#750) 2023-11-08 20:55:08 -07:00
Geometrically
6fe1fa3455 Fix crash when MC updates (#14)
* Fix crash when MC updates

* Fix fabric ordering + add neoforge support

* run clippy

* run clippy
2023-11-08 17:57:37 -07:00
Geometrically
aab95444a8 Fix download counts (#746) (#747)
* Fix download counts (#746)

* Fix download counts

* remove unsafe send

* update indexing time

* run prep

* run prep again
2023-11-06 15:04:32 -07:00
Wyatt Verchere
40f28be3b4 rounds dates for revenue (#745)
* rounds dates for revenue

* analytics tests pass
2023-11-03 21:03:30 -07:00
Jackson Kruger
911d442340 Version ordering [MOD-551] (#740)
* Version ordering

* cargo sqlx prepare

* Use version ordering for maven

* Use version ordering when sorting versions in Rust (not just SQL)

* Thanks clippy
2023-11-01 09:36:39 -07:00
Jackson Kruger
d5594b03e3 Fix organizations route typo (#743) 2023-10-31 10:58:05 -05:00
Geometrically
89f1ddf4d7 Route to view user's orgs (#742) 2023-10-30 16:59:53 -07:00
Jackson Kruger
6cfd4637db OAuth 2.0 Authorization Server [MOD-559] (#733)
* WIP end-of-day push

* Authorize endpoint, accept endpoints, DB stuff for oauth clients, their redirects, and client authorizations

* OAuth Client create route

* Get user clients

* Client delete

* Edit oauth client

* Include redirects in edit client route

* Database stuff for tokens

* Reorg oauth stuff out of auth/flows and into its own module

* Impl OAuth get access token endpoint

* Accept oauth access tokens as auth and update through AuthQueue

* User OAuth authorization management routes

* Forgot to actually add the routes lol

* Bit o cleanup

* Happy path test for OAuth and minor fixes for things it found

* Add dummy data oauth client (and detect/handle dummy data version changes)

* More tests

* Another test

* More tests and reject endpoint

* Test oauth client and authorization management routes

* cargo sqlx prepare

* dead code warning

* Auto clippy fixes

* Uri refactoring

* minor name improvement

* Don't compile-time check the test sqlx queries

* Trying to fix db concurrency problem to get tests to pass

* Try fix from test PR

* Fixes for updated sqlx

* Prevent restricted scopes from being requested or issued

* Get OAuth client(s)

* Remove joined oauth client info from authorization returns

* Add default conversion to OAuthError::error so we can use ?

* Rework routes

* Consolidate scopes into SESSION_ACCESS

* Cargo sqlx prepare

* Parse to OAuthClientId automatically through serde and actix

* Cargo clippy

* Remove validation requiring 1 redirect URI on oauth client creation

* Use serde(flatten) on OAuthClientCreationResult
2023-10-30 09:14:38 -07:00
Jackson Kruger
8803e11945 Upgrade to sqlx 0.7.2 (#736)
* Update to sqlx 0.7.2

* Somehow missed one (and remove queries from other branch)
2023-10-23 14:30:39 -05:00
Geometrically
b1ca2cc2b6 Fix quilt + fabric intermediary syncing issues (#13)
* Fix quilt + fabric intermediary syncing issues

* fix comp

* Fix random versions not working/updating meta
2023-10-21 13:09:21 -07:00
Geometrically
9a8f3d7bad Fix analytics routes + add revenue route (#734) 2023-10-19 09:42:49 -07:00
Wyatt Verchere
9d0e762f36 More tests (#729)
* permissions tests

* finished permissions; organization tests

* clippy, fmt

* post-merge fixes

* teams changes

* refactored to use new api

* fmt, clippy

* sqlx prepare

* revs

* revs

* re-tested

* re-added name

* reverted to matrix
2023-10-17 00:53:10 -07:00
Jackson Kruger
abf4cd71ba Add redis caching to getting user notifications and projects [MOD-540] (#723)
* Add redis caching to getting a user's project ids

* Run `cargo sqlx prepare` to update the sqlx-data.json

* Add redis caching for getting user notifications

* Fix new clippy warnings

* Remove log that shouldn't have been committed

* Batch insert of notifications (untested)

* sqlx prepare...

* Fix merge conflict things and use new redis struct

* Fix bug with calling delete_many without any elements (caught by tests)

* cargo sqlx prepare

* Add tests around cache invalidation (and fix bug they caught!)

* Some test reorg based on code review suggestions
2023-10-12 15:52:24 -07:00
Geometrically
d66270eef0 Remove scheduling webhook check (#728) 2023-10-12 11:21:26 -07:00
Geometrically
07ecd13554 Switch to Trolley for Modrinth Payments (#727)
* most of trolley

* Switch to trolley for payments

* run prepare

* fix clippy

* fix more

* Fix most tests + bitflags

* Update src/auth/flows.rs

Co-authored-by: Jackson Kruger <jak.kruger@gmail.com>

* Finish trolley

* run prep for merge

* Update src/queue/payouts.rs

Co-authored-by: Jackson Kruger <jak.kruger@gmail.com>

---------

Co-authored-by: Jackson Kruger <jak.kruger@gmail.com>
2023-10-11 15:55:01 -07:00
BasiqueEvangelist
f1ff88f452 fix maven filters for versions with dashes (#725)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-10-11 14:01:32 -07:00
Jackson Kruger
d92272ffa0 Batch inserts [MOD-555] (#726)
* Batch a bunch of inserts, but still more to do

* Insert many for clickhouse (+ tests)

* Batch the remaining ones except those requiring deduplication

* Risky dedups

* Bit o cleanup and formatting

* cargo sqlx prepare

* Add test around batch editing project categories

* Add struct to satisfy clippy

* Fix silly mistake that was caught by the tests!

* Leave room for growth in dummy_data
2023-10-11 11:32:58 -07:00
Jackson Kruger
dfa43f3c5a Add /metrics endpoint for Prometheus (#724) 2023-10-06 15:58:02 -07:00
Wyatt Verchere
259c5ef3d0 Tests (#719)
* computer switch

* some fixes; github action

* added pr to master

* sqlx database setup

* switched intial GHA test db

* removed sqlx database setup

* unfinished patch route

* bug fixes + tests

* more tests, more fixes, cargo fmt

* merge fixes

* more tests, full reorganization

* fmt, clippy

* sqlx-data

* revs

* removed comments

* delete revs
2023-10-06 09:57:33 -07:00
Wyatt Verchere
a1b59d4545 Organizations (#712)
* untested, unformatted, un-refactored

* minor simplification

* simplification fix

* refactoring, changes

* some fixes

* fixes, refactoring

* missed cache

* revs

* revs - more!

* removed donation links; added all org members to route

* renamed slug to title

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-10-02 10:56:57 -07:00
Emma Alexia Triphora
58a61051b9 Don't show processing projects in thread message inbox (#722)
They already show up in the Review tab, so, no reason to also have them in the Messages tab
2023-10-01 13:22:29 -07:00
Emma Alexia Triphora
51777c3f33 Replace moderation project queue with moderation action log (#718) 2023-09-24 12:04:58 -04:00
Emma Alexia Triphora
3767e9fae9 Only show mod files in pack external dependencies (#710) 2023-09-24 11:49:41 -04:00
Emma Alexia Triphora
4bbfc8ccc5 Add NeoForge modpack and autoupdater support (#707) 2023-09-24 11:40:03 -04:00
Wyatt Verchere
531b8214c0 Analytics query (#716) 2023-09-24 11:22:35 -04:00
Emma Alexia Triphora
5cab618bf7 Fix reports sometimes being broken on the mod dashboard (#717) 2023-09-24 08:04:18 -05:00
Wyatt Verchere
3380f4d11c downloads route (#713) 2023-09-20 08:11:09 -07:00
Wyatt Verchere
4bf030993a Collections users route (#711)
* users route

* Added user route

* collections
2023-09-18 11:43:58 -07:00
Emma Alexia Triphora
f65f949a36 Fix issue with thread messages not being sent to moderators on non-processing projects (#705) 2023-09-15 15:39:03 -04:00
Julian Vennen
2864abd8c2 Add created and modified timestamp to search facets (#708) 2023-09-15 15:29:16 -04:00
Wyatt Verchere
9bd2cb3c7e Collections (#688)
* initial draft; unfinished

* images, fixes

* fixes

* println

* revisions

* fixes

* alternate context setup version

* rev

* partial revs

* rev

* clippy ,fmt

* fmt/clippy/prepare

* fixes

* revs
2023-09-13 22:22:32 -07:00
Wyatt Verchere
35cd277fcf analytics (#695)
* playtime

* other routes, improvements

* fmt clippy

* revs

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-09-13 16:35:47 -07:00
Geometrically
57b1932b5e Fix minecraft meta links (#12)
* Fix minecraft meta links

* remove debug

* bump v

* no mut
2023-09-12 14:19:24 -04:00
Emma Alexia Triphora
e6818023a3 Allow moderators to edit version info (#703)
* Allow moderators to edit version info

* Clippy fix
2023-09-11 14:21:50 -04:00
BasiqueEvangelist
35a541f99b Maven version filters for duplicate version numbers (#625) 2023-09-10 12:59:10 -04:00
Konicai
5fb00a947c Allow BungeeCord plugins that use bungee.yml (#698)
Co-authored-by: Emma Alexia Triphora <emma@modrinth.com>
2023-09-10 12:49:35 -04:00
Modrinth Bot
6288f679b9 [no ci] synced file(s) with modrinth/.github (#706) 2023-09-10 12:45:44 -04:00
Geometrically
e766759b8c Fix scheduling bug (#693)
* Fix scheduling bug

* Fix comp err
2023-08-23 12:08:18 -04:00
Jai A
c1d28381e8 actually bump version 2023-08-21 14:36:43 -04:00
Geometrically
2fa8371bae Neoforge support (#11) 2023-08-21 14:34:22 -04:00
Wyatt Verchere
a1cfdf1a5b Socket cleanup (#682)
* testing changes

* added success

* removed success

* Fix compile error

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Jai A <jaiagr+gpg@pm.me>
2023-08-21 14:21:05 -04:00
Emma Alexia Triphora
e9c7f5d664 Fix #683 (#691)
* Fix #683

* whitespace

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-08-21 10:44:09 -04:00
Emma Alexia Triphora
c85f12fe2c Fix closing reports not marking the report as closed (#690)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-08-21 10:43:06 -04:00
Geometrically
13e5644c89 Fix creator payouts + scheduler (#686) 2023-08-21 10:42:33 -04:00
Sasha Sorokin
eac029aef4 Webhook emojis update (#685) 2023-08-20 16:33:36 -04:00
Geometrically
a5195920fa update validators (#678) 2023-08-08 14:27:38 -07:00
Geometrically
5676a13290 Fix sponge validator (#679) 2023-08-08 13:20:15 -07:00
Geometrically
d11f0e864e gv loader cache bring back (#677)
* gv loader cache bring back

* run prep fmt
2023-08-08 10:14:49 -07:00
Geometrically
df83fcc5b9 Optimizations (#676) 2023-08-07 23:05:08 -07:00
Geometrically
f21c756793 Attempt to fix db timeouts (#674) 2023-08-07 12:37:29 -07:00
Adrian
4b07ee2fa8 Added Paper Plugins support (#673)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-08-07 11:53:21 -07:00
Jamalam
ae3a39ee65 remove 'source file' validation (#671) 2023-08-07 11:41:49 -07:00
Geometrically
e9f5bd4ac1 Fix redis pool timeout (#669)
* Fix redis pool timeout

* remove search dep project issues

* run fmt + prep
2023-08-06 15:34:03 -07:00
Geometrically
1f4ad732fd fix analytics route not working (#668) 2023-08-05 12:07:38 -07:00
Geometrically
5637d37ee1 fix updates route (#667) 2023-08-04 22:52:17 -07:00
Geometrically
c370da2fef Fix version deps (#666) 2023-08-04 22:15:19 -07:00
Geometrically
d168209721 put assets in docker (#665) 2023-08-04 20:20:05 -07:00
Geometrically
ca0468b8d5 Auth fixes (#664)
* Auth fixes

* destroy flows after use

* fix comp err

* add bearer err msg
2023-08-04 16:22:15 -07:00
Geometrically
039d26feeb Add launcher analytics (#661)
* Add more analytics

* finish hydra move

* Finish websocket flow

* add minecraft account flow

* Finish playtime vals + payout automation
2023-08-02 14:43:04 -07:00
Geometrically
10e7b66f38 Fix loader ordering (#10) 2023-07-28 16:22:38 -07:00
Geometrically
4bb47d7e01 Finish authentication (#659) 2023-07-18 15:02:54 -07:00
Geometrically
ec80c2b9db Next auth fixes (#658) 2023-07-14 22:55:00 -07:00
Geometrically
a89418e33b First auth fixes (#656) 2023-07-13 19:50:42 -07:00
Geometrically
0d88ff8dae Verify Email + Reset Password flows (#654)
* verifiers

* add missing emails

* fix gh perms
2023-07-12 20:40:24 -07:00
Geometrically
4bdf9bff3a 2FA + Add/Remove Auth Providers (#652)
* 2FA + Add/Remove Auth Providers

* fix fmt issue
2023-07-11 19:13:07 -07:00
Geometrically
7fbb8838e7 Scoped PATs (#651)
* Scoped PATs

* fix threads issues

* fix migration
2023-07-10 16:44:40 -07:00
Geometrically
366ea63209 Fix session del (#650) 2023-07-08 22:07:11 -07:00
Geometrically
6c0ad7fe1a Sessions Route + Password Auth (#649)
* Sessions Route + Password Auth

* run prep + fix clippy

* changing passwords + logging in

* register login
2023-07-08 14:29:17 -07:00
Modrinth Bot
ef9c90a43a [no ci] synced local '.github/ISSUE_TEMPLATE/' with remote 'issue_templates/' (#645)
Co-authored-by: modrinth-bot <null>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-07-08 14:11:30 -07:00
Geometrically
239214ef92 Initial Auth Impl + More Caching (#647)
* Port redis to staging

* redis cache on staging

* add back legacy auth callback

* Begin work on new auth flows

* Finish all auth flows

* Finish base session authentication

* run prep + fix clippy

* make compilation work
2023-07-07 12:20:16 -07:00
Emma
b0057b130e [no ci] Sync issue templates (#641) 2023-06-21 19:32:36 -04:00
Wyatt Verchere
d64c043838 sends own addr to auth callback (#639)
* sends own addr to auth callback

* shouldn't have http on local

* actually, both should have

* changed for consistency
2023-06-15 16:12:02 -07:00
Wyatt Verchere
dd3599f5b3 removed route (#637)
* removed route

* email change support
2023-06-13 14:43:20 -07:00
Geometrically
0d56127758 Add new mojang args (#9) 2023-06-11 13:40:31 -07:00
Geometrically
ea043517c5 Fix version file visibility (#630)
* Fix version file visibility

* add missing

* update prepare
2023-06-08 20:25:03 -07:00
Wyatt Verchere
b84d9c5d55 github token support (#629)
* github token support

* sqlx-data

* renamed github, modrinth tokens

* removed prints
2023-06-08 16:35:12 -07:00
Geometrically
6c628afe5d Auto doing fixes (#8) 2023-06-02 16:13:00 -07:00
Geometrically
abc99c7e69 Fix report + mod deletion (#626) 2023-06-01 08:53:16 -07:00
Wyatt Verchere
fe25cd3bec Minos push (#589) (#590)
* Minos push (#589)

* moving to other computer

* working redirection

* incomplete pat setup

* no more errors

* new migrations

* fixed bugs; added user check

* pats

* resized pats

* removed testing callback

* lowered kratos_id size

* metadata support

* google not working

* refactoring

* restructured github_id

* kratos-id optional, legacy accounts connect

* default picture

* merge mistake

* clippy

* sqlx-data.json

* env vars, clippy

* merge error

* scopes into an i64, name

* requested changes

* removed banning

* partial completion of github flow

* revision

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-05-31 16:03:08 -07:00
Prospector
2eb51edfb6 Merge pull request #623 from modrinth/reports-fixes
Reports fixes
2023-05-25 12:53:40 -07:00
Geometrically
715d564028 Merge branch 'master' into reports-fixes 2023-05-25 12:36:13 -07:00
Jai A
989b704efc Reports fixes 2023-05-25 15:27:15 -04:00
triphora
0bfaeb8521 Update Discord webhook emojis (#592)
Fixes #569
2023-05-19 11:04:40 -07:00
Geometrically
3db00534c2 Fix launcher overrides (#7)
* Fix launcher overrides

* Remove none set

* Patch to work with legacy versions

* remove mc
2023-05-12 20:42:18 -07:00
triphora
b713b324f9 Fix incorrect response to invalid input on bulk edit route (#579) 2023-05-06 10:45:29 -04:00
Geometrically
6512dbae1c Merge pull request #6 from modrinth/quilt-m1-support
Support for ARM + Quilt
2023-04-26 12:05:46 -07:00
Jai A
3afda71349 fix some data 2023-04-26 10:41:43 -07:00
Jai A
568e5a9bb8 run fmt 2023-04-25 19:37:56 -07:00
Jai A
89e56ae279 Support for ARM + Quilt 2023-04-25 19:36:21 -07:00
Geometrically
339ac05443 Fix TM pending in project route (#584) 2023-04-22 17:44:51 -07:00
Geometrically
72cfa683cf Fix edit bug staging (#582)
* Fix edit bug staging

* Fix comp err

* Fix mod message bug

* Fix compile bug

* Run fmt

* Fix other bug
2023-04-21 13:55:50 -07:00
Geometrically
3a6b9f04f9 Fix reports creation (#580) 2023-04-21 11:10:57 -07:00
Geometrically
59f24df294 Add dependencies to search (#578)
* Add dependencies to search

* add attrs for faceting

* run prepare

* Add user data route from token

* update to 24hrs

* Fix report bugs
2023-04-20 16:38:30 -07:00
Geometrically
5c559af936 Fix project scheduling (#576)
* Fix project scheduling

* fix comp

* Fix again

* Fix compile err

* Fix compile err

* Fix compile err

* fix random err
2023-04-18 17:12:44 -07:00
Geometrically
bb80505b76 Return pending TMs, fix notifs serde (#575)
* Return pending TMs, fix notifs serde

* fix compile
2023-04-18 14:16:41 -07:00
Geometrically
a560f6e9f6 Monetization status, additional files fix, deps fix (#574) 2023-04-16 20:03:53 -07:00
Geometrically
95ae981698 Overhaul notifs + threads fixes (#573)
* Overhaul notifs + threads fixes

* fix lang
2023-04-15 19:48:21 -07:00
Geometrically
969eb67217 Add replies, private notes, get many threads (#572)
* Add replies, private notes, get many threads

* register multiple route

* filter out moderators in threads
2023-04-13 15:04:08 -07:00
Prospector
0dfebbad9d Merge pull request #571 from modrinth/threads-fixes
more threads fixes
2023-04-13 10:18:28 -07:00
Jai A
f87f4bd8cc more threads fixes 2023-04-13 09:54:03 -07:00
Geometrically
352caa85da Fix messages not showing (#570) 2023-04-12 21:18:03 -07:00
Geometrically
8f61e9876f Add report + moderation messaging (#567)
* Add report + moderation messaging

* Add system messages

* address review comments

* Remove ds store

* Update messaging

* run prep

---------

Co-authored-by: Geometrically <geometrically@Jais-MacBook-Pro.local>
2023-04-12 17:59:43 -07:00
Geometrically
fd19bb7cd5 Merge pull request #4 from modrinth/many-fixes-again
Fix forge syncing not working
2023-04-05 12:40:17 -07:00
Jai A
0c2e9137a2 Update id merging 2023-04-05 12:39:16 -07:00
Jai A
bf5a25a96f fmt + clippy 2023-04-05 12:02:47 -07:00
Jai A
aa84f21fde Fix forge syncing not working 2023-04-05 12:02:00 -07:00
Geometrically
b9de2b4b58 Merge pull request #3 from modrinth/many-fixes
Many fixes
2023-04-04 21:17:41 -07:00
Jai A
9754f2d1c5 Add limiter for forge downloading 2023-04-04 21:17:19 -07:00
Jai A
f66fc06b4f fix debug stuff 2023-04-04 20:26:10 -07:00
Jai A
79ceb56c60 Fix issues 2023-04-04 20:25:17 -07:00
triphora
7605df1bd9 Fix some routes not working (#566) 2023-03-23 09:39:46 -07:00
Geometrically
b91ec48178 Fix notification spam (#565) 2023-03-17 09:36:12 -07:00
triphora
3c2f144795 Perses finale (#558)
* Move v2 routes to v2 module

* Remove v1 routes and make it run

* Make config declaration consistent, add v3 module

* Readd API v1 msgs

* Fix imports
2023-03-16 11:56:04 -07:00
masecla22
0271337f8e Fixed an issue with colliding slugs when modifying a project (#562)
* Fixed an issue with colliding slugs when modifying a project

* Update projects.rs

---------

Co-authored-by: triphora <emma@modrinth.com>
2023-03-14 15:06:33 -07:00
Geometrically
630a71c46c Queue Dates + Warnings, some cleanup (#549)
* Queue Dates + Warnings, some cleanup

* Fix ping

* Fix repeated discord messaging

* Fix compile error + run fmt
2023-03-14 14:48:46 -07:00
Geometrically
150329dd4a Fix GV + Loader syncing on version create (#564)
* Fix GV + Loader syncing on version create

* Update rustc v
2023-03-12 16:42:25 -07:00
triphora
59d7bce518 Actually fix pagination (#557) 2023-03-04 08:53:26 -07:00
Magnus Jensen
3c1e3cd38e Fix version name can be empty string (#537) 2023-03-03 18:20:04 -05:00
triphora
a2eb0bf9fe Add license name to license_text response (#555) 2023-03-03 18:09:27 -05:00
Orchid system (Emma)
5d48ecf86a Project Perses (API v1 yeetenings): parts 4 and 5 (#554)
* Project Perses (API v1 yeetenings): parts 4 and 5

Resolves MOD-219
Resolves MOD-220

* Note to Emma: Please test your stuff
2023-03-02 10:50:46 -07:00
Geometrically
00d09aa01e Housekeeping + Fix DB perf issues (#542)
* Housekeeping + fix db perf issues

* run prep
2023-02-22 16:11:14 -07:00
triphora
9afdc55416 Add project color to embed accent color (#522)
Closes #521

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-02-21 17:44:18 -07:00
Geometrically
2c942c8809 Fix limit ordering (#543) 2023-02-16 14:24:57 -07:00
Geometrically
c15acc4ce3 Add API V1 flickers (#541)
Co-authored-by: triphora <emmaffle@modrinth.com>
2023-02-15 17:05:28 -05:00
Geometrically
b056610eaa Version slugs (#533)
* Version slugs

* Get rid of new field, finish it up
2023-02-15 13:38:37 -07:00
Magnus Jensen
8eb9fb1834 fix slug colliding error not showing when editing project (#534)
* fix slug colliding error not showing when editing project

* format

* re-introduce old code

* run sqlx prepare

---------

Co-authored-by: MagnusHJensen <magnus.holm.jensen@lego.dk>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-02-15 10:41:00 -07:00
Magnus Jensen
7ec518b41c update index to use approval date if set (#540)
Co-authored-by: MagnusHJensen <magnus.holm.jensen@lego.dk>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2023-02-15 10:20:34 -07:00
Magnus Jensen
dc15914a85 chore(validate): update error description to space actual error (#538)
Co-authored-by: MagnusHJensen <magnus.holm.jensen@lego.dk>
2023-02-15 09:40:32 -07:00
Geometrically
3b22f59988 Fix ariadne req URL (#531) 2023-02-01 11:17:46 -07:00
Geometrically
afdab0300e Update Analytics req + Fix versions list (#529)
* Update Analytics req + Fix versions list

* Fix sentry support

* Fix lint
2023-02-01 10:08:02 -07:00
Geometrically
26533c47e7 Add loaders + game versions param to mods (#528) 2023-01-27 19:24:40 -07:00
triphora
df3aeed291 Add scary warning for people still using API v1 (#525)
* Add scary warning for people still using API v1

* change [] brackets to headers
2023-01-17 15:01:26 -07:00
Geometrically
867ba7b68f Fix various issues (#524)
* Fix various issues

* Fix multipart body hang

* drop req if error

* Make multipart errors more helpful
2023-01-16 16:45:19 -07:00
triphora
1679a3f844 Fix file uploading for admins (#519) 2023-01-07 19:35:40 -07:00
triphora
1611049623 Fix query params giving plain text error (#509) 2023-01-05 17:14:39 -05:00
Geometrically
7d195367a8 Bulk Editing + Random Projects Route (#517)
* Bulk Editing + Random Projects Route

* Run fmt + clippy + prepare

* Remove license_url
2023-01-04 19:23:47 -07:00
Geometrically
88a4f25689 Required slugs (#516) 2022-12-30 20:03:13 -07:00
BasiqueEvangelist
161dee89ec Add notification for project status updates (#511)
* Add notification for project status updates

* aaaaaa

* cargo sqlx prepare

* use friendly name of statuses

* Update src/models/projects.rs

Co-authored-by: triphora <emma@modrinth.com>

* only send notifications to accepted users

* only send notifications for people not on the team

* cargo sqlx prepare

* !=

* fully address pr comments

Co-authored-by: triphora <emma@modrinth.com>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-12-30 14:40:00 -07:00
Geometrically
34af33607b Update file restrictions, download counting, project colors, etc (#515)
* Update file restrictions, download counting, project colors, etc

* Run fmt + clippy + prepare
2022-12-30 13:56:41 -07:00
Geometrically
5bb188a822 Project Colors (#512)
* Inital tests

* Finish project colors

* Run fmt + clippy + prepare

* Fix dp+rp fmting
2022-12-29 17:20:50 -07:00
Geometrically
60bb6f105d Nullable file types (#514) 2022-12-29 09:32:46 -07:00
Geometrically
df4680ee09 Fix data pack webhook (#513) 2022-12-29 09:13:39 -07:00
Geometrically
6aaab09601 Fix file type setting (#510)
Co-authored-by: triphora <emmaffle@modrinth.com>
2022-12-26 13:21:37 -07:00
Geometrically
5da42575fd Add validator for file types + datapacks (#507)
* Add validator for file types + datapacks

* Make it compile
2022-12-23 19:49:47 -07:00
Geometrically
fe256d6a62 File types (#506)
* File types

* Run prepare + fmt

* Switch to struct

* Update docker version
2022-12-23 16:36:53 -07:00
triphora
5f175141e1 Improve error messages for report with invalid input (#505) 2022-12-23 15:24:45 -05:00
triphora
983e2df065 Miscellaneous improvements and removals (#502) 2022-12-23 15:19:15 -05:00
triphora
16d5a70c08 Add ordering to categories, gallery images, and team members (#501) 2022-12-23 14:34:04 -05:00
Jai A
b7e2d7fb8e Update copyright 2022-12-22 20:14:26 -07:00
Jai A
fb5f7a336d bump rust version in container (2) 2022-12-22 20:12:05 -07:00
Jai A
78dc5f4bf4 bump rust version in container 2022-12-22 20:11:22 -07:00
Jai A
45dbf5393f Bump versions + switch AWS/S3 library 2022-12-22 19:01:41 -07:00
Geometrically
9fed1cde25 Fix webhook again (#499) 2022-12-09 12:29:16 -07:00
Geometrically
5c7b175e90 Webhook update (#498)
* Update webhook

* Run clippy
2022-12-08 19:44:46 -07:00
Geometrically
30b29de8ce Fix statuses again (#497)
* Fix statuses again

* Make it compile
2022-12-08 17:25:24 -07:00
Geometrically
a5f9331023 Fix hashes not showing (#496)
* Fix hashes not showing

* Run prepare + fmt
2022-12-08 15:42:59 -07:00
Geometrically
d8b9d8431e Shader fixes (#495)
* Shader fixes

* Add core shaders validator

* Update validator again

* Rename shaders

* Fix build
2022-12-08 15:13:01 -07:00
Geometrically
91a2ce2b3f Switch out references of 'TO_JSONB' (#494) 2022-12-07 23:30:41 -07:00
Geometrically
4da1871567 Public Webhook Fixes (#493)
* Public discord webhook

* Switch to jsonb for most queries + make gallery featured first

* Run fmt + clippy + prepare
2022-12-07 09:56:53 -07:00
Geometrically
e809f77461 Public discord webhook (#492) 2022-12-06 19:51:03 -07:00
Geometrically
e96d23cc3f Initial work on new status sys + scheduling releases (#489)
* Initial work on new status sys + scheduling releases

* Finish project statuses + begin work on version statuses

* Finish version statuses

* Regenerate prepare

* Run fmt + clippy
2022-12-06 09:14:52 -07:00
Geometrically
c34e2ab3e1 Fix team member splits being set to 0 by default (#490) 2022-12-02 10:32:17 -07:00
triphora
820519b4f7 Move to SPDX licenses (#449)
* Move to SPDX licenses

Found a way to do this without breaking API compat, so here it is, instead of waiting for v3

Resolves MOD-129
Resolves #396

* License URL updates

* what was I thinking

* Do a thing

* Add open source filter

* Remove dead imports

* Borrow

* Update 20220910132835_spdx-licenses.sql

* Add license text route

* Update migration

* Address comments
2022-11-29 21:53:24 -07:00
triphora
34688852a4 Remove redundant files (#488) 2022-11-26 19:11:56 -07:00
Geometrically
151f28081a [skip ci] Add security notice (#486)
* [skip ci] Add security notice

* Fix grammar
2022-11-24 22:13:31 -07:00
Geometrically
213a64b1ff FlameAnvil fixes (#484) 2022-11-21 18:59:21 -07:00
Geometrically
f259d81249 FlameAnvil Project Sync (#481)
* FlameAnvil Project Sync

* Perm fixes

* Fix compile

* Fix clippy + run prepare
2022-11-20 19:50:14 -07:00
triphora
589761bfd9 Statistics route (#453)
* Statistics route

Staging: https://i.imgur.com/YWx9uPA.png

* Remove users

* Address comment
2022-11-20 15:35:22 -07:00
triphora
18fde86a20 Fix #464 (#467)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-11-19 17:54:56 -07:00
Tom Martin
ba28bc94d3 Fix all default clippy warnings (#480)
All trivial, fixes were for:
 - #[deny(clippy::if_same_then_else)]
 - #[warn(clippy::explicit_auto_deref)]
 - #[warn(clippy::bool_assert_comparison)]

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-11-17 10:09:53 -07:00
Geometrically
da19a07943 Payouts fees changes (#478)
* Payouts fees changes

* Update src/queue/payouts.rs

Co-authored-by: triphora <emmaffle@modrinth.com>

Co-authored-by: triphora <emmaffle@modrinth.com>
2022-11-16 13:46:36 -07:00
Geometrically
ecc500fc91 Fix payouts bug with decimals (#477) 2022-11-11 10:04:46 -07:00
Geometrically
c22ac1e60a Support unenrolling from payouts (#476) 2022-11-09 16:01:10 -07:00
Geometrically
55d9aa2a4c Allow owner payout split to be edited (#475) 2022-11-08 19:15:30 -07:00
Geometrically
1d391e68e5 Better ser/deser for payouts vals (#474) 2022-11-08 14:14:07 -07:00
Geometrically
0429c44d18 Fix payouts conditions (#473)
* Fix payouts conditions

* Make it build
2022-11-07 20:17:44 -07:00
Geometrically
2c1bcaafc1 Use auto payments with paypal (#472)
* Use auto payments with paypal

* Remove sandbox key
2022-11-07 15:38:25 -07:00
Geometrically
35891c74cd Final fixes payouts (#471)
* Final fixes payouts

* add minimum payout
2022-11-01 09:53:43 -07:00
Geometrically
2ca6e67b37 Payouts finish (#470)
* Almost done

* More work on midas

* Finish payouts backend

* Update Cargo.lock

* Run fmt + prepare
2022-10-30 23:34:56 -07:00
Geometrically
6e72be54cb R2 impl (#466)
* Add Cloudflare R2 impl

* Bump actix version

* Fix sec issues
2022-10-22 21:23:31 -07:00
Geometrically
07edb998e4 Fix integration with backblaze API (#461)
* Fix integration with backblaze API

* Remove keys (already reset, dw)
2022-10-09 19:10:06 -07:00
Geometrically
3e52f804a7 More reasonable length restrictions (#458) 2022-09-26 18:09:50 -07:00
Geometrically
75b7583832 Increase dependency limit (#454) 2022-09-21 21:27:02 -07:00
wafflecoffee
d754eb74f7 Ignore any dependencies set manually for modpacks (#433)
* Ignore any dependencies set manually for modpacks

* actually build
2022-09-17 13:17:32 -07:00
wafflecoffee
60252267d5 Add slug to searchable attributes and add project_id to facets (#447)
Fixes #358

Adding project_id to the facets would allow the inclusion or exclusion of individual projects from search. For example, this would allow people to be able to exclude projects which they've already followed or are not interested in. My personal vision for this is to merge the [followed projects page](https://modrinth.com/settings/follows) into search itself.
2022-09-05 21:39:50 -07:00
Geometrically
b25af641e2 Fix gallery date display (#445)
* Fix gallery date display

* Fix approved date not setting
2022-09-04 13:37:11 -07:00
Geometrically
e7c3f8bf47 Initial work on payouts (badges, perms, splits) (#440)
* Initial work on payouts (badges, perms, splits)

* Fix clippy error, bitflag consistency
2022-09-02 12:38:58 -07:00
wafflecoffee
4c1dca73c4 Replace remaining icon.ext image paths with hash (#435)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-09-02 12:19:39 -07:00
mooz
0bbb6b91fe Add plugin.yml support for bungeecord & waterfall (#438) 2022-09-02 08:39:58 -07:00
wafflecoffee
ee93d9b495 Sort version files and mods on user profile (#432)
* ???

* Moardering fixes

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-08-28 21:54:42 -07:00
Geometrically
bf8ac214a1 Attempt to fix multipart errors (#436) 2022-08-27 15:18:25 -07:00
Geometrically
9c7b34d5e6 Apply additional categories fix to search indexing (#428)
* Apply additional categories fix to search indexing

* fix edit version validator
2022-08-20 22:34:00 -07:00
Geometrically
76c0fa2fe2 Remove database requirement (#427) 2022-08-17 22:06:28 -07:00
Geometrically
ac3a17b178 Fix plugin validator, fix version urls, clippy lints, additional categories (#421) 2022-08-16 17:42:04 -07:00
Geometrically
c76b527b93 Make maven support duplicate versions (#418) 2022-08-13 18:53:12 -07:00
Geometrically
ded4f95537 Fix additional category editing (#417) 2022-08-12 20:48:01 -07:00
Geometrically
8272386733 Fix approved value setting (#415) 2022-08-07 09:21:49 -07:00
Geometrically
33988ed3fb Allow duplicate version numbers, fix version sorting, edit validators (#414) 2022-08-06 17:44:16 -07:00
wafflecoffee
411b8e3cb6 Initial work on site moderation improvements (#410) 2022-08-02 23:31:56 -07:00
Geometrically
916da16523 Fix filters (#389) 2022-08-01 21:23:13 -07:00
Geometrically
d165c081f7 Fix API breakage project creation (#409) 2022-07-31 21:54:17 -07:00
Geometrically
992de7d66e Fix search not working (#408) 2022-07-31 15:26:25 -07:00
Geometrically
46ab7bbcbe Fix category query (#407) 2022-07-31 14:42:22 -07:00
Geometrically
b04bced37f More project data (#406)
* More project data

* Array_agg fixes + cleanup

* fix prepare

* Add approval dates to search

* Update migrations/20220725204351_more-project-data.sql

Co-authored-by: wafflecoffee <emmaffle@modrinth.com>

* Add category labels + display categories

Co-authored-by: wafflecoffee <emmaffle@modrinth.com>
2022-07-31 13:29:20 -07:00
Ricky12Awesome
13335cadc6 Adds /teams route (#373)
* basic list (no grouping yet)

* now groups and checks auth, moved Team::get_many to TeamMember::get_from_team_full_many

* Ran 'cargo sqlx prepare'

* batch TeamMember::get_from_user_id

* Batches before for loop

* Ran 'cargo sqlx prepare'

Co-authored-by: Emma Cypress <emmaffle@modrinth.com>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-07-26 22:40:20 -07:00
wafflecoffee
b864791fa6 Limit 'superuser' status of current moderators (#386)
Resolves MOD-88

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-07-23 18:47:32 -07:00
Emma Cypress ⚘
6614b56298 Revert "Add auto-reporting inappropriate text content" (#397)
* Revert "Add auto-reporting inappropriate text content (#387)"

This reverts commit 68f7dc9512.

* Maybe don't revert the whole thing
2022-07-10 10:02:41 -07:00
Emma Cypress ⚘
02c3894fc9 Add even more validators (#385)
* Add even more validators

I was gonna add shaderpacks too, but those have no standard metadata file at all.

* Make it compile

* Fix logic

* Update validators

* fix mistake

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-07-09 20:25:44 -07:00
Emma Cypress ⚘
68f7dc9512 Add auto-reporting inappropriate text content (#387)
* Add initial support for blocking inappropriate text content

To make something clear, **nothing** is automatically censored or
deleted as a result of this pull request. This pull request is
meant to add two things:
- Regenerate new IDs (project, version, user, etc.) with profanity
- Send reports to the moderators for new inappropriate content

* Make it build

* Fix logic issue

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-07-09 18:51:55 -07:00
venashial
18d1bc56fd Fix HTTP status code 200 -> 204 (#394) 2022-07-08 13:18:41 -07:00
Geometrically
1e4d07a52c Download counting (#388) 2022-07-01 19:31:37 -07:00
Geometrically
1fc579e907 Add project check route (#384)
* Add project check route

* Fix responsee
2022-06-28 14:36:47 -07:00
Danielle Hutzley
827c4e31ee fixup! Migrate to piston-meta 2022-06-27 13:59:06 -07:00
Danielle Hutzley
c2a1ed926e Migrate to piston-meta 2022-06-27 13:54:09 -07:00
Geometrically
4f86c117c3 Merge pull request #2 from modrinth/feature/bincode
Add Bincode support
2022-06-26 18:41:00 -07:00
Danielle Hutzley
93817ba92f Actually compile without Bincode 2022-06-26 16:41:22 -07:00
Danielle Hutzley
18153e0fcc Bump version 2022-06-26 16:23:57 -07:00
Danielle Hutzley
3123f6444f Add Bincode feature for efficient binary storage 2022-06-26 16:23:41 -07:00
Geometrically
4e97a3b3d5 More project type validators (#383) 2022-06-26 10:39:38 -07:00
Emma Cypress
134c43ad9e Always return the username as fetched from the database (#382)
Co-authored-by: Patrick <cryne@gmx.de>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-06-25 22:04:34 -07:00
John Paul
e74b4b35b9 Don't consider a user's name taken by self (#376)
* Don't consider a user's name taken if self

* Fix incorrect types

* try-use more idiomatic Option tech

* true if `None`

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-06-25 21:50:41 -07:00
Emma Cypress
16e7194dfe Add Embedded dependency type (#380)
* Add Embedded dependency type

I couldn't find any SQL tables or anything for dependency types, so I'm going to assume there aren't any

* Make modpacks use Embedded instead of Required

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-06-25 19:16:46 -07:00
Emma Cypress
932b0ccf24 Allow archived projects to show in search (#381)
Resolves MOD-96
2022-06-24 14:26:18 -07:00
Emma Cypress
3e5c7f62d0 Update Mojang meta URL (#379)
Urgent, needs to be on prod before the next snapshot comes out
2022-06-22 12:22:19 -07:00
Geometrically
bf19f5b9c0 Fix auth URL condition again (#378) 2022-06-19 17:20:21 -07:00
Geometrically
08a879bbb1 Fix auth URL condition (#377) 2022-06-19 14:41:41 -07:00
Geometrically
cd514285d9 Fix rejected files showing in hash routes (#375)
* Fix rejected files showing in hash routes

* Run prepare and formatter

* Add modrinth.com exception for callback URLs

* run fmt
2022-06-18 14:09:37 -07:00
Geometrically
782bb11894 Secure auth route, fix quilt deps bug, optimize queries more (#374)
* Secure auth route, fix quilt deps bug, optimize queries more

* Add to_lowercase for multiple hashes functions
2022-06-17 16:56:28 -07:00
Geometrically
355689ed19 Replace ignore IP system with keys (#368) 2022-06-09 15:28:40 -07:00
Geometrically
75614fb13c Move downloads to queue for better performance (#367) 2022-06-09 12:21:51 -07:00
Geometrically
5c4a864680 Fix dep out of bounds error (#366) 2022-06-08 23:03:22 -07:00
Geometrically
eaeff891d6 Reimplement old database code for better performance (#365) 2022-06-08 22:24:20 -07:00
Geometrically
f0ab40d748 Fix update route (#364)
* Fix version updates route

* Run formatter, fix clippy, run prepare
2022-06-08 21:17:17 -07:00
Geometrically
e497af4c26 Add deps list for override mods, fix version editing for packs (#363) 2022-06-05 10:42:33 -07:00
Geometrically
f860f57363 Fix version editing (#362) 2022-06-02 20:56:12 -07:00
Geometrically
02bf5ada89 Increase file size limit (#361) 2022-05-30 16:29:23 -07:00
4JX
d3b578fe8f Fix wrongfully parsed hashes for the version_files route(s) (#353)
* Fix wrongfully parsed hashes for the version_files route(s)

* Remove the hex dependency

* Remove unwraps

.

.

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-05-26 18:32:32 -07:00
Geometrically
d29d910ac6 Add mod lists for modpacks, liteloader support, update actix, fix moderation webhook (#357) 2022-05-26 10:08:19 -07:00
Jai A
e7b41f9a4c Urgent fixes 2022-05-15 12:05:44 -07:00
Geometrically
dd0aed4614 Bump version (#348) 2022-05-15 09:27:02 -07:00
Geometrically
b9b4f2bb7f Fix download count logic (#347) 2022-05-14 15:55:02 -07:00
Geometrically
3533d2a2cc Fix search rules, register download route (#346) 2022-05-11 18:09:29 -04:00
Geometrically
26d9ef5398 Rework download route (#345) 2022-05-11 11:32:01 -04:00
Geometrically
a0f840bcf8 Add quilt validators, Deps fix, Fix slug collisions (#338) 2022-04-24 13:02:41 -07:00
stairman06
33d2a77e37 Maven fix (#337) 2022-04-24 10:55:56 -07:00
Geometrically
80e00a80d5 Switch to time crate, add file sizes (#329)
* Switch to time crate, add file sizes

* Update deps, adjust pack format

* Run formatter, fix clippy
2022-03-29 19:35:09 -07:00
Geometrically
a3d5479878 Optimize DB pooling (#328) 2022-03-28 19:39:02 -07:00
Jai A
a49dc04f5d Add download set check 2022-03-27 19:14:39 -07:00
Geometrically
d1c0c9739d Shulkers of fixes (#327)
* Shulkers of fixes

* Fix validation message

* Update deps

* Bump docker image version
2022-03-27 19:12:42 -07:00
Emma C. Pointer-Null
7415b07586 Add more version creation data aliases (#325)
For consistency and also for making the OpenAPI spec a bit less janky
2022-03-24 19:58:07 -07:00
Geometrically
023663b268 Fix permissions checks for projects, fix gallery URLs (#321) 2022-03-16 07:49:09 -07:00
Geometrically
3883c509b9 Bypass compile-time query check (#317) 2022-03-06 21:06:29 -07:00
Geometrically
18f34b4f83 Fix dep route again (#316) 2022-03-05 13:09:19 -07:00
Jai A
caed86d846 Fix filenames, dep route again 2022-02-28 16:00:12 -07:00
Geometrically
459e36c027 Run fmt, fix dep route (#312) 2022-02-27 21:44:00 -07:00
Geometrically
725f8571bb Fix deps, download URLs, remove duplicate deps (#310) 2022-02-26 21:16:11 -07:00
Danielle
b7c7c0e862 Fix download counting (#309) 2022-02-26 08:37:24 -07:00
Danielle
3f671b918a Move download counting to worker (#306)
* Move download counting to worker

* Run `cargo sqlx prepare`

* Format & some Clippy fixes
2022-02-21 19:57:40 -07:00
Geometrically
9492363b22 Fix uploading (#305)
* Upgrade rust-s3 to fix tokio panics

* Run fmt

* Update deps
2022-02-20 20:16:32 -07:00
BasiqueEvangelist
3ee144459f Allow setting primary file when creating version (#304)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-02-20 19:44:59 -07:00
Geometrically
c0c80c0fdf See if RateLimit is cors issue (#303) 2022-02-19 17:51:24 -07:00
ramidzkh
7c80b61666 Automatically generate updates.json for Forge mods (#298)
* Automatically generate updates.json for Forge mods

https://api.modrinth.com/updates/{id}/forge_updates.json serves a minimal update JSON for the Forge update checker

Closes #281

* Authenticate update JSON requests

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-02-19 13:09:09 -07:00
Geometrically
d128f3e14e Send CORS wildcard, allow editing of non-accepted team members (#299) 2022-02-15 13:09:10 -07:00
Geometrically
4498b89ac4 Fix lax cors configuration (#295) 2022-02-12 19:57:00 -07:00
Geometrically
e576a58ead Fix error messages, auth routes, and remove category unique constraint (#293)
* Fix error messages, auth routes, and remove category unique constraint

* Run prepare

* Remove debug secrets

* Fix team member editing
2022-02-10 10:56:45 -07:00
Geometrically
eb4375258e Fix routing errors 2 + Version File Response (#290) 2022-02-06 17:54:01 -07:00
Geometrically
0cbc2001e2 Fix routing errors] (#289) 2022-02-06 16:35:52 -07:00
Geometrically
6bf5dbabee Upgrade to Actix V2, bump SQLX version, code cleanup, intergrate ratelimiter (#288)
* Upgrade to Actix V2, bump SQLX version, code cleanup, intergrate ratelimiter

* Add pack file path validation

* Fix compilation error caused by incorrect merge
2022-02-05 23:08:30 -07:00
BasiqueEvangelist
6a89646e66 Support other project types in maven (#284) 2022-02-05 22:19:02 -07:00
Emma
f3234a6b5e fix v1 parity issue: local- removed from mod_id field (#283) 2022-01-28 16:04:34 -07:00
Geometrically
73a8c302e9 Fix duplicate dates (#282) 2022-01-25 13:00:32 -07:00
BasiqueEvangelist
989f2d3001 Add support for hashes in Maven (#264)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2022-01-25 10:57:45 -07:00
Geometrically
2badcfa546 Fix dependency updates and creating versions with no game versions/loaders (#280) 2022-01-24 11:27:39 -07:00
Geometrically
384e14b32d Fix pack URL validation, Version file update route, and spaces in file download URLs (#275) 2022-01-09 15:35:01 -07:00
Geometrically
016e743653 Fix version deletion (#273) 2021-12-29 22:46:37 -05:00
Jai A
00adf3631a Remove replacing assets index URL in manifest to preserve file integrity 2021-12-20 09:11:40 -07:00
Jai A
09aef18999 Fix incorrect condition for forge and incorrect fabric loader version ordering 2021-12-19 20:51:34 -07:00
Jai A
1d86aac338 Fix java version not being optional 2021-12-19 19:56:56 -07:00
Jai A
80173634a0 Fix java version specifier being in snake case 2021-12-19 19:04:42 -07:00
Jai A
c9598b674c Fix forge erroring out due to wonky version 2021-12-19 18:52:00 -07:00
Jai A
2a588d1e9a Make java stuff public, fix forge erroring out due to ratelimiting 2021-12-19 15:09:36 -07:00
Jai A
5a6c06c8a3 Host all loaders for forge, fix stable markers, add java version to daedalus 2021-12-18 22:55:03 -07:00
Geometrically
b2ef4e9619 Fix modpack env field being required (#270) 2021-12-14 22:25:46 -07:00
Geometrically
9e9d6e45b4 Set pack format content type (#269) 2021-12-14 21:07:43 -07:00
Geometrically
6752457ad8 Pack format changes (#268) 2021-12-14 18:08:53 -07:00
Geometrically
ddcb5cd4d3 Fix game version ordering, fix deleting versions with deps not working (#265) 2021-12-09 18:12:21 -07:00
Geometrically
a54b2db81b Fix gallery creation validation and validators returning incorrect er… (#263)
* Fix gallery creation validation and validators returning incorrect errors

* Remove docker image

* Add URL validation for pack files

* Remove unneeded dependencies
2021-11-30 20:07:23 -07:00
Geometrically
6740124364 Fix search returning incorrect ownership information (#261) 2021-11-24 13:20:25 -07:00
Geometrically
157731e4f8 Remove package lists to preserve space (#260) 2021-11-14 17:49:55 -07:00
Geometrically
2dd1496ef4 Fix HTTPS requests not working in the image (#259) 2021-11-14 16:27:33 -07:00
Geometrically
d1e4e72693 Switch docker image OS (#258) 2021-11-14 14:36:01 -07:00
Geometrically
77e8143290 Fix transferring ownership (#256) 2021-11-13 16:35:21 -07:00
Geometrically
7f791d4919 Move validators to seperate thread, other fixes (#253)
* Move validators to seperate thread, other fixes

* Update rust version in Dockerfile

* Fix notifs not working

* Fix pack validator not enforcing files
2021-11-13 15:46:08 -07:00
Jai A
d7e0468776 Fix library URL being set incorrectly 2021-11-10 18:40:12 -07:00
Jai A
f6c611bbba Add classpath variable for libraries 2021-11-10 17:34:53 -07:00
Jai A
0990ac4fc1 Add forge data to main version info 2021-11-08 20:17:20 -07:00
Jai A
e91f8f693b Add local libs to modrinth maven and other fixes 2021-11-07 18:42:33 -07:00
Jai A
2a7dbda133 Bump version + Fix partial version and full version argument joining 2021-11-03 17:49:58 -07:00
Jai A
793e542312 Bump version 2021-11-02 21:44:59 -07:00
Jai A
240269eb25 Fix lib not parsing maven file extensions 2021-11-02 21:44:31 -07:00
Jai A
c744dc8cc3 Bump library version 2021-11-02 20:25:55 -07:00
Jai A
d596bdb454 Fix import errors 2021-11-02 20:24:51 -07:00
Jai A
bec54b4283 Fix action title 2021-11-02 20:20:55 -07:00
Jai A
061b88f5b5 Fix docker action again 2021-11-02 20:19:43 -07:00
Jai A
8704eff632 Fix docker action 2021-11-02 20:17:12 -07:00
Jai A
fb16f25b07 Fixes in forge universal lib + other things 2021-11-02 19:59:10 -07:00
Jai A
e8057a5c8a Fix incorrect docker registry 2021-10-24 16:22:02 -07:00
Geometrically
3c5edb6171 Delete clippy.yml~ 2021-10-24 16:18:17 -07:00
Jai A
e36a191240 Fix incorrect file names (again) 2021-10-24 16:16:49 -07:00
Jai A
ecdfd65f50 Fix incorrect file names 2021-10-24 16:16:23 -07:00
Jai A
4294081abb Add GitHub actions again 2021-10-24 16:13:04 -07:00
Jai A
5218543c58 Add GitHub Actions 2021-10-24 16:10:38 -07:00
Jai A
d8332a27e5 Finish newer forge versions 2021-10-24 14:25:24 -07:00
Jai A
673658dfd2 Simplify mod loader manifests, start work on new forge profiles 2021-10-19 23:08:44 -07:00
Jai A
6528d3d7da Legacy Forge Support (forgot to git add) 2021-10-17 23:23:27 -07:00
Jai A
16af479b83 Legacy Forge Support 2021-10-17 23:22:23 -07:00
Leo Chen
13187de97d Rustic cleanups, dedups and making the code less hard to read in general (#251)
* typos :help_me:

* (part 1/?) massive cleanup to make the code more Rust-ic and cut down heap allocations.

* (part 2/?) massive cleanup to make the code more Rust-ic and cut down heap allocations.

* (part 3/?) cut down some pretty major heap allocations here - more Bytes and BytesMuts, less Vec<u8>s

also I don't really understand why you need to `to_vec` when you don't really use it again afterwards

* (part 4/?) deduplicate error handling in backblaze logic

* (part 5/?) fixes, cleanups, refactors, and reformatting

* (part 6/?) cleanups and refactors

* remove loads of `as_str` in types that already are `Display`

* Revert "remove loads of `as_str` in types that already are `Display`"

This reverts commit 4f974310cfb167ceba03001d81388db4f0fbb509.

* reformat and move routes util to the util module

* use streams

* Run prepare + formatting issues

Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2021-10-11 20:26:59 -07:00
Jai A
32850f6770 Fabric support 2021-10-09 14:04:45 -07:00
Jai A
4a7f4bde4a Working mirroring of minecraft metadata 2021-10-05 22:52:17 -07:00
Kir_Antipov
0010119440 Maven repo should return primary file by default (#252)
* Maven repo should return primary file by default

* Added fallback for versions that don't have a primary file
2021-10-02 16:10:16 -07:00
Emma
91065a6168 [no ci] Remove contributing file and instead direct people to docs (#248)
This fixes a long standing issue where people would point to the frontend homepage where it says 'fully documented' then point to the README here and say 'well that's sure hypocritical'

Thus the branch name :tiny_pumpkin:

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2021-09-26 15:41:10 -07:00
Redblueflame
efa8d5c575 Added monitoring, limited concurent connections (#245)
* reduced the default, and added environment override.

* Using parse is more stable and doesn't fail CI this time :P

* Added support for monitoring
This support is currently basic, but it can be improved later down the road.

* Forgot scheduler file

* Added health check

* Cargo fix

* Update cargo.lock to avoid action fails.

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2021-09-20 21:26:16 -07:00
Geometrically
04998d0215 Fix gallery validation (again) (#247)
* Remove accidental URL validation for gallery

* Remove accidental URL validation for gallery
2021-09-02 22:39:29 -07:00
Geometrically
d0efa5d3fe Remove accidental URL validation for gallery (#246) 2021-09-01 21:19:49 -07:00
Geometrically
c87e72e08e Switch to alternate query strategy which simplifies code (#244) 2021-09-01 06:04:38 -07:00
Geometrically
efb82847cb Switch to ARRAY_AGG for database aggregations to improve peformance + fix gallery images not showing up (#242) 2021-08-31 15:29:51 -07:00
Geometrically
f37e267a5e Make gallery item featuring exclusive (#241)
* Make gallery featured value exclusive

* Run prepare
2021-08-29 15:18:45 -07:00
Geometrically
69928219a3 Fix project creation hash lookups failing (#239) 2021-08-25 23:18:44 -07:00
Geometrically
fdf8845a2f Fix validators (#237)
* Fix file extension checks not working

* Fix validators not validating files of a non-matching extension
2021-08-22 09:11:38 -07:00
Geometrically
4073a7abc3 Force files to be unique, require all new versions to have at least one file (#236) 2021-08-21 19:38:32 -07:00
Geometrically
ffd9a34cf5 Query optimization (#235)
* Optimize version queries and decrease some query complexity

* Run formatter
2021-08-20 16:33:09 -07:00
Johan Novak
07226c6d21 Fix Docs Link (#233)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2021-08-14 12:39:06 -07:00
Geometrically
b1bc7c1fc2 Add fields to gallery items (#234) 2021-08-05 22:01:26 -07:00
Geometrically
1b33f0cea9 Fix multiple projects query taking seconds to complete (#232) 2021-08-01 19:55:36 -07:00
Geometrically
8ece3b00f5 Fix project dependencies returning invalid values (#231)
* Fix project dependencies returning invalid values

* Run prepare
2021-08-01 16:11:07 -07:00
Geometrically
c9c58b65a6 Optimize dependencies route and change return value (#230) 2021-07-31 19:23:06 -07:00
Geometrically
66becbc4cc Fix dependencies route only showing one dependency per version (#229) 2021-07-28 11:34:47 -07:00
Geometrically
5b8612c919 Fix dependency route having incorrect return value (#228) 2021-07-27 18:48:56 -07:00
Geometrically
430c22e06e Add gallery parameter to meilisearch attributes (#227) 2021-07-27 18:10:40 -07:00
Geometrically
76b62eda3a Allow gallery featuring, add gallery images to search, rename rejection reasons, transfer ownership route (#226) 2021-07-27 16:50:07 -07:00
venashial
bc983162f3 Detect if redirect url contains a query string (#225) 2021-07-27 16:31:41 -07:00
Geometrically
4922598aee Add gallery item deletion + making them optional (#224) 2021-07-21 09:54:29 -07:00
Geometrically
b2f8bb9990 Fix panic on requesting projects (#223) 2021-07-19 14:47:14 -07:00
Geometrically
9ee92fb9e9 Project gallery, webhook fixes, remove cache, re-enable donation URLs (#222) 2021-07-19 11:30:39 -07:00
Geometrically
981bf1d56f Fix caching bug, and moderation webhook being sent at the wrong time (#215) 2021-06-19 22:01:11 -07:00
Geometrically
d2c2503cfa Final V2 Changes (#212)
* Redo dependencies, add rejection reasons, make notifications more readable

* Fix errors, add dependency route, finish PR

* Fix clippy errors
2021-06-16 09:05:35 -07:00
Geometrically
2a4caa856e More APIv2 Fixes (#210)
* Refactor search to not spam the database with queries, new utility routes for V2

* Run prepare

* More V2 Fixes

* Run prepare + formatter
2021-06-05 20:59:21 -07:00
Geometrically
157962e42a Improve peformance of search indexing, v2 fixes + new routes (#205)
* Refactor search to not spam the database with queries, new utility routes for V2

* Run prepare
2021-06-02 18:33:11 -07:00
Geometrically
16db28060c Project Types, Code Cleanup, and Rename Mods -> Projects (#192)
* Initial work for modpacks and project types

* Code cleanup, fix some issues

* Username route getting, remove pointless tests

* Base validator types + fixes

* Fix strange IML generation

* Multiple hash requests for version files

* Fix docker build (hopefully)

* Legacy routes

* Finish validator architecture

* Update rust version in dockerfile

* Added caching and fixed typo (#203)

* Added caching and fixed typo

* Fixed clippy error

* Removed log for cache

* Add final validators, fix how loaders are handled and add icons to tags

* Fix search module

* Fix parts of legacy API not working

Co-authored-by: Redblueflame <contact@redblueflame.com>
2021-05-30 15:02:07 -07:00
Mysterious_Dev
712424c339 Add alphabetically sorting (#190) 2021-04-24 19:44:11 -07:00
BasiqueEvangelist
15c56dfcb8 Maven endpoint support (#180)
* Basic maven endpoint

* Clean up maven endpoint

* cargo sqlx prepare

* Minor cleanup

* Remove indentation

* Borrow &str instead of &String

* Refactor mod_data-getting
2021-03-29 10:36:55 +02:00
Redblueflame
b98ad47618 Fixed spacing (#181)
Co-authored-by: 0SoggyMustache0 <george@georgekazan.dev>

Co-authored-by: 0SoggyMustache0 <george@georgekazan.dev>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2021-03-28 22:30:56 -07:00
Redblueflame
4778c5f5e8 Github container repository support (#183)
* Add GHCR support

* Add layer caching
2021-03-28 14:01:19 -07:00
MulverineX
d041671dc5 Update banner URL & Fix spelling (#105)
* Update banner URL

From https://gist.github.com/MulverineX/6f0a27bc67692f14567816ce1c3f7710 instead of a discord attachment

* Fix spelling of proper laboratory

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Redblueflame <contact@redblueflame.com>
2021-03-28 12:40:21 -07:00
Geometrically
5cab65d197 Fix #178 (#179) 2021-03-26 22:10:30 -07:00
Jai A
b5bf627fb1 Fix ID serialization being broken on report create route 2021-03-11 15:52:38 -07:00
Jai A
6104150b77 Fix users not being able to see their own unapproved mods 2021-03-11 10:32:47 -07:00
Jai A
a13bae2f39 Fix report create route 2021-03-10 20:20:37 -07:00
Jai A
fd80e98207 Register report type routes 2021-03-09 21:18:04 -07:00
Jai A
f43b95f001 Fix primary files not showing when requesting multiple versions 2021-03-09 13:40:19 -07:00
Geometrically
38802d3522 Fix primary files, file deletion, checks for mod following, fix user following route (#175) 2021-03-08 12:52:48 -07:00
Jai A
9f7813622d Fix team invites 2021-03-07 20:46:04 -07:00
Jai A
75d67207aa Register users notification route 2021-03-07 18:53:38 -07:00
Jai A
e596a8f731 Fix search 2021-03-07 18:44:24 -07:00
Jai A
5b0cc73792 Change server side and client side fields to optionals 2021-03-07 17:52:04 -07:00
Jai A
2163d4465f Fix license field not working: 2021-03-07 17:07:32 -07:00
Jai A
8becf45714 Fix broken search 2021-03-07 16:39:47 -07:00
Jai A
853ead26ca Register notification routes, add action method for notifications, and fix auto-featuring versions 2021-03-06 13:47:49 -07:00
Geometrically
0ccb6cb873 Follows (#172)
* Follows initial

* Fix #171, Fix #170, Fix #169, Fix #164

* More work on follows

* Fix compile error

* Upgrade meili version, add follows to search
2021-03-04 20:35:23 -07:00
BasiqueEvangelist
e46ff3de8b Add slug validation (#168) 2021-02-23 08:04:10 -07:00
Geometrically
a02e08a879 Fix featured versions sorting (#166)
* Reports WIP

* Finish reports

* Clippy fixes

* Fix featured versions sorting

Co-authored-by: Geometrically <geometrically@pop-os.localdomain>
2021-02-13 22:38:00 -07:00
Geometrically
109d7d87bd Reports (#165)
* Reports WIP

* Finish reports

* Clippy fixes

Co-authored-by: Geometrically <geometrically@pop-os.localdomain>
2021-02-13 12:11:13 -07:00
Geometrically
3df740702c Fix version hash (#154) 2021-01-30 16:51:32 -07:00
Geometrically
06bb6f7bff Dependencies, fix panic on version get, Filtered Versions route (#153) 2021-01-29 20:35:29 -07:00
Geometrically
e33738a876 Fix queries, also add error message for database (#152) 2021-01-28 14:36:53 -07:00
Geometrically
f887f5dca3 Return database error so I can actually debug this 2021-01-28 11:15:29 -07:00
Geometrically
257c16690a Merge pull request #151 from modrinth/fix-duplicates
Fix some mods and versions 'dissapearing'
2021-01-28 09:36:23 -07:00
Geometrically
7114e88992 Merge branch 'master' into fix-duplicates 2021-01-28 09:08:08 -07:00
Geometrically
de7e869ca9 Fix some mods and versions 'dissapearing' 2021-01-28 09:07:10 -07:00
Geometrically
eaee9c9522 Merge pull request #150 from modrinth/fix-duplicates
Fix several bugs
2021-01-27 16:01:03 -07:00
Geometrically
bee11a6d41 Fix several bugs 2021-01-27 15:43:44 -07:00
Geometrically
647e44147f Merge pull request #149 from modrinth/fix-version-get
Fix version get
2021-01-27 09:18:08 -07:00
Geometrically
4a5d46915d Fix version get 2021-01-27 08:46:38 -07:00
Geometrically
8ad19585cb Merge pull request #148 from modrinth/sentry
Sentry Integration
2021-01-27 07:27:21 -07:00
AppleTheGolden
951a33fae9 Add Sentry integration 2021-01-27 14:38:18 +01:00
Geometrically
bcf174cd1e Delete labelsync.yml 2021-01-25 15:27:04 -07:00
Geometrically
1e82909f58 Update issue templates 2021-01-25 15:23:07 -07:00
Geometrically
33e83b8414 Merge pull request #144 from modrinth/query-optimizations
Fixes
2021-01-25 14:43:43 -07:00
Geometrically
592c56cb20 Merge branch 'master' into query-optimizations 2021-01-25 14:43:36 -07:00
Geometrically
710528c01a Fixes 2021-01-25 14:42:26 -07:00
Geometrically
03cadc2604 Delete labels.yml 2021-01-25 14:35:57 -07:00
Geometrically
dd73bb95a4 Merge pull request #143 from modrinth/query-optimizations
Query Optimization
2021-01-25 14:14:42 -07:00
Geometrically
40e0a55378 Merge branch 'master' into query-optimizations 2021-01-25 13:51:48 -07:00
Geometrically
269884d9f3 Fix docker build failing 2021-01-25 13:15:32 -07:00
Geometrically
fcd548c313 Fix queries not working 2021-01-25 13:00:04 -07:00
Geometrically
140a8b6804 Fix docker image build failing 2021-01-24 22:49:28 -07:00
Geometrically
b5378c1296 Query Optimization 2021-01-24 21:33:32 -07:00
Geometrically
1445d6ea8c Merge pull request #142 from modrinth/follows
Hotfix everything broken
2021-01-23 11:49:12 -07:00
Geometrically
5385431051 make compile 2021-01-23 11:37:25 -07:00
Geometrically
f14f4498fb Hotfix everything broken 2021-01-23 11:32:32 -07:00
Geometrically
fc2786f5e8 Merge pull request #138 from modrinth/fixes
A number of fixes
2021-01-18 10:46:17 -07:00
Geometrically
174dbb5e74 Fix auth params, Add params to indexing, Order version game versions, Remove version moderation, Return donation platforms in get routes 2021-01-18 10:10:45 -07:00
Geometrically
68517c15f2 Merge pull request #136 from modrinth/small-fixes
Fix version number editing
2021-01-15 08:18:15 -07:00
Geometrically
11ee142e4b Merge branch 'master' into small-fixes 2021-01-15 07:59:39 -07:00
Geometrically
1e1d047e07 Run prepare 2021-01-15 07:48:37 -07:00
Geometrically
62f1e39e6e Fix version number editing 2021-01-15 07:45:59 -07:00
Geometrically
05756da495 Merge pull request #135 from modrinth/fix/file-upload-permissions
Fix #134
2021-01-15 07:36:43 -07:00
Mikhail Oleynikov
fa35b2a66f Fix #134 2021-01-15 16:13:08 +03:00
Geometrically
d2094e2b68 Merge pull request #132 from modrinth/move-descriptions
Bug Fixes
2021-01-14 13:41:09 -07:00
Geometrically
075b2df738 Fix lint failures 2021-01-14 12:23:39 -07:00
Geometrically
ec3c31a106 Move descriptions to database, switch to SHA-512 hashes, fix declining invites not working, allow user deletion, fix broken permission checks for many things, security fixes 2021-01-14 10:08:38 -07:00
Geometrically
e2183c2214 Merge pull request #123 from modrinth/fix-ratelimit
Bump ratelimit to 200 RPM, allow specified IPs to have lax ratelimit …
2021-01-01 11:19:14 -07:00
Geometrically
4994064e6e Fix lint 2021-01-01 09:27:37 -07:00
Geometrically
a40b9f4054 Fix clippy error (?) 2021-01-01 09:13:27 -07:00
Geometrically
ee8d65977d Fix YAML syntax error 2020-12-31 21:47:17 -07:00
Geometrically
b4ea11e55e Fix actions to use build cache 2020-12-31 19:25:19 -07:00
Geometrically
b8d2ef1eb5 Fix SQLX errors 2020-12-31 19:09:23 -07:00
Geometrically
0efeffeaa3 Bump ratelimit to 200 RPM, allow specified IPs to have lax ratelimit restrictions, and allow wildcard for CORS 2020-12-31 18:54:58 -07:00
Geometrically
7a86d272bb Merge pull request #121 from modrinth/fix-description-cache
Fix description cache
2020-12-31 10:58:40 -07:00
Geometrically
4d780904d1 Fix formatting, remove failing backblaze test due to expired token 2020-12-31 10:16:32 -07:00
Geometrically
0a6fa65075 Fix clippy 2020-12-31 10:09:57 -07:00
Geometrically
6440220640 Fix again 2020-12-29 22:25:52 -07:00
Geometrically
4350c9a72b Fix again 2020-12-29 22:19:03 -07:00
Geometrically
77feae0ab9 Merge branch 'fix-description-cache' of https://github.com/modrinth/labrinth into fix-description-cache 2020-12-29 22:12:00 -07:00
Geometrically
1261696ba2 Make it compile 2020-12-29 22:11:23 -07:00
Geometrically
6b35a0a527 Merge branch 'master' into fix-description-cache 2020-12-29 21:57:54 -07:00
Geometrically
fd33ff81c9 Fix description cache 2020-12-29 21:49:01 -07:00
Geometrically
178b1e8bb0 Merge pull request #120 from modrinth/team-member-fixes
Fix revocation of invites, allow for /user_id/teams to be useful
2020-12-28 10:51:16 -07:00
Geometrically
9d50f03cb1 Fix game version editing for versions 2020-12-27 18:58:27 -07:00
Geometrically
8c1688657a Fix revocation of invites, allow for /user_id/teams to be useful 2020-12-27 18:50:10 -07:00
Geometrically
833cb99f41 Merge pull request #119 from modrinth/team-member-fixes
Add 'accepted' field to TeamMember
2020-12-27 08:18:34 -07:00
Geometrically
bd5d84abcd Add 'accepted' field to TeamMember 2020-12-26 22:49:07 -07:00
Geometrically
d451ba9b6e Merge pull request #118 from modrinth/fix-invites
Fix invites
2020-12-26 15:21:41 -07:00
Geometrically
14bd02a569 Merge branch 'master' into fix-invites 2020-12-26 15:18:47 -07:00
Geometrically
4beace1bb0 Fix inites (again) 2020-12-26 15:17:51 -07:00
Geometrically
6996dfcd3b Fix incorrect route for team fetching (#117)
* Fix invites failing

* Fix incorrect route for team fetching
2020-12-26 12:25:27 -07:00
Geometrically
42c46d7d5c Fix incorrect route for team fetching 2020-12-26 12:20:23 -07:00
Geometrically
9b31ce83c5 Fix invites failing (#116) 2020-12-26 12:13:09 -07:00
Geometrically
cb5250527b Fix invites failing 2020-12-26 12:08:28 -07:00
Geometrically
f0b73fd696 Change index interval, add slug to search documents (#110)
* Change index interval, add slug to search documents

* Allow the removal of '@' for slug get

* Fix

* Remove name and rename side type

* Run prepare
2020-12-13 18:10:58 -07:00
Geometrically
df5684a9f8 Fix access controls (#109)
* Fix access controls

* Remove CF indexing, fix some stuff
2020-12-02 10:24:20 -07:00
Aeledfyr
b3f724c799 Hotfix: fix version delete permissions and CORS allowed methods (#107) 2020-11-30 10:45:59 -07:00
Geometrically
a7be6504a2 Fix hash (#106)
* More mod info

* Downloading mods

* Run prepare

* User editing + icon editing

* Finish

* Some fixes

* Fix clippy errors

* Fix hash lookup

* Run prepare

* Run formatter
2020-11-29 14:27:40 -07:00
Geometrically
1da5357df6 More mod info (#104)
* More mod info

* Downloading mods

* Run prepare

* User editing + icon editing

* Finish

* Some fixes

* Fix clippy errors
2020-11-27 10:57:04 -07:00
Aeledfyr
92e1847c59 Hotfix: route new moderation routes (#103) 2020-11-15 20:47:45 -07:00
Geometrically
0500994def Moderation + Mod Editing (#101)
* Moderation + Mod Editing WIP

* Run prepare, fix perms

* Make it compile

* Finish moderation and edit routes

* More fixes

* Use better queries

* Final Fixes
2020-11-15 19:58:11 -07:00
Aeledfyr
da911bfeb8 Minor fixes to orderings and permission serialization (#102)
* Fix latest_version in search results

* Handle users with invalid permissions instead of skipping them

* Specify order of some queries, fix serialization of permissions

* Run sqlx prepare
2020-11-10 09:27:36 -07:00
Geometrically
578d673a4e Team routes (#92)
* Team routes template

* More work on teams

* Updating routes WIP

* Edit routes

* Fixes

* Run prepare, prevent non-members from seeing perms

* More fixes

* Finish team routes

* More fixes

* Unpushed changes

* Some more fixes and error handling

* Fix sqlx prepare, formatting

Co-authored-by: Aeledfyr <aeledfyr@gmail.com>
2020-11-09 19:39:23 -07:00
Aeledfyr
c8e58a1e5b Fix indexing, upgrade MeiliSearch sdk (#100)
* Hotfixes for indexing

* Handles missing INDEX_CACHE_PATH environment variable
* Exits on startup if environment variables are missing. The flag
  --allow-missing-vars disables this, but that is generally a bad
  idea, since most environment variables are required (and the ones
  that aren't should be marked as such).
* Disables the query loggers

* Upgrade meilisearch-sdk to 0.4.0 for MeiliSearch 0.16 support

* Fix swap of Forge and Fabric labeling
2020-11-05 08:38:03 -07:00
Aeledfyr
d477874535 Optimize and fix some bugs in indexing (#98)
* Improve curseforge and local indexing

This should make curseforge indexing more efficient, and reuses
some of the normal local indexing for the queued indexing of
recently created mods.

* Unify impls for single and multiple routes for mods and versions

This uses the same backend for the single and multiple query
routes so that they no longer return inconsistent information.

* Cache valid curseforge mod ids to reduce request load

This caches the ids of minecraft mods and reuses them on indexing
to reduce the amount of unused addons that are returned.
2020-11-03 17:55:50 -07:00
Aeledfyr
da79386cc3 Track and sort by release date of game_versions tags (#95) 2020-10-31 21:06:47 -07:00
Aeledfyr
a4ba6d1444 Game Version types, indexing, and bugfixes (#91)
* Add types to game_versions, allow filtering by version type

- Fixes an issue with version numbers in the initial mod indexing
  queue
- Modifies the /api/v1/categories/game_versions route to take an
  optional query parameter `type` to filter the listed game versions
- Creating tags is now idempotent
- Creating game_versions now requires a JSON body that specifies
  the version type

* Implement automatic indexing of new Minecraft versions

It's currently set to run every 6 hours and isn't configurable; we
could add config for it, but it doesn't seem likely to be rate
limited or have issues with frequency.
2020-10-28 09:11:49 -07:00
Geometrically
ef28459b61 Rate limiting + version fixes (#90)
* Rate limiting + version fixes

* Move patch to proper place

* More fixes

* Fix commit hash pin
2020-10-25 13:51:07 -07:00
Geometrically
1ff8c908b8 Add way to fetch team members (#89)
* Add way to fetch team members, fix files not being returned with version route

* Make it compile

* Fixes

* Use default error handling
2020-10-21 12:30:35 -07:00
Aeledfyr
e966ef96e5 Await an unused future and change #![allow(unused)] to dead_code (#88)
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2020-10-19 22:24:40 -07:00
Geometrically
b05b38b269 Add files to initial versions/mods (#84)
* Add files to initial versions/mods

* Remove useless code, fix actual problem

* Remove debug text

* Rename body to description
2020-10-19 22:08:47 -07:00
Geometrically
8e1f1ff2e6 Fix another bug (#87) 2020-10-19 20:04:26 -07:00
Geometrically
680d6c20ca Search hotfix (#86) 2020-10-19 19:46:33 -07:00
Geometrically
c886e7949e Support for using a master key (#83)
* Support for using a master key

* Expand scope of PR, add wrapper struct, add files to intitial versions/mods

* Change changelog path, run formatter

* Split file changes into different PR

* Formatting, rename main variable

Co-authored-by: Aeledfyr <aeledfyr@gmail.com>
2020-10-19 14:23:05 -07:00
Geometrically
e0b972f6d6 Add S3 File Host (#81)
* Add S3 File Host

* Fix tests, set default ACL level to public

* Refactor

* Fix merge conflicts

* Env fixes

* Run formatter

* Remove extra allocations
2020-10-18 13:26:13 -07:00
Aeledfyr
25daa9f2da Update actix-web to 3.0, update deps (#82) 2020-10-18 10:50:37 -07:00
Aeledfyr
d0fb5c3bd5 Refactor mod creation route, add more checks (#80)
This also removes the `team_members` field of `InitialModData`, as
team members are no longer specified at mod creation.
2020-10-17 19:34:23 -07:00
Aeledfyr
520b12e56b Make mod creation always create initial versions & don't require mod id for mod creation versions (#79)
* Make mod creation always create initial versions, other fixes

* Fix sqlx prepare

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2020-10-16 13:28:53 -07:00
Geometrically
5c8ffe961e Fix local indexing (#78) 2020-10-16 11:21:07 -07:00
Aeledfyr
7983e82b60 Fix some issues with search and mod creation (#77) 2020-10-16 10:04:38 -07:00
Geometrically
77d35b61a9 Fix indexing (#76) 2020-10-15 09:56:41 -07:00
Geometrically
285a97aaf8 Creation fix (#74)
* Lots of little fixes

* Change + Add TODOs back that were incomplete

* Fix migrations

* Run prepare

* Minor fixes

* Fix formatting

* SQLX Prepare

* Add status to creation query
2020-10-14 20:43:40 -07:00
Geometrically
ad29f2477e Lots of little fixes (#73)
* Lots of little fixes

* Change + Add TODOs back that were incomplete

* Fix migrations

* Run prepare

* Minor fixes

* Fix formatting

* SQLX Prepare
2020-10-14 13:19:38 -07:00
Aeledfyr
1072d1306b Make indexing date format consistent (#72) 2020-10-12 14:08:43 -07:00
Geometrically
b8eda40937 Fix date format (#71) 2020-10-11 11:35:50 -07:00
Geometrically
2719ae5df2 Add API routes to request multiple of an item (#70)
* Change header name

* Add default bio value

* Remove default

* Make name null

* Run prepare

* Add new API Routes for requesting multiple of an item

* Run formatter

* Simplify get mods query

* Run prepare

* Refactor to use one query for most routes, change version create route to have mod_id in data

* More fixes
2020-10-05 14:25:32 -07:00
Geometrically
68ee2bdcdc Fix another GitHub OAuth Bug, allow users to register with null names. (#69)
* Change header name

* Add default bio value

* Remove default

* Make name null

* Run prepare
2020-10-03 16:31:10 -07:00
Geometrically
da654fdff5 Add default bio value, to fix GitHub integration errors (#68)
* Change header name

* Add default bio value

* Remove default
2020-10-03 12:11:36 -07:00
Geometrically
d7f9d5a66f Change header name (#67) 2020-10-02 15:55:47 -07:00
Aeledfyr
c4fb7b7928 General cleanup: fix some bugs, some refactoring (#65)
* Merged mod file upload in version creation, mod creation and
  version file add to one function;  This makes sure that they are
  consistent
* Made some fields on `User` optional: `github_id`, `avatar_url`, `bio`.
    * We may not want to publicly show the `github_id` to everyone
      with access to the API
    * If we allow non-github users, some of those fields would be
      invalid; some oauth providers may not have avatars or bios
* Made CORS origins should configurable
* Made `--reconfigure-indices` and `--reset-indices` exit after
  completion instead of starting the server
2020-09-30 22:07:52 -07:00
Geometrically
43a791db65 Merge pull request #64 from modrinth/auth-fix
Make scopes safe for browser
2020-09-29 12:56:58 -07:00
Jai A
217311211a Remove org read scope 2020-09-29 11:55:12 -07:00
Jai A
ca55890ad2 Make scopes safe for browser 2020-09-29 11:46:11 -07:00
Geometrically
d6ecf5b8a9 Merge pull request #63 from modrinth/ghauth
GitHub Authentication
2020-09-29 11:27:56 -07:00
Jai A
e52edde11f Run prepare scripts 2020-09-29 07:36:28 -07:00
Jai A
2e514735ec User retrieval routes 2020-09-28 22:30:13 -07:00
Jai A
3d32c30d2d Authenticate protected routes 2020-09-28 21:05:42 -07:00
Jai A
05235f8385 Implement users in API 2020-09-28 10:48:15 -07:00
Jai A
cd28a75c86 Authentication workflow complete, add database link 2020-09-27 22:49:38 -07:00
Jai A
34075738ea Basic GitHub integration 2020-09-26 22:49:16 -07:00
Geometrically
88c0b8a8f0 Search fixes (#62) 2020-09-12 18:16:05 -07:00
Geometrically
e8bbc117e1 Allow for API user to change the amount of mods responded with in search (#61)
* Add more info to search route:

* Run formatter

* Allow for API user to change the amount of mods responded with in search

* Refactor SearchResults

* Fix searchresults usage
2020-09-07 11:44:21 -07:00
Geometrically
b99f45874f Add more info to search route (#60)
* Add more info to search route:

* Run formatter
2020-09-06 08:19:53 -07:00
Geometrically
0dfa378e38 Add modrinth.com to CORS (#59)
Co-authored-by: Redblueflame <redblueflame1@gmail.Com>
2020-09-02 08:24:42 -07:00
Redblueflame
3da0c07bcd fix: Fixed ssl error with the docker container. (#58) 2020-08-29 20:24:15 +02:00
Redblueflame
2196b53075 Switched base container for the docker build (#57) 2020-08-29 07:56:50 -07:00
Redblueflame
a8340f37bb Update Dockerfile (#56)
* Update Dockerfile

* Update Dockerfile
2020-08-28 12:03:00 -07:00
Redblueflame
7b1710ee63 Update Dockerfile (#55) 2020-08-28 20:24:43 +02:00
Redblueflame
38b7d9724e feat(migration): Added automatic migration (#54) 2020-08-28 08:48:01 -07:00
Geometrically
2b1ed49e9a Update Meilisearch SDK (#53)
* Update Meilisearch SDK

* Run Formatter

* Fixes
2020-08-27 17:53:40 -07:00
Geometrically
017cf9e464 Make CORS work (#52)
* Make CORS work

* Add use statement

* Add to TOML
2020-08-27 08:47:18 -07:00
Aeledfyr
781f0c843e Implement more database methods and basic API routes (#50)
* feat: Implement more database methods & add mod and version routes

* feat: Implement deleting mods/versions & implement categories

* feat: Implement routes for categories, game versions & loaders

* feat: Reorganize API routes in a (hopefully) usable way
2020-08-12 12:54:03 -07:00
Geometrically
e2bf474332 Version Creation (#47)
* Creation Stuff

* Make it work

* Response structs + Mod ID validation

* Run code formatter

* Push local changes

* Finish up version creation - fix comments, impl file creation

* fix: Add sqlx prepare data

Co-authored-by: Aeledfyr <aeledfyr@gmail.com>
2020-08-12 11:05:49 -07:00
AppleTheGolden
7e2f1c9a8b Update rust version in dockerfile (#49) 2020-08-02 09:48:02 -07:00
Aeledfyr
8e798dde48 feat(search): Faceted search based on mod host (curse/modrinth) (#48)
This also adds a commandline argument library (gumdrop) for dealing
with indices - reseting, reconfiguring, and skipping them. I don't
know which library is best for this case, but gumdrop has shorter
compile times and many fewer dependencies than clap, which is why
I chose it.
2020-07-31 18:18:23 -07:00
Aeledfyr
c05ae6e94c fix(postgres): Fix sqlx's misinterpretation of Ids, update sqlx (#46) 2020-07-30 20:45:22 -07:00
Aeledfyr
ff28ea8fa8 Refactor Meilisearch, update to latest SDK, and implement faceted search (#44)
* feat(indexing): Reindex curseforge & local database at an interval

* fix(indexing): Use strings for meilisearch primary key

Fixes #17 by prefixing curseforge ids with "curse-" and local ids
with "local-".

* feat(indexing): Add newly created mods to the index more quickly

* feat(indexing): Implement faceted search, update to meilisearch master

Fixes #9, but only uses faceted search for categories.  It should
be reasonably simple to add support for versions, but it may not
be as useful due to the large number of versions and the large
number of supported versions for each mod.

* feat(indexing): Allow skipping initial indexing

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
2020-07-27 16:54:10 -07:00
Charalampos Fanoulis
7914e89212 fix: quote the numbers on the github label (#43) 2020-07-23 23:26:53 +02:00
Valentin Ricard
558ff90e27 fix/ci: Fix build matrix (#42)
* ci: Test #1

* ci: Test #2

* ci: Fix an unrelated issue

* ci: Restore the branch filter
2020-07-23 23:18:36 +02:00
AppleTheGolden
ee69653a83 Switch to Postgres (#39)
* WIP Switch to Postgres

* feat(postgres): more work on porting to postgres, now compiles

* feat(docker-compose): Changed the docker-compose.yml file to use postgres.

* Update docker, documentation, gh actions...

* Remove bson dependency

* Remove bson import

* feat: move mock filehost to trait rather than cargo feature

* feat(postgres): transactions for mod creation, multipart refactor

* fix: Add Cargo.lock so that sqlx functions

* Update sqlx offline build data

* fix: Use SQLX_OFFLINE to force sqlx into offline mode for CI

* Default release channels

* feat(postgres): refactor database models to fit postgres models

* fix: Fix sqlx prepare, fix double allocation in indexing

* Add dockerfile (#40)

Co-authored-by: Charalampos Fanoulis <charalampos.fanoulis@gmail.com>

Co-authored-by: Aeledfyr <aeledfyr@gmail.com>
Co-authored-by: redblueflame <contact@redblueflame.com>
Co-authored-by: Jai A <jai.a@tuta.io>
Co-authored-by: Valentin Ricard <redblueflame1@gmail.Com>
Co-authored-by: Charalampos Fanoulis <charalampos.fanoulis@gmail.com>
2020-07-23 22:46:33 +02:00
Aeledfyr
95339a8338 Create a mock file host for dev, Fix mod creation route (#38)
* fix(mod-creation): fix actix server data & mod creation route

* feat(file-host): implement mock file hosting

This implements a mock file hosting system backed by the system's
filesystem.  It mirrors the API of the backblaze integration, but
puts the files directly on disk in the path specified by the
MOCK_FILE_PATH environment variable (defaults to /tmp/modrinth).

The mock file hosting is enabled by default using cargo features
to allow people to work on modrinth without access to a valid
backblaze account and setup.  To enable backblaze, specify the
cargo feature "backblaze" when running, ex. `cargo run --features
backblaze`.

* feat(file-hosting): implement basic backblaze API error handling

* fix(mod-creation): fix extension parsing, use base62 ids for paths
fix(file-hosting): reduce unnecessary allocations

* fix: fix auth with docker mongodb

* fix: fix failing checks

* fix: remove testing files
2020-07-16 21:06:58 -07:00
Geometrically
39b1435725 Mod Creation (#34)
* Inital creation stuff

* File Reader

* Upload bodies

* Major rework:

* Finish Multiple Files

* Proper Error Handling

* Switch to database models

* Run formatter

* Make dependencies dependent on Versions over mods

* Fixes

* Fix clippy

* Run lint one last time

* Update src/models/mods.rs

Co-authored-by: AppleTheGolden <scotsbox@protonmail.com>

Co-authored-by: AppleTheGolden <scotsbox@protonmail.com>
2020-07-16 10:16:35 -07:00
Aeledfyr
b1d3e258bd fix(indexing): chunk adding documents to indexing server (#36)
This should prevent adding too many mods and going over
meilisearch's request size limit by attempting to add all mods in
one request.
2020-07-15 15:01:49 -07:00
Aeledfyr
6ff7fa74e2 Improve error handling (#33)
* refactor: improve error handling

* fix: specify bind address instead of port

* fix: remove temporary testing file

* fix(errors): change error names to snake_case

* refactor(errors): split indexing error types, remove unused errors

* feat: add env variable checking at program start

This just checks whether the enviroment variables exist and can
parse to the given type and gives a warning if they can't. This
should prevent cases where the program fails at runtime due to
checking an environment variable that doesn't exist.
2020-07-03 10:44:39 -07:00
Geometrically
91305262f1 Add Backblaze Driver (#32)
* Backblaze Driver

* Update action to work with new tests

* Fix minor issues

* Run Formatter + Switch to reqwest json parser
2020-07-02 14:00:04 +02:00
Aeledfyr
6d16b68f11 Create schema for the API (#28)
* feat(schema): add basic structs for schema

* feat(schema): implement base62 id parsing

* docs(schema): add documentation for schema structs
fix(schema): prevent integer overflow in base62 decoding

* refactor(schema): move ids into submodules, reexport from ids mod

* feat(schema): add random generation of base62 ids
style: run rustfmt
2020-07-01 22:24:42 +02:00
Charalampos Fanoulis
f22e4f1cc7 feat: Add label syncing. (#30)
* ci: Add label syncing

* ci: add more label sprinkles
2020-07-01 11:32:23 -07:00
MulverineX
2851d12357 readme edits (#29)
* Update README.md

* Update README.md
2020-07-01 08:43:44 +02:00
Valentin Ricard
73968e4277 fix(env): Hotfixed the env name (#27) 2020-06-30 20:00:41 +02:00
Valentin Ricard
7a6ecd86c6 Rewrite the app (#23)
* chore: Removed everything not needed, and added base for rewrite
feat(error_handling): Added 404 general cache
feat(index): Added informations about the app in the / route.

* feat(indexing): Brought back the indexing, with conditions to make it easier

* fix: Fixed build error with a forgotten call

* feat: Add Docker development enviroment (#19)

* ci: add a *lot* of new actions

* fix: rename linting action

* fix: invalid yaml begone(?)

* ci: Added cache to speed up build times

* fix(ci): 🦀ed the yaml errors

* fix(ci): fixed a missing hyphen

* ci: Added matrix of rust versions, and changed way to install rust toolchain

* fix(ci): Added names to build with the matrix so it's easier to find the source of the problem

* style(ci): Added eof lines

* refactor: Finished moving the search.rs file to a separate module.

* Search Endpoint

* refactor: Moved around functions and struct for a better understanding of what it does.

* chore: Change env default settings to resolve conversation

* refactor: Removed #[use_macros]
fix: Fixed meilisearch address from env

* chore: Added email to Aeledfyr

* fix: Brought back the dotenv variables

* style: Ran `cargo fmt`

Co-authored-by: Charalampos Fanoulis <charalampos.fanoulis@gmail.com>
Co-authored-by: Jai A <jai.a@tuta.io>
2020-06-30 19:23:52 +02:00
Jai A
1ff2a08d19 Final push before rewrite 2020-06-28 13:53:03 -07:00
Geometrically
d1efd62e7b Merge pull request #14 from Aeledfyr/master
Minify icons and implement SVG spritesheets
2020-06-27 20:18:01 -07:00
Aeledfyr
366c95cd3c Fix a few display bugs 2020-06-27 17:30:35 -05:00
Aeledfyr
7e03f3958e Implement SVG sprite sheet for icons, use thumbnails for mod icons 2020-06-27 17:30:34 -05:00
Aeledfyr
e069184721 Compress/Minify icons 2020-06-27 17:26:59 -05:00
Geometrically
5182441cb3 Merge pull request #12 from joaoh1/master
Make category badges more generic
2020-06-27 15:06:02 -07:00
joaoh1
0de55a8ff5 Make category badges more generic 2020-06-27 16:22:09 -03:00
Jai A
0900d7c764 Add staging popup 2020-06-27 10:29:49 -07:00
Jai A
8540e09ba7 Fix #11 and Cleanup dependencies 2020-06-26 22:35:20 -07:00
Jai A
6e301601f9 Fix minor styling issues 2020-06-25 22:40:29 -07:00
Geometrically
1bf0eab2d9 Merge pull request #7 from Aeledfyr/master
Improve styling for narrower screens
2020-06-25 19:23:16 -07:00
Aeledfyr
d560f656f4 Reduce duplication of Forge logo using svg <use>
This uses svg <use href="#..."> to avoid having to duplicate the
logo at every use.  Surprisingly this just works with the current
theme css, and doesn't require any changes.
2020-06-25 21:14:50 -05:00
Aeledfyr
0bc256aa23 Miscellaneous fixes and requested changes 2020-06-25 20:19:07 -05:00
Aeledfyr
ebc073a52e Tweak styles for narrower screens, add indicator for no results
Makes the styles of search results work better with narrower
screens.  The category badges on each result collapse to just an
icon (with title text) when the screen is too narrow.

Adds a text label for the Forge/Fabric icons.

Adds a message for when a query returns no results.
2020-06-25 17:14:12 -05:00
Jai A
23503dc439 Base creation page 2020-06-22 21:04:17 -07:00
Geometrically
1f4985c7dd Merge pull request #6 from emilyploszaj/master
Implemented a dark theme
2020-06-22 19:57:37 -07:00
emilyploszaj
ed88d9e10d Implemented a dark theme 2020-06-22 21:48:42 -05:00
Jai A
906196bac3 Staging Test 2020-06-20 18:19:19 -07:00
Jai A
cb9751be04 Add CONTRIBUTING.md 2020-06-19 20:19:19 -07:00
Geometrically
09e03d579b Merge pull request #4 from Geometrically/dependabot/add-v2-config-file
Create Dependabot config file
2020-06-18 22:21:47 -07:00
dependabot-preview[bot]
9fcca2698e Create Dependabot config file 2020-06-19 05:21:30 +00:00
Geometrically
bba8cd9b58 Merge pull request #2 from Geometrically/dependabot/cargo/bson-1.0.0
Update bson requirement from 0.14.1 to 1.0.0
2020-06-18 22:21:07 -07:00
dependabot-preview[bot]
c8bb30289a Update bson requirement from 0.14.1 to 1.0.0
Updates the requirements on [bson](https://github.com/mongodb/bson-rust) to permit the latest version.
- [Release notes](https://github.com/mongodb/bson-rust/releases)
- [Commits](https://github.com/mongodb/bson-rust/compare/v0.14.1...v1.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-19 05:13:06 +00:00
Geometrically
69f56c1eb7 Merge pull request #3 from Geometrically/dependabot/cargo/mongodb-1.0.0
Update mongodb requirement from 0.10.0 to 1.0.0
2020-06-18 22:11:42 -07:00
dependabot-preview[bot]
adc8e23356 Update mongodb requirement from 0.10.0 to 1.0.0
Updates the requirements on [mongodb](https://github.com/mongodb/mongo-rust-driver) to permit the latest version.
- [Release notes](https://github.com/mongodb/mongo-rust-driver/releases)
- [Commits](https://github.com/mongodb/mongo-rust-driver/compare/v0.10.0...v1.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-06-19 05:09:44 +00:00
Jai A
88005c6603 API Page 2020-06-09 21:57:52 -07:00
Jai A
33ee4c36b4 API Page base 2020-06-05 20:19:19 -07:00
Jai A
1a142b7fe6 Versions Search 2020-06-04 20:19:19 -07:00
Jai A
90373806c0 Main mod page 2020-06-03 20:19:19 -07:00
Jai A
a47634bf49 Static content serving from non-root routes 2020-06-02 20:59:20 -07:00
Jai A
e03e58323b Cleanup + Database code 2020-06-02 12:03:45 -07:00
Jai A
ab1e31c0e7 Finish search 2020-06-01 17:01:13 -07:00
Jai A
aa5505d693 Edit CF indexer 2020-05-31 22:20:16 -07:00
Jai A
74f8f687cf Github Actions checks 2020-05-30 17:09:24 -07:00
Jai A
d54500bad5 Sort Types 2020-05-30 16:53:56 -07:00
Jai A
4966b4d58d Sort by WIP 2020-05-29 22:21:53 -07:00
Jai A
b75a4667c2 Migrate to MongoDB 2020-05-28 13:28:58 -07:00
Geometrically
14579a9320 Merge pull request #1 from Scotsguy/master
Fix clippy warnings & rustfmt
2020-05-27 12:28:57 -07:00
AppleTheGolden
1d92eff974 Fix serde attributes 2020-05-27 21:25:39 +02:00
AppleTheGolden
91274267e5 Run rustfmt on everything 2020-05-27 20:51:28 +02:00
AppleTheGolden
c49f0ede16 Fix clippy warnings 2020-05-27 20:46:59 +02:00
Jai A
42a0f452b1 FOSS 2020-05-27 11:03:19 -07:00
Jai A
c24ab9831a Add categories + Infinite Scroll 2020-05-23 21:06:36 -07:00
Jai A
506a68ee6a Fixes + Finish Indexer 2020-05-22 22:48:17 -07:00
Jai A
1291f792ab Curseforge Indexer 2020-05-21 22:38:39 -07:00
Jai A
51cfb1903d CF Indexer start 2020-05-20 22:32:57 -07:00
Jai A
1b1c15694a Fix Mod page styling 2020-05-19 22:39:05 -07:00
Jai A
251f1941a9 Finish mod main page 2020-05-18 22:35:03 -07:00
Jai A
625617bb20 Mod navigation bar 2020-05-18 22:20:34 -07:00
Jai A
f80f161886 Start mods page 2020-05-16 22:37:02 -07:00
Jai A
4b4889d5f2 Finish database code 2020-05-15 22:00:47 -07:00
Jai A
fee34ba257 Basic Database 2020-05-14 22:53:22 -07:00
Jai A
c29ab25dd2 Version Selector 2020-05-13 22:34:19 -07:00
Jai A
e0308a11c9 Search filters 2020-05-12 22:27:31 -07:00
Jai A
a738998c2c Badges + Refactoring 2020-05-11 22:53:08 -07:00
Jai A
6be22c474d Working Search 2020-05-10 22:30:28 -07:00
Jai A
da19743ba5 Initial Commit 2020-05-09 22:11:48 -07:00
950 changed files with 119751 additions and 1218 deletions

43
.github/workflows/daedalus-docker.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: daedalus-docker-build
on:
push:
branches: [ "main" ]
paths:
- .github/workflows/daedalus-docker.yml
- 'apps/daedalus_client/**'
pull_request:
types: [ opened, synchronize ]
paths:
- .github/workflows/daedalus-docker.yml
- 'apps/daedalus_client/**'
merge_group:
types: [ checks_requested ]
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Fetch docker metadata
id: docker_meta
uses: docker/metadata-action@v3
with:
images: ghcr.io/modrinth/daedalus
-
name: Login to GitHub Images
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
file: ./apps/daedalus_client/Dockerfile
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}

52
.github/workflows/daedalus-run.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
name: Run Meta
on:
schedule:
- cron: '*/5 * * * *'
workflow_dispatch:
jobs:
run-docker:
if: github.repository_owner == 'modrinth'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pull Docker image from GHCR
run: docker pull ghcr.io/modrinth/daedalus:main
- name: Run Docker container
env:
BASE_URL: ${{ secrets.BASE_URL }}
S3_ACCESS_TOKEN: ${{ secrets.S3_ACCESS_TOKEN }}
S3_SECRET: ${{ secrets.S3_SECRET }}
S3_URL: ${{ secrets.S3_URL }}
S3_REGION: ${{ secrets.S3_REGION }}
S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
CLOUDFLARE_INTEGRATION: ${{ secrets.CLOUDFLARE_INTEGRATION }}
CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }}
CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }}
run: |
docker run \
--name daedalus \
-e RUST_LOG=warn,daedalus_client=trace \
-e BASE_URL=$BASE_URL \
-e S3_ACCESS_TOKEN=$S3_ACCESS_TOKEN \
-e S3_SECRET=$S3_SECRET \
-e S3_URL=$S3_URL \
-e S3_REGION=$S3_REGION \
-e S3_BUCKET_NAME=$S3_BUCKET_NAME \
-e CLOUDFLARE_INTEGRATION=$CLOUDFLARE_INTEGRATION \
-e CLOUDFLARE_TOKEN=$CLOUDFLARE_TOKEN \
-e CLOUDFLARE_ZONE_ID=$CLOUDFLARE_ZONE_ID \
ghcr.io/modrinth/daedalus:main

View File

@@ -1,6 +1,9 @@
name: Deploy frontend
name: Clear pages cache
on: push
on:
push:
branches:
- prod
jobs:
wait:
@@ -16,7 +19,6 @@ jobs:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: '9ddae624c98677d68d93df6e524a6061'
project: 'frontend'
githubToken: ${{ secrets.GITHUB_TOKEN }}
commitHash: ${{ steps.push-changes.outputs.commit-hash }}
- name: Purge cache
if: github.ref == 'refs/heads/prod'

46
.github/workflows/labrinth-docker.yml vendored Normal file
View File

@@ -0,0 +1,46 @@
name: docker-build
on:
push:
branches: [ "main" ]
paths:
- .github/workflows/labrinth-docker.yml
- 'apps/labrinth/**'
pull_request:
types: [ opened, synchronize ]
paths:
- .github/workflows/labrinth-docker.yml
- 'apps/labrinth/**'
merge_group:
types: [ checks_requested ]
jobs:
docker:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./apps/labrinth
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Fetch docker metadata
id: docker_meta
uses: docker/metadata-action@v3
with:
images: ghcr.io/modrinth/labrinth
-
name: Login to GitHub Images
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
context: ./apps/labrinth
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}

View File

@@ -62,9 +62,19 @@ jobs:
- name: Build
run: pnpm build
env:
SQLX_OFFLINE: true
- name: Lint
run: pnpm lint
env:
SQLX_OFFLINE: true
- name: Start docker compose
run: docker compose up -d
- name: Test
run: pnpm test
env:
SQLX_OFFLINE: true
DATABASE_URL: postgresql://labrinth:labrinth@localhost/postgres

6
.gitignore vendored
View File

@@ -55,3 +55,9 @@ generated
# app testing dir
app-playground-data/*
# soley because i need the PORT to be 3002 due to WSL stuff
.env
apps/frontend/.env
.astro

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

15
.idea/daedalus.iml generated Normal file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/daedalus_client_new/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/apps/daedalus_client/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/packages/daedalus/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

7
.idea/discord.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>

26
.idea/libraries/KotlinJavaRuntime.xml generated Normal file
View File

@@ -0,0 +1,26 @@
<component name="libraryTable">
<library name="KotlinJavaRuntime" type="repository">
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.0/kotlin-stdlib-jdk8-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.0/kotlin-stdlib-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.0/kotlin-stdlib-common-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.0/kotlin-stdlib-jdk7-1.8.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.0/kotlin-stdlib-jdk8-1.8.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.0/kotlin-stdlib-1.8.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.0/kotlin-stdlib-common-1.8.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.0/kotlin-stdlib-jdk7-1.8.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.0/kotlin-stdlib-jdk8-1.8.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.0/kotlin-stdlib-1.8.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.0/kotlin-stdlib-common-1.8.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.0/kotlin-stdlib-jdk7-1.8.0-sources.jar!/" />
</SOURCES>
</library>
</component>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/daedalus.iml" filepath="$PROJECT_DIR$/.idea/daedalus.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

4424
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,10 @@ resolver = '2'
members = [
'./packages/app-lib',
'./apps/app-playground',
'./apps/app'
'./apps/app',
'./apps/labrinth',
'./apps/daedalus_client',
'./packages/daedalus',
]
# Optimize for speed and reduce size on release builds

View File

@@ -22,7 +22,7 @@ This repository contains two primary packages. For detailed development informat
## Contributing
We welcome contributions! Before submitting any contributions, please read our [contributing guidelines](https://support.modrinth.com/en/articles/8802215-contributing-to-modrinth).
We welcome contributions! Before submitting any contributions, please read our [contributing guidelines](https://docs.modrinth.com/contributing/getting-started/).
If you plan to fork this repository for your own purposes, please review our [copying guidelines](COPYING.md).

View File

@@ -1,7 +1,7 @@
{
"name": "@modrinth/app-frontend",
"private": true,
"version": "0.8.8",
"version": "0.8.9",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -230,7 +230,7 @@ async function repairInstance() {
</p>
<p>You may be able to fix it through one of the following ways:</p>
<ul>
<li>Ennsuring you are connected to the internet, then try restarting the app.</li>
<li>Ensuring you are connected to the internet, then try restarting the app.</li>
<li>Redownloading the app.</li>
</ul>
</template>

View File

@@ -218,7 +218,6 @@ import { get_game_versions, get_loader_versions } from '@/helpers/metadata'
import { handleError } from '@/store/notifications.js'
import Multiselect from 'vue-multiselect'
import { trackEvent } from '@/helpers/analytics'
import { listen } from '@tauri-apps/api/event'
import { install_from_file } from '@/helpers/pack.js'
import {
get_default_launcher_path,
@@ -226,6 +225,7 @@ import {
import_instance,
} from '@/helpers/import.js'
import ProgressBar from '@/components/ui/ProgressBar.vue'
import { getCurrentWebview } from '@tauri-apps/api/webview'
const profile_name = ref('')
const game_version = ref('')
@@ -255,13 +255,15 @@ defineExpose({
isShowing.value = true
modal.value.show()
unlistener.value = await listen('tauri://file-drop', async (event) => {
unlistener.value = await getCurrentWebview().onDragDropEvent(async (event) => {
// Only if modal is showing
if (!isShowing.value) return
if (event.payload.type !== 'drop') return
if (creationType.value !== 'from file') return
hide()
if (event.payload && event.payload.length > 0 && event.payload[0].endsWith('.mrpack')) {
await install_from_file(event.payload[0]).catch(handleError)
const { paths } = event.payload
if (paths && paths.length > 0 && paths[0].endsWith('.mrpack')) {
await install_from_file(paths[0]).catch(handleError)
trackEvent('InstanceCreate', {
source: 'CreationModalFileDrop',
})

View File

@@ -473,7 +473,7 @@ async function purgeCache() {
:min="8"
:max="maxMemory"
:step="64"
unit="mb"
unit="MB"
/>
</div>
</Card>

View File

@@ -378,7 +378,6 @@ import {
} from '@/helpers/profile.js'
import { handleError } from '@/store/notifications.js'
import { trackEvent } from '@/helpers/analytics'
import { listen } from '@tauri-apps/api/event'
import { highlightModInProfile } from '@/helpers/utils.js'
import { MenuIcon, ToggleIcon, TextInputIcon, AddProjectImage, PackageIcon } from '@/assets/icons'
import ExportModal from '@/components/ui/ExportModal.vue'
@@ -393,6 +392,7 @@ import {
import { profile_listener } from '@/helpers/events.js'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import ShareModalWrapper from '@/components/ui/modal/ShareModalWrapper.vue'
import { getCurrentWebview } from '@tauri-apps/api/webview'
const props = defineProps({
instance: {
@@ -879,8 +879,10 @@ async function refreshProjects() {
refreshingProjects.value = false
}
const unlisten = await listen('tauri://file-drop', async (event) => {
for (const file of event.payload) {
const unlisten = await getCurrentWebview().onDragDropEvent(async (event) => {
if (event.payload.type !== 'drop') return
for (const file of event.payload.paths) {
if (file.endsWith('.mrpack')) continue
await add_project_from_path(props.instance.path, file).catch(handleError)
}

View File

@@ -2,7 +2,7 @@
"name": "@modrinth/app-playground",
"scripts": {
"build": "cargo build --release",
"lint": "cargo fmt --check && cargo clippy -- -D warnings",
"lint": "cargo fmt --check && cargo clippy --all-targets --all-features -- -D warnings",
"fix": "cargo fmt && cargo clippy --fix",
"dev": "cargo run",
"test": "cargo test"

View File

@@ -1,6 +1,6 @@
[package]
name = "theseus_gui"
version = "0.8.8"
version = "0.8.9"
description = "The Modrinth App is a desktop application for managing your Minecraft mods"
license = "GPL-3.0-only"
repository = "https://github.com/modrinth/code/apps/app/"
@@ -28,7 +28,7 @@ tauri-plugin-single-instance = { version = "2.0.0-rc" }
tokio = { version = "1", features = ["full"] }
thiserror = "1.0"
futures = "0.3"
daedalus = "0.2.3"
daedalus = { path = "../../packages/daedalus" }
chrono = "0.4.26"
dirs = "5.0.1"

View File

@@ -2,9 +2,7 @@
"identifier": "plugins",
"description": "",
"local": true,
"windows": [
"main"
],
"windows": ["main"],
"permissions": [
"dialog:allow-open",
"dialog:allow-confirm",

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
"tauri": "tauri",
"dev": "tauri dev",
"test": "cargo test",
"lint": "cargo fmt --check && cargo clippy -- -D warnings",
"lint": "cargo fmt --check && cargo clippy --all-targets -- -D warnings",
"fix": "cargo fmt && cargo clippy --fix"
},
"devDependencies": {
@@ -13,6 +13,7 @@
},
"dependencies": {
"@modrinth/app-frontend": "workspace:*",
"@modrinth/app-lib": "workspace:*"
"@modrinth/app-lib": "workspace:*",
"@modrinth/daedalus": "workspace:*"
}
}
}

View File

@@ -21,86 +21,3 @@ document.addEventListener(
window.open = (url, target, features) => {
window.top.postMessage({ modrinthOpenUrl: url }, 'https://modrinth.com')
}
function muteAudioContext() {
if (window.AudioContext || window.webkitAudioContext) {
const AudioContext = window.AudioContext || window.webkitAudioContext
const originalCreateMediaElementSource = AudioContext.prototype.createMediaElementSource
const originalCreateMediaStreamSource = AudioContext.prototype.createMediaStreamSource
const originalCreateMediaStreamTrackSource = AudioContext.prototype.createMediaStreamTrackSource
const originalCreateBufferSource = AudioContext.prototype.createBufferSource
const originalCreateOscillator = AudioContext.prototype.createOscillator
AudioContext.prototype.createGain = function () {
const gain = originalCreateGain.call(this)
gain.gain.value = 0
return gain
}
AudioContext.prototype.createMediaElementSource = function (mediaElement) {
const source = originalCreateMediaElementSource.call(this, mediaElement)
source.connect(this.createGain())
return source
}
AudioContext.prototype.createMediaStreamSource = function (mediaStream) {
const source = originalCreateMediaStreamSource.call(this, mediaStream)
source.connect(this.createGain())
return source
}
AudioContext.prototype.createMediaStreamTrackSource = function (mediaStreamTrack) {
const source = originalCreateMediaStreamTrackSource.call(this, mediaStreamTrack)
source.connect(this.createGain())
return source
}
AudioContext.prototype.createBufferSource = function () {
const source = originalCreateBufferSource.call(this)
source.connect(this.createGain())
return source
}
AudioContext.prototype.createOscillator = function () {
const oscillator = originalCreateOscillator.call(this)
oscillator.connect(this.createGain())
return oscillator
}
}
}
function muteVideo(mediaElement) {
let count = Number(mediaElement.getAttribute('data-modrinth-muted-count') ?? 0)
if (!mediaElement.muted || mediaElement.volume !== 0) {
mediaElement.muted = true
mediaElement.volume = 0
mediaElement.setAttribute('data-modrinth-muted-count', count + 1)
}
if (count > 5) {
// Video is detected as malicious, so it is removed from the page
mediaElement.remove()
}
}
function muteVideos() {
document.querySelectorAll('video, audio').forEach(function (mediaElement) {
muteVideo(mediaElement)
if (!mediaElement.hasAttribute('data-modrinth-muted')) {
mediaElement.addEventListener('volumechange', () => muteVideo(mediaElement))
mediaElement.setAttribute('data-modrinth-muted', 'true')
}
})
}
document.addEventListener('DOMContentLoaded', () => {
muteVideos()
muteAudioContext()
const observer = new MutationObserver(muteVideos)
observer.observe(document.body, { childList: true, subtree: true })
})

View File

@@ -317,7 +317,7 @@ fn main() {
MessageDialog::new()
.set_type(MessageType::Error)
.set_title("Initialization error")
.set_text("Your Microsoft Edge WebView2 installation is corrupt.\n\nMicrosoft Edge WebView2 is required to run Modrinth App.\n\nLearn how to repair it at https://docs.modrinth.com/faq/app/webview2")
.set_text("Your Microsoft Edge WebView2 installation is corrupt.\n\nMicrosoft Edge WebView2 is required to run Modrinth App.\n\nLearn how to repair it at https://support.modrinth.com/en/articles/8797765-corrupted-microsoft-edge-webview2-installation")
.show_alert()
.unwrap();

View File

@@ -49,7 +49,7 @@
]
},
"productName": "Modrinth App",
"version": "0.8.8",
"version": "0.8.9",
"mainBinaryName": "Modrinth App",
"identifier": "ModrinthApp",
"plugins": {

15
apps/daedalus_client/.env Normal file
View File

@@ -0,0 +1,15 @@
RUST_LOG=warn,daedalus_client=trace
BASE_URL=http://localhost:9000/meta
CONCURRENCY_LIMIT=10
S3_ACCESS_TOKEN=none
S3_SECRET=none
S3_URL=http://localhost:9000
S3_REGION=path-style
S3_BUCKET_NAME=meta
CLOUDFLARE_INTEGRATION=false
CLOUDFLARE_TOKEN=none
CLOUDFLARE_ZONE_ID=none

View File

@@ -0,0 +1,42 @@
[package]
name = "daedalus_client"
version = "0.2.2"
authors = ["Jai A <jai@modrinth.com>"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
daedalus = { path = "../../packages/daedalus" }
tokio = { version = "1", features = ["full"] }
futures = "0.3.25"
dotenvy = "0.15.6"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde-xml-rs = "0.6.0"
lazy_static = "1.4.0"
thiserror = "1.0"
reqwest = { version = "0.12.5", default-features = false, features = [
"stream",
"json",
"rustls-tls-native-roots",
] }
async_zip = { version = "0.0.17", features = ["full"] }
semver = "1.0"
chrono = { version = "0.4", features = ["serde"] }
bytes = "1.6.0"
rust-s3 = { version = "0.33.0", default-features = false, features = [
"fail-on-err",
"tags",
"tokio-rustls-tls",
"reqwest",
] }
dashmap = "5.5.3"
sha1_smol = { version = "1.0.0", features = ["std"] }
indexmap = { version = "2.2.6", features = ["serde"] }
itertools = "0.13.0"
tracing-error = "0.2.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-futures = { version = "0.2.5", features = ["futures", "tokio"] }

View File

@@ -0,0 +1,21 @@
FROM rust:1.82.0 as build
ENV PKG_CONFIG_ALLOW_CROSS=1
WORKDIR /usr/src/daedalus
COPY . .
RUN cargo build --release --package daedalus_client
FROM debian:bookworm-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates openssl \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN update-ca-certificates
COPY --from=build /usr/src/daedalus/target/release/daedalus_client /daedalus/daedalus_client
WORKDIR /daedalus_client
CMD /daedalus/daedalus_client

View File

@@ -0,0 +1,7 @@
Copyright © 2024 Rinth, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,9 @@
# Daedalus
Daedalus is a powerful tool which queries and generates metadata for the Minecraft (and other games in the future!) game
and mod loaders for:
- Performance (Serving static files can be easily cached and is extremely quick)
- Ease for Launcher Devs (Metadata is served in an easy to query and use format)
- Reliability (Provides a versioning system which ensures no breakage with updates)
Daedalus supports the original Minecraft data and reposting for the Forge, Fabric, Quilt, and NeoForge loaders.

View File

@@ -0,0 +1,17 @@
version: '3'
services:
minio:
image: quay.io/minio/minio
volumes:
- minio-data:/data
ports:
- "9000:9000"
- "9001:9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: miniosecret
command: server /data --console-address ":9001"
volumes:
minio-data:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
{
"name": "@modrinth/daedalus_client",
"scripts": {
"build": "cargo build --release",
"lint": "cargo fmt --check && cargo clippy --all-targets --all-features -- -D warnings",
"fix": "cargo fmt && cargo clippy --fix",
"dev": "cargo run",
"test": "cargo test"
},
"dependencies": {
"@modrinth/daedalus": "workspace:*"
}
}

View File

@@ -0,0 +1,63 @@
use tracing_error::InstrumentError;
#[derive(thiserror::Error, Debug)]
pub enum ErrorKind {
#[error("Daedalus Error: {0}")]
Daedalus(#[from] daedalus::Error),
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Error while managing asynchronous tasks")]
TaskError(#[from] tokio::task::JoinError),
#[error("Error while deserializing JSON: {0}")]
SerdeJSON(#[from] serde_json::Error),
#[error("Error while deserializing XML: {0}")]
SerdeXML(#[from] serde_xml_rs::Error),
#[error("Failed to validate file checksum at url {url} with hash {hash} after {tries} tries")]
ChecksumFailure {
hash: String,
url: String,
tries: u32,
},
#[error("Unable to fetch {item}")]
Fetch { inner: reqwest::Error, item: String },
#[error("Error while uploading file to S3: {file}")]
S3 {
inner: s3::error::S3Error,
file: String,
},
#[error("Error acquiring semaphore: {0}")]
Acquire(#[from] tokio::sync::AcquireError),
#[error("Tracing error: {0}")]
Tracing(#[from] tracing::subscriber::SetGlobalDefaultError),
#[error("Zip error: {0}")]
Zip(#[from] async_zip::error::ZipError),
}
#[derive(Debug)]
pub struct Error {
pub source: tracing_error::TracedError<ErrorKind>,
}
impl std::fmt::Display for Error {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(fmt, "{}", self.source)
}
}
impl<E: Into<ErrorKind>> From<E> for Error {
fn from(source: E) -> Self {
let error = Into::<ErrorKind>::into(source);
Self {
source: error.in_current_span(),
}
}
}
impl ErrorKind {
pub fn as_error(self) -> Error {
self.into()
}
}
pub type Result<T> = core::result::Result<T, Error>;

View File

@@ -0,0 +1,301 @@
use crate::util::{download_file, fetch_json, format_url};
use crate::{insert_mirrored_artifact, Error, MirrorArtifact, UploadFile};
use daedalus::modded::{Manifest, PartialVersionInfo, DUMMY_REPLACE_STRING};
use dashmap::DashMap;
use serde::Deserialize;
use std::sync::Arc;
use tokio::sync::Semaphore;
#[tracing::instrument(skip(semaphore, upload_files, mirror_artifacts))]
pub async fn fetch_fabric(
semaphore: Arc<Semaphore>,
upload_files: &DashMap<String, UploadFile>,
mirror_artifacts: &DashMap<String, MirrorArtifact>,
) -> Result<(), Error> {
fetch(
daedalus::modded::CURRENT_FABRIC_FORMAT_VERSION,
"fabric",
"https://meta.fabricmc.net/v2",
"https://maven.fabricmc.net/",
&[],
semaphore,
upload_files,
mirror_artifacts,
)
.await
}
#[tracing::instrument(skip(semaphore, upload_files, mirror_artifacts))]
pub async fn fetch_quilt(
semaphore: Arc<Semaphore>,
upload_files: &DashMap<String, UploadFile>,
mirror_artifacts: &DashMap<String, MirrorArtifact>,
) -> Result<(), Error> {
fetch(
daedalus::modded::CURRENT_QUILT_FORMAT_VERSION,
"quilt",
"https://meta.quiltmc.org/v3",
"https://maven.quiltmc.org/repository/release/",
&[
// This version is broken as it contains invalid library coordinates
"0.17.5-beta.4",
],
semaphore,
upload_files,
mirror_artifacts,
)
.await
}
#[allow(clippy::too_many_arguments)]
#[tracing::instrument(skip(semaphore, upload_files, mirror_artifacts))]
async fn fetch(
format_version: usize,
mod_loader: &str,
meta_url: &str,
maven_url: &str,
skip_versions: &[&str],
semaphore: Arc<Semaphore>,
upload_files: &DashMap<String, UploadFile>,
mirror_artifacts: &DashMap<String, MirrorArtifact>,
) -> Result<(), Error> {
let modrinth_manifest = fetch_json::<Manifest>(
&format_url(&format!("{mod_loader}/v{format_version}/manifest.json",)),
&semaphore,
)
.await
.ok();
let fabric_manifest = fetch_json::<FabricVersions>(
&format!("{meta_url}/versions"),
&semaphore,
)
.await?;
// We check Modrinth's fabric version manifest and compare if the fabric version exists in Modrinth's database
// We also check intermediary versions that are newly added to query
let (fetch_fabric_versions, fetch_intermediary_versions) =
if let Some(modrinth_manifest) = modrinth_manifest {
let (mut fetch_versions, mut fetch_intermediary_versions) =
(Vec::new(), Vec::new());
for version in &fabric_manifest.loader {
if !modrinth_manifest
.game_versions
.iter()
.any(|x| x.loaders.iter().any(|x| x.id == version.version))
&& !skip_versions.contains(&&*version.version)
{
fetch_versions.push(version);
}
}
for version in &fabric_manifest.intermediary {
if !modrinth_manifest
.game_versions
.iter()
.any(|x| x.id == version.version)
&& fabric_manifest
.game
.iter()
.any(|x| x.version == version.version)
{
fetch_intermediary_versions.push(version);
}
}
(fetch_versions, fetch_intermediary_versions)
} else {
(
fabric_manifest
.loader
.iter()
.filter(|x| !skip_versions.contains(&&*x.version))
.collect(),
fabric_manifest.intermediary.iter().collect(),
)
};
const DUMMY_GAME_VERSION: &str = "1.21";
if !fetch_intermediary_versions.is_empty() {
for x in &fetch_intermediary_versions {
insert_mirrored_artifact(
&x.maven,
None,
vec![maven_url.to_string()],
false,
mirror_artifacts,
)?;
}
}
if !fetch_fabric_versions.is_empty() {
let fabric_version_manifest_urls = fetch_fabric_versions
.iter()
.map(|x| {
format!(
"{}/versions/loader/{}/{}/profile/json",
meta_url, DUMMY_GAME_VERSION, x.version
)
})
.collect::<Vec<_>>();
let fabric_version_manifests = futures::future::try_join_all(
fabric_version_manifest_urls
.iter()
.map(|x| download_file(x, None, &semaphore)),
)
.await?
.into_iter()
.map(|x| serde_json::from_slice(&x))
.collect::<Result<Vec<PartialVersionInfo>, serde_json::Error>>()?;
let patched_version_manifests = fabric_version_manifests
.into_iter()
.map(|mut version_info| {
for lib in &mut version_info.libraries {
let new_name = lib
.name
.replace(DUMMY_GAME_VERSION, DUMMY_REPLACE_STRING);
// Hard-code: This library is not present on fabric's maven, so we fetch it from MC libraries
if &*lib.name == "net.minecraft:launchwrapper:1.12" {
lib.url = Some(
"https://libraries.minecraft.net/".to_string(),
);
}
// If a library is not intermediary, we add it to mirror artifacts to be mirrored
if lib.name == new_name {
insert_mirrored_artifact(
&new_name,
None,
vec![lib
.url
.clone()
.unwrap_or_else(|| maven_url.to_string())],
false,
mirror_artifacts,
)?;
} else {
lib.name = new_name;
}
lib.url = Some(format_url("maven/"));
}
version_info.id = version_info
.id
.replace(DUMMY_GAME_VERSION, DUMMY_REPLACE_STRING);
version_info.inherits_from = version_info
.inherits_from
.replace(DUMMY_GAME_VERSION, DUMMY_REPLACE_STRING);
Ok(version_info)
})
.collect::<Result<Vec<_>, Error>>()?;
let serialized_version_manifests = patched_version_manifests
.iter()
.map(|x| serde_json::to_vec(x).map(bytes::Bytes::from))
.collect::<Result<Vec<_>, serde_json::Error>>()?;
serialized_version_manifests
.into_iter()
.enumerate()
.for_each(|(index, bytes)| {
let loader = fetch_fabric_versions[index];
let version_path = format!(
"{mod_loader}/v{format_version}/versions/{}.json",
loader.version
);
upload_files.insert(
version_path,
UploadFile {
file: bytes,
content_type: Some("application/json".to_string()),
},
);
});
}
if !fetch_fabric_versions.is_empty()
|| !fetch_intermediary_versions.is_empty()
{
let fabric_manifest_path =
format!("{mod_loader}/v{format_version}/manifest.json",);
let loader_versions = daedalus::modded::Version {
id: DUMMY_REPLACE_STRING.to_string(),
stable: true,
loaders: fabric_manifest
.loader
.into_iter()
.map(|x| {
let version_path = format!(
"{mod_loader}/v{format_version}/versions/{}.json",
x.version,
);
daedalus::modded::LoaderVersion {
id: x.version,
url: format_url(&version_path),
stable: x.stable,
}
})
.collect(),
};
let manifest = daedalus::modded::Manifest {
game_versions: std::iter::once(loader_versions)
.chain(fabric_manifest.game.into_iter().map(|x| {
daedalus::modded::Version {
id: x.version,
stable: x.stable,
loaders: vec![],
}
}))
.collect(),
};
upload_files.insert(
fabric_manifest_path,
UploadFile {
file: bytes::Bytes::from(serde_json::to_vec(&manifest)?),
content_type: Some("application/json".to_string()),
},
);
}
Ok(())
}
#[derive(Deserialize, Debug, Clone)]
struct FabricVersions {
pub loader: Vec<FabricLoaderVersion>,
pub game: Vec<FabricGameVersion>,
#[serde(alias = "hashed")]
pub intermediary: Vec<FabricIntermediaryVersion>,
}
#[derive(Deserialize, Debug, Clone)]
struct FabricLoaderVersion {
// pub separator: String,
// pub build: u32,
// pub maven: String,
pub version: String,
#[serde(default)]
pub stable: bool,
}
#[derive(Deserialize, Debug, Clone)]
struct FabricIntermediaryVersion {
pub maven: String,
pub version: String,
}
#[derive(Deserialize, Debug, Clone)]
struct FabricGameVersion {
pub version: String,
pub stable: bool,
}

View File

@@ -0,0 +1,792 @@
use crate::util::{download_file, fetch_json, fetch_xml, format_url};
use crate::{insert_mirrored_artifact, Error, MirrorArtifact, UploadFile};
use chrono::{DateTime, Utc};
use daedalus::get_path_from_artifact;
use daedalus::modded::PartialVersionInfo;
use dashmap::DashMap;
use futures::io::Cursor;
use indexmap::IndexMap;
use itertools::Itertools;
use serde::de::DeserializeOwned;
use serde::Deserialize;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Semaphore;
#[tracing::instrument(skip(semaphore, upload_files, mirror_artifacts))]
pub async fn fetch_forge(
semaphore: Arc<Semaphore>,
upload_files: &DashMap<String, UploadFile>,
mirror_artifacts: &DashMap<String, MirrorArtifact>,
) -> Result<(), Error> {
let forge_manifest = fetch_json::<IndexMap<String, Vec<String>>>(
"https://files.minecraftforge.net/net/minecraftforge/forge/maven-metadata.json",
&semaphore,
)
.await?;
let mut format_version = 0;
let forge_versions = forge_manifest.into_iter().flat_map(|(game_version, versions)| versions.into_iter().map(|loader_version| {
// Forge versions can be in these specific formats:
// 1.10.2-12.18.1.2016-failtests
// 1.9-12.16.0.1886
// 1.9-12.16.0.1880-1.9
// 1.14.4-28.1.30
// This parses them to get the actual Forge version. Ex: 1.15.2-31.1.87 -> 31.1.87
let version_split = loader_version.split('-').nth(1).unwrap_or(&loader_version).to_string();
// Forge has 3 installer formats:
// - Format 0 (Unsupported ATM): Forge Legacy (pre-1.5.2). Uses Binary Patch method to install
// To install: Download patch, download minecraft client JAR. Combine patch and client JAR and delete META-INF/.
// (pre-1.3-2) Client URL: https://maven.minecraftforge.net/net/minecraftforge/forge/{version}/forge-{version}-client.zip
// (pre-1.3-2) Server URL: https://maven.minecraftforge.net/net/minecraftforge/forge/{version}/forge-{version}-server.zip
// (1.3-2-onwards) Universal URL: https://maven.minecraftforge.net/net/minecraftforge/forge/{version}/forge-{version}-universal.zip
// - Format 1: Forge Installer Legacy (1.5.2-1.12.2ish)
// To install: Extract install_profile.json from archive. "versionInfo" is the profile's version info. Convert it to the modern format
// Extract forge library from archive. Path is at "install"."path".
// - Format 2: Forge Installer Modern
// To install: Extract install_profile.json from archive. Extract version.json from archive. Combine the two and extract all libraries
// which are embedded into the installer JAR.
// Then upload. The launcher will need to run processors!
if format_version != 1 && &*version_split == "7.8.0.684" {
format_version = 1;
} else if format_version != 2 && &*version_split == "14.23.5.2851" {
format_version = 2;
}
ForgeVersion {
format_version,
installer_url: format!("https://maven.minecraftforge.net/net/minecraftforge/forge/{0}/forge-{0}-installer.jar", loader_version),
raw: loader_version,
loader_version: version_split,
game_version: game_version.clone(),
}
})
.collect::<Vec<_>>())
// TODO: support format version 0 (see above)
.filter(|x| x.format_version != 0)
.filter(|x| {
// These following Forge versions are broken and cannot be installed
const BLACKLIST : &[&str] = &[
// Not supported due to `data` field being `[]` even though the type is a map
"1.12.2-14.23.5.2851",
// Malformed Archives
"1.6.1-8.9.0.749",
"1.6.1-8.9.0.751",
"1.6.4-9.11.1.960",
"1.6.4-9.11.1.961",
"1.6.4-9.11.1.963",
"1.6.4-9.11.1.964",
];
!BLACKLIST.contains(&&*x.raw)
})
.collect::<Vec<_>>();
fetch(
daedalus::modded::CURRENT_FORGE_FORMAT_VERSION,
"forge",
"https://maven.minecraftforge.net/",
forge_versions,
semaphore,
upload_files,
mirror_artifacts,
)
.await
}
#[tracing::instrument(skip(semaphore, upload_files, mirror_artifacts))]
pub async fn fetch_neo(
semaphore: Arc<Semaphore>,
upload_files: &DashMap<String, UploadFile>,
mirror_artifacts: &DashMap<String, MirrorArtifact>,
) -> Result<(), Error> {
#[derive(Debug, Deserialize)]
struct Metadata {
versioning: Versioning,
}
#[derive(Debug, Deserialize)]
struct Versioning {
versions: Versions,
}
#[derive(Debug, Deserialize)]
struct Versions {
version: Vec<String>,
}
let forge_versions = fetch_xml::<Metadata>(
"https://maven.neoforged.net/net/neoforged/forge/maven-metadata.xml",
&semaphore,
)
.await?;
let neo_versions = fetch_xml::<Metadata>(
"https://maven.neoforged.net/net/neoforged/neoforge/maven-metadata.xml",
&semaphore,
)
.await?;
let parsed_versions = forge_versions.versioning.versions.version.into_iter().map(|loader_version| {
// NeoForge Forge versions can be in these specific formats:
// 1.20.1-47.1.74
// 47.1.82
// This parses them to get the actual Forge version. Ex: 1.20.1-47.1.74 -> 47.1.74
let version_split = loader_version.split('-').nth(1).unwrap_or(&loader_version).to_string();
Ok(ForgeVersion {
format_version: 2,
installer_url: format!("https://maven.neoforged.net/net/neoforged/forge/{0}/forge-{0}-installer.jar", loader_version),
raw: loader_version,
loader_version: version_split,
game_version: "1.20.1".to_string(), // All NeoForge Forge versions are for 1.20.1
})
}).chain(neo_versions.versioning.versions.version.into_iter().map(|loader_version| {
let mut parts = loader_version.split('.');
// NeoForge Forge versions are in this format: 20.2.29-beta, 20.6.119
// Where the first number is the major MC version, the second is the minor MC version, and the third is the NeoForge version
let major = parts.next().ok_or_else(
|| crate::ErrorKind::InvalidInput(format!("Unable to find major game version for NeoForge {loader_version}"))
)?;
let minor = parts.next().ok_or_else(
|| crate::ErrorKind::InvalidInput(format!("Unable to find minor game version for NeoForge {loader_version}"))
)?;
let game_version = if minor == "0" {
format!("1.{major}")
} else {
format!("1.{major}.{minor}")
};
Ok(ForgeVersion {
format_version: 2,
installer_url: format!("https://maven.neoforged.net/net/neoforged/neoforge/{0}/neoforge-{0}-installer.jar", loader_version),
loader_version: loader_version.clone(),
raw: loader_version,
game_version,
})
}))
.collect::<Result<Vec<_>, Error>>()?
.into_iter()
.filter(|x| {
// These following Forge versions are broken and cannot be installed
const BLACKLIST : &[&str] = &[
// Unreachable / 404
"1.20.1-47.1.7",
"47.1.82",
];
!BLACKLIST.contains(&&*x.raw)
}).collect();
fetch(
daedalus::modded::CURRENT_NEOFORGE_FORMAT_VERSION,
"neo",
"https://maven.neoforged.net/",
parsed_versions,
semaphore,
upload_files,
mirror_artifacts,
)
.await
}
#[tracing::instrument(skip(
forge_versions,
semaphore,
upload_files,
mirror_artifacts
))]
async fn fetch(
format_version: usize,
mod_loader: &str,
maven_url: &str,
forge_versions: Vec<ForgeVersion>,
semaphore: Arc<Semaphore>,
upload_files: &DashMap<String, UploadFile>,
mirror_artifacts: &DashMap<String, MirrorArtifact>,
) -> Result<(), Error> {
let modrinth_manifest = fetch_json::<daedalus::modded::Manifest>(
&format_url(&format!("{mod_loader}/v{format_version}/manifest.json",)),
&semaphore,
)
.await
.ok();
let fetch_versions = if let Some(modrinth_manifest) = modrinth_manifest {
let mut fetch_versions = Vec::new();
for version in &forge_versions {
if !modrinth_manifest.game_versions.iter().any(|x| {
x.id == version.game_version
&& x.loaders.iter().any(|x| x.id == version.loader_version)
}) {
fetch_versions.push(version);
}
}
fetch_versions
} else {
forge_versions.iter().collect()
};
if !fetch_versions.is_empty() {
let forge_installers = futures::future::try_join_all(
fetch_versions
.iter()
.map(|x| download_file(&x.installer_url, None, &semaphore)),
)
.await?;
#[tracing::instrument(skip(raw, upload_files, mirror_artifacts))]
async fn read_forge_installer(
raw: bytes::Bytes,
loader: &ForgeVersion,
maven_url: &str,
mod_loader: &str,
upload_files: &DashMap<String, UploadFile>,
mirror_artifacts: &DashMap<String, MirrorArtifact>,
) -> Result<PartialVersionInfo, Error> {
tracing::trace!(
"Reading forge installer for {}",
loader.loader_version
);
type ZipFileReader = async_zip::base::read::seek::ZipFileReader<
Cursor<bytes::Bytes>,
>;
let cursor = Cursor::new(raw);
let mut zip = ZipFileReader::new(cursor).await?;
#[tracing::instrument(skip(zip))]
async fn read_file(
zip: &mut ZipFileReader,
file_name: &str,
) -> Result<Option<Vec<u8>>, Error> {
let zip_index_option =
zip.file().entries().iter().position(|f| {
f.filename().as_str().unwrap_or_default() == file_name
});
if let Some(zip_index) = zip_index_option {
let mut buffer = Vec::new();
let mut reader = zip.reader_with_entry(zip_index).await?;
reader.read_to_end_checked(&mut buffer).await?;
Ok(Some(buffer))
} else {
Ok(None)
}
}
#[tracing::instrument(skip(zip))]
async fn read_json<T: DeserializeOwned>(
zip: &mut ZipFileReader,
file_name: &str,
) -> Result<Option<T>, Error> {
if let Some(file) = read_file(zip, file_name).await? {
Ok(Some(serde_json::from_slice(&file)?))
} else {
Ok(None)
}
}
if loader.format_version == 1 {
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct ForgeInstallerProfileInstallDataV1 {
// pub mirror_list: String,
// pub target: String,
/// Path to the Forge universal library
pub file_path: String,
// pub logo: String,
// pub welcome: String,
// pub version: String,
/// Maven coordinates of the Forge universal library
pub path: String,
// pub profile_name: String,
pub minecraft: String,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct ForgeInstallerProfileManifestV1 {
pub id: String,
pub libraries: Vec<daedalus::minecraft::Library>,
pub main_class: Option<String>,
pub minecraft_arguments: Option<String>,
pub release_time: DateTime<Utc>,
pub time: DateTime<Utc>,
pub type_: daedalus::minecraft::VersionType,
// pub assets: Option<String>,
// pub inherits_from: Option<String>,
// pub jar: Option<String>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct ForgeInstallerProfileV1 {
pub install: ForgeInstallerProfileInstallDataV1,
pub version_info: ForgeInstallerProfileManifestV1,
}
let install_profile = read_json::<ForgeInstallerProfileV1>(
&mut zip,
"install_profile.json",
)
.await?
.ok_or_else(|| {
crate::ErrorKind::InvalidInput(format!(
"No install_profile.json present for loader {}",
loader.installer_url
))
})?;
let forge_library =
read_file(&mut zip, &install_profile.install.file_path)
.await?
.ok_or_else(|| {
crate::ErrorKind::InvalidInput(format!(
"No forge library present for loader {}",
loader.installer_url
))
})?;
upload_files.insert(
format!(
"maven/{}",
get_path_from_artifact(&install_profile.install.path)?
),
UploadFile {
file: bytes::Bytes::from(forge_library),
content_type: None,
},
);
Ok(PartialVersionInfo {
id: install_profile.version_info.id,
inherits_from: install_profile.install.minecraft,
release_time: install_profile.version_info.release_time,
time: install_profile.version_info.time,
main_class: install_profile.version_info.main_class,
minecraft_arguments: install_profile
.version_info
.minecraft_arguments
.clone(),
arguments: install_profile
.version_info
.minecraft_arguments
.map(|x| {
[(
daedalus::minecraft::ArgumentType::Game,
x.split(' ')
.map(|x| {
daedalus::minecraft::Argument::Normal(
x.to_string(),
)
})
.collect(),
)]
.iter()
.cloned()
.collect()
}),
libraries: install_profile
.version_info
.libraries
.into_iter()
.map(|mut lib| {
// For all libraries besides the forge lib extracted, we mirror them from maven servers
// unless the URL is empty/null or available on Minecraft's servers
if let Some(ref url) = lib.url {
if lib.name == install_profile.install.path {
lib.url = Some(format_url("maven/"));
} else if !url.is_empty()
&& !url.contains(
"https://libraries.minecraft.net/",
)
{
insert_mirrored_artifact(
&lib.name,
None,
vec![
url.clone(),
"https://maven.creeperhost.net/"
.to_string(),
maven_url.to_string(),
],
false,
mirror_artifacts,
)?;
lib.url = Some(format_url("maven/"));
}
}
Ok(lib)
})
.collect::<Result<Vec<_>, Error>>()?,
type_: install_profile.version_info.type_,
data: None,
processors: None,
})
} else if loader.format_version == 2 {
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct ForgeInstallerProfileV2 {
// pub spec: i32,
// pub profile: String,
// pub version: String,
// pub json: String,
// pub path: Option<String>,
// pub minecraft: String,
pub data: HashMap<String, daedalus::modded::SidedDataEntry>,
pub libraries: Vec<daedalus::minecraft::Library>,
pub processors: Vec<daedalus::modded::Processor>,
}
let install_profile = read_json::<ForgeInstallerProfileV2>(
&mut zip,
"install_profile.json",
)
.await?
.ok_or_else(|| {
crate::ErrorKind::InvalidInput(format!(
"No install_profile.json present for loader {}",
loader.installer_url
))
})?;
let mut version_info =
read_json::<PartialVersionInfo>(&mut zip, "version.json")
.await?
.ok_or_else(|| {
crate::ErrorKind::InvalidInput(format!(
"No version.json present for loader {}",
loader.installer_url
))
})?;
version_info.processors = Some(install_profile.processors);
version_info.libraries.extend(
install_profile.libraries.into_iter().map(|mut x| {
x.include_in_classpath = false;
x
}),
);
async fn mirror_forge_library(
mut zip: ZipFileReader,
mut lib: daedalus::minecraft::Library,
maven_url: &str,
upload_files: &DashMap<String, UploadFile>,
mirror_artifacts: &DashMap<String, MirrorArtifact>,
) -> Result<daedalus::minecraft::Library, Error>
{
let artifact_path = get_path_from_artifact(&lib.name)?;
if let Some(ref mut artifact) =
lib.downloads.as_mut().and_then(|x| x.artifact.as_mut())
{
if !artifact.url.is_empty() {
insert_mirrored_artifact(
&lib.name,
Some(artifact.sha1.clone()),
vec![artifact.url.clone()],
true,
mirror_artifacts,
)?;
artifact.url =
format_url(&format!("maven/{}", artifact_path));
return Ok(lib);
}
} else if let Some(url) = &lib.url {
if !url.is_empty() {
insert_mirrored_artifact(
&lib.name,
None,
vec![
url.clone(),
"https://libraries.minecraft.net/"
.to_string(),
"https://maven.creeperhost.net/"
.to_string(),
maven_url.to_string(),
],
false,
mirror_artifacts,
)?;
lib.url = Some(format_url("maven/"));
return Ok(lib);
}
}
// Other libraries are generally available in the "maven" directory of the installer. If they are
// not present here, they will be generated by Forge processors.
let extract_path = format!("maven/{artifact_path}");
if let Some(file) =
read_file(&mut zip, &extract_path).await?
{
upload_files.insert(
extract_path,
UploadFile {
file: bytes::Bytes::from(file),
content_type: None,
},
);
lib.url = Some(format_url("maven/"));
} else {
lib.downloadable = false;
}
Ok(lib)
}
version_info.libraries = futures::future::try_join_all(
version_info.libraries.into_iter().map(|lib| {
mirror_forge_library(
zip.clone(),
lib,
maven_url,
upload_files,
mirror_artifacts,
)
}),
)
.await?;
// In Minecraft Forge modern installers, processors are run during the install process. Some processors
// are extracted from the installer JAR. This function finds these files, extracts them, and uploads them
// and registers them as libraries instead.
// Ex:
// "BINPATCH": {
// "client": "/data/client.lzma",
// "server": "/data/server.lzma"
// },
// Becomes:
// "BINPATCH": {
// "client": "[net.minecraftforge:forge:1.20.3-49.0.1:shim:client@lzma]",
// "server": "[net.minecraftforge:forge:1.20.3-49.0.1:shim:server@lzma]"
// },
// And the resulting library is added to the profile's libraries
let mut new_data = HashMap::new();
for (key, entry) in install_profile.data {
async fn extract_data(
zip: &mut ZipFileReader,
key: &str,
value: &str,
upload_files: &DashMap<String, UploadFile>,
libs: &mut Vec<daedalus::minecraft::Library>,
mod_loader: &str,
version: &ForgeVersion,
) -> Result<String, Error> {
let extract_file =
read_file(zip, &value[1..value.len()])
.await?
.ok_or_else(|| {
crate::ErrorKind::InvalidInput(format!(
"Unable reading data key {key} at path {value}",
))
})?;
let file_name = value.split('/').last()
.ok_or_else(|| {
crate::ErrorKind::InvalidInput(format!(
"Unable reading filename for data key {key} at path {value}",
))
})?;
let mut file = file_name.split('.');
let file_name = file.next()
.ok_or_else(|| {
crate::ErrorKind::InvalidInput(format!(
"Unable reading filename only for data key {key} at path {value}",
))
})?;
let ext = file.next()
.ok_or_else(|| {
crate::ErrorKind::InvalidInput(format!(
"Unable reading extension only for data key {key} at path {value}",
))
})?;
let path = format!(
"com.modrinth.daedalus:{}-installer-extracts:{}:{}@{}",
mod_loader,
version.raw,
file_name,
ext
);
upload_files.insert(
format!("maven/{}", get_path_from_artifact(&path)?),
UploadFile {
file: bytes::Bytes::from(extract_file),
content_type: None,
},
);
libs.push(daedalus::minecraft::Library {
downloads: None,
extract: None,
name: path.clone(),
url: Some(format_url("maven/")),
natives: None,
rules: None,
checksums: None,
include_in_classpath: false,
downloadable: true,
});
Ok(format!("[{path}]"))
}
let client = if entry.client.starts_with('/') {
extract_data(
&mut zip,
&key,
&entry.client,
upload_files,
&mut version_info.libraries,
mod_loader,
loader,
)
.await?
} else {
entry.client.clone()
};
let server = if entry.server.starts_with('/') {
extract_data(
&mut zip,
&key,
&entry.server,
upload_files,
&mut version_info.libraries,
mod_loader,
loader,
)
.await?
} else {
entry.server.clone()
};
new_data.insert(
key.clone(),
daedalus::modded::SidedDataEntry { client, server },
);
}
version_info.data = Some(new_data);
Ok(version_info)
} else {
Err(crate::ErrorKind::InvalidInput(format!(
"Unknown format version {} for loader {}",
loader.format_version, loader.installer_url
))
.into())
}
}
let forge_version_infos = futures::future::try_join_all(
forge_installers
.into_iter()
.enumerate()
.map(|(index, raw)| {
let loader = fetch_versions[index];
read_forge_installer(
raw,
loader,
maven_url,
mod_loader,
upload_files,
mirror_artifacts,
)
}),
)
.await?;
let serialized_version_manifests = forge_version_infos
.iter()
.map(|x| serde_json::to_vec(x).map(bytes::Bytes::from))
.collect::<Result<Vec<_>, serde_json::Error>>()?;
serialized_version_manifests
.into_iter()
.enumerate()
.for_each(|(index, bytes)| {
let loader = fetch_versions[index];
let version_path = format!(
"{mod_loader}/v{format_version}/versions/{}.json",
loader.loader_version
);
upload_files.insert(
version_path,
UploadFile {
file: bytes,
content_type: Some("application/json".to_string()),
},
);
});
let forge_manifest_path =
format!("{mod_loader}/v{format_version}/manifest.json",);
let manifest = daedalus::modded::Manifest {
game_versions: forge_versions
.into_iter()
.sorted_by(|a, b| b.game_version.cmp(&a.game_version))
.rev()
.chunk_by(|x| x.game_version.clone())
.into_iter()
.map(|(game_version, loaders)| daedalus::modded::Version {
id: game_version,
stable: true,
loaders: loaders
.map(|x| daedalus::modded::LoaderVersion {
url: format_url(&format!(
"{mod_loader}/v{format_version}/versions/{}.json",
x.loader_version
)),
id: x.loader_version,
stable: false,
})
.collect(),
})
.collect(),
};
upload_files.insert(
forge_manifest_path,
UploadFile {
file: bytes::Bytes::from(serde_json::to_vec(&manifest)?),
content_type: Some("application/json".to_string()),
},
);
}
Ok(())
}
#[derive(Debug)]
struct ForgeVersion {
pub format_version: usize,
pub raw: String,
pub loader_version: String,
pub game_version: String,
pub installer_url: String,
}

View File

@@ -0,0 +1,218 @@
use crate::util::{
format_url, upload_file_to_bucket, upload_url_to_bucket_mirrors,
REQWEST_CLIENT,
};
use daedalus::get_path_from_artifact;
use dashmap::{DashMap, DashSet};
use std::sync::Arc;
use tokio::sync::Semaphore;
use tracing_error::ErrorLayer;
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
mod error;
mod fabric;
mod forge;
mod minecraft;
pub mod util;
pub use error::{Error, ErrorKind, Result};
#[tokio::main]
async fn main() -> Result<()> {
dotenvy::dotenv().ok();
let subscriber = tracing_subscriber::registry()
.with(fmt::layer())
.with(EnvFilter::from_default_env())
.with(ErrorLayer::default());
tracing::subscriber::set_global_default(subscriber)?;
tracing::info!("Initialized tracing. Starting Daedalus!");
if check_env_vars() {
tracing::error!("Some environment variables are missing!");
return Ok(());
}
let semaphore = Arc::new(Semaphore::new(
dotenvy::var("CONCURRENCY_LIMIT")
.ok()
.and_then(|x| x.parse().ok())
.unwrap_or(10),
));
// path, upload file
let upload_files: DashMap<String, UploadFile> = DashMap::new();
// path, mirror artifact
let mirror_artifacts: DashMap<String, MirrorArtifact> = DashMap::new();
minecraft::fetch(semaphore.clone(), &upload_files, &mirror_artifacts)
.await?;
fabric::fetch_fabric(semaphore.clone(), &upload_files, &mirror_artifacts)
.await?;
fabric::fetch_quilt(semaphore.clone(), &upload_files, &mirror_artifacts)
.await?;
forge::fetch_neo(semaphore.clone(), &upload_files, &mirror_artifacts)
.await?;
forge::fetch_forge(semaphore.clone(), &upload_files, &mirror_artifacts)
.await?;
futures::future::try_join_all(upload_files.iter().map(|x| {
upload_file_to_bucket(
x.key().clone(),
x.value().file.clone(),
x.value().content_type.clone(),
&semaphore,
)
}))
.await?;
futures::future::try_join_all(mirror_artifacts.iter().map(|x| {
upload_url_to_bucket_mirrors(
format!("maven/{}", x.key()),
x.value()
.mirrors
.iter()
.map(|mirror| {
if mirror.entire_url {
mirror.path.clone()
} else {
format!("{}{}", mirror.path, x.key())
}
})
.collect(),
x.sha1.clone(),
&semaphore,
)
}))
.await?;
if dotenvy::var("CLOUDFLARE_INTEGRATION")
.ok()
.and_then(|x| x.parse::<bool>().ok())
.unwrap_or(false)
{
if let Ok(token) = dotenvy::var("CLOUDFLARE_TOKEN") {
if let Ok(zone_id) = dotenvy::var("CLOUDFLARE_ZONE_ID") {
let cache_clears = upload_files
.into_iter()
.map(|x| format_url(&x.0))
.chain(
mirror_artifacts
.into_iter()
.map(|x| format_url(&format!("maven/{}", x.0))),
)
.collect::<Vec<_>>();
// Cloudflare ratelimits cache clears to 500 files per request
for chunk in cache_clears.chunks(500) {
REQWEST_CLIENT.post(format!("https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache"))
.bearer_auth(&token)
.json(&serde_json::json!({
"files": chunk
}))
.send()
.await
.map_err(|err| {
ErrorKind::Fetch {
inner: err,
item: "cloudflare clear cache".to_string(),
}
})?
.error_for_status()
.map_err(|err| {
ErrorKind::Fetch {
inner: err,
item: "cloudflare clear cache".to_string(),
}
})?;
}
}
}
}
Ok(())
}
pub struct UploadFile {
file: bytes::Bytes,
content_type: Option<String>,
}
pub struct MirrorArtifact {
pub sha1: Option<String>,
pub mirrors: DashSet<Mirror>,
}
#[derive(Eq, PartialEq, Hash)]
pub struct Mirror {
path: String,
entire_url: bool,
}
#[tracing::instrument(skip(mirror_artifacts))]
pub fn insert_mirrored_artifact(
artifact: &str,
sha1: Option<String>,
mirrors: Vec<String>,
entire_url: bool,
mirror_artifacts: &DashMap<String, MirrorArtifact>,
) -> Result<()> {
let val = mirror_artifacts
.entry(get_path_from_artifact(artifact)?)
.or_insert(MirrorArtifact {
sha1,
mirrors: DashSet::new(),
});
for mirror in mirrors {
val.mirrors.insert(Mirror {
path: mirror,
entire_url,
});
}
Ok(())
}
fn check_env_vars() -> bool {
let mut failed = false;
fn check_var<T: std::str::FromStr>(var: &str) -> bool {
if dotenvy::var(var)
.ok()
.and_then(|s| s.parse::<T>().ok())
.is_none()
{
tracing::warn!(
"Variable `{}` missing in dotenvy or not of type `{}`",
var,
std::any::type_name::<T>()
);
true
} else {
false
}
}
failed |= check_var::<String>("BASE_URL");
failed |= check_var::<String>("S3_ACCESS_TOKEN");
failed |= check_var::<String>("S3_SECRET");
failed |= check_var::<String>("S3_URL");
failed |= check_var::<String>("S3_REGION");
failed |= check_var::<String>("S3_BUCKET_NAME");
if dotenvy::var("CLOUDFLARE_INTEGRATION")
.ok()
.and_then(|x| x.parse::<bool>().ok())
.unwrap_or(false)
{
failed |= check_var::<String>("CLOUDFLARE_TOKEN");
failed |= check_var::<String>("CLOUDFLARE_ZONE_ID");
}
failed
}

View File

@@ -0,0 +1,230 @@
use crate::util::fetch_json;
use crate::{
util::download_file, util::format_url, util::sha1_async, Error,
MirrorArtifact, UploadFile,
};
use daedalus::minecraft::{
merge_partial_library, Library, PartialLibrary, VersionInfo,
VersionManifest, VERSION_MANIFEST_URL,
};
use dashmap::DashMap;
use serde::Deserialize;
use std::sync::Arc;
use tokio::sync::Semaphore;
#[tracing::instrument(skip(semaphore, upload_files, _mirror_artifacts))]
pub async fn fetch(
semaphore: Arc<Semaphore>,
upload_files: &DashMap<String, UploadFile>,
_mirror_artifacts: &DashMap<String, MirrorArtifact>,
) -> Result<(), Error> {
let modrinth_manifest = fetch_json::<VersionManifest>(
&format_url(&format!(
"minecraft/v{}/manifest.json",
daedalus::minecraft::CURRENT_FORMAT_VERSION
)),
&semaphore,
)
.await
.ok();
let mojang_manifest =
fetch_json::<VersionManifest>(VERSION_MANIFEST_URL, &semaphore).await?;
// TODO: experimental snapshots: https://github.com/PrismLauncher/meta/blob/main/meta/common/mojang-minecraft-experiments.json
// TODO: old snapshots: https://github.com/PrismLauncher/meta/blob/main/meta/common/mojang-minecraft-old-snapshots.json
// We check Modrinth's version manifest and compare if the version 1) exists in Modrinth's database and 2) is unchanged
// If they are not, we will fetch them
let (fetch_versions, existing_versions) =
if let Some(mut modrinth_manifest) = modrinth_manifest {
let (mut fetch_versions, mut existing_versions) =
(Vec::new(), Vec::new());
for version in mojang_manifest.versions {
if let Some(index) = modrinth_manifest
.versions
.iter()
.position(|x| x.id == version.id)
{
let modrinth_version =
modrinth_manifest.versions.remove(index);
if modrinth_version
.original_sha1
.as_ref()
.map(|x| x == &version.sha1)
.unwrap_or(false)
{
existing_versions.push(modrinth_version);
} else {
fetch_versions.push(version);
}
} else {
fetch_versions.push(version);
}
}
(fetch_versions, existing_versions)
} else {
(mojang_manifest.versions, Vec::new())
};
if !fetch_versions.is_empty() {
let version_manifests = futures::future::try_join_all(
fetch_versions
.iter()
.map(|x| download_file(&x.url, Some(&x.sha1), &semaphore)),
)
.await?
.into_iter()
.map(|x| serde_json::from_slice(&x))
.collect::<Result<Vec<VersionInfo>, serde_json::Error>>()?;
// Patch libraries of Minecraft versions for M-series Mac Support, Better Linux Compatibility, etc
let library_patches = fetch_library_patches()?;
let patched_version_manifests = version_manifests
.into_iter()
.map(|mut x| {
if !library_patches.is_empty() {
let mut new_libraries = Vec::new();
for library in x.libraries {
let mut libs = patch_library(&library_patches, library);
new_libraries.append(&mut libs)
}
x.libraries = new_libraries
}
x
})
.collect::<Vec<_>>();
// serialize + compute hashes
let serialized_version_manifests = patched_version_manifests
.iter()
.map(|x| serde_json::to_vec(x).map(bytes::Bytes::from))
.collect::<Result<Vec<_>, serde_json::Error>>()?;
let hashes_version_manifests = futures::future::try_join_all(
serialized_version_manifests
.iter()
.map(|x| sha1_async(x.clone())),
)
.await?;
// We upload the new version manifests and add them to the versions list
let mut new_versions = patched_version_manifests
.into_iter()
.zip(serialized_version_manifests.into_iter())
.zip(hashes_version_manifests.into_iter())
.map(|((version, bytes), hash)| {
let version_path = format!(
"minecraft/v{}/versions/{}.json",
daedalus::minecraft::CURRENT_FORMAT_VERSION,
version.id
);
let url = format_url(&version_path);
upload_files.insert(
version_path,
UploadFile {
file: bytes,
content_type: Some("application/json".to_string()),
},
);
daedalus::minecraft::Version {
original_sha1: fetch_versions
.iter()
.find(|x| x.id == version.id)
.map(|x| x.sha1.clone()),
id: version.id,
type_: version.type_,
url,
time: version.time,
release_time: version.release_time,
sha1: hash,
compliance_level: 1,
}
})
.chain(existing_versions.into_iter())
.collect::<Vec<_>>();
new_versions.sort_by(|a, b| b.release_time.cmp(&a.release_time));
// create and upload the new manifest
let version_manifest_path = format!(
"minecraft/v{}/manifest.json",
daedalus::minecraft::CURRENT_FORMAT_VERSION
);
let new_manifest = VersionManifest {
latest: mojang_manifest.latest,
versions: new_versions,
};
upload_files.insert(
version_manifest_path,
UploadFile {
file: bytes::Bytes::from(serde_json::to_vec(&new_manifest)?),
content_type: Some("application/json".to_string()),
},
);
}
Ok(())
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct LibraryPatch {
#[serde(rename = "_comment")]
pub _comment: String,
#[serde(rename = "match")]
pub match_: Vec<String>,
pub additional_libraries: Option<Vec<Library>>,
#[serde(rename = "override")]
pub override_: Option<PartialLibrary>,
pub patch_additional_libraries: Option<bool>,
}
fn fetch_library_patches() -> Result<Vec<LibraryPatch>, Error> {
let patches = include_bytes!("../library-patches.json");
Ok(serde_json::from_slice(patches)?)
}
pub fn patch_library(
patches: &Vec<LibraryPatch>,
mut library: Library,
) -> Vec<Library> {
let mut val = Vec::new();
let actual_patches = patches
.iter()
.filter(|x| x.match_.contains(&library.name))
.collect::<Vec<_>>();
if !actual_patches.is_empty() {
for patch in actual_patches {
if let Some(override_) = &patch.override_ {
library = merge_partial_library(override_.clone(), library);
}
if let Some(additional_libraries) = &patch.additional_libraries {
for additional_library in additional_libraries {
if patch.patch_additional_libraries.unwrap_or(false) {
let mut libs =
patch_library(patches, additional_library.clone());
val.append(&mut libs)
} else {
val.push(additional_library.clone());
}
}
}
}
val.push(library);
} else {
val.push(library);
}
val
}

View File

@@ -0,0 +1,234 @@
use crate::{Error, ErrorKind};
use bytes::Bytes;
use s3::creds::Credentials;
use s3::{Bucket, Region};
use serde::de::DeserializeOwned;
use std::sync::Arc;
use tokio::sync::Semaphore;
lazy_static::lazy_static! {
static ref BUCKET : Bucket = {
let region = dotenvy::var("S3_REGION").unwrap();
let b = Bucket::new(
&dotenvy::var("S3_BUCKET_NAME").unwrap(),
if &*region == "r2" {
Region::R2 {
account_id: dotenvy::var("S3_URL").unwrap(),
}
} else {
Region::Custom {
region: region.clone(),
endpoint: dotenvy::var("S3_URL").unwrap(),
}
},
Credentials::new(
Some(&*dotenvy::var("S3_ACCESS_TOKEN").unwrap()),
Some(&*dotenvy::var("S3_SECRET").unwrap()),
None,
None,
None,
).unwrap(),
).unwrap();
if region == "path-style" {
b.with_path_style()
} else {
b
}
};
}
lazy_static::lazy_static! {
pub static ref REQWEST_CLIENT: reqwest::Client = {
let mut headers = reqwest::header::HeaderMap::new();
if let Ok(header) = reqwest::header::HeaderValue::from_str(&format!(
"modrinth/daedalus/{} (support@modrinth.com)",
env!("CARGO_PKG_VERSION")
)) {
headers.insert(reqwest::header::USER_AGENT, header);
}
reqwest::Client::builder()
.tcp_keepalive(Some(std::time::Duration::from_secs(10)))
.timeout(std::time::Duration::from_secs(15))
.default_headers(headers)
.build()
.unwrap()
};
}
#[tracing::instrument(skip(bytes, semaphore))]
pub async fn upload_file_to_bucket(
path: String,
bytes: Bytes,
content_type: Option<String>,
semaphore: &Arc<Semaphore>,
) -> Result<(), Error> {
let _permit = semaphore.acquire().await?;
let key = path.clone();
const RETRIES: i32 = 3;
for attempt in 1..=(RETRIES + 1) {
tracing::trace!("Attempting file upload, attempt {attempt}");
let result = if let Some(ref content_type) = content_type {
BUCKET
.put_object_with_content_type(key.clone(), &bytes, content_type)
.await
} else {
BUCKET.put_object(key.clone(), &bytes).await
}
.map_err(|err| ErrorKind::S3 {
inner: err,
file: path.clone(),
});
match result {
Ok(_) => return Ok(()),
Err(_) if attempt <= RETRIES => continue,
Err(_) => {
result?;
}
}
}
unreachable!()
}
pub async fn upload_url_to_bucket_mirrors(
upload_path: String,
mirrors: Vec<String>,
sha1: Option<String>,
semaphore: &Arc<Semaphore>,
) -> Result<(), Error> {
if mirrors.is_empty() {
return Err(ErrorKind::InvalidInput(
"No mirrors provided!".to_string(),
)
.into());
}
for (index, mirror) in mirrors.iter().enumerate() {
let result = upload_url_to_bucket(
upload_path.clone(),
mirror.clone(),
sha1.clone(),
semaphore,
)
.await;
if result.is_ok() || (result.is_err() && index == (mirrors.len() - 1)) {
return result;
}
}
unreachable!()
}
#[tracing::instrument(skip(semaphore))]
pub async fn upload_url_to_bucket(
path: String,
url: String,
sha1: Option<String>,
semaphore: &Arc<Semaphore>,
) -> Result<(), Error> {
let data = download_file(&url, sha1.as_deref(), semaphore).await?;
upload_file_to_bucket(path, data, None, semaphore).await?;
Ok(())
}
#[tracing::instrument(skip(bytes))]
pub async fn sha1_async(bytes: Bytes) -> Result<String, Error> {
let hash = tokio::task::spawn_blocking(move || {
sha1_smol::Sha1::from(bytes).hexdigest()
})
.await?;
Ok(hash)
}
#[tracing::instrument(skip(semaphore))]
pub async fn download_file(
url: &str,
sha1: Option<&str>,
semaphore: &Arc<Semaphore>,
) -> Result<bytes::Bytes, crate::Error> {
let _permit = semaphore.acquire().await?;
tracing::trace!("Starting file download");
const RETRIES: u32 = 10;
for attempt in 1..=(RETRIES + 1) {
let result = REQWEST_CLIENT
.get(url.replace("http://", "https://"))
.send()
.await
.and_then(|x| x.error_for_status());
match result {
Ok(x) => {
let bytes = x.bytes().await;
if let Ok(bytes) = bytes {
if let Some(sha1) = sha1 {
if &*sha1_async(bytes.clone()).await? != sha1 {
if attempt <= 3 {
continue;
} else {
return Err(
crate::ErrorKind::ChecksumFailure {
hash: sha1.to_string(),
url: url.to_string(),
tries: attempt,
}
.into(),
);
}
}
}
return Ok(bytes);
} else if attempt <= RETRIES {
continue;
} else if let Err(err) = bytes {
return Err(crate::ErrorKind::Fetch {
inner: err,
item: url.to_string(),
}
.into());
}
}
Err(_) if attempt <= RETRIES => continue,
Err(err) => {
return Err(crate::ErrorKind::Fetch {
inner: err,
item: url.to_string(),
}
.into())
}
}
}
unreachable!()
}
pub async fn fetch_json<T: DeserializeOwned>(
url: &str,
semaphore: &Arc<Semaphore>,
) -> Result<T, Error> {
Ok(serde_json::from_slice(
&download_file(url, None, semaphore).await?,
)?)
}
pub async fn fetch_xml<T: DeserializeOwned>(
url: &str,
semaphore: &Arc<Semaphore>,
) -> Result<T, Error> {
Ok(serde_xml_rs::from_reader(
&*download_file(url, None, semaphore).await?,
)?)
}
pub fn format_url(path: &str) -> String {
format!("{}/{}", &*dotenvy::var("BASE_URL").unwrap(), path)
}

21
apps/docs/.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store

4
apps/docs/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

11
apps/docs/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

34
apps/docs/LICENSE Normal file
View File

@@ -0,0 +1,34 @@
Creative Commons Legal Code
CC0 1.0 Universal
Official translations of this legal tool are available
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others.
For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following:
the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work;
moral rights retained by the original author(s) and/or performer(s);
publicity and privacy rights pertaining to a person's image or likeness depicted in a Work;
rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below;
rights protecting the extraction, dissemination, use and reuse of data in a Work;
database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and
other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose.
4. Limitations and Disclaimers.
No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document.
Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law.
Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work.
Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work.

23
apps/docs/README.md Normal file
View File

@@ -0,0 +1,23 @@
# Modrinth Documentation
Welcome to the Modrinth documentation!
## Development
### Pre-requisites
Before you begin, ensure you have the following installed on your machine:
- [Node.js](https://nodejs.org/en/)
- [pnpm](https://pnpm.io/)
### Setup
Follow these steps to set up your development environment:
```bash
pnpm install
pnpm docs:dev
```
You should now have a development build of the documentation site running with hot-reloading enabled. Any changes you make to the code will automatically refresh the browser.

View File

@@ -0,0 +1,52 @@
import starlight from '@astrojs/starlight'
import { defineConfig } from 'astro/config'
import starlightOpenAPI, { openAPISidebarGroups } from 'starlight-openapi'
// https://astro.build/config
export default defineConfig({
site: 'https://docs.modrinth.com',
integrations: [
starlight({
title: 'Modrinth Documentation',
favicon: '/favicon.ico',
editLink: {
baseUrl: 'https://github.com/modrinth/code/edit/main/apps/docs/',
},
social: {
github: 'https://github.com/modrinth/code',
discord: 'https://discord.modrinth.com',
'x.com': 'https://x.com/modrinth',
mastodon: 'https://floss.social/@modrinth',
threads: 'https://threads.net/@modrinth',
},
logo: {
light: './src/assets/light-logo.svg',
dark: './src/assets/dark-logo.svg',
replacesTitle: true,
},
customCss: [
'@modrinth/assets/styles/variables.scss',
'@modrinth/assets/styles/inter.scss',
'./src/styles/modrinth.css',
],
plugins: [
// Generate the OpenAPI documentation pages.
starlightOpenAPI([
{
base: 'api',
label: 'Modrinth API',
schema: './public/openapi.yaml',
},
]),
],
sidebar: [
{
label: 'Contributing to Modrinth',
autogenerate: { directory: 'contributing' },
},
// Add the generated sidebar group to the sidebar.
...openAPISidebarGroups,
],
}),
],
})

21
apps/docs/package.json Normal file
View File

@@ -0,0 +1,21 @@
{
"name": "@modrinth/docs",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro check && astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/check": "^0.9.3",
"@astrojs/starlight": "^0.26.3",
"@modrinth/assets": "workspace:*",
"astro": "^4.10.2",
"sharp": "^0.32.5",
"starlight-openapi": "^0.7.0",
"typescript": "^5.5.4"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,79 @@
- type: text
text: https://cdn.discordapp.com/attachments/734084240408444949/975414177550200902/welcome-channel.png
- type: embed
embeds:
- title: __Welcome to Modrinth's Discord server!__
url: https://modrinth.com
color: 0x1bd96a
description: "Modrinth is the place for Minecraft mods, plugins, data packs, shaders, resource packs, and
modpacks. Discover, play, and share Minecraft content through our open-source platform built for the community."
- type: embed
embeds:
- title: "**:scroll: __Rules__**"
color: 0x4f9cff
description: "Modrinth's rules are easy to follow. Despite this, please keep in mind that this is not an entirely
open forum. First and foremost, this Discord server is intended to facilitate the development of Modrinth and
for communication regarding Modrinth. Ultimately, it is up to the discretion of the moderators whether your
messages are in violation of our rules.\n\n
Modrinth's rules are split up into two categories: the **__DOs__** and the **__DO NOTs__**."
- title: ":white_check_mark: Do:"
color: 0x1bd96a
description: >-
1. Treat every user with respect and consider the opinions and viewpoints of others
2. Stay on-topic in all channels; all channels are only for discussion of **Modrinth itself** with the
exceptions of <#783091855616901200>, <#1109517383074328686>, and <#1061855024252207167>
3. Follow Discord's rules, including the [Community Guidelines](https://discord.com/guidelines) and the [Terms
of Service](https://discord.com/terms) (this also means that discussions regarding "cracked" launchers and
Discord client modifications are **prohibited under all circumstances**)
4. Contact the moderators at any time via the <@&895382919772766219> ping
5. Respect the use of accessibility and self-identity tools such as [PluralKit](https://pluralkit.me)
- title: ":no_entry: Do not:"
color: 0xff496e
description: >-
6. Harass, bother, provoke, or insult anyone, including by sending unsolicited DMs or friend requests
7. Cause problems or impede Modrinth's development
8. Discuss drama from other places, including bashing or hating on other websites and platforms (though
constructive criticism for the betterment of Modrinth is encouraged)
9. Report Modrinth content in the Discord (use the Report button on the website)
10. Assume staff member's opinions reflect those of Modrinth
- title: ":pencil2: Nickname policy:"
color: 0xffa347
description: >-
We want to keep this server clean and therefore require that display names of all members on the server are
readable, accessible, and free of attention-seeking elements, which includes, but is not limited to, display
names that begin with hoisting characters, have an excessive number of emojis in them, or use "fancy fonts",
"glitch effects" and any other Unicode characters, which are either very inaccessible to screen readers or cause
annoyance to other members.
When we find that your display name does not adhere to this policy, we will try to correct it by changing your
nickname on the server. Repetitive attempts to revert to a violating display name may result in your removal
from the server. We will also permanently remove any users whose profiles contain inappropriate content.
- type: links
color: 0x4f9cff
title: "**:link: __Links__**"
links:
Website: https://modrinth.com
Support: https://support.modrinth.com
Status page: https://status.modrinth.com
Roadmap: https://roadmap.modrinth.com
Blog and newsletter: https://blog.modrinth.com/subscribe?utm_medium=social&utm_source=discord&utm_campaign=welcome
API documentation: https://docs.modrinth.com
Modrinth source code: https://github.com/modrinth
Help translate Modrinth: https://crowdin.com/project/modrinth
Follow Modrinth on Mastodon: https://floss.social/@modrinth
Follow Modrinth on Twitter: https://twitter.com/modrinth
- type: text
text: |
**Main server:** <https://discord.modrinth.com>
**Testing server:** <https://discord.modrinth.com/labs>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1558 207.2">
<!-- Generator: Adobe Illustrator 28.6.0, SVG Export Plug-In . SVG Version: 1.2.0 Build 709) -->
<g>
<g id="Layer_1">
<g id="icon" fill="#1bd96a">
<path d="M146.1,109.8l-14.4,17.6-24.1,7.6-10.8-12-61.9,37.1c-2.9-3.8-6.4-9.1-8.5-14.3l61.7-37-5.6-14.9,17.6-18.1,22.3-4.8,6.4,7.9-10.3,10.4-8.9,2.8-6.4,6.6,3.1,8.7,6.4,6.8,9-2.4,6.4-7,13.9-4.4s4.1,9.3,4.1,9.3Z" fill-rule="evenodd"/>
<path d="M208.6,86.6l-48.9,13.2c-.8-4.7-1.6-8.9-4.3-16.1l49-13.2c2.1,5.5,3.5,10.9,4.2,16.1Z" fill-rule="evenodd"/>
<path d="M33.7,110.5c3.5,41,37.9,73.2,79.7,73.2s59-18.4,72-45.1l15.9,5.5c-15.3,33.2-48.9,56.3-87.9,56.3S20.3,160.7,16.8,110.5c0,0,16.8,0,16.8,0ZM17.1,93.8C22,45,63.3,6.8,113.4,6.8s96.8,43.4,96.8,96.8-1.1,16.9-3.2,24.8l-15.9-5.5c1.6-6.3,2.4-12.8,2.3-19.3,0-44.2-35.9-80-80-80S38.8,54.3,34,93.8h-16.9,0Z" fill-rule="evenodd"/>
<path d="M113.1,57c-25.7,0-46.6,20.9-46.6,46.7s20.9,46.7,46.7,46.7,2.6,0,3.9-.2l4.7,16.3c-2.8.4-5.7.6-8.5.6-35,0-63.4-28.4-63.4-63.4s28.4-63.4,63.4-63.4,1.7,0,2.6,0c0,0-2.6,16.7-2.6,16.7ZM132.2,43.2c25.7,8.1,44.4,32.1,44.4,60.5s-16,48.8-38.7,58.4l-4.6-16.2c15.8-7.5,26.7-23.6,26.7-42.2s-12.6-37.1-30.3-43.7l2.7-16.8h0Z" fill-rule="evenodd"/>
</g>
<path id="modrinth" fill="#fff" d="M362,74.2c11.7,0,21.1,3.3,27.9,10.1,6.8,7,10.3,17.1,10.3,30.6v53.3h-21.8v-50.5c0-8.2-1.7-14.3-5.4-18.4-3.6-4-8.7-6.1-15.5-6.1s-13.2,2.4-17.6,7.1c-4.4,4.9-6.6,11.8-6.6,20.7v47.2h-21.8v-50.5c0-8.2-1.7-14.3-5.4-18.4-3.6-4-8.7-6.1-15.5-6.1s-13.2,2.4-17.6,7.1c-4.4,4.7-6.6,11.7-6.6,20.7v47.2h-21.8v-93h20.7v11.8c3.5-4.2,7.8-7.3,13.1-9.6,5.2-2.3,11-3.3,17.4-3.3s13.2,1.2,18.6,3.8c5.4,2.8,9.6,6.6,12.7,11.7,3.8-4.9,8.7-8.7,14.8-11.5,6.1-2.6,12.7-4,20-4h0ZM470.1,169.4c-9.4,0-17.9-2.1-25.4-6.1-7.4-3.9-13.5-9.8-17.6-17.1-4.4-7.1-6.4-15.3-6.4-24.6s2.1-17.4,6.4-24.5c4.2-7.2,10.3-13,17.6-16.9,7.5-4,16-6.1,25.4-6.1s18.1,2.1,25.6,6.1c7.5,4,13.4,9.8,17.8,16.9,4.2,7.1,6.3,15.3,6.3,24.5s-2.1,17.4-6.3,24.6c-4.4,7.3-10.3,13.1-17.8,17.1-7.5,4-16,6.1-25.6,6.1h0ZM470.1,150.8c8,0,14.6-2.6,19.9-8s7.8-12.4,7.8-21.1-2.6-15.7-7.8-21.1-11.8-8-19.9-8-14.6,2.6-19.7,8c-5.2,5.4-7.8,12.4-7.8,21.1s2.6,15.7,7.8,21.1c5,5.4,11.7,8,19.7,8ZM631.2,39v129.2h-20.9v-12c-3.6,4.3-8.1,7.7-13.2,9.9-5.4,2.3-11.1,3.3-17.6,3.3s-16.9-1.9-24-5.9-12.9-9.6-16.9-16.9c-4-7.1-6.1-15.5-6.1-24.9s2.1-17.8,6.1-24.9c4-7.1,9.8-12.7,16.9-16.7s15.1-5.9,24-5.9,11.8,1.1,16.9,3.1c5,2.1,9.5,5.3,13.1,9.4v-47.7h21.8,0ZM582.1,150.8c5.2,0,9.9-1.2,14.1-3.7,4.2-2.3,7.5-5.7,9.9-10.1,2.4-4.4,3.7-9.6,3.7-15.3s-1.2-11-3.7-15.3c-2.4-4.3-5.8-7.8-9.9-10.3-4.2-2.3-8.9-3.5-14.1-3.5s-9.9,1.2-14.1,3.5c-4.2,2.4-7.5,5.9-9.9,10.3-2.4,4.3-3.7,9.6-3.7,15.3s1.2,11,3.7,15.3c2.4,4.4,5.8,7.8,9.9,10.1,4.2,2.4,8.9,3.7,14.1,3.7ZM679.9,88.8c6.3-9.8,17.2-14.6,33.1-14.6v20.7c-1.7-.3-3.3-.5-5-.5-8.5,0-15.2,2.4-19.8,7.3-4.7,5.1-7.1,12.2-7.1,21.4v45.1h-21.8v-93h20.7v13.6h0ZM730.9,75.2h21.8v93h-21.8s0-93,0-93ZM741.9,59.9c-4,0-7.3-1.2-9.9-3.8-2.6-2.4-4-5.7-4-9.2,0-3.7,1.4-6.8,4-9.4,2.6-2.4,5.9-3.7,9.9-3.7s7.3,1.2,9.9,3.5c2.6,2.4,4,5.4,4,9s-1.2,7-3.8,9.6c-2.6,2.6-6.1,4-10.1,4ZM833.5,74.2c11.7,0,21.1,3.5,28.2,10.3s10.6,17.1,10.6,30.5v53.3h-21.8v-50.5c0-8.2-1.9-14.3-5.7-18.4-3.8-4-9.2-6.1-16.4-6.1s-14.3,2.4-19,7.1c-4.7,4.9-7,11.8-7,20.9v47h-21.8v-93h20.7v12c3.7-4.3,8.2-7.5,13.6-9.8s11.7-3.3,18.5-3.3h0ZM955.3,163.2c-2.7,2.2-5.9,3.8-9.2,4.7-3.8,1.1-7.7,1.6-11.7,1.6-10.3,0-18.1-2.6-23.7-8-5.6-5.4-8.4-13.1-8.4-23.3v-44.8h-15.3v-17.4h15.3v-21.2h21.8v21.2h24.9v17.4h-24.9v44.2c0,4.5,1,8,3.3,10.3,2.3,2.4,5.4,3.6,9.6,3.6s8.9-1.2,12.2-3.8l6.1,15.5h0ZM1025.5,74.2c11.7,0,21.1,3.5,28.2,10.3s10.6,17.1,10.6,30.5v53.3h-21.8v-50.5c0-8.2-1.9-14.3-5.8-18.4-3.8-4-9.2-6.1-16.4-6.1s-14.3,2.4-19,7.1c-4.7,4.9-7,11.8-7,20.9v47h-21.8V39h21.8v47c3.7-3.8,8-6.8,13.4-8.9,5.2-1.9,11.1-3,17.6-3h0Z"/>
<g id="docs" fill="#1bd96a">
<path d="M1177.8,169.7c-8.9,0-16.9-2-24-6-7.1-4-12.7-9.5-16.7-16.6-4-7.1-6.1-15.4-6.1-25s2-17.9,6.1-25c4-7.1,9.6-12.6,16.7-16.5,7.1-3.9,15.1-5.9,24-5.9s14.7,1.7,20.8,5.1c6.1,3.4,11,8.6,14.6,15.5,3.6,6.9,5.4,15.9,5.4,26.7s-1.7,19.6-5.2,26.6c-3.5,7-8.3,12.2-14.4,15.7-6.1,3.5-13.2,5.2-21.2,5.2ZM1180.4,151.2c5.2,0,9.9-1.2,14-3.5,4.1-2.3,7.4-5.7,9.9-10.1,2.5-4.4,3.7-9.5,3.7-15.4s-1.2-11.2-3.7-15.5c-2.5-4.3-5.8-7.7-9.9-10-4.1-2.3-8.8-3.5-14-3.5s-9.9,1.2-14,3.5c-4.1,2.3-7.4,5.6-9.9,10-2.5,4.3-3.7,9.5-3.7,15.5s1.2,11,3.7,15.4c2.5,4.4,5.8,7.8,9.9,10.1,4.1,2.3,8.8,3.5,14,3.5ZM1208.6,168.5v-21.9l.9-24.6-1.7-24.6v-57.6h21.5v128.8h-20.7Z"/>
<path d="M1299.5,169.7c-9.5,0-17.9-2.1-25.3-6.2-7.4-4.1-13.2-9.7-17.5-16.9-4.3-7.2-6.4-15.3-6.4-24.5s2.1-17.4,6.4-24.6c4.3-7.1,10.1-12.7,17.5-16.7,7.4-4,15.8-6.1,25.3-6.1s18.1,2,25.6,6.1c7.5,4.1,13.3,9.6,17.5,16.7,4.2,7.1,6.3,15.3,6.3,24.6s-2.1,17.3-6.3,24.5c-4.2,7.2-10.1,12.8-17.5,16.9-7.5,4.1-16,6.2-25.6,6.2ZM1299.5,151.2c5.3,0,10.1-1.2,14.2-3.5,4.2-2.3,7.4-5.7,9.8-10.1,2.4-4.4,3.6-9.5,3.6-15.4s-1.2-11.2-3.6-15.5c-2.4-4.3-5.6-7.7-9.8-10-4.2-2.3-8.8-3.5-14.1-3.5s-10,1.2-14.1,3.5c-4.1,2.3-7.4,5.6-9.8,10-2.4,4.3-3.6,9.5-3.6,15.5s1.2,11,3.6,15.4c2.4,4.4,5.7,7.8,9.8,10.1,4.1,2.3,8.8,3.5,14,3.5Z"/>
<path d="M1412.3,169.7c-9.7,0-18.4-2.1-25.9-6.2-7.6-4.1-13.5-9.7-17.8-16.9-4.3-7.2-6.4-15.3-6.4-24.5s2.1-17.4,6.4-24.6c4.3-7.1,10.2-12.7,17.8-16.7,7.6-4,16.2-6.1,25.9-6.1s17,1.8,23.9,5.5c6.9,3.6,12.1,9,15.7,16.1l-16.7,9.7c-2.8-4.4-6.2-7.6-10.2-9.7-4-2.1-8.3-3.1-12.9-3.1s-10.1,1.2-14.4,3.5c-4.3,2.3-7.6,5.6-10.1,10-2.4,4.3-3.6,9.5-3.6,15.5s1.2,11.2,3.6,15.5c2.4,4.3,5.8,7.7,10.1,10,4.3,2.3,9.1,3.5,14.4,3.5s8.9-1,12.9-3.1c4-2.1,7.4-5.3,10.2-9.7l16.7,9.7c-3.6,6.9-8.8,12.3-15.7,16.1-6.9,3.8-14.8,5.6-23.9,5.6Z"/>
<path d="M1499.2,169.7c-7.8,0-15.2-1-22.3-3-7.1-2-12.8-4.5-16.9-7.4l8.3-16.5c4.2,2.7,9.1,4.9,14.9,6.6,5.8,1.7,11.6,2.6,17.4,2.6s11.8-.9,14.8-2.8c3.1-1.8,4.6-4.3,4.6-7.5s-1-4.5-3.1-5.8c-2.1-1.3-4.8-2.3-8.2-3-3.4-.7-7.1-1.3-11.2-1.9-4.1-.6-8.2-1.4-12.3-2.3-4.1-1-7.8-2.4-11.2-4.3-3.4-1.9-6.1-4.5-8.2-7.7-2.1-3.2-3.1-7.5-3.1-12.8s1.7-11,5-15.4c3.4-4.3,8.1-7.7,14.1-10.1,6.1-2.4,13.3-3.6,21.6-3.6s12.6.7,18.9,2.1c6.4,1.4,11.6,3.4,15.8,5.9l-8.3,16.5c-4.4-2.7-8.8-4.5-13.3-5.5-4.5-1-8.9-1.5-13.3-1.5-6.6,0-11.5,1-14.7,2.9-3.2,2-4.8,4.5-4.8,7.5s1,4.9,3.1,6.2c2.1,1.4,4.8,2.5,8.2,3.3,3.4.8,7.1,1.5,11.2,2,4.1.5,8.2,1.3,12.2,2.3,4,1,7.8,2.5,11.2,4.3,3.4,1.8,6.2,4.3,8.2,7.5,2.1,3.2,3.1,7.5,3.1,12.7s-1.7,10.8-5.1,15.1c-3.4,4.3-8.2,7.6-14.5,10-6.2,2.4-13.7,3.6-22.4,3.6Z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1558 207.2">
<!-- Generator: Adobe Illustrator 28.6.0, SVG Export Plug-In . SVG Version: 1.2.0 Build 709) -->
<g>
<g id="Layer_1">
<g id="icon" fill="#00af5c">
<path d="M146.1,109.8l-14.4,17.6-24.1,7.6-10.8-12-61.9,37.1c-2.9-3.8-6.4-9.1-8.5-14.3l61.7-37-5.6-14.9,17.6-18.1,22.3-4.8,6.4,7.9-10.3,10.4-8.9,2.8-6.4,6.6,3.1,8.7,6.4,6.8,9-2.4,6.4-7,13.9-4.4s4.1,9.3,4.1,9.3Z" fill-rule="evenodd"/>
<path d="M208.6,86.6l-48.9,13.2c-.8-4.7-1.6-8.9-4.3-16.1l49-13.2c2.1,5.5,3.5,10.9,4.2,16.1Z" fill-rule="evenodd"/>
<path d="M33.7,110.5c3.5,41,37.9,73.2,79.7,73.2s59-18.4,72-45.1l15.9,5.5c-15.3,33.2-48.9,56.3-87.9,56.3S20.3,160.7,16.8,110.5c0,0,16.8,0,16.8,0ZM17.1,93.8C22,45,63.3,6.8,113.4,6.8s96.8,43.4,96.8,96.8-1.1,16.9-3.2,24.8l-15.9-5.5c1.6-6.3,2.4-12.8,2.3-19.3,0-44.2-35.9-80-80-80S38.8,54.3,34,93.8h-16.9,0Z" fill-rule="evenodd"/>
<path d="M113.1,57c-25.7,0-46.6,20.9-46.6,46.7s20.9,46.7,46.7,46.7,2.6,0,3.9-.2l4.7,16.3c-2.8.4-5.7.6-8.5.6-35,0-63.4-28.4-63.4-63.4s28.4-63.4,63.4-63.4,1.7,0,2.6,0c0,0-2.6,16.7-2.6,16.7ZM132.2,43.2c25.7,8.1,44.4,32.1,44.4,60.5s-16,48.8-38.7,58.4l-4.6-16.2c15.8-7.5,26.7-23.6,26.7-42.2s-12.6-37.1-30.3-43.7l2.7-16.8h0Z" fill-rule="evenodd"/>
</g>
<path id="modrinth" fill="#000" d="M362,74.2c11.7,0,21.1,3.3,27.9,10.1,6.8,7,10.3,17.1,10.3,30.6v53.3h-21.8v-50.5c0-8.2-1.7-14.3-5.4-18.4-3.6-4-8.7-6.1-15.5-6.1s-13.2,2.4-17.6,7.1c-4.4,4.9-6.6,11.8-6.6,20.7v47.2h-21.8v-50.5c0-8.2-1.7-14.3-5.4-18.4-3.6-4-8.7-6.1-15.5-6.1s-13.2,2.4-17.6,7.1c-4.4,4.7-6.6,11.7-6.6,20.7v47.2h-21.8v-93h20.7v11.8c3.5-4.2,7.8-7.3,13.1-9.6,5.2-2.3,11-3.3,17.4-3.3s13.2,1.2,18.6,3.8c5.4,2.8,9.6,6.6,12.7,11.7,3.8-4.9,8.7-8.7,14.8-11.5,6.1-2.6,12.7-4,20-4h0ZM470.1,169.4c-9.4,0-17.9-2.1-25.4-6.1-7.4-3.9-13.5-9.8-17.6-17.1-4.4-7.1-6.4-15.3-6.4-24.6s2.1-17.4,6.4-24.5c4.2-7.2,10.3-13,17.6-16.9,7.5-4,16-6.1,25.4-6.1s18.1,2.1,25.6,6.1c7.5,4,13.4,9.8,17.8,16.9,4.2,7.1,6.3,15.3,6.3,24.5s-2.1,17.4-6.3,24.6c-4.4,7.3-10.3,13.1-17.8,17.1-7.5,4-16,6.1-25.6,6.1h0ZM470.1,150.8c8,0,14.6-2.6,19.9-8s7.8-12.4,7.8-21.1-2.6-15.7-7.8-21.1-11.8-8-19.9-8-14.6,2.6-19.7,8c-5.2,5.4-7.8,12.4-7.8,21.1s2.6,15.7,7.8,21.1c5,5.4,11.7,8,19.7,8ZM631.2,39v129.2h-20.9v-12c-3.6,4.3-8.1,7.7-13.2,9.9-5.4,2.3-11.1,3.3-17.6,3.3s-16.9-1.9-24-5.9-12.9-9.6-16.9-16.9c-4-7.1-6.1-15.5-6.1-24.9s2.1-17.8,6.1-24.9c4-7.1,9.8-12.7,16.9-16.7s15.1-5.9,24-5.9,11.8,1.1,16.9,3.1c5,2.1,9.5,5.3,13.1,9.4v-47.7h21.8,0ZM582.1,150.8c5.2,0,9.9-1.2,14.1-3.7,4.2-2.3,7.5-5.7,9.9-10.1,2.4-4.4,3.7-9.6,3.7-15.3s-1.2-11-3.7-15.3c-2.4-4.3-5.8-7.8-9.9-10.3-4.2-2.3-8.9-3.5-14.1-3.5s-9.9,1.2-14.1,3.5c-4.2,2.4-7.5,5.9-9.9,10.3-2.4,4.3-3.7,9.6-3.7,15.3s1.2,11,3.7,15.3c2.4,4.4,5.8,7.8,9.9,10.1,4.2,2.4,8.9,3.7,14.1,3.7ZM679.9,88.8c6.3-9.8,17.2-14.6,33.1-14.6v20.7c-1.7-.3-3.3-.5-5-.5-8.5,0-15.2,2.4-19.8,7.3-4.7,5.1-7.1,12.2-7.1,21.4v45.1h-21.8v-93h20.7v13.6h0ZM730.9,75.2h21.8v93h-21.8s0-93,0-93ZM741.9,59.9c-4,0-7.3-1.2-9.9-3.8-2.6-2.4-4-5.7-4-9.2,0-3.7,1.4-6.8,4-9.4,2.6-2.4,5.9-3.7,9.9-3.7s7.3,1.2,9.9,3.5c2.6,2.4,4,5.4,4,9s-1.2,7-3.8,9.6c-2.6,2.6-6.1,4-10.1,4ZM833.5,74.2c11.7,0,21.1,3.5,28.2,10.3s10.6,17.1,10.6,30.5v53.3h-21.8v-50.5c0-8.2-1.9-14.3-5.7-18.4-3.8-4-9.2-6.1-16.4-6.1s-14.3,2.4-19,7.1c-4.7,4.9-7,11.8-7,20.9v47h-21.8v-93h20.7v12c3.7-4.3,8.2-7.5,13.6-9.8s11.7-3.3,18.5-3.3h0ZM955.3,163.2c-2.7,2.2-5.9,3.8-9.2,4.7-3.8,1.1-7.7,1.6-11.7,1.6-10.3,0-18.1-2.6-23.7-8-5.6-5.4-8.4-13.1-8.4-23.3v-44.8h-15.3v-17.4h15.3v-21.2h21.8v21.2h24.9v17.4h-24.9v44.2c0,4.5,1,8,3.3,10.3,2.3,2.4,5.4,3.6,9.6,3.6s8.9-1.2,12.2-3.8l6.1,15.5h0ZM1025.5,74.2c11.7,0,21.1,3.5,28.2,10.3s10.6,17.1,10.6,30.5v53.3h-21.8v-50.5c0-8.2-1.9-14.3-5.8-18.4-3.8-4-9.2-6.1-16.4-6.1s-14.3,2.4-19,7.1c-4.7,4.9-7,11.8-7,20.9v47h-21.8V39h21.8v47c3.7-3.8,8-6.8,13.4-8.9,5.2-1.9,11.1-3,17.6-3h0Z"/>
<g id="docs" fill="#00af5c">
<path d="M1177.8,169.7c-8.9,0-16.9-2-24-6-7.1-4-12.7-9.5-16.7-16.6-4-7.1-6.1-15.4-6.1-25s2-17.9,6.1-25c4-7.1,9.6-12.6,16.7-16.5,7.1-3.9,15.1-5.9,24-5.9s14.7,1.7,20.8,5.1c6.1,3.4,11,8.6,14.6,15.5,3.6,6.9,5.4,15.9,5.4,26.7s-1.7,19.6-5.2,26.6c-3.5,7-8.3,12.2-14.4,15.7-6.1,3.5-13.2,5.2-21.2,5.2ZM1180.4,151.2c5.2,0,9.9-1.2,14-3.5,4.1-2.3,7.4-5.7,9.9-10.1,2.5-4.4,3.7-9.5,3.7-15.4s-1.2-11.2-3.7-15.5c-2.5-4.3-5.8-7.7-9.9-10-4.1-2.3-8.8-3.5-14-3.5s-9.9,1.2-14,3.5c-4.1,2.3-7.4,5.6-9.9,10-2.5,4.3-3.7,9.5-3.7,15.5s1.2,11,3.7,15.4c2.5,4.4,5.8,7.8,9.9,10.1,4.1,2.3,8.8,3.5,14,3.5ZM1208.6,168.5v-21.9l.9-24.6-1.7-24.6v-57.6h21.5v128.8h-20.7Z"/>
<path d="M1299.5,169.7c-9.5,0-17.9-2.1-25.3-6.2-7.4-4.1-13.2-9.7-17.5-16.9-4.3-7.2-6.4-15.3-6.4-24.5s2.1-17.4,6.4-24.6c4.3-7.1,10.1-12.7,17.5-16.7,7.4-4,15.8-6.1,25.3-6.1s18.1,2,25.6,6.1c7.5,4.1,13.3,9.6,17.5,16.7,4.2,7.1,6.3,15.3,6.3,24.6s-2.1,17.3-6.3,24.5c-4.2,7.2-10.1,12.8-17.5,16.9-7.5,4.1-16,6.2-25.6,6.2ZM1299.5,151.2c5.3,0,10.1-1.2,14.2-3.5,4.2-2.3,7.4-5.7,9.8-10.1,2.4-4.4,3.6-9.5,3.6-15.4s-1.2-11.2-3.6-15.5c-2.4-4.3-5.6-7.7-9.8-10-4.2-2.3-8.8-3.5-14.1-3.5s-10,1.2-14.1,3.5c-4.1,2.3-7.4,5.6-9.8,10-2.4,4.3-3.6,9.5-3.6,15.5s1.2,11,3.6,15.4c2.4,4.4,5.7,7.8,9.8,10.1,4.1,2.3,8.8,3.5,14,3.5Z"/>
<path d="M1412.3,169.7c-9.7,0-18.4-2.1-25.9-6.2-7.6-4.1-13.5-9.7-17.8-16.9-4.3-7.2-6.4-15.3-6.4-24.5s2.1-17.4,6.4-24.6c4.3-7.1,10.2-12.7,17.8-16.7,7.6-4,16.2-6.1,25.9-6.1s17,1.8,23.9,5.5c6.9,3.6,12.1,9,15.7,16.1l-16.7,9.7c-2.8-4.4-6.2-7.6-10.2-9.7-4-2.1-8.3-3.1-12.9-3.1s-10.1,1.2-14.4,3.5c-4.3,2.3-7.6,5.6-10.1,10-2.4,4.3-3.6,9.5-3.6,15.5s1.2,11.2,3.6,15.5c2.4,4.3,5.8,7.7,10.1,10,4.3,2.3,9.1,3.5,14.4,3.5s8.9-1,12.9-3.1c4-2.1,7.4-5.3,10.2-9.7l16.7,9.7c-3.6,6.9-8.8,12.3-15.7,16.1-6.9,3.8-14.8,5.6-23.9,5.6Z"/>
<path d="M1499.2,169.7c-7.8,0-15.2-1-22.3-3-7.1-2-12.8-4.5-16.9-7.4l8.3-16.5c4.2,2.7,9.1,4.9,14.9,6.6,5.8,1.7,11.6,2.6,17.4,2.6s11.8-.9,14.8-2.8c3.1-1.8,4.6-4.3,4.6-7.5s-1-4.5-3.1-5.8c-2.1-1.3-4.8-2.3-8.2-3-3.4-.7-7.1-1.3-11.2-1.9-4.1-.6-8.2-1.4-12.3-2.3-4.1-1-7.8-2.4-11.2-4.3-3.4-1.9-6.1-4.5-8.2-7.7-2.1-3.2-3.1-7.5-3.1-12.8s1.7-11,5-15.4c3.4-4.3,8.1-7.7,14.1-10.1,6.1-2.4,13.3-3.6,21.6-3.6s12.6.7,18.9,2.1c6.4,1.4,11.6,3.4,15.8,5.9l-8.3,16.5c-4.4-2.7-8.8-4.5-13.3-5.5-4.5-1-8.9-1.5-13.3-1.5-6.6,0-11.5,1-14.7,2.9-3.2,2-4.8,4.5-4.8,7.5s1,4.9,3.1,6.2c2.1,1.4,4.8,2.5,8.2,3.3,3.4.8,7.1,1.5,11.2,2,4.1.5,8.2,1.3,12.2,2.3,4,1,7.8,2.5,11.2,4.3,3.4,1.8,6.2,4.3,8.2,7.5,2.1,3.2,3.1,7.5,3.1,12.7s-1.7,10.8-5.1,15.1c-3.4,4.3-8.2,7.6-14.5,10-6.2,2.4-13.7,3.6-22.4,3.6Z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -0,0 +1,6 @@
import { defineCollection } from 'astro:content';
import { docsSchema } from '@astrojs/starlight/schema';
export const collections = {
docs: defineCollection({ schema: docsSchema() }),
};

View File

@@ -0,0 +1,4 @@
---
title: Daedalus (Metadata service)
description: Guide for contributing to Modrinth's frontend
---

View File

@@ -0,0 +1,52 @@
---
title: Getting started
description: How can I contribute to Modrinth?
sidebar:
order: 1
---
# Contributing to Modrinth
Every public-facing aspect of Modrinth, including everything from our [API/backend][labrinth] and [frontend][knossos] to our [Gradle plugin][minotaur] and [launcher][theseus], is released under free and open source licenses on [GitHub]. As such, we love contributions from community members! Before proceeding to do so, though, there are a number of things you'll want to keep in mind throughout the process, as well as some details specific to certain projects.
## Things to keep in mind
### Consult people on Discord
There are a number of reasons to want to consult with people on our [Discord] before making a pull request. For example, if you're not sure whether something is a good idea or not, if you're not sure how to implement something, or if you can't get something working, these would all be good opportunities to create a thread in the `#development` forum channel.
If you intend to work on new features, to make significant codebase changes, or to make UI/design changes, please open a discussion thread first to ensure your work is put to its best use.
### Don't get discouraged
At times, pull requests may be rejected or left unmerged for a variation of reasons. Don't take it personally, and don't get discouraged! Sometimes a contribution just isn't the right fit for the time, or it might have just been lost in the mess of other things to do. Remember, the core Modrinth team are often busy, whether it be on a specific project/task or on external factors such as offline responsibilities. It all falls back to the same thing: don't get discouraged!
### Code isn't the only way to contribute
You don't need to know how to program to contribute to Modrinth. Quality assurance, supporting the community, coming up with feature ideas, and making sure your voice is heard in public decisions are all great ways to contribute to Modrinth. If you find bugs, reporting them on the appropriate issue tracker is your responsibility; however, remember that potential security breaches and exploits must instead be reported in accordance with our [security policy](https://modrinth.com/legal/security).
## Project-specific details
If you wish to contribute code to a specific project, here's the place to start. Most of Modrinth is written in the [Rust language](https://www.rust-lang.org), but some things are written in other languages/frameworks like [Nuxt.js](https://nuxtjs.org) or Java.
Most of Modrinth's code is in our monorepo, which you can find [here](https://github.com/modrinth/code). Our monorepo is powered by [Turborepo](https://turborepo.org).
Follow the project-specific instructions below to get started:
- [Knossos (frontend)](/contributing/knossos)
- [Theseus (Modrinth App)](/contributing/theseus)
- [Minotaur (Gradle plugin)](/contributing/minotaur)
- [Labrinth (API/backend)](/contributing/labrinth)
- [Daedalus (Metadata service)](/contributing/daedalus)
### Documentation
The [documentation](https://github.com/modrinth/docs) (which you are reading right now!) is the place to find any and all general information about Modrinth and its API. The instructions are largely the same as [knossos](#knossos-frontend), except that the docs have no lint.
[Discord]: https://discord.modrinth.com
[GitHub]: https://github.com/modrinth
[knossos]: https://github.com/modrinth/code/tree/main/apps/frontend
[labrinth]: https://github.com/modrinth/labrinth
[theseus]: https://github.com/modrinth/theseus
[minotaur]: https://github.com/modrinth/minotaur
[Rust]: https://www.rust-lang.org/tools/install
[pnpm]: https://pnpm.io

View File

@@ -0,0 +1,35 @@
---
title: Knossos (Frontend)
description: Guide for contributing to Modrinth's frontend
---
This project is our [monorepo](https://github.com/modrinth/code). You can find the frontend in the `apps/frontend` directory.
[knossos] is the Nuxt.js frontend. You will need to install [pnpm] and run the standard commands:
```bash
pnpm install
pnpm run web:dev
```
Once that's done, you'll be serving knossos on `localhost:3000` with hot reloading. You can replace the `dev` in `pnpm run dev` with `build` to build for a production server and `start` to start the server. You can also use `pnpm run lint` to find any eslint problems, and `pnpm run fix` to try automatically fixing those problems.
<details>
<summary>.env variables & command line options</summary>
#### Basic configuration
`SITE_URL`: The URL of the site (used for auth redirects). Default: `http://localhost:3000`
`BASE_URL`: The base URL for the API. Default: `https://staging-api.modrinth.com/v2/`
`BROWSER_BASE_URL`: The base URL for the API used in the browser. Default: `https://staging-api.modrinth.com/v2/`
</details>
#### Ready to open a PR?
If you're prepared to contribute by submitting a pull request, ensure you have met the following criteria:
- `pnpm run fix` has been run.
[knossos]: https://github.com/modrinth/code/tree/main/apps/frontend
[pnpm]: https://pnpm.io

View File

@@ -0,0 +1,115 @@
---
title: Labrinth (API)
description: Guide for contributing to Modrinth's backend
---
[labrinth] is the Rust-based backend serving Modrinth's API with the help of the [Actix](https://actix.rs) framework. To get started with a labrinth instance, install docker, docker-compose (which comes with Docker), and [Rust]. The initial startup can be done simply with the command `docker-compose up`, or with `docker compose up` (Compose V2 and later). That will deploy a PostgreSQL database on port 5432 and a MeiliSearch instance on port 7700. To run the API itself, you'll need to use the `cargo run` command, this will deploy the API on port 8000.
Now, you'll have to install the sqlx CLI, which can be done with cargo:
```bash
cargo install --git https://github.com/launchbadge/sqlx sqlx-cli --no-default-features --features postgres,rustls
```
From there, you can create the database and perform all database migrations with one simple command:
```bash
sqlx database setup
```
Finally, if on Linux, you will need the OpenSSL library. On Debian-based systems, this involves the `pkg-config` and `libssl-dev` packages.
To enable labrinth to create a project, you need to add two things.
1. An entry in the `loaders` table.
2. An entry in the `loaders_project_types` table.
A minimal setup can be done from the command line with [psql](https://www.postgresql.org/docs/current/app-psql.html):
```bash
psql --host=localhost --port=5432 -U <username, default is labrinth> -W
```
The default password for the database is `labrinth`. Once you've connected, run
```sql
INSERT INTO loaders VALUES (0, 'placeholder_loader');
INSERT INTO loaders_project_types VALUES (0, 1); -- modloader id, supported type id
INSERT INTO categories VALUES (0, 'placeholder_category', 1); -- category id, category, project type id
```
This will initialize your database with a modloader called 'placeholder_loader', with id 0, and marked as supporting mods only. It will also create a category called 'placeholder_category' that is marked as supporting mods only
If you would like 'placeholder_loader' to be marked as supporting modpacks too, run
```sql
INSERT INTO loaders_project_types VALUES (0, 2); -- modloader id, supported type id
```
If you would like 'placeholder_category' to be marked as supporting modpacks too, run
```sql
INSERT INTO categories VALUES (0, 'placeholder_category', 2); -- modloader id, supported type id
```
The majority of configuration is done at runtime using [dotenvy](https://crates.io/crates/dotenvy) and the `.env` file. Each of the variables and what they do can be found in the dropdown below. Additionally, there are three command line options that can be used to specify to MeiliSearch what you want to do.
<details>
<summary>.env variables & command line options</summary>
#### Basic configuration
`DEBUG`: Whether debugging tools should be enabled
`RUST_LOG`: Specifies what information to log, from rust's [`env-logger`](https://github.com/env-logger-rs/env_logger); a reasonable default is `info,sqlx::query=warn`
`SITE_URL`: The main URL to be used for CORS
`CDN_URL`: The publicly accessible base URL for files uploaded to the CDN
`MODERATION_DISCORD_WEBHOOK`: The URL for a Discord webhook where projects pending approval will be sent
`CLOUDFLARE_INTEGRATION`: Whether labrinth should integrate with Cloudflare's spam protection
`DATABASE_URL`: The URL for the PostgreSQL database
`DATABASE_MIN_CONNECTIONS`: The minimum number of concurrent connections allowed to the database at the same time
`DATABASE_MAX_CONNECTIONS`: The maximum number of concurrent connections allowed to the database at the same time
`MEILISEARCH_ADDR`: The URL for the MeiliSearch instance used for search
`MEILISEARCH_KEY`: The name that MeiliSearch is given
`BIND_ADDR`: The bind address for the server. Supports both IPv4 and IPv6
`MOCK_FILE_PATH`: The path used to store uploaded files; this has no default value and will panic if unspecified
#### CDN options
`STORAGE_BACKEND`: Controls what storage backend is used. This can be either `local`, `backblaze`, or `s3`, but defaults to `local`
The Backblaze and S3 configuration options are fairly self-explanatory in name, so here's simply their names:
`BACKBLAZE_KEY_ID`, `BACKBLAZE_KEY`, `BACKBLAZE_BUCKET_ID`
`S3_ACCESS_TOKEN`, `S3_SECRET`, `S3_URL`, `S3_REGION`, `S3_BUCKET_NAME`
#### Search, OAuth, and miscellaneous options
`LOCAL_INDEX_INTERVAL`: The interval, in seconds, at which the local database is reindexed for searching. Defaults to `3600` seconds (1 hour).
`VERSION_INDEX_INTERVAL`: The interval, in seconds, at which versions are reindexed for searching. Defaults to `1800` seconds (30 minutes).
The OAuth configuration options are fairly self-explanatory. For help setting up authentication, please contact us on [Discord].
`RATE_LIMIT_IGNORE_IPS`: An array of IPs that should have a lower rate limit factor. This can be useful for allowing the front-end to have a lower rate limit to prevent accidental timeouts.
#### Command line options
`--skip-first-index`: Skips indexing the local database on startup. This is useful to prevent doing unnecessary work when frequently restarting.
`--reconfigure-indices`: Resets the MeiliSearch settings for the search indices and exits.
`--reset-indices`: Resets the MeiliSearch indices and exits; this clears all previously indexed mods.
</details>
#### Ready to open a PR?
If you're prepared to contribute by submitting a pull request, ensure you have met the following criteria:
- `cargo fmt` has been run.
- `cargo clippy` has been run.
- `cargo check` has been run.
- `cargo sqlx prepare` has been run.
> Note: If you encounter issues with `sqlx` saying 'no queries found' after running `cargo sqlx prepare`, you may need to ensure the installed version of `sqlx-cli` matches the current version of `sqlx` used [in labrinth](https://github.com/modrinth/labrinth/blob/master/Cargo.toml).
[Discord]: https://discord.modrinth.com
[GitHub]: https://github.com/modrinth
[labrinth]: https://github.com/modrinth/labrinth
[Rust]: https://www.rust-lang.org/tools/install

View File

@@ -0,0 +1,10 @@
---
title: Minotaur (Gradle plugin)
description: Guide for contributing to Modrinth's gradle plugin
---
[Minotaur][minotaur] is the Gradle plugin used to automatically publish artifacts to Modrinth. To run your copy of the plugin in a project, publish it to your local Maven with `./gradlew publishToMavenLocal` and add `mavenLocal()` to your buildscript.
Minotaur contains two test environments within it - one with ForgeGradle and one with Fabric Loom. You may tweak with these environments to test whatever you may be trying; just make sure that the `modrinth` task within each still functions properly. GitHub Actions will validate this if you're making a pull request, so you may want to use [`act pull_request`](https://github.com/nektos/act) to test them locally.
[minotaur]: https://github.com/modrinth/minotaur

View File

@@ -0,0 +1,43 @@
---
title: Theseus (Modrinth App)
description: Guide for contributing to Modrinth's desktop app
---
This project is our [monorepo](https://github.com/modrinth/code).
[theseus] is the Tauri-based launcher that lets users conveniently play any mod or modpack on Modrinth. It uses the Rust-based Tauri as the backend and Vue.js as the frontend. To get started, install [pnpm], [Rust], and the [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) for your system. Then, run the following commands:
```bash
pnpm install
pnpm run app:dev
```
Once the commands finish, you'll be viewing a Tauri window with Nuxt.js hot reloading.
You can use `pnpm run lint` to find any eslint problems, and `pnpm run fix` to try automatically fixing those problems.
### Theseus Architecture
Theseus is split up into three parts:
- `apps/app-frontend`: The Vue.JS frontend for the app
- `packages/app-lib`: The library holding all the core logic for the desktop app
- `apps/app`: The Tauri-based Rust app. This primarily wraps around the library with some additional logic for Tauri.
The app's internal database is stored in SQLite. For production builds, this is found at <APPDIR>/app.db.
When running SQLX commands, be sure to set the `DATABASE_URL` environment variable to the path of the database.
You can edit the app's data directory using the `THESEUS_CONFIG_DIR` environment variable.
#### Ready to open a PR?
If you're prepared to contribute by submitting a pull request, ensure you have met the following criteria:
- Run `pnpm run fix` to address any fixable issues automatically.
- Run `cargo fmt` to format Rust-related code.
- Run `cargo clippy` to validate Rust-related code.
- Run `cargo sqlx prepare --package theseus` if you've changed any SQL code to validate statements.
[theseus]: https://github.com/modrinth/code/tree/main/apps/app
[Rust]: https://www.rust-lang.org/tools/install
[pnpm]: https://pnpm.io

View File

@@ -0,0 +1,15 @@
---
title: Modrinth docs
description: Developer documentation for Modrinth!
template: splash
hero:
tagline: Developer documentation for Modrinth
actions:
- text: API documentation
link: /api
icon: right-arrow
- text: Get support with Modrinth
link: https://support.modrinth.com
icon: external
variant: minimal
---

2
apps/docs/src/env.d.ts vendored Normal file
View File

@@ -0,0 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />

View File

@@ -0,0 +1,54 @@
:root,
::backdrop,
:root[data-theme='light'],
[data-theme='light'] ::backdrop{
--sl-font-system: Inter, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Roboto,
Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
--sl-color-white: var(--color-contrast); /* “white” */
--sl-color-gray-1: var(--color-base);
--sl-color-gray-2: var(--color-base);
--sl-color-gray-3: var(--color-base);
--sl-color-gray-4: var(--color-raised-bg);
--sl-color-gray-5: var(--color-button-bg);
--sl-color-gray-6: var(--color-raised-bg);
--sl-color-black: var(--color-accent-contrast);
--sl-color-accent-low: var(--color-green-highlight);
--sl-color-accent: var(--color-brand);
--sl-color-accent-high: var(--color-brand-highlight);
--sl-color-orange-low: var(--color-orange-highlight);
--sl-color-orange: var(--color-orange);
--sl-color-orange-high: var(--color-orange-highlight);
--sl-color-green-low: var(--color-green-highlight);
--sl-color-green: var(--color-green);
--sl-color-green-high: var(--color-green-highlight);
--sl-color-blue-low: var(--color-blue-highlight);
--sl-color-blue: var(--color-blue);
--sl-color-blue-high: var(--color-blue-highlight);
--sl-color-purple-low: var(--color-purple-highlight);
--sl-color-purple: var(--color-purple);
--sl-color-purple-high: var(--color-purple-highlight);
--sl-color-red-low: var(--color-red-highlight);
--sl-color-red: var(--color-red);
--sl-color-red-high: var(--color-red-highlight);
--sl-color-text: var(--color-base);
--sl-color-text-accent: var(--color-brand);
--sl-color-text-invert: var(--color-accent-contrast);
--sl-color-bg: var(--color-bg);
--sl-color-bg-nav: var(--color-raised-bg);
--sl-color-bg-sidebar: var(--color-raised-bg);
--sl-color-bg-inline-code: var(--color-button-bg);
--sl-color-bg-accent: var(--color-brand-highlight);
}
:root[data-theme='light'],
[data-theme='light'] ::backdrop{
--sl-color-bg: var(--color-raised-bg);
}

3
apps/docs/tsconfig.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "astro/tsconfigs/strict"
}

View File

@@ -1,4 +1,7 @@
module.exports = {
root: true,
extends: ["../../packages/eslint-config-custom/nuxt.js"],
rules: {
"import/no-unresolved": "off",
},
};

View File

@@ -176,7 +176,6 @@ export default defineNuxtConfig({
$fetch(`${API_URL}projects_random?count=60`, headers),
$fetch(`${API_URL}search?limit=3&query=leave&index=relevance`, headers),
$fetch(`${API_URL}search?limit=3&query=&index=updated`, headers),
// TODO: dehardcode
$fetch(`${API_URL.replace("/v2/", "/_internal/")}billing/products`, headers),
]);
@@ -321,8 +320,10 @@ export default defineNuxtConfig({
apiBaseUrl: process.env.BASE_URL ?? globalThis.BASE_URL ?? getApiUrl(),
// @ts-ignore
rateLimitKey: process.env.RATE_LIMIT_IGNORE_KEY ?? globalThis.RATE_LIMIT_IGNORE_KEY,
pyroBaseUrl: process.env.PYRO_BASE_URL,
public: {
apiBaseUrl: getApiUrl(),
pyroBaseUrl: process.env.PYRO_BASE_URL,
siteUrl: getDomain(),
production: isProduction(),
featureFlagOverrides: getFeatureFlagOverrides(),
@@ -361,7 +362,7 @@ export default defineNuxtConfig({
},
},
},
modules: ["@vintl/nuxt", "@nuxtjs/turnstile"],
modules: ["@vintl/nuxt", "@nuxtjs/turnstile", "@pinia/nuxt"],
vintl: {
defaultLocale: "en-US",
locales: [
@@ -462,6 +463,7 @@ function getDomain() {
return "https://modrinth.com";
}
} else {
return "http://localhost:3000";
const port = process.env.PORT || 3000;
return `http://localhost:${port}`;
}
}

View File

@@ -16,6 +16,7 @@
"@formatjs/cli": "^6.2.12",
"@nuxt/devtools": "^1.3.3",
"@nuxtjs/turnstile": "^0.8.0",
"@types/dompurify": "^3.0.5",
"@types/node": "^20.1.0",
"@vintl/compact-number": "^2.0.5",
"@vintl/how-ago": "^3.0.1",
@@ -38,8 +39,13 @@
"@modrinth/assets": "workspace:*",
"@modrinth/ui": "workspace:*",
"@modrinth/utils": "workspace:*",
"@pinia/nuxt": "^0.5.1",
"@vintl/vintl": "^4.4.1",
"@vueuse/core": "^11.1.0",
"ace-builds": "^1.36.2",
"ansi-to-html": "^0.7.2",
"dayjs": "^1.11.7",
"dompurify": "^3.1.7",
"floating-vue": "2.0.0-beta.20",
"fuse.js": "^6.6.2",
"highlight.js": "^11.7.0",
@@ -48,9 +54,12 @@
"jszip": "^3.10.1",
"markdown-it": "14.1.0",
"pathe": "^1.1.2",
"pinia": "^2.1.7",
"qrcode.vue": "^3.4.0",
"semver": "^7.5.4",
"vue-multiselect": "3.0.0",
"vue-multiselect": "3.0.0-alpha.2",
"vue-typed-virtual-list": "^1.0.10",
"vue3-ace-editor": "^2.2.4",
"vue3-apexcharts": "^1.5.2",
"xss": "^1.0.14"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

View File

@@ -23,9 +23,19 @@ import { ChevronRightIcon } from "@modrinth/assets";
useHead({
script: [
{
// Clean.io
src: "https://cadmus.script.ac/d14pdm1b7fi5kh/script.js",
},
{
// Aditude
src: "https://dn0qt3r0xannq.cloudfront.net/modrinth-7JfmkEIXEp/modrinth-longform/prebid-load.js",
async: true,
},
{
// Optima
src: "https://bservr.com/o.js?uid=8118d1fdb2e0d6f32180bd27",
async: true,
},
{
src: "/inmobi.js",
async: true,
@@ -42,11 +52,22 @@ useHead({
onMounted(() => {
window.tude = window.tude || { cmd: [] };
window.Raven = window.Raven || { cmd: [] };
window.Raven.cmd.push(({ config }) => {
config.setCustom({
param1: "web",
});
});
tude.cmd.push(function () {
tude.refreshAdsViaDivMappings([
{
divId: "modrinth-rail-1",
baseDivId: "pb-slot-square-2",
targeting: {
location: "web",
},
},
]);
});

View File

@@ -119,7 +119,7 @@ export default {
}
svg {
color: var(--color-brand-inverted);
color: var(--color-accent-contrast, var(--color-brand-inverted));
stroke-width: 0.2rem;
height: 0.8rem;
width: 0.8rem;

View File

@@ -45,7 +45,7 @@ export default {
margin: 0;
padding: 0.25rem 0.5rem;
background-color: var(--color-code-bg);
width: min-content;
width: fit-content;
border-radius: 10px;
user-select: text;
transition:
@@ -55,7 +55,6 @@ export default {
outline 0.2s ease-in-out;
span {
max-width: 10rem;
overflow: hidden;
text-overflow: ellipsis;
}

View File

@@ -1,6 +1,7 @@
<template>
<nav
class="experimental-styles-within relative flex w-fit overflow-clip rounded-full bg-bg-raised p-1 text-sm font-bold"
ref="scrollContainer"
class="experimental-styles-within relative flex w-fit overflow-x-auto rounded-full bg-bg-raised p-1 text-sm font-bold"
>
<NuxtLink
v-for="(link, index) in filteredLinks"
@@ -18,7 +19,9 @@
<span class="text-nowrap">{{ link.label }}</span>
</NuxtLink>
<div
:class="`navtabs-transition pointer-events-none absolute h-[calc(100%-0.5rem)] overflow-hidden rounded-full p-1 ${subpageSelected ? 'bg-button-bg' : 'bg-brand-highlight'}`"
:class="`navtabs-transition pointer-events-none absolute h-[calc(100%-0.5rem)] overflow-hidden rounded-full p-1 ${
subpageSelected ? 'bg-button-bg' : 'bg-brand-highlight'
}`"
:style="{
left: sliderLeftPx,
top: sliderTopPx,
@@ -32,6 +35,8 @@
</template>
<script setup lang="ts">
import { ref, computed, watch, onMounted } from "vue";
const route = useNativeRoute();
interface Tab {
@@ -47,12 +52,13 @@ const props = defineProps<{
query?: string;
}>();
const scrollContainer = ref<HTMLElement | null>(null);
const sliderLeft = ref(4);
const sliderTop = ref(4);
const sliderRight = ref(4);
const sliderBottom = ref(4);
const activeIndex = ref(-1);
const oldIndex = ref(-1);
const subpageSelected = ref(false);
const filteredLinks = computed(() =>
@@ -63,6 +69,8 @@ const sliderTopPx = computed(() => `${sliderTop.value}px`);
const sliderRightPx = computed(() => `${sliderRight.value}px`);
const sliderBottomPx = computed(() => `${sliderBottom.value}px`);
const tabLinkElements = ref();
function pickLink() {
let index = -1;
subpageSelected.value = false;
@@ -86,16 +94,13 @@ function pickLink() {
if (activeIndex.value !== -1) {
startAnimation();
} else {
oldIndex.value = -1;
sliderLeft.value = 0;
sliderRight.value = 0;
}
}
const tabLinkElements = ref();
function startAnimation() {
const el = tabLinkElements.value[activeIndex.value].$el;
const el = tabLinkElements.value[activeIndex.value]?.$el;
if (!el || !el.offsetParent) return;
@@ -141,21 +146,19 @@ function startAnimation() {
}
onMounted(() => {
window.addEventListener("resize", pickLink);
pickLink();
});
onUnmounted(() => {
window.removeEventListener("resize", pickLink);
});
watch(route, () => pickLink());
watch(
() => route.path,
() => pickLink(),
);
</script>
<style scoped>
.navtabs-transition {
/* Delay on opacity is to hide any jankiness as the page loads */
transition:
all 150ms cubic-bezier(0.4, 0, 0.2, 1) 0s,
all 150ms cubic-bezier(0.4, 0, 0.2, 1),
opacity 250ms cubic-bezier(0.5, 0, 0.2, 1) 50ms;
}
</style>

View File

@@ -571,6 +571,10 @@ function getMessages() {
gap: var(--spacing-card-sm);
}
.notification__actions .iconified-button.square-button svg {
margin-right: 0;
}
.unknown-type {
color: var(--color-red);
}

View File

@@ -384,6 +384,8 @@ const submitForReview = async () => {
}
.author-actions {
margin-top: var(--spacing-card-md);
&:empty {
display: none;
}

View File

@@ -134,7 +134,6 @@
:data="analytics.formattedData.value.revenue.chart.data"
:labels="analytics.formattedData.value.revenue.chart.labels"
is-money
suffix="<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><line x1='12' y1='2' x2='12' y2='22'></line><path d='M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6'></path></svg>"
:colors="
isUsingProjectColors
? analytics.formattedData.value.revenue.chart.colors
@@ -193,15 +192,20 @@
class="country-value"
>
<div class="country-flag-container">
<img
:src="
name.toLowerCase() === 'xx' || !name
? 'https://cdn.modrinth.com/placeholder-banner.svg'
: countryCodeToFlag(name)
"
alt="Hidden country"
class="country-flag"
/>
<template v-if="name.toLowerCase() === 'xx' || !name">
<img
src="https://cdn.modrinth.com/placeholder-banner.svg"
alt="Placeholder flag"
class="country-flag"
/>
</template>
<template v-else>
<img
:src="countryCodeToFlag(name)"
:alt="`${countryCodeToName(name)}'s flag`"
class="country-flag"
/>
</template>
</div>
<div class="country-text">
<strong class="country-name"
@@ -247,15 +251,20 @@
class="country-value"
>
<div class="country-flag-container">
<img
:src="
name.toLowerCase() === 'xx' || !name
? 'https://cdn.modrinth.com/placeholder-banner.svg'
: countryCodeToFlag(name)
"
alt="Hidden country"
class="country-flag"
/>
<template v-if="name.toLowerCase() === 'xx' || !name">
<img
src="https://cdn.modrinth.com/placeholder-banner.svg"
alt="Placeholder flag"
class="country-flag"
/>
</template>
<template v-else>
<img
:src="countryCodeToFlag(name)"
:alt="`${countryCodeToName(name)}'s flag`"
class="country-flag"
/>
</template>
</div>
<div class="country-text">
<strong class="country-name">

View File

@@ -0,0 +1,95 @@
<template>
<NewModal ref="modal" header="Creating backup" @show="focusInput">
<div class="flex flex-col gap-2 md:w-[600px]">
<div class="font-semibold text-contrast">Name</div>
<input
ref="input"
v-model="backupName"
type="text"
class="bg-bg-input w-full rounded-lg p-4"
placeholder="e.g. Before 1.21"
maxlength="64"
/>
<div class="flex items-center gap-2">
<InfoIcon class="hidden sm:block" />
<span class="text-sm text-secondary">
If left empty, the backup name will default to
<span class="font-semibold"> Backup #{{ newBackupAmount }}</span>
</span>
</div>
</div>
<div class="mb-1 mt-4 flex justify-start gap-4">
<ButtonStyled color="brand">
<button :disabled="isCreating" @click="createBackup">
<PlusIcon />
Create backup
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="hideModal">
<XIcon />
Cancel
</button>
</ButtonStyled>
</div>
</NewModal>
</template>
<script setup lang="ts">
import { ref, nextTick } from "vue";
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { PlusIcon, XIcon, InfoIcon } from "@modrinth/assets";
import type { Server } from "~/composables/pyroServers";
const props = defineProps<{
server: Server<["general", "mods", "backups", "network", "startup", "ws", "fs"]>;
}>();
const emit = defineEmits(["backupCreated"]);
const modal = ref<InstanceType<typeof NewModal>>();
const input = ref<HTMLInputElement>();
const isCreating = ref(false);
const backupError = ref<string | null>(null);
const backupName = ref("");
const newBackupAmount = computed(() =>
props.server.backups?.data?.length === undefined ? 1 : props.server.backups?.data?.length + 1,
);
const focusInput = () => {
nextTick(() => {
setTimeout(() => {
input.value?.focus();
}, 100);
});
};
const hideModal = () => {
modal.value?.hide();
backupName.value = "";
};
const createBackup = async () => {
if (!backupName.value.trim()) {
backupName.value = `Backup #${newBackupAmount.value}`;
}
isCreating.value = true;
try {
await props.server.backups?.create(backupName.value);
await props.server.refresh();
hideModal();
emit("backupCreated", { success: true, message: "Backup created successfully" });
} catch (error) {
backupError.value = error instanceof Error ? error.message : String(error);
emit("backupCreated", { success: false, message: backupError.value });
} finally {
isCreating.value = false;
}
};
defineExpose({
show: () => modal.value?.show(),
hide: hideModal,
});
</script>

View File

@@ -0,0 +1,86 @@
<template>
<NewModal ref="modal" danger header="Deleting backup">
<div class="flex flex-col gap-4">
<div class="relative flex w-full flex-col gap-2 rounded-2xl bg-[#0e0e0ea4] p-6">
<div class="text-2xl font-extrabold text-contrast">
{{ backupName }}
</div>
<div class="flex gap-2 font-semibold text-contrast">
<CalendarIcon />
{{ formattedDate }}
</div>
</div>
</div>
<div class="mb-1 mt-4 flex justify-end gap-4">
<ButtonStyled color="red">
<button :disabled="isDeleting" @click="deleteBackup">
<TrashIcon />
Delete backup
</button>
</ButtonStyled>
<ButtonStyled type="transparent">
<button @click="hideModal">Cancel</button>
</ButtonStyled>
</div>
</NewModal>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { TrashIcon, CalendarIcon } from "@modrinth/assets";
import type { Server } from "~/composables/pyroServers";
const props = defineProps<{
server: Server<["general", "mods", "backups", "network", "startup", "ws", "fs"]>;
backupId: string;
backupName: string;
backupCreatedAt: string;
}>();
const emit = defineEmits(["backupDeleted"]);
const modal = ref<InstanceType<typeof NewModal>>();
const isDeleting = ref(false);
const backupError = ref<string | null>(null);
const formattedDate = computed(() => {
return new Date(props.backupCreatedAt).toLocaleString("en-US", {
month: "numeric",
day: "numeric",
year: "2-digit",
hour: "numeric",
minute: "numeric",
hour12: true,
});
});
const hideModal = () => {
modal.value?.hide();
};
const deleteBackup = async () => {
if (!props.backupId) {
emit("backupDeleted", { success: false, message: "No backup selected" });
return;
}
isDeleting.value = true;
try {
await props.server.backups?.delete(props.backupId);
await props.server.refresh();
hideModal();
emit("backupDeleted", { success: true, message: "Backup deleted successfully" });
} catch (error) {
backupError.value = error instanceof Error ? error.message : String(error);
emit("backupDeleted", { success: false, message: backupError.value });
} finally {
isDeleting.value = false;
}
};
defineExpose({
show: () => modal.value?.show(),
hide: hideModal,
});
</script>

View File

@@ -0,0 +1,86 @@
<template>
<NewModal ref="modal" header="Renaming backup" @show="focusInput">
<div class="flex flex-col gap-2 md:w-[600px]">
<div class="font-semibold text-contrast">Name</div>
<input
ref="input"
v-model="backupName"
type="text"
class="bg-bg-input w-full rounded-lg p-4"
placeholder="e.g. Before 1.21"
/>
</div>
<div class="mb-1 mt-4 flex justify-start gap-4">
<ButtonStyled color="brand">
<button :disabled="isRenaming" @click="renameBackup">
<SaveIcon />
Rename backup
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="hideModal">
<XIcon />
Cancel
</button>
</ButtonStyled>
</div>
</NewModal>
</template>
<script setup lang="ts">
import { ref, nextTick } from "vue";
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { SaveIcon, XIcon } from "@modrinth/assets";
import type { Server } from "~/composables/pyroServers";
const props = defineProps<{
server: Server<["general", "mods", "backups", "network", "startup", "ws", "fs"]>;
currentBackupId: string;
}>();
const emit = defineEmits(["backupRenamed"]);
const modal = ref<InstanceType<typeof NewModal>>();
const input = ref<HTMLInputElement>();
const backupName = ref("");
const isRenaming = ref(false);
const backupError = ref<string | null>(null);
const focusInput = () => {
nextTick(() => {
setTimeout(() => {
input.value?.focus();
}, 100);
});
};
const hideModal = () => {
backupName.value = "";
modal.value?.hide();
};
const renameBackup = async () => {
if (!backupName.value.trim() || !props.currentBackupId) {
emit("backupRenamed", { success: false, message: "Backup name cannot be empty" });
return;
}
isRenaming.value = true;
try {
await props.server.backups?.rename(props.currentBackupId, backupName.value);
await props.server.refresh();
hideModal();
emit("backupRenamed", { success: true, message: "Backup renamed successfully" });
} catch (error) {
backupError.value = error instanceof Error ? error.message : String(error);
emit("backupRenamed", { success: false, message: backupError.value });
} finally {
isRenaming.value = false;
}
};
defineExpose({
show: () => modal.value?.show(),
hide: hideModal,
});
</script>

View File

@@ -0,0 +1,82 @@
<template>
<NewModal ref="modal" header="Restoring backup">
<div class="flex flex-col gap-4">
<div class="relative flex w-full flex-col gap-2 rounded-2xl bg-bg p-6">
<div class="text-2xl font-extrabold text-contrast">
{{ backupName }}
</div>
<div class="flex gap-2 font-semibold text-contrast">
<CalendarIcon />
{{ formattedDate }}
</div>
</div>
</div>
<div class="mb-1 mt-4 flex justify-end gap-4">
<ButtonStyled color="brand">
<button :disabled="isRestoring" @click="restoreBackup">Restore backup</button>
</ButtonStyled>
<ButtonStyled type="transparent">
<button @click="hideModal">Cancel</button>
</ButtonStyled>
</div>
</NewModal>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { CalendarIcon } from "@modrinth/assets";
import type { Server } from "~/composables/pyroServers";
const props = defineProps<{
server: Server<["general", "mods", "backups", "network", "startup", "ws", "fs"]>;
backupId: string;
backupName: string;
backupCreatedAt: string;
}>();
const emit = defineEmits(["backupRestored"]);
const modal = ref<InstanceType<typeof NewModal>>();
const isRestoring = ref(false);
const backupError = ref<string | null>(null);
const formattedDate = computed(() => {
return new Date(props.backupCreatedAt).toLocaleString("en-US", {
month: "numeric",
day: "numeric",
year: "2-digit",
hour: "numeric",
minute: "numeric",
hour12: true,
});
});
const hideModal = () => {
modal.value?.hide();
};
const restoreBackup = async () => {
if (!props.backupId) {
emit("backupRestored", { success: false, message: "No backup selected" });
return;
}
isRestoring.value = true;
try {
await props.server.backups?.restore(props.backupId);
hideModal();
emit("backupRestored", { success: true, message: "Backup restored successfully" });
} catch (error) {
backupError.value = error instanceof Error ? error.message : String(error);
emit("backupRestored", { success: false, message: backupError.value });
} finally {
isRestoring.value = false;
}
};
defineExpose({
show: () => modal.value?.show(),
hide: hideModal,
});
</script>

View File

@@ -0,0 +1,201 @@
<template>
<NewModal ref="modal" header="Editing auto backup settings">
<div class="flex flex-col gap-4 md:w-[600px]">
<div class="flex flex-col gap-2">
<div class="font-semibold text-contrast">Auto backup</div>
<p class="m-0">
Automatically create a backup of your server every
<strong>{{ autoBackupInterval == 1 ? "hour" : `${autoBackupInterval} hours` }}</strong>
</p>
</div>
<div v-if="isLoadingSettings" class="py-2 text-sm text-secondary">Loading settings...</div>
<template v-else>
<input
id="auto-backup-toggle"
v-model="autoBackupEnabled"
class="switch stylized-toggle"
type="checkbox"
:disabled="isSaving"
/>
<div class="flex flex-col gap-2">
<div class="font-semibold text-contrast">Interval</div>
<p class="m-0">
The amount of hours between each backup. This will only backup your server if it has
been modified since the last backup.
</p>
</div>
<div class="flex items-center gap-2 text-contrast">
<div
class="flex w-fit items-center rounded-xl border border-solid border-button-border bg-table-alternateRow"
>
<button
class="rounded-l-xl p-3 text-secondary enabled:hover:text-contrast [&&]:bg-transparent enabled:[&&]:hover:bg-button-bg"
:disabled="!autoBackupEnabled || isSaving"
@click="autoBackupInterval = Math.max(autoBackupInterval - 1, 1)"
>
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="2" viewBox="-2 0 18 2">
<path
d="M18,12H6"
transform="translate(-5 -11)"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
/>
</svg>
</button>
<input
id="auto-backup-interval"
v-model="autoBackupInterval"
class="w-16 !appearance-none text-center [&&]:bg-transparent [&&]:focus:shadow-none"
type="number"
style="-moz-appearance: textfield; appearance: none"
min="1"
max="24"
step="1"
:disabled="!autoBackupEnabled || isSaving"
/>
<button
class="rounded-r-xl p-3 text-secondary enabled:hover:text-contrast [&&]:bg-transparent enabled:[&&]:hover:bg-button-bg"
:disabled="!autoBackupEnabled || isSaving"
@click="autoBackupInterval = Math.min(autoBackupInterval + 1, 24)"
>
<PlusIcon />
</button>
</div>
{{ autoBackupInterval == 1 ? "hour" : "hours" }}
</div>
<div class="mt-4 flex justify-start gap-4">
<ButtonStyled color="brand">
<button :disabled="!hasChanges || isSaving" @click="saveSettings">
<SaveIcon class="h-5 w-5" />
{{ isSaving ? "Saving..." : "Save changes" }}
</button>
</ButtonStyled>
<ButtonStyled>
<button :disabled="isSaving" @click="modal?.hide()">
<XIcon />
Cancel
</button>
</ButtonStyled>
</div>
</template>
</div>
</NewModal>
</template>
<script setup lang="ts">
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { PlusIcon, XIcon, SaveIcon } from "@modrinth/assets";
import { ref, computed } from "vue";
import type { Server } from "~/composables/pyroServers";
const props = defineProps<{
server: Server<["backups"]>;
}>();
const modal = ref<InstanceType<typeof NewModal>>();
const initialSettings = ref<{ interval: number; enabled: boolean } | null>(null);
const autoBackupEnabled = ref(false);
const autoBackupInterval = ref(1);
const isLoadingSettings = ref(true);
const isSaving = ref(false);
const validatedBackupInterval = computed(() => {
const roundedValue = Math.round(autoBackupInterval.value);
if (roundedValue < 1) {
return 1;
} else if (roundedValue > 24) {
return 24;
}
return roundedValue;
});
const hasChanges = computed(() => {
if (!initialSettings.value) return false;
return (
autoBackupEnabled.value !== initialSettings.value.enabled ||
autoBackupInterval.value !== initialSettings.value.interval
);
});
const fetchSettings = async () => {
isLoadingSettings.value = true;
try {
const settings = await props.server.backups?.getAutoBackup();
initialSettings.value = settings as { interval: number; enabled: boolean };
autoBackupEnabled.value = settings?.enabled ?? false;
autoBackupInterval.value = settings?.interval || 1;
} catch (error) {
console.error("Error fetching backup settings:", error);
addNotification({
group: "server",
title: "Error",
text: "Failed to load backup settings",
type: "error",
});
} finally {
isLoadingSettings.value = false;
}
};
const saveSettings = async () => {
isSaving.value = true;
try {
await props.server.backups?.updateAutoBackup(
autoBackupEnabled.value ? "enable" : "disable",
autoBackupInterval.value,
);
initialSettings.value = {
enabled: autoBackupEnabled.value,
interval: autoBackupInterval.value,
};
addNotification({
group: "server",
title: "Success",
text: "Backup settings updated successfully",
type: "success",
});
modal.value?.hide();
} catch (error) {
console.error("Error saving backup settings:", error);
addNotification({
group: "server",
title: "Error",
text: "Failed to save backup settings",
type: "error",
});
} finally {
isSaving.value = false;
}
};
watch(autoBackupInterval, () => {
autoBackupInterval.value = validatedBackupInterval.value;
});
defineExpose({
show: async () => {
await fetchSettings();
modal.value?.show();
},
});
</script>
<style scoped>
.stylized-toggle:checked::after {
background: var(--color-accent-contrast) !important;
}
</style>

View File

@@ -0,0 +1,229 @@
<template>
<li
role="button"
data-pyro-file
:class="containerClasses"
tabindex="0"
@click="selectItem"
@contextmenu="openContextMenu"
@keydown="(e) => e.key === 'Enter' && selectItem()"
>
<div data-pyro-file-metadata class="flex w-full items-center gap-4 truncate">
<div
class="flex size-8 items-center justify-center rounded-full bg-bg-raised p-[6px] group-hover:bg-brand-highlight group-hover:text-brand group-focus:bg-brand-highlight group-focus:text-brand"
>
<component :is="iconComponent" class="size-6" />
</div>
<div class="flex w-full flex-col truncate">
<span
class="w-[98%] truncate font-bold group-hover:text-contrast group-focus:text-contrast"
>{{ name }}</span
>
<span class="text-xs text-secondary group-hover:text-primary">
{{ subText }}
</span>
</div>
</div>
<div data-pyro-file-actions class="flex w-fit flex-shrink-0 items-center gap-4 md:gap-12">
<span class="w-[160px] text-nowrap text-right font-mono text-sm text-secondary">{{
formattedDate
}}</span>
<ButtonStyled circular type="transparent">
<UiServersTeleportOverflowMenu :options="menuOptions" direction="left" position="bottom">
<MoreHorizontalIcon class="h-5 w-5 bg-transparent" />
<template #rename> <EditIcon /> Rename </template>
<template #move> <RightArrowIcon /> Move </template>
<template #download> <DownloadIcon /> Download </template>
<template #delete> <TrashIcon /> Delete </template>
</UiServersTeleportOverflowMenu>
</ButtonStyled>
</div>
</li>
</template>
<script setup lang="ts">
import { ButtonStyled } from "@modrinth/ui";
import {
MoreHorizontalIcon,
EditIcon,
DownloadIcon,
TrashIcon,
FolderOpenIcon,
FileIcon,
RightArrowIcon,
} from "@modrinth/assets";
import { computed, shallowRef, ref } from "vue";
import { useRouter, useRoute } from "vue-router";
import {
UiServersIconsCogFolderIcon,
UiServersIconsEarthIcon,
UiServersIconsCodeFileIcon,
UiServersIconsTextFileIcon,
UiServersIconsImageFileIcon,
} from "#components";
import PaletteIcon from "~/assets/icons/palette.svg?component";
interface FileItemProps {
name: string;
type: "directory" | "file";
size?: number;
count?: number;
modified: number;
path: string;
}
const props = defineProps<FileItemProps>();
const emit = defineEmits(["rename", "download", "delete", "move", "edit", "contextmenu"]);
const codeExtensions = Object.freeze([
"json",
"json5",
"jsonc",
"java",
"kt",
"kts",
"sh",
"bat",
"ps1",
"yml",
"yaml",
"toml",
"js",
"ts",
"py",
"rb",
"php",
"html",
"css",
"cpp",
"c",
"h",
"rs",
"go",
]);
const textExtensions = Object.freeze(["txt", "md", "log", "cfg", "conf", "properties", "ini"]);
const imageExtensions = Object.freeze(["png", "jpg", "jpeg", "gif", "svg", "webp"]);
const units = Object.freeze(["B", "KB", "MB", "GB", "TB", "PB", "EB"]);
const route = shallowRef(useRoute());
const router = useRouter();
const containerClasses = computed(() => [
"group m-0 p-0 focus:!outline-none flex w-full select-none items-center justify-between overflow-hidden border-0 border-b border-solid border-bg-raised p-3 last:border-none hover:bg-bg-raised focus:bg-bg-raised",
isEditableFile.value ? "cursor-pointer" : props.type === "directory" ? "cursor-pointer" : "",
]);
const fileExtension = computed(() => props.name.split(".").pop()?.toLowerCase() || "");
const menuOptions = computed(() => [
{
id: "rename",
action: () => emit("rename", { name: props.name, type: props.type, path: props.path }),
},
{
id: "move",
action: () => emit("move", { name: props.name, type: props.type, path: props.path }),
},
{
id: "download",
action: () => emit("download", { name: props.name, type: props.type, path: props.path }),
shown: props.type !== "directory",
},
{
id: "delete",
action: () => emit("delete", { name: props.name, type: props.type, path: props.path }),
color: "red" as const,
},
]);
const iconComponent = computed(() => {
if (props.type === "directory") {
if (props.name === "config") return UiServersIconsCogFolderIcon;
if (props.name === "world") return UiServersIconsEarthIcon;
if (props.name === "resourcepacks") return PaletteIcon;
return FolderOpenIcon;
}
const ext = fileExtension.value;
if (codeExtensions.includes(ext)) return UiServersIconsCodeFileIcon;
if (textExtensions.includes(ext)) return UiServersIconsTextFileIcon;
if (imageExtensions.includes(ext)) return UiServersIconsImageFileIcon;
return FileIcon;
});
const subText = computed(() => {
if (props.type === "directory") {
return `${props.count} ${props.count === 1 ? "item" : "items"}`;
}
return formattedSize.value;
});
const formattedDate = computed(() => {
const date = new Date(props.modified * 1000);
return `${date.toLocaleDateString("en-US", {
month: "2-digit",
day: "2-digit",
year: "2-digit",
})}, ${date.toLocaleTimeString("en-US", {
hour: "numeric",
minute: "numeric",
hour12: true,
})}`;
});
const isEditableFile = computed(() => {
if (props.type === "file") {
const ext = fileExtension.value;
return (
!props.name.includes(".") ||
textExtensions.includes(ext) ||
codeExtensions.includes(ext) ||
imageExtensions.includes(ext)
);
}
return false;
});
const formattedSize = computed(() => {
if (props.size === undefined) return "";
const bytes = props.size;
if (bytes === 0) return "0 B";
const exponent = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
const size = (bytes / Math.pow(1024, exponent)).toFixed(2);
return `${size} ${units[exponent]}`;
});
const openContextMenu = (event: MouseEvent) => {
event.preventDefault();
emit("contextmenu", event.clientX, event.clientY);
};
const navigateToFolder = () => {
const currentPath = route.value.query.path?.toString() || "";
const newPath = currentPath.endsWith("/")
? `${currentPath}${props.name}`
: `${currentPath}/${props.name}`;
router.push({ query: { path: newPath, page: 1 } });
};
const isNavigating = ref(false);
const selectItem = () => {
if (isNavigating.value) return;
isNavigating.value = true;
if (props.type === "directory") {
navigateToFolder();
} else if (props.type === "file" && isEditableFile.value) {
emit("edit", { name: props.name, type: props.type, path: props.path });
}
setTimeout(() => {
isNavigating.value = false;
}, 500);
};
</script>

View File

@@ -0,0 +1,40 @@
<template>
<div class="flex h-full w-full items-center justify-center gap-6 p-20">
<FileIcon class="size-28" />
<div class="flex flex-col gap-2">
<h3 class="text-red-500 m-0 text-2xl font-bold">{{ title }}</h3>
<p class="m-0 text-sm text-secondary">
{{ message }}
</p>
<div class="flex gap-2">
<ButtonStyled>
<button size="sm" @click="$emit('refetch')">
<UiServersIconsLoadingIcon class="h-5 w-5" />
Try again
</button>
</ButtonStyled>
<ButtonStyled>
<button size="sm" @click="$emit('home')">
<HomeIcon class="h-5 w-5" />
Go to home folder
</button>
</ButtonStyled>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { FileIcon, HomeIcon } from "@modrinth/assets";
import { ButtonStyled } from "@modrinth/ui";
defineProps<{
title: string;
message: string;
}>();
defineEmits<{
(e: "refetch"): void;
(e: "home"): void;
}>();
</script>

View File

@@ -0,0 +1,120 @@
<template>
<div ref="listContainer" data-pyro-files-virtual-list-root class="relative w-full">
<div
:style="{
position: 'relative',
minHeight: `${totalHeight}px`,
}"
data-pyro-files-virtual-height-watcher
>
<ul
class="list-none"
:style="{
position: 'absolute',
top: `${visibleTop}px`,
width: '100%',
margin: 0,
padding: 0,
}"
data-pyro-files-virtual-list
>
<UiServersFileItem
v-for="item in visibleItems"
:key="item.path"
:count="item.count"
:created="item.created"
:modified="item.modified"
:name="item.name"
:path="item.path"
:type="item.type"
:size="item.size"
@delete="$emit('delete', item)"
@rename="$emit('rename', item)"
@download="$emit('download', item)"
@move="$emit('move', item)"
@edit="$emit('edit', item)"
@contextmenu="(x, y) => $emit('contextmenu', item, x, y)"
/>
</ul>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from "vue";
const props = defineProps<{
items: any[];
}>();
const emit = defineEmits<{
(e: "delete", item: any): void;
(e: "rename", item: any): void;
(e: "download", item: any): void;
(e: "move", item: any): void;
(e: "edit", item: any): void;
(e: "contextmenu", item: any, x: number, y: number): void;
(e: "loadMore"): void;
}>();
const ITEM_HEIGHT = 61;
const BUFFER_SIZE = 5;
const listContainer = ref<HTMLElement | null>(null);
const windowScrollY = ref(0);
const windowHeight = ref(0);
const totalHeight = computed(() => props.items.length * ITEM_HEIGHT);
const visibleRange = computed(() => {
if (!listContainer.value) return { start: 0, end: 0 };
const containerTop = listContainer.value.getBoundingClientRect().top + window.scrollY;
const relativeScrollTop = Math.max(0, windowScrollY.value - containerTop);
const start = Math.floor(relativeScrollTop / ITEM_HEIGHT);
const visibleCount = Math.ceil(windowHeight.value / ITEM_HEIGHT);
return {
start: Math.max(0, start - BUFFER_SIZE),
end: Math.min(props.items.length, start + visibleCount + BUFFER_SIZE * 2),
};
});
const visibleTop = computed(() => {
return visibleRange.value.start * ITEM_HEIGHT;
});
const visibleItems = computed(() => {
return props.items.slice(visibleRange.value.start, visibleRange.value.end);
});
const handleScroll = () => {
windowScrollY.value = window.scrollY;
if (!listContainer.value) return;
const containerBottom = listContainer.value.getBoundingClientRect().bottom;
const remainingScroll = containerBottom - window.innerHeight;
if (remainingScroll < windowHeight.value * 0.2) {
emit("loadMore");
}
};
const handleResize = () => {
windowHeight.value = window.innerHeight;
};
onMounted(() => {
windowHeight.value = window.innerHeight;
window.addEventListener("scroll", handleScroll, { passive: true });
window.addEventListener("resize", handleResize, { passive: true });
handleScroll();
});
onUnmounted(() => {
window.removeEventListener("scroll", handleScroll);
window.removeEventListener("resize", handleResize);
});
</script>

View File

@@ -0,0 +1,231 @@
<template>
<div ref="pyroFilesSentinel" class="sentinel" data-pyro-files-sentinel />
<header
:class="[
'duration-20 h-26 top-0 flex select-none flex-col justify-between gap-2 bg-table-alternateRow p-3 transition-[border-radius] sm:h-12 sm:flex-row',
!isStuck ? 'rounded-t-2xl' : 'sticky top-0 z-20',
]"
data-pyro-files-state="browsing"
aria-label="File navigation"
>
<nav
aria-label="Breadcrumb navigation"
class="m-0 flex min-w-0 flex-shrink items-center p-0 text-contrast"
>
<ol class="m-0 flex min-w-0 flex-shrink list-none items-center p-0">
<li class="-ml-1 flex-shrink-0">
<ButtonStyled type="transparent">
<button
v-tooltip="'Back to home'"
type="button"
class="mr-2 grid h-12 w-10 place-content-center focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand"
@click="$emit('navigate', -1)"
>
<span
class="grid size-8 place-content-center rounded-full bg-button-bg p-[6px] group-hover:bg-brand-highlight group-hover:text-brand"
>
<HomeIcon class="h-5 w-5" />
<span class="sr-only">Home</span>
</span>
</button>
</ButtonStyled>
</li>
<li class="m-0 -ml-2 min-w-0 flex-shrink p-0">
<ol class="m-0 flex min-w-0 flex-shrink items-center overflow-hidden p-0">
<TransitionGroup
name="breadcrumb"
tag="span"
class="relative flex min-w-0 flex-shrink items-center"
>
<li
v-for="(segment, index) in breadcrumbSegments"
:key="`${segment || index}-group`"
class="relative flex min-w-0 flex-shrink items-center text-sm"
>
<div class="flex min-w-0 flex-shrink items-center">
<ButtonStyled type="transparent">
<button
class="cursor-pointer truncate focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand"
:aria-current="
index === breadcrumbSegments.length - 1 ? 'location' : undefined
"
:class="{
'!text-contrast': index === breadcrumbSegments.length - 1,
}"
@click="$emit('navigate', index)"
>
{{ segment || "" }}
</button>
</ButtonStyled>
<ChevronRightIcon
v-if="index < breadcrumbSegments.length - 1"
class="size-4 flex-shrink-0 text-secondary"
aria-hidden="true"
/>
</div>
</li>
</TransitionGroup>
</ol>
</li>
</ol>
</nav>
<div class="flex flex-shrink-0 items-center gap-1">
<div class="flex w-full flex-row-reverse sm:flex-row">
<ButtonStyled type="transparent">
<UiServersTeleportOverflowMenu
position="bottom"
direction="left"
aria-label="Sort files"
:options="[
{ id: 'normal', action: () => $emit('sort', 'default') },
{ id: 'modified', action: () => $emit('sort', 'modified') },
{ id: 'filesOnly', action: () => $emit('sort', 'filesOnly') },
{ id: 'foldersOnly', action: () => $emit('sort', 'foldersOnly') },
]"
>
<span class="hidden whitespace-pre text-sm font-medium sm:block">
{{ sortMethodLabel }}
</span>
<SortAscendingIcon aria-hidden="true" />
<DropdownIcon aria-hidden="true" class="h-5 w-5 text-secondary" />
<template #normal> Alphabetical </template>
<template #modified> Date modified </template>
<template #filesOnly> Files only </template>
<template #foldersOnly> Folders only </template>
</UiServersTeleportOverflowMenu>
</ButtonStyled>
<div class="mx-1 w-full text-sm sm:w-40">
<label for="search-folder" class="sr-only">Search folder</label>
<div class="relative">
<SearchIcon
class="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2"
aria-hidden="true"
/>
<input
id="search-folder"
:value="searchQuery"
type="search"
name="search"
autocomplete="off"
class="h-8 min-h-[unset] w-full border-[1px] border-solid border-button-bg bg-transparent py-2 pl-9"
placeholder="Search..."
@input="$emit('update:searchQuery', ($event.target as HTMLInputElement).value)"
/>
</div>
</div>
</div>
<ButtonStyled type="transparent">
<UiServersTeleportOverflowMenu
position="bottom"
direction="left"
aria-label="Create new..."
:options="[
{ id: 'file', action: () => $emit('create', 'file') },
{ id: 'directory', action: () => $emit('create', 'directory') },
{ id: 'upload', action: () => $emit('upload') },
]"
>
<PlusIcon aria-hidden="true" />
<DropdownIcon aria-hidden="true" class="h-5 w-5 text-secondary" />
<template #file> <BoxIcon aria-hidden="true" /> New file </template>
<template #directory> <FolderOpenIcon aria-hidden="true" /> New folder </template>
<template #upload> <UploadIcon aria-hidden="true" /> Upload file </template>
</UiServersTeleportOverflowMenu>
</ButtonStyled>
</div>
</header>
</template>
<script setup lang="ts">
import {
BoxIcon,
PlusIcon,
UploadIcon,
DropdownIcon,
FolderOpenIcon,
SearchIcon,
SortAscendingIcon,
HomeIcon,
ChevronRightIcon,
} from "@modrinth/assets";
import { ButtonStyled } from "@modrinth/ui";
import { ref, computed } from "vue";
import { useIntersectionObserver } from "@vueuse/core";
const props = defineProps<{
breadcrumbSegments: string[];
searchQuery: string;
sortMethod: string;
}>();
defineEmits<{
(e: "navigate", index: number): void;
(e: "sort", method: string): void;
(e: "create", type: "file" | "directory"): void;
(e: "upload"): void;
(e: "update:searchQuery", value: string): void;
}>();
const pyroFilesSentinel = ref<HTMLElement | null>(null);
const isStuck = ref(false);
useIntersectionObserver(
pyroFilesSentinel,
([{ isIntersecting }]) => {
isStuck.value = !isIntersecting;
},
{ threshold: [0, 1] },
);
const sortMethodLabel = computed(() => {
switch (props.sortMethod) {
case "modified":
return "Date modified";
case "filesOnly":
return "Files only";
case "foldersOnly":
return "Folders only";
default:
return "Alphabetical";
}
});
</script>
<style scoped>
.sentinel {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
visibility: hidden;
}
.breadcrumb-move,
.breadcrumb-enter-active,
.breadcrumb-leave-active {
transition: all 0.2s ease;
}
.breadcrumb-enter-from {
opacity: 0;
transform: translateX(-10px) scale(0.9);
}
.breadcrumb-leave-to {
opacity: 0;
transform: translateX(-10px) scale(0.8);
filter: blur(4px);
}
.breadcrumb-leave-active {
position: relative;
pointer-events: none;
}
.breadcrumb-move {
z-index: 1;
}
</style>

View File

@@ -0,0 +1,106 @@
<template>
<div
class="fixed"
:style="{
transform: `translateY(${isAtBottom ? '-100%' : '0'})`,
top: `${y}px`,
left: `${x}px`,
}"
>
<Transition>
<div
v-if="item"
id="item-context-menu"
ref="ctxRef"
:style="{
border: '1px solid var(--color-button-bg)',
borderRadius: 'var(--radius-md)',
backgroundColor: 'var(--color-raised-bg)',
padding: 'var(--gap-sm)',
boxShadow: 'var(--shadow-floating)',
gap: 'var(--gap-xs)',
width: 'max-content',
}"
class="flex h-fit w-fit select-none flex-col"
>
<button
class="btn btn-transparent flex !w-full items-center"
@click="$emit('rename', item)"
>
<EditIcon class="h-5 w-5" />
Rename
</button>
<button class="btn btn-transparent flex !w-full items-center" @click="$emit('move', item)">
<ArrowBigUpDashIcon class="h-5 w-5" />
Move
</button>
<button
v-if="item.type !== 'directory'"
class="btn btn-transparent flex !w-full items-center"
@click="$emit('download', item)"
>
<DownloadIcon class="h-5 w-5" />
Download
</button>
<button
class="btn btn-transparent btn-red flex !w-full items-center"
@click="$emit('delete', item)"
>
<TrashIcon class="h-5 w-5" />
Delete
</button>
</div>
</Transition>
</div>
</template>
<script setup lang="ts">
import { EditIcon, ArrowBigUpDashIcon, DownloadIcon, TrashIcon } from "@modrinth/assets";
interface FileItem {
type: string;
name: string;
[key: string]: any;
}
defineProps<{
item: FileItem | null;
x: number;
y: number;
isAtBottom: boolean;
}>();
const ctxRef = ref<HTMLElement | null>(null);
defineEmits<{
(e: "rename", item: FileItem): void;
(e: "move", item: FileItem): void;
(e: "download", item: FileItem): void;
(e: "delete", item: FileItem): void;
}>();
defineExpose({
ctxRef,
});
</script>
<style scoped>
#item-context-menu {
transition:
transform 0.1s ease,
opacity 0.1s ease;
transform-origin: top left;
}
#item-context-menu.v-enter-active,
#item-context-menu.v-leave-active {
transform: scale(1);
opacity: 1;
}
#item-context-menu.v-enter-from,
#item-context-menu.v-leave-to {
transform: scale(0.5);
opacity: 0;
}
</style>

View File

@@ -0,0 +1,95 @@
<template>
<NewModal ref="modal" :header="`Creating a ${type}`">
<form class="flex flex-col gap-4 md:w-[600px]" @submit.prevent="handleSubmit">
<div class="flex flex-col gap-2">
<div class="font-semibold text-contrast">Name</div>
<input
ref="createInput"
v-model="itemName"
autofocus
type="text"
class="bg-bg-input w-full rounded-lg p-4"
:placeholder="`e.g. ${type === 'file' ? 'config.yml' : 'plugins'}`"
required
/>
<div v-if="submitted && error" class="text-red">{{ error }}</div>
</div>
<div class="flex justify-start gap-4">
<ButtonStyled color="brand">
<button :disabled="!!error" type="submit">
<PlusIcon class="h-5 w-5" />
Create
</button>
</ButtonStyled>
<ButtonStyled>
<button type="button" @click="hide">
<XIcon class="h-5 w-5" />
Cancel
</button>
</ButtonStyled>
</div>
</form>
</NewModal>
</template>
<script setup lang="ts">
import { PlusIcon, XIcon } from "@modrinth/assets";
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { ref, computed, nextTick } from "vue";
const props = defineProps<{
type: "file" | "directory";
}>();
const emit = defineEmits<{
(e: "create", name: string): void;
}>();
const modal = ref<typeof NewModal>();
const createInput = ref<HTMLInputElement | null>(null);
const itemName = ref("");
const submitted = ref(false);
const error = computed(() => {
if (!itemName.value) {
return "Name is required.";
}
if (props.type === "file") {
const validPattern = /^[a-zA-Z0-9-_.\s]+$/;
if (!validPattern.test(itemName.value)) {
return "Name must contain only alphanumeric characters, dashes, underscores, dots, or spaces.";
}
} else {
const validPattern = /^[a-zA-Z0-9-_\s]+$/;
if (!validPattern.test(itemName.value)) {
return "Name must contain only alphanumeric characters, dashes, underscores, or spaces.";
}
}
return "";
});
const handleSubmit = () => {
submitted.value = true;
if (!error.value) {
emit("create", itemName.value);
hide();
}
};
const show = () => {
itemName.value = "";
submitted.value = false;
modal.value?.show();
nextTick(() => {
setTimeout(() => {
createInput.value?.focus();
}, 100);
});
};
const hide = () => {
modal.value?.hide();
};
defineExpose({ show, hide });
</script>

View File

@@ -0,0 +1,77 @@
<template>
<NewModal ref="modal" danger :header="`Deleting ${item?.type}`">
<form class="flex flex-col gap-4 md:w-[600px]" @submit.prevent="handleSubmit">
<div
class="relative flex w-full items-center gap-2 rounded-2xl border border-solid border-[#cb224436] bg-[#f57b7b0e] p-6 shadow-md dark:border-0 dark:bg-[#0e0e0ea4]"
>
<div
class="flex h-9 w-9 items-center justify-center rounded-full bg-[#3f1818a4] p-[6px] group-hover:bg-brand-highlight group-hover:text-brand"
>
<FolderOpenIcon v-if="item?.type === 'directory'" class="h-5 w-5" />
<FileIcon v-else-if="item?.type === 'file'" class="h-5 w-5" />
</div>
<div class="flex flex-col">
<span class="font-bold group-hover:text-contrast">{{ item?.name }}</span>
<span
v-if="item?.type === 'directory'"
class="text-xs text-secondary group-hover:text-primary"
>
{{ item?.count }} items
</span>
<span v-else class="text-xs text-secondary group-hover:text-primary">
{{ ((item?.size ?? 0) / 1024 / 1024).toFixed(2) }} MB
</span>
</div>
</div>
<div class="flex justify-start gap-4">
<ButtonStyled color="red">
<button type="submit">
<TrashIcon class="h-5 w-5" />
Delete {{ item?.type }}
</button>
</ButtonStyled>
<ButtonStyled>
<button type="button" @click="hide">
<XIcon class="h-5 w-5" />
Cancel
</button>
</ButtonStyled>
</div>
</form>
</NewModal>
</template>
<script setup lang="ts">
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { FileIcon, FolderOpenIcon, TrashIcon, XIcon } from "@modrinth/assets";
defineProps<{
item: {
name: string;
type: string;
count?: number;
size?: number;
} | null;
}>();
const emit = defineEmits<{
(e: "delete"): void;
}>();
const modal = ref<typeof NewModal>();
const handleSubmit = () => {
emit("delete");
hide();
};
const show = () => {
modal.value?.show();
};
const hide = () => {
modal.value?.hide();
};
defineExpose({ show, hide });
</script>

View File

@@ -0,0 +1,140 @@
<template>
<header
data-pyro-files-state="editing"
class="flex h-12 select-none items-center justify-between rounded-t-2xl bg-table-alternateRow p-3"
aria-label="File editor navigation"
>
<nav
aria-label="Breadcrumb navigation"
class="m-0 flex list-none items-center p-0 text-contrast"
>
<ol class="m-0 flex list-none items-center p-0">
<li class="-ml-1">
<ButtonStyled type="transparent">
<button
v-tooltip="'Back to home'"
type="button"
class="grid h-12 w-10 place-content-center focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand"
@click="goHome"
>
<span
class="grid size-8 place-content-center rounded-full bg-button-bg p-[6px] group-hover:bg-brand-highlight group-hover:text-brand"
>
<HomeIcon class="h-5 w-5" />
<span class="sr-only">Home</span>
</span>
</button>
</ButtonStyled>
</li>
<li class="m-0 -ml-2 p-0">
<ol class="m-0 flex items-center p-0">
<li
v-for="(segment, index) in breadcrumbSegments"
:key="index"
class="flex items-center text-sm"
>
<ButtonStyled type="transparent">
<button
class="cursor-pointer focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand"
:class="{ '!text-contrast': index === breadcrumbSegments.length - 1 }"
@click="$emit('navigate', index)"
>
{{ segment || "" }}
</button>
</ButtonStyled>
<ChevronRightIcon
v-if="index < breadcrumbSegments.length"
class="size-4 text-secondary"
aria-hidden="true"
/>
</li>
<li class="flex items-center px-3 text-sm">
<span class="font-semibold !text-contrast" aria-current="location">{{
fileName
}}</span>
</li>
</ol>
</li>
</ol>
</nav>
<div v-if="!isImage" class="flex gap-2">
<Button
v-if="isLogFile"
v-tooltip="'Share to mclo.gs'"
icon-only
transparent
aria-label="Share to mclo.gs"
@click="$emit('share')"
>
<ShareIcon />
</Button>
<ButtonStyled type="transparent">
<UiServersTeleportOverflowMenu
position="bottom"
direction="left"
aria-label="Save file"
:options="[
{ id: 'save', action: () => $emit('save') },
{ id: 'save-as', action: () => $emit('save-as') },
{ id: 'save&restart', action: () => $emit('save-restart') },
]"
>
<SaveIcon aria-hidden="true" />
<DropdownIcon aria-hidden="true" class="h-5 w-5 text-secondary" />
<template #save> <SaveIcon aria-hidden="true" /> Save </template>
<template #save-as> <SaveIcon aria-hidden="true" /> Save as... </template>
<template #save&restart>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M15.312 11.424a5.5 5.5 0 0 1-9.201 2.466l-.312-.311h2.433a.75.75 0 0 0 0-1.5H3.989a.75.75 0 0 0-.75.75v4.242a.75.75 0 0 0 1.5 0v-2.43l.31.31a7 7 0 0 0 11.712-3.138.75.75 0 0 0-1.449-.39Zm1.23-3.723a.75.75 0 0 0 .219-.53V2.929a.75.75 0 0 0-1.5 0V5.36l-.31-.31A7 7 0 0 0 3.239 8.188a.75.75 0 1 0 1.448.389A5.5 5.5 0 0 1 13.89 6.11l.311.31h-2.432a.75.75 0 0 0 0 1.5h4.243a.75.75 0 0 0 .53-.219Z"
clip-rule="evenodd"
/>
</svg>
Save & restart
</template>
</UiServersTeleportOverflowMenu>
</ButtonStyled>
</div>
</header>
</template>
<script setup lang="ts">
import { DropdownIcon, SaveIcon, ShareIcon, HomeIcon, ChevronRightIcon } from "@modrinth/assets";
import { Button, ButtonStyled } from "@modrinth/ui";
import { computed } from "vue";
import { useRoute, useRouter } from "vue-router";
const props = defineProps<{
breadcrumbSegments: string[];
fileName?: string;
isImage: boolean;
filePath?: string;
}>();
const isLogFile = computed(() => {
return props.filePath?.startsWith("logs") || props.filePath?.endsWith(".log");
});
const route = useRoute();
const router = useRouter();
const emit = defineEmits<{
(e: "cancel"): void;
(e: "save"): void;
(e: "save-as"): void;
(e: "save-restart"): void;
(e: "share"): void;
(e: "navigate", index: number): void;
}>();
const goHome = () => {
emit("cancel");
router.push({ path: "/servers/manage/" + route.params.id + "/files" });
};
</script>

View File

@@ -0,0 +1,159 @@
<template>
<div class="flex h-[calc(100vh-12rem)] w-full flex-col items-center bg-bg-raised">
<div
ref="container"
class="relative w-full flex-grow overflow-hidden bg-bg-raised"
@mousedown="startPan"
@mousemove="pan"
@mouseup="endPan"
@mouseleave="endPan"
@wheel.prevent="handleWheel"
>
<UiServersPyroLoading v-if="loading" />
<div v-if="error" class="flex h-full w-full flex-col items-center justify-center gap-8">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="size-12"
>
<path d="M4 13c3.5-2 8-2 10 2a5.5 5.5 0 0 1 8 5" />
<path
d="M5.15 17.89c5.52-1.52 8.65-6.89 7-12C11.55 4 11.5 2 13 2c3.22 0 5 5.5 5 8 0 6.5-4.2 12-10.49 12C5.11 22 2 22 2 20c0-1.5 1.14-1.55 3.15-2.11Z"
/>
</svg>
<p class="m-0">Invalid or empty image file.</p>
</div>
<img
v-show="!loading && !error"
ref="image"
:src="imageUrl"
class="pointer-events-none absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 transform"
:style="{
transform: `translate(-50%, -50%) scale(${scale}) translate(${translateX}px, ${translateY}px)`,
transition: isPanning ? 'none' : 'transform 0.3s ease-out',
}"
alt="Viewed image"
@load="onImageLoad"
@error="onImageError"
/>
</div>
<div
v-if="!error"
class="absolute bottom-0 mb-2 flex w-fit justify-center space-x-4 rounded-xl bg-bg p-2"
>
<Button icon-only transparent @click="zoomIn">
<ZoomInIcon />
</Button>
<Button icon-only transparent @click="resetZoom">
<HomeIcon />
</Button>
<Button icon-only transparent @click="zoomOut">
<ZoomOutIcon />
</Button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from "vue";
import { HomeIcon, ZoomInIcon, ZoomOutIcon } from "@modrinth/assets";
import { Button } from "@modrinth/ui";
const props = defineProps({
imageBlob: {
type: Blob,
required: true,
},
});
const container = ref(null);
const image = ref(null);
const scale = ref(1);
const translateX = ref(0);
const translateY = ref(0);
const isPanning = ref(false);
const startX = ref(0);
const startY = ref(0);
const imageUrl = ref("");
const loading = ref(true);
const error = ref(false);
const createImageUrl = (blob) => {
if (imageUrl.value) {
URL.revokeObjectURL(imageUrl.value);
}
imageUrl.value = URL.createObjectURL(blob);
};
watch(
() => props.imageBlob,
(newBlob) => {
if (newBlob) {
loading.value = true;
error.value = false;
createImageUrl(newBlob);
}
},
);
onMounted(() => {
if (props.imageBlob) {
createImageUrl(props.imageBlob);
}
});
const onImageLoad = () => {
loading.value = false;
resetZoom();
};
const onImageError = () => {
loading.value = false;
error.value = true;
};
const zoomIn = () => {
scale.value = Math.min(scale.value * 1.2, 5);
};
const zoomOut = () => {
scale.value = Math.max(scale.value / 1.2, 0.1);
};
const resetZoom = () => {
scale.value = 0.5;
translateX.value = 0;
translateY.value = 0;
};
const startPan = (e) => {
isPanning.value = true;
startX.value = e.clientX - translateX.value;
startY.value = e.clientY - translateY.value;
};
const pan = (e) => {
if (isPanning.value) {
translateX.value = e.clientX - startX.value;
translateY.value = e.clientY - startY.value;
}
};
const endPan = () => {
isPanning.value = false;
};
const handleWheel = (e) => {
const delta = (e.deltaY * -0.01) / 10;
const newScale = Math.max(0.1, Math.min(scale.value + delta, 5));
scale.value = newScale;
};
</script>

View File

@@ -0,0 +1,81 @@
<template>
<NewModal ref="modal" :header="`Moving ${item?.name}`">
<form class="flex flex-col gap-4 md:w-[600px]" @submit.prevent="handleSubmit">
<div class="flex flex-col gap-2">
<input
ref="destinationInput"
v-model="destination"
autofocus
type="text"
class="bg-bg-input w-full rounded-lg p-4"
placeholder="e.g. mods/modname"
required
/>
</div>
<div class="flex items-center gap-2 text-nowrap">
New location:
<div class="w-full rounded-lg bg-table-alternateRow p-2 font-bold text-contrast">
<span class="text-secondary">/root</span>{{ newpath }}
</div>
</div>
<div class="flex justify-start gap-4">
<ButtonStyled color="brand">
<button type="submit">
<ArrowBigUpDashIcon class="h-5 w-5" />
Move
</button>
</ButtonStyled>
<ButtonStyled>
<button type="button" @click="hide">
<XIcon class="h-5 w-5" />
Cancel
</button>
</ButtonStyled>
</div>
</form>
</NewModal>
</template>
<script setup lang="ts">
import { ArrowBigUpDashIcon, XIcon } from "@modrinth/assets";
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { ref, nextTick } from "vue";
const destinationInput = ref<HTMLInputElement | null>(null);
const props = defineProps<{
item: { name: string } | null;
currentPath: string;
}>();
const emit = defineEmits<{
(e: "move", destination: string): void;
}>();
const modal = ref<typeof NewModal>();
const destination = ref("");
const newpath = computed(() => {
return destination.value.replace("//", "/");
});
const handleSubmit = () => {
emit("move", destination.value);
hide();
};
const show = () => {
destination.value = props.currentPath;
modal.value?.show();
nextTick(() => {
setTimeout(() => {
destinationInput.value?.focus();
}, 100);
});
};
const hide = () => {
modal.value?.hide();
};
defineExpose({ show, hide });
</script>

View File

@@ -0,0 +1,94 @@
<template>
<NewModal ref="modal" :header="`Renaming ${item?.type}`">
<form class="flex flex-col gap-4 md:w-[600px]" @submit.prevent="handleSubmit">
<div class="flex flex-col gap-2">
<div class="font-semibold text-contrast">Name</div>
<input
ref="renameInput"
v-model="itemName"
autofocus
type="text"
class="bg-bg-input w-full rounded-lg p-4"
required
/>
<div v-if="submitted && error" class="text-red">{{ error }}</div>
</div>
<div class="flex justify-start gap-4">
<ButtonStyled color="brand">
<button :disabled="!!error" type="submit">
<EditIcon class="h-5 w-5" />
Rename
</button>
</ButtonStyled>
<ButtonStyled>
<button type="button" @click="hide">
<XIcon class="h-5 w-5" />
Cancel
</button>
</ButtonStyled>
</div>
</form>
</NewModal>
</template>
<script setup lang="ts">
import { EditIcon, XIcon } from "@modrinth/assets";
import { ButtonStyled, NewModal } from "@modrinth/ui";
import { ref, computed, nextTick } from "vue";
const props = defineProps<{
item: { name: string; type: string } | null;
}>();
const emit = defineEmits<{
(e: "rename", newName: string): void;
}>();
const modal = ref<typeof NewModal>();
const renameInput = ref<HTMLInputElement | null>(null);
const itemName = ref("");
const submitted = ref(false);
const error = computed(() => {
if (!itemName.value) {
return "Name is required.";
}
if (props.item?.type === "file") {
const validPattern = /^[a-zA-Z0-9-_.\s]+$/;
if (!validPattern.test(itemName.value)) {
return "Name must contain only alphanumeric characters, dashes, underscores, dots, or spaces.";
}
} else {
const validPattern = /^[a-zA-Z0-9-_\s]+$/;
if (!validPattern.test(itemName.value)) {
return "Name must contain only alphanumeric characters, dashes, underscores, or spaces.";
}
}
return "";
});
const handleSubmit = () => {
submitted.value = true;
if (!error.value) {
emit("rename", itemName.value);
hide();
}
};
const show = (item: { name: string; type: string }) => {
itemName.value = item.name;
submitted.value = false;
modal.value?.show();
nextTick(() => {
setTimeout(() => {
renameInput.value?.focus();
}, 100);
});
};
const hide = () => {
modal.value?.hide();
};
defineExpose({ show, hide });
</script>

View File

@@ -0,0 +1,76 @@
<template>
<div
v-for="loader in loaders"
:key="loader.name"
class="group relative flex items-center justify-between rounded-2xl p-2 pr-2.5 hover:bg-bg"
>
<div class="flex items-center gap-4">
<div
class="grid size-10 place-content-center rounded-xl border-[1px] border-solid border-button-border bg-button-bg shadow-sm"
:class="isCurrentLoader(loader.name) ? '[&&]:bg-bg-green' : ''"
>
<UiServersIconsLoaderIcon
:loader="loader.name"
class="[&&]:size-6"
:class="isCurrentLoader(loader.name) ? 'text-brand' : ''"
/>
</div>
<div class="flex flex-col gap-0.5">
<div class="flex flex-row items-center gap-2">
<h1 class="m-0 text-xl font-bold leading-none text-contrast">
{{ loader.displayName }}
</h1>
<span
v-if="isCurrentLoader(loader.name)"
class="hidden items-center gap-1 rounded-full bg-bg-green p-1 px-1.5 text-xs font-semibold text-brand sm:flex"
>
<CheckIcon class="h-4 w-4" />
Current
</span>
</div>
<p v-if="isCurrentLoader(loader.name)" class="m-0 text-xs text-secondary">
{{ data.loader_version }}
</p>
</div>
</div>
<ButtonStyled>
<button @click="selectLoader(loader.name)">
<DownloadIcon class="h-5 w-5" />
{{ isCurrentLoader(loader.name) ? "Reinstall" : "Install" }}
</button>
</ButtonStyled>
</div>
</template>
<script setup lang="ts">
import { CheckIcon, DownloadIcon } from "@modrinth/assets";
import { ButtonStyled } from "@modrinth/ui";
const props = defineProps<{
data: {
loader: string | null;
loader_version: string | null;
};
}>();
const emit = defineEmits<{
(e: "selectLoader", loader: string): void;
}>();
const loaders = [
{ name: "Vanilla" as const, displayName: "Vanilla" },
{ name: "Fabric" as const, displayName: "Fabric" },
{ name: "Quilt" as const, displayName: "Quilt" },
{ name: "Forge" as const, displayName: "Forge" },
{ name: "NeoForge" as const, displayName: "NeoForge" },
];
const isCurrentLoader = (loaderName: string) => {
return props.data.loader?.toLowerCase() === loaderName.toLowerCase();
};
const selectLoader = (loader: string) => {
emit("selectLoader", loader);
};
</script>

View File

@@ -0,0 +1,107 @@
<template>
<div class="parsed-log group relative w-full overflow-hidden px-6 py-1">
<div
ref="logContent"
class="log-content selectable whitespace-pre-wrap selection:bg-black selection:text-white dark:selection:bg-white dark:selection:text-black"
v-html="sanitizedLog"
></div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from "vue";
import Convert from "ansi-to-html";
import DOMPurify from "dompurify";
const props = defineProps<{
log: string;
index: number;
}>();
const logContent = ref<HTMLElement | null>(null);
const colors = {
30: "#101010",
31: "#EFA6A2",
32: "#80C990",
33: "#A69460",
34: "#A3B8EF",
35: "#E6A3DC",
36: "#50CACD",
37: "#808080",
90: "#454545",
91: "#E0AF85",
92: "#5ACCAF",
93: "#C8C874",
94: "#CCACED",
95: "#F2A1C2",
96: "#74C3E4",
97: "#C0C0C0",
};
const convert = new Convert({
fg: "#FFF",
bg: "#000",
newline: false,
escapeXML: true,
stream: false,
colors,
});
const urlRegex = /https?:\/\/[^\s]+/g;
const usernameRegex = /&lt;([^&]+)&gt;/g;
const sanitizedLog = computed(() => {
let html = convert.toHtml(props.log);
html = html.replace(
urlRegex,
(url) =>
`<a style="color:var(--color-link);text-decoration:underline;" href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>`,
);
html = html.replace(
usernameRegex,
(_, username) => `<span class="minecraft-username">&lt;${username}&gt;</span>`,
);
return DOMPurify.sanitize(html, {
ALLOWED_TAGS: ["span", "a"],
ALLOWED_ATTR: ["style", "href", "target", "rel", "class"],
ADD_ATTR: ["target"],
RETURN_TRUSTED_TYPE: true,
USE_PROFILES: { html: true },
});
});
</script>
<style scoped>
.parsed-log:hover:not(.selected) {
border-radius: 0.5rem;
}
html.light-mode .parsed-log:hover:not(.selected) {
background-color: #ccc;
}
html.dark-mode .parsed-log:hover:not(.selected) {
background-color: #222;
}
html.oled-mode .parsed-log:hover:not(.selected) {
background-color: #222;
}
.minecraft-username {
font-weight: bold;
}
::v-deep(.log-content) {
user-select: none;
}
::v-deep(.log-content.selectable) {
user-select: text;
}
::v-deep(.log-content *) {
user-select: text;
}
</style>

View File

@@ -0,0 +1,660 @@
<template>
<div class="flex items-center justify-center">
<div class="w-full overflow-hidden">
<div class="mb-4">
<div
v-for="(line, lineIndex) in motd"
:key="lineIndex"
class="relative mb-2 rounded bg-button-bg p-2"
>
<div
class="font-minecraft text-white"
:contenteditable="true"
spellcheck="false"
@input="handleInput($event, lineIndex)"
@keydown.enter.prevent
@paste.prevent="handlePaste($event, lineIndex)"
@mouseup="handleSelection(lineIndex)"
v-html="renderLine(line)"
></div>
<div class="text-sm text-gray-400">
{{ motd[lineIndex].reduce((sum, segment) => sum + segment.text.length, 0) }}/45
characters
</div>
</div>
</div>
</div>
<transition name="fade">
<div
v-if="showPopup"
:style="{ top: `${popupY}px`, left: `${popupX}px` }"
class="fixed z-10 flex flex-col items-end gap-2 transition-all duration-300 ease-in-out"
>
<div class="rounded-xl border bg-table-alternateRow p-2 shadow-lg">
<div class="flex space-x-2">
<Button
v-for="style in styles"
:key="style.name"
icon-only
transparent
@click="applyStyle({ [style.name]: !currentStyle[style.name] })"
>
<component :is="style.icon" class="h-4 w-4" />
</Button>
<div class="relative overflow-y-scroll">
<Button icon-only transparent :class="colorPicker ?? 'hidden'" @click="pickColor">
<PaintBrushIcon />
</Button>
</div>
</div>
</div>
<div
v-if="colorPicker"
icon-only
class="w-fit overflow-y-auto rounded-xl p-2 [&&]:bg-table-alternateRow"
>
<div :class="colorPicker ? `grid grid-flow-col grid-rows-4 gap-2` : '[&&]:hidden'">
<button
v-for="format in sortedFormatCodes()"
:key="format.code"
class="rounded-full p-3"
:style="{ backgroundColor: format.color }"
:title="format.description"
@click="applyStyle({ color: format.color })"
></button>
</div>
</div>
</div>
</transition>
</div>
</template>
<script setup>
import {
ItalicIcon,
BoldIcon,
StrikethroughIcon,
UnderlineIcon,
PaintBrushIcon,
ChevronLeftIcon,
} from "@modrinth/assets";
import { Button } from "@modrinth/ui";
const props = defineProps({
server: {
type: Object,
required: true,
},
});
const formatCodes = [
{ code: "§f", color: "white", description: "White" },
{ code: "§7", color: "#AAAAAA", description: "Gray" },
{ code: "§8", color: "#555555", description: "Dark Gray" },
{ code: "§0", color: "#000000", description: "Black" },
{ code: "§9", color: "#5555FF", description: "Blue" },
{ code: "§1", color: "#0000AA", description: "Dark Blue" },
{ code: "§b", color: "#55FFFF", description: "Aqua" },
{ code: "§3", color: "#00AAAA", description: "Dark Aqua" },
{ code: "§a", color: "#55FF55", description: "Green" },
{ code: "§2", color: "#00AA00", description: "Dark Green" },
{ code: "§e", color: "#FFFF55", description: "Yellow" },
{ code: "§6", color: "#FFAA00", description: "Gold" },
{ code: "§c", color: "#FF5555", description: "Red" },
{ code: "§4", color: "#AA0000", description: "Dark Red" },
{ code: "§d", color: "#FF55FF", description: "Light Purple" },
{ code: "§5", color: "#AA00AA", description: "Dark Purple" },
];
const sortedFormatCodes = () => {
const colors = formatCodes;
if (colors[0].description === "White") {
colors.reverse();
}
return colors;
};
const minecraftEmojis = [
{ char: "☺", name: "SMILING FACE" },
{ char: "☹", name: "FROWNING FACE" },
{ char: "☠", name: "SKULL AND CROSSBONES" },
{ char: "❣", name: "HEART EXCLAMATION" },
{ char: "❤", name: "RED HEART" },
{ char: "✌", name: "VICTORY HAND" },
{ char: "☝", name: "INDEX POINTING UP" },
{ char: "✍", name: "WRITING HAND" },
{ char: "♨", name: "HOT SPRINGS" },
{ char: "✈", name: "AIRPLANE" },
{ char: "⌛", name: "HOURGLASS DONE" },
{ char: "⌚", name: "WATCH" },
{ char: "☀", name: "SUN" },
{ char: "☁", name: "CLOUD" },
{ char: "☂", name: "UMBRELLA" },
{ char: "❄", name: "SNOWFLAKE" },
{ char: "☃", name: "SNOWMAN" },
{ char: "☄", name: "COMET" },
{ char: "♠", name: "SPADE SUIT" },
{ char: "♥", name: "HEART SUIT" },
{ char: "♦", name: "DIAMOND SUIT" },
{ char: "♣", name: "CLUB SUIT" },
{ char: "♟", name: "CHESS PAWN" },
{ char: "☎", name: "TELEPHONE" },
{ char: "⌨", name: "KEYBOARD" },
{ char: "✉", name: "ENVELOPE" },
{ char: "✏", name: "PENCIL" },
{ char: "✒", name: "BLACK PEN" },
{ char: "✂", name: "SCISSORS" },
{ char: "☢", name: "RADIOACTIVE" },
{ char: "☣", name: "BIOHAZARD" },
{ char: "⬆", name: "UP ARROW" },
{ char: "⬇", name: "DOWN ARROW" },
{ char: "➡", name: "RIGHT ARROW" },
{ char: "⬅", name: "LEFT ARROW" },
{ char: "↗", name: "UP-RIGHT ARROW" },
{ char: "↘", name: "DOWN-RIGHT ARROW" },
{ char: "↙", name: "DOWN-LEFT ARROW" },
{ char: "↖", name: "UP-LEFT ARROW" },
{ char: "↕", name: "UP-DOWN ARROW" },
{ char: "↔", name: "LEFT-RIGHT ARROW" },
{ char: "↩", name: "RIGHT ARROW CURVING LEFT" },
{ char: "↪", name: "LEFT ARROW CURVING RIGHT" },
{ char: "✡", name: "STAR OF DAVID" },
{ char: "☸", name: "WHEEL OF DHARMA" },
{ char: "☯", name: "YIN YANG" },
{ char: "✝", name: "LATIN CROSS" },
{ char: "☦", name: "ORTHODOX CROSS" },
{ char: "☪", name: "STAR AND CRESCENT" },
{ char: "☮", name: "PEACE SYMBOL" },
{ char: "♈", name: "ARIES" },
{ char: "♉", name: "TAURUS" },
{ char: "♊", name: "GEMINI" },
{ char: "♋", name: "CANCER" },
{ char: "♌", name: "LEO" },
{ char: "♍", name: "VIRGO" },
{ char: "♎", name: "LIBRA" },
{ char: "♏", name: "SCORPIO" },
{ char: "♐", name: "SAGITTARIUS" },
{ char: "♑", name: "CAPRICORN" },
{ char: "♒", name: "AQUARIUS" },
{ char: "♓", name: "PISCES" },
{ char: "▶", name: "PLAY BUTTON" },
{ char: "◀", name: "REVERSE BUTTON" },
{ char: "♀", name: "FEMALE SIGN" },
{ char: "♂", name: "MALE SIGN" },
{ char: "✖", name: "MULTIPLY" },
{ char: "‼", name: "DOUBLE EXCLAMATION MARK" },
{ char: "〰", name: "WAVY DASH" },
{ char: "☑", name: "CHECK BOX WITH CHECK" },
{ char: "✔", name: "CHECK MARK" },
{ char: "✳", name: "EIGHT-SPOKED ASTERISK" },
{ char: "✴", name: "EIGHT-POINTED STAR" },
{ char: "❇", name: "SPARKLE" },
{ char: "©", name: "COPYRIGHT" },
{ char: "®", name: "REGISTERED" },
{ char: "™", name: "TRADE MARK" },
{ char: "Ⓜ", name: "CIRCLED M" },
{ char: "㊗", name: 'JAPANESE "CONGRATULATIONS" BUTTON' },
{ char: "㊙", name: 'JAPANESE "SECRET" BUTTON' },
{ char: "▪", name: "BLACK SMALL SQUARE" },
{ char: "▫", name: "WHITE SMALL SQUARE" },
{ char: "☷", name: "TRIGRAM FOR EARTH" },
{ char: "☵", name: "TRIGRAM FOR WATER" },
{ char: "☶", name: "TRIGRAM FOR MOUNTAIN" },
{ char: "☋", name: "DESCENDING NODE" },
{ char: "☌", name: "CONJUNCTION" },
{ char: "♜", name: "BLACK CHESS ROOK" },
{ char: "♕", name: "WHITE CHESS QUEEN" },
{ char: "♡", name: "WHITE HEART SUIT" },
{ char: "♬", name: "BEAMED SIXTEENTH NOTES" },
{ char: "☚", name: "BLACK LEFT POINTING INDEX" },
{ char: "♮", name: "MUSIC NATURAL SIGN" },
{ char: "♝", name: "BLACK CHESS BISHOP" },
{ char: "♯", name: "SHARP" },
{ char: "☴", name: "TRIGRAM FOR WIND" },
{ char: "♭", name: "FLAT" },
{ char: "☓", name: "SALTIRE" },
{ char: "☛", name: "BLACK RIGHT POINTING INDEX" },
{ char: "☭", name: "HAMMER AND SICKLE" },
{ char: "♢", name: "WHITE DIAMOND SUIT" },
{ char: "✐", name: "UPPER RIGHT PENCIL" },
{ char: "♖", name: "WHITE CHESS ROOK" },
{ char: "☈", name: "THUNDERSTORM" },
{ char: "☒", name: "BALLOT BOX WITH X" },
{ char: "★", name: "BLACK STAR" },
{ char: "♚", name: "BLACK CHESS KING" },
{ char: "♛", name: "BLACK CHESS QUEEN" },
{ char: "✎", name: "LOWER RIGHT PENCIL" },
{ char: "♪", name: "EIGHTH NOTE" },
{ char: "☰", name: "TRIGRAM FOR HEAVEN" },
{ char: "☽", name: "FIRST QUARTER MOON" },
{ char: "☡", name: "CAUTION SIGN" },
{ char: "☼", name: "WHITE SUN WITH RAYS" },
{ char: "♅", name: "URANUS" },
{ char: "☐", name: "BALLOT BOX" },
{ char: "☟", name: "WHITE DOWN POINTING INDEX" },
{ char: "❦", name: "FLORAL HEART" },
{ char: "☊", name: "ASCENDING NODE" },
{ char: "☍", name: "OPPOSITION" },
{ char: "☬", name: "ADI SHAKTI" },
{ char: "♧", name: "WHITE CLUB SUIT" },
{ char: "☫", name: "FARSI SYMBOL" },
{ char: "☱", name: "TRIGRAM FOR LAKE" },
{ char: "☾", name: "LAST QUARTER MOON" },
{ char: "☤", name: "CADUCEUS" },
{ char: "❧", name: "ROTATED FLORAL HEART BULLET" },
{ char: "♄", name: "SATURN" },
{ char: "♁", name: "EARTH" },
{ char: "♔", name: "WHITE CHESS KING" },
{ char: "❥", name: "ROTATED HEAVY BLACK HEART BULLET" },
{ char: "☥", name: "ANKH" },
{ char: "☻", name: "BLACK SMILING FACE" },
{ char: "♤", name: "WHITE SPADE SUIT" },
{ char: "♞", name: "BLACK CHESS KNIGHT" },
{ char: "♆", name: "NEPTUNE" },
{ char: "#", name: "HASH SIGN" },
{ char: "♃", name: "JUPITER" },
{ char: "♩", name: "QUARTER NOTE" },
{ char: "☇", name: "LIGHTNING" },
{ char: "☞", name: "WHITE RIGHT POINTING INDEX" },
{ char: "♫", name: "BEAMED EIGHTH NOTES" },
{ char: "☏", name: "WHITE TELEPHONE" },
{ char: "♘", name: "WHITE CHESS KNIGHT" },
{ char: "☧", name: "CHI RHO" },
{ char: "☉", name: "SUN" },
{ char: "♇", name: "PLUTO" },
{ char: "☩", name: "CROSS OF JERUSALEM" },
{ char: "♙", name: "WHITE CHESS PAWN" },
{ char: "☜", name: "WHITE LEFT POINTING INDEX" },
{ char: "☲", name: "TRIGRAM FOR FIRE" },
{ char: "☨", name: "CROSS OF LORRAINE" },
{ char: "♗", name: "WHITE CHESS BISHOP" },
{ char: "☳", name: "TRIGRAM FOR THUNDER" },
{ char: "⚔", name: "CROSSED SWORDS" },
{ char: "⚀", name: "DICE ONE" },
];
const rawMotd = ref(props.server.general?.motd ?? "");
const motd = computed(() => {
const lines = rawMotd.value.split("\n");
return lines.map((line) => {
const segments = [];
let currentSegment = { text: "", color: "White" };
let i = 0;
while (i < line.length) {
if (line[i] === "§") {
if (currentSegment.text) {
segments.push({ ...currentSegment });
currentSegment = { text: "", color: "White" };
}
const formatCode = line.substr(i, 2);
const format = formatCodes.find((f) => f.code === formatCode);
console.log(format);
console.log(formatCode);
if (format) {
currentSegment.color = format.color;
i += 2;
continue;
} else if (formatCode === "§l") {
currentSegment.bold = true;
i += 2;
continue;
} else if (formatCode === "§o") {
currentSegment.italic = true;
i += 2;
continue;
} else if (formatCode === "§n") {
currentSegment.underline = true;
i += 2;
continue;
} else if (formatCode === "§m") {
currentSegment.strikethrough = true;
i += 2;
continue;
}
}
currentSegment.text += line[i];
i++;
}
if (currentSegment.text) {
segments.push(currentSegment);
}
return segments;
});
});
const styles = [
{
name: "bold",
icon: BoldIcon,
},
{
name: "italic",
icon: ItalicIcon,
},
{
name: "underline",
icon: UnderlineIcon,
},
{
name: "strikethrough",
icon: StrikethroughIcon,
},
];
const showPopup = ref(false);
const popupX = ref(0);
const popupY = ref(0);
const currentLineIndex = ref(0);
const selectionStart = ref(0);
const selectionEnd = ref(0);
const colorPicker = ref(false);
const pickColor = () => {
colorPicker.value = !colorPicker.value;
};
const totalCharacters = computed(() => {
return motd.value.reduce((sum, line) => {
return Math.max(
sum,
line.reduce((lineSum, segment) => lineSum + segment.text.length, 0),
);
}, 0);
});
const minecraftFormat = computed(() => {
return motd.value
.map((line) => {
return line
.map((segment) => {
let format = getColorCode(segment.color);
if (segment.bold) format += "§l";
if (segment.italic) format += "§o";
if (segment.underline) format += "§n";
if (segment.strikethrough) format += "§m";
return format + segment.text;
})
.join("");
})
.join("\n");
});
const currentStyle = computed(() => {
const line = motd.value[currentLineIndex.value];
if (!line) return {};
let start = 0;
for (const segment of line) {
if (start + segment.text.length > selectionStart.value) {
return {
color: segment.color || "White",
bold: segment.bold || false,
italic: segment.italic || false,
underline: segment.underline || false,
strikethrough: segment.strikethrough || false,
};
}
start += segment.text.length;
}
return {};
});
function getColorCode(color) {
const format = formatCodes.find((f) => f.description === color);
return format ? format.code : "§f";
}
function renderLine(line) {
return line
.map((segment) => {
let style = `color: ${segment.color};`;
if (segment.bold) style += "font-weight: 900;";
if (segment.italic) style += "font-style: italic;";
if (segment.underline) style += "text-decoration: underline;";
if (segment.strikethrough) style += "text-decoration: line-through;";
return `<span style="${style}">${segment.text}</span>`;
})
.join("");
}
function handleSelection(lineIndex) {
const selection = window.getSelection();
if (selection.toString().length > 0) {
const range = selection.getRangeAt(0);
const rect = range.getBoundingClientRect();
popupX.value = rect.left;
popupY.value = rect.bottom;
showPopup.value = true;
currentLineIndex.value = lineIndex;
const lineElement = document.querySelectorAll("[contenteditable]")[lineIndex];
const rangeClone = range.cloneRange();
rangeClone.selectNodeContents(lineElement);
rangeClone.setEnd(range.startContainer, range.startOffset);
selectionStart.value = rangeClone.toString().length;
selectionEnd.value = selectionStart.value + range.toString().length;
} else {
showPopup.value = false;
colorPicker.value = false;
}
}
function applyStyle(newStyle) {
const line = motd.value[currentLineIndex.value];
const newLine = [];
let currentPos = 0;
for (const segment of line) {
if (currentPos + segment.text.length <= selectionStart.value) {
newLine.push(segment);
} else if (currentPos >= selectionEnd.value) {
newLine.push(segment);
} else {
const beforeSelection = segment.text.slice(0, Math.max(0, selectionStart.value - currentPos));
const inSelection = segment.text.slice(
Math.max(0, selectionStart.value - currentPos),
Math.min(segment.text.length, selectionEnd.value - currentPos),
);
const afterSelection = segment.text.slice(
Math.min(segment.text.length, selectionEnd.value - currentPos),
);
console.log(beforeSelection);
console.log(inSelection);
console.log(afterSelection);
if (beforeSelection) newLine.push({ ...segment, text: beforeSelection });
if (inSelection) {
const mergedStyle = { ...segment, ...newStyle };
for (const key in newStyle) {
if (newStyle[key] === false) {
delete mergedStyle[key];
}
}
newLine.push({ ...mergedStyle, text: inSelection });
}
if (afterSelection) newLine.push({ ...segment, text: afterSelection });
}
currentPos += segment.text.length;
}
motd.value[currentLineIndex.value] = newLine;
showPopup.value = false;
colorPicker.value = false;
// Rerender the line to reflect the changes
nextTick(() => {
const lineElement = document.querySelectorAll("[contenteditable]")[currentLineIndex.value];
lineElement.innerHTML = renderLine(newLine);
});
}
function insertEmoji() {
const emoji = "☺";
if (totalCharacters.value + emoji.length <= 90) {
applyStyle({ text: emoji });
}
}
function handleInput(event, lineIndex) {
const newText = event.target.textContent;
const oldText = motd.value[lineIndex].reduce((acc, segment) => acc + segment.text, "");
const diff = newText.length - oldText.length;
if (newText.length <= 45) {
const selection = window.getSelection();
const range = selection.getRangeAt(0);
const cursorOffset = getCursorOffset(event.target, range);
const newLine = [];
let currentPos = 0;
for (const segment of motd.value[lineIndex]) {
const segmentEnd = currentPos + segment.text.length;
const newSegmentText = newText.slice(currentPos, Math.min(segmentEnd, newText.length));
if (newSegmentText) {
newLine.push({ ...segment, text: newSegmentText });
}
currentPos = segmentEnd;
if (currentPos >= newText.length) break;
}
if (currentPos < newText.length) {
newLine.push({ text: newText.slice(currentPos), color: "White" });
}
motd.value[lineIndex] = newLine;
nextTick(() => {
const lineElement = event.target;
lineElement.innerHTML = renderLine(newLine);
const newRange = document.createRange();
const sel = window.getSelection();
const { node, offset } = getCursorNodeAndOffset(lineElement, cursorOffset);
if (node) {
newRange.setStart(node, offset);
newRange.collapse(true);
sel.removeAllRanges();
sel.addRange(newRange);
}
});
} else {
event.target.innerHTML = renderLine(motd.value[lineIndex]);
}
}
// Helper function to get cursor offset considering styled spans
function getCursorOffset(element, range) {
let offset = 0;
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
let node;
while ((node = walker.nextNode())) {
if (node === range.startContainer) {
return offset + range.startOffset;
}
offset += node.length;
}
return offset;
}
// Helper function to find the node and offset for cursor placement
function getCursorNodeAndOffset(element, targetOffset) {
let currentOffset = 0;
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
let node;
while ((node = walker.nextNode())) {
if (currentOffset + node.length >= targetOffset) {
return { node, offset: targetOffset - currentOffset };
}
currentOffset += node.length;
}
// If we've gone past the end, return the last possible position
const lastTextNode = element.lastChild?.lastChild;
return { node: lastTextNode, offset: lastTextNode?.length || 0 };
}
function handlePaste(event, lineIndex) {
event.preventDefault();
const pastedText = (event.clipboardData || window.clipboardData).getData("text");
const selection = window.getSelection();
const range = selection.getRangeAt(0);
const startOffset = range.startOffset;
const currentText = motd.value[lineIndex].reduce((acc, segment) => acc + segment.text, "");
const newText = currentText.slice(0, startOffset) + pastedText + currentText.slice(startOffset);
if (newText.length <= 45) {
// Preserve existing styles by matching new text with old segments
const newLine = [];
let currentPos = 0;
for (const segment of motd.value[lineIndex]) {
if (currentPos < startOffset) {
const segmentEnd = Math.min(currentPos + segment.text.length, startOffset);
newLine.push({ ...segment, text: newText.slice(currentPos, segmentEnd) });
currentPos = segmentEnd;
} else if (currentPos >= startOffset + pastedText.length) {
newLine.push({ ...segment, text: newText.slice(currentPos) });
break;
}
}
// Insert pasted text as a new segment
if (currentPos < startOffset + pastedText.length) {
newLine.push({
text: newText.slice(currentPos, startOffset + pastedText.length),
color: "White",
});
}
motd.value[lineIndex] = newLine;
nextTick(() => {
const lineElement = document.querySelectorAll("[contenteditable]")[lineIndex];
lineElement.innerHTML = renderLine(newLine);
const newRange = document.createRange();
const sel = window.getSelection();
newRange.setStart(lineElement.childNodes[0], startOffset + pastedText.length);
newRange.collapse(true);
sel.removeAllRanges();
sel.addRange(newRange);
});
}
}
</script>
<style scoped>
.minecraft-font {
font-family: "Minecraft", monospace;
font-size: 16px;
line-height: 1.5;
}
[contenteditable] {
outline: none;
}
</style>
<style scoped>
@font-face {
font-family: "Monocraft";
src: url("/Monocraft.ttf") format("truetype");
}
.font-minecraft {
font-family: "Monocraft", monospace;
}
.mcbg {
background: url("@/assets/images/servers/minecraft-background-dark.png") repeat center center;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease-in-out;
}
.fade-enter, .fade-leave-to /* .fade-leave-active in <2.1.8 */ {
opacity: 0;
}
</style>

View File

@@ -0,0 +1,53 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:serif="http://www.serif.com/"
version="1.1"
viewBox="0 0 1793 199"
>
<g>
<g id="Layer_1">
<g id="green" fill="var(--color-brand)">
<path
d="M1184.1,166.6c-8,0-15.6-1-22.9-3.1s-13.1-4.6-17.4-7.6l8.5-16.9c4.3,2.7,9.4,5,15.3,6.8,5.9,1.8,11.9,2.7,17.8,2.7s12.1-.9,15.2-2.8c3.1-1.9,4.7-4.5,4.7-7.7s-1.1-4.6-3.2-6c-2.1-1.4-4.9-2.4-8.4-3.1-3.4-.7-7.3-1.4-11.5-2-4.2-.6-8.4-1.4-12.6-2.4-4.2-1-8-2.5-11.5-4.5-3.4-2-6.2-4.6-8.4-7.9-2.1-3.3-3.2-7.7-3.2-13.2s1.7-11.3,5.2-15.8c3.4-4.5,8.3-7.9,14.5-10.3,6.2-2.4,13.6-3.7,22.2-3.7s12.9.7,19.4,2.1c6.5,1.4,11.9,3.4,16.2,6.1l-8.5,16.9c-4.5-2.7-9.1-4.6-13.6-5.6-4.6-1-9.1-1.5-13.6-1.5-6.8,0-11.8,1-15,3-3.3,2-4.9,4.6-4.9,7.7s1.1,5,3.2,6.4c2.1,1.4,4.9,2.6,8.4,3.4,3.4.8,7.3,1.5,11.5,2,4.2.5,8.4,1.3,12.6,2.4,4.2,1.1,8,2.5,11.5,4.4,3.5,1.8,6.3,4.4,8.5,7.7,2.1,3.3,3.2,7.7,3.2,13s-1.8,11.1-5.3,15.5c-3.5,4.4-8.5,7.8-14.9,10.2-6.4,2.4-14.1,3.7-23,3.7Z"
/>
<path
d="M1291.1,166.6c-10.6,0-19.8-2.1-27.7-6.3-7.9-4.2-14-10-18.3-17.4-4.3-7.4-6.5-15.7-6.5-25.1s2.1-17.9,6.3-25.2c4.2-7.3,10-13,17.5-17.2,7.4-4.2,15.9-6.2,25.4-6.2s17.5,2,24.8,6.1c7.2,4,12.9,9.7,17.1,17.1,4.2,7.4,6.2,16,6.2,26s0,2,0,3.2c0,1.2-.2,2.3-.3,3.4h-79.3v-14.8h67.5l-8.7,4.6c.1-5.5-1-10.3-3.4-14.4-2.4-4.2-5.6-7.4-9.7-9.8-4.1-2.4-8.8-3.6-14.2-3.6s-10.2,1.2-14.3,3.6c-4.1,2.4-7.3,5.7-9.6,9.9-2.3,4.2-3.5,9.2-3.5,14.9v3.6c0,5.7,1.3,10.7,3.9,15.1,2.6,4.4,6.3,7.8,11,10.2,4.7,2.4,10.2,3.6,16.4,3.6s10.2-.8,14.4-2.5c4.3-1.7,8.1-4.3,11.4-7.8l11.9,13.7c-4.3,5-9.6,8.8-16.1,11.5-6.5,2.7-13.9,4-22.2,4Z"
/>
<path
d="M1357.2,165.3v-95.1h21.2v26.2l-2.5-7.7c2.8-6.4,7.3-11.3,13.4-14.6,6.1-3.3,13.7-5,22.9-5v21.2c-1-.2-1.8-.4-2.7-.4-.8,0-1.7,0-2.5,0-8.4,0-15.1,2.5-20.1,7.4-5,4.9-7.5,12.3-7.5,22v46.1h-22.3Z"
/>
<path d="M1460,165.3l-40.8-95.1h23.2l35.1,83.9h-11.4l36.3-83.9h21.4l-40.8,95.1h-23Z" />
<path
d="M1579.6,166.6c-10.6,0-19.8-2.1-27.7-6.3-7.9-4.2-14-10-18.3-17.4-4.3-7.4-6.5-15.7-6.5-25.1s2.1-17.9,6.3-25.2c4.2-7.3,10-13,17.5-17.2,7.4-4.2,15.9-6.2,25.4-6.2s17.5,2,24.8,6.1c7.2,4,12.9,9.7,17.1,17.1,4.2,7.4,6.2,16,6.2,26s0,2,0,3.2c0,1.2-.2,2.3-.3,3.4h-79.3v-14.8h67.5l-8.7,4.6c.1-5.5-1-10.3-3.4-14.4-2.4-4.2-5.6-7.4-9.7-9.8-4.1-2.4-8.8-3.6-14.2-3.6s-10.2,1.2-14.3,3.6c-4.1,2.4-7.3,5.7-9.6,9.9-2.3,4.2-3.5,9.2-3.5,14.9v3.6c0,5.7,1.3,10.7,3.9,15.1,2.6,4.4,6.3,7.8,11,10.2,4.7,2.4,10.2,3.6,16.4,3.6s10.2-.8,14.4-2.5c4.3-1.7,8.1-4.3,11.4-7.8l11.9,13.7c-4.3,5-9.6,8.8-16.1,11.5-6.5,2.7-13.9,4-22.2,4Z"
/>
<path
d="M1645.7,165.3v-95.1h21.2v26.2l-2.5-7.7c2.8-6.4,7.3-11.3,13.4-14.6,6.1-3.3,13.7-5,22.9-5v21.2c-1-.2-1.8-.4-2.7-.4-.8,0-1.7,0-2.5,0-8.4,0-15.1,2.5-20.1,7.4-5,4.9-7.5,12.3-7.5,22v46.1h-22.3Z"
/>
<path
d="M1749.9,166.6c-8,0-15.6-1-22.9-3.1s-13.1-4.6-17.4-7.6l8.5-16.9c4.3,2.7,9.4,5,15.3,6.8,5.9,1.8,11.9,2.7,17.8,2.7s12.1-.9,15.2-2.8c3.1-1.9,4.7-4.5,4.7-7.7s-1.1-4.6-3.2-6c-2.1-1.4-4.9-2.4-8.4-3.1-3.4-.7-7.3-1.4-11.5-2-4.2-.6-8.4-1.4-12.6-2.4-4.2-1-8-2.5-11.5-4.5-3.4-2-6.2-4.6-8.4-7.9-2.1-3.3-3.2-7.7-3.2-13.2s1.7-11.3,5.2-15.8c3.4-4.5,8.3-7.9,14.5-10.3,6.2-2.4,13.6-3.7,22.2-3.7s12.9.7,19.4,2.1c6.5,1.4,11.9,3.4,16.2,6.1l-8.5,16.9c-4.5-2.7-9.1-4.6-13.6-5.6-4.6-1-9.1-1.5-13.6-1.5-6.8,0-11.8,1-15,3-3.3,2-4.9,4.6-4.9,7.7s1.1,5,3.2,6.4c2.1,1.4,4.9,2.6,8.4,3.4,3.4.8,7.3,1.5,11.5,2,4.2.5,8.4,1.3,12.6,2.4,4.2,1.1,8,2.5,11.5,4.4,3.5,1.8,6.3,4.4,8.5,7.7,2.1,3.3,3.2,7.7,3.2,13s-1.8,11.1-5.3,15.5c-3.5,4.4-8.5,7.8-14.9,10.2-6.4,2.4-14.1,3.7-23,3.7Z"
/>
<g>
<path
d="M9.8,143l63.4-38.1-5.8-15.3,18.1-18.6,22.9-4.9,6.6,8.2-10.6,10.7-9.2,2.9-6.6,6.8,3.2,9,6.5,6.9,9.2-2.5,6.6-7.2,14.3-4.5,4.3,9.6-14.8,18.1-24.8,7.8-11.1-12.4-63.6,38.2c-3-3.9-6.5-9.4-8.8-14.7ZM192.8,65.4l-50.4,13.6c2.8,7.4,3.7,11.7,4.5,16.5l50.3-13.6c-.8-5.4-2.2-10.8-4.4-16.5Z"
fill-rule="evenodd"
/>
<path
d="M17.3,106.5c3.6,42.1,38.9,75.2,82,75.2s60.7-18.9,74-46.3l16.4,5.7c-15.8,34.1-50.3,57.9-90.4,57.9S3.6,158.2,0,106.5h17.3ZM.3,89.4C5.3,39.2,47.8,0,99.3,0s99.5,44.6,99.5,99.5-1.1,17.4-3.3,25.5l-16.3-5.7c1.6-6.5,2.4-13.1,2.4-19.8,0-45.4-36.9-82.3-82.3-82.3S22.6,48.7,17.6,89.4H.3Z"
fill-rule="evenodd"
/>
<path
d="M99,51.6c-26.4,0-47.9,21.5-47.9,48s21.5,48,48,48,2.7,0,4-.2l4.8,16.8c-2.9.4-5.8.6-8.8.6-36,0-65.2-29.2-65.2-65.2S63.1,34.4,99,34.4s1.8,0,2.7,0l-2.7,17.1ZM118.6,37.4c26.4,8.3,45.6,33,45.6,62.2s-16.4,50.2-39.8,60l-4.8-16.7c16.2-7.7,27.4-24.2,27.4-43.3s-13-38.1-31.1-44.9l2.7-17.2Z"
fill-rule="evenodd"
/>
</g>
</g>
<g id="black" fill="currentColor">
<path
d="M354.8,69.2c12,0,21.7,3.4,28.6,10.4,7,7.2,10.6,17.5,10.6,31.5v54.8h-22.4v-51.9c0-8.4-1.8-14.7-5.5-19-3.8-4.1-8.9-6.3-15.9-6.3s-13.6,2.5-18.1,7.3c-4.5,5-6.8,12.2-6.8,21.3v48.5h-22.4v-51.9c0-8.4-1.8-14.7-5.5-19-3.8-4.1-8.9-6.3-15.9-6.3s-13.6,2.5-18.1,7.3c-4.5,4.8-6.8,12-6.8,21.3v48.5h-22.4v-95.6h21.3v12.2c3.6-4.3,8.1-7.5,13.4-9.8,5.4-2.3,11.3-3.4,17.9-3.4s13.6,1.3,19.2,3.9c5.5,2.9,9.8,6.8,13.1,12,3.9-5,8.9-8.9,15.2-11.8,6.3-2.7,13.1-4.1,20.6-4.1ZM466,167.2c-9.7,0-18.4-2.1-26.1-6.3-7.6-4-13.8-10.1-18.1-17.5-4.5-7.3-6.6-15.7-6.6-25.2s2.1-17.9,6.6-25.2c4.3-7.4,10.6-13.4,18.1-17.4,7.7-4.1,16.5-6.3,26.1-6.3s18.6,2.1,26.3,6.3c7.7,4.1,13.8,10,18.3,17.4,4.3,7.3,6.4,15.7,6.4,25.2s-2.1,17.9-6.4,25.2c-4.5,7.5-10.6,13.4-18.3,17.5-7.7,4.1-16.5,6.3-26.3,6.3h0ZM466,148c8.2,0,15-2.7,20.4-8.2,5.4-5.5,8.1-12.7,8.1-21.7s-2.7-16.1-8.1-21.7c-5.4-5.5-12.2-8.2-20.4-8.2s-15,2.7-20.2,8.2c-5.4,5.5-8.1,12.7-8.1,21.7s2.7,16.1,8.1,21.7c5.2,5.5,12,8.2,20.2,8.2ZM631.5,33.1v132.8h-21.5v-12.3c-3.7,4.4-8.3,7.9-13.6,10.2-5.5,2.3-11.5,3.4-18.1,3.4s-17.4-2-24.7-6.1c-7.3-4.1-13.2-9.8-17.4-17.4-4.1-7.3-6.3-15.9-6.3-25.6s2.1-18.3,6.3-25.6c4.1-7.3,10-13.1,17.4-17.2,7.3-4.1,15.6-6.1,24.7-6.1s12.2,1.1,17.4,3.2c5.2,2.1,9.8,5.4,13.4,9.7v-49h22.4ZM581.1,148c5.4,0,10.2-1.3,14.5-3.8,4.3-2.3,7.7-5.9,10.2-10.4,2.5-4.5,3.8-9.8,3.8-15.7s-1.3-11.3-3.8-15.7c-2.5-4.5-5.9-8.1-10.2-10.6-4.3-2.3-9.1-3.6-14.5-3.6s-10.2,1.3-14.5,3.6c-4.3,2.5-7.7,6.1-10.2,10.6-2.5,4.5-3.8,9.8-3.8,15.7s1.3,11.3,3.8,15.7c2.5,4.5,5.9,8.1,10.2,10.4,4.3,2.5,9.1,3.8,14.5,3.8ZM681.6,84.3c6.4-10,17.7-15,34-15v21.3c-1.7-.3-3.4-.5-5.2-.5-8.8,0-15.6,2.5-20.4,7.5-4.8,5.2-7.3,12.5-7.3,22v46.4h-22.4v-95.6h21.3v14h0ZM734.1,70.3h22.4v95.6h-22.4v-95.6ZM745.4,54.6c-4.1,0-7.5-1.3-10.2-3.9-2.7-2.4-4.2-5.9-4.1-9.5,0-3.8,1.4-7,4.1-9.7,2.7-2.5,6.1-3.8,10.2-3.8s7.5,1.3,10.2,3.6c2.7,2.5,4.1,5.5,4.1,9.3s-1.3,7.2-3.9,9.8c-2.7,2.7-6.3,4.1-10.4,4.1ZM839.5,69.2c12,0,21.7,3.6,29,10.6,7.3,7,10.9,17.5,10.9,31.3v54.8h-22.4v-51.9c0-8.4-2-14.7-5.9-19-3.9-4.1-9.5-6.3-16.8-6.3s-14.7,2.5-19.5,7.3c-4.8,5-7.2,12.2-7.2,21.5v48.3h-22.4v-95.6h21.3v12.3c3.8-4.5,8.4-7.7,14-10,5.5-2.3,12-3.4,19-3.4ZM964.8,160.7c-2.8,2.2-6,3.9-9.5,4.8-3.9,1.1-7.9,1.6-12,1.6-10.6,0-18.6-2.7-24.3-8.2-5.7-5.5-8.6-13.4-8.6-24v-46h-15.7v-17.9h15.7v-21.8h22.4v21.8h25.6v17.9h-25.6v45.5c0,4.7,1.1,8.2,3.4,10.6,2.3,2.5,5.5,3.8,9.8,3.8s9.1-1.3,12.5-3.9l6.3,15.9ZM1036.9,69.2c12,0,21.7,3.6,29,10.6,7.3,7,10.9,17.5,10.9,31.3v54.8h-22.4v-51.9c0-8.4-2-14.7-5.9-19-3.9-4.1-9.5-6.3-16.8-6.3s-14.7,2.5-19.5,7.3c-4.8,5-7.2,12.2-7.2,21.5v48.3h-22.4V33.1h22.4v48.3c3.8-3.9,8.2-7,13.8-9.1,5.4-2,11.5-3,18.1-3Z"
/>
</g>
</g>
</g>
</svg>
</template>

Some files were not shown because too many files have changed in this diff Show More