diff options
| -rw-r--r-- | backend/src/Database/RangedWeaponsAccessLayer.zig | 2 | ||||
| -rw-r--r-- | backend/src/Handler.zig | 7 | ||||
| -rw-r--r-- | backend/src/Models/RangedWeapon.zig | 2 | ||||
| -rw-r--r-- | backend/src/Models/User.zig | 6 | ||||
| -rw-r--r-- | backend/src/main.zig | 12 | ||||
| -rw-r--r-- | front/package-lock.json | 37 | ||||
| -rw-r--r-- | front/package.json | 1 | ||||
| -rw-r--r-- | front/src/App.css | 2 | ||||
| -rw-r--r-- | front/src/Config.ts | 1 | ||||
| -rw-r--r-- | front/src/Emelents/Elements.css | 31 | ||||
| -rw-r--r-- | front/src/Fonts.css | 2 | ||||
| -rw-r--r-- | front/src/Locales/ru_RU.ts | 1 | ||||
| -rw-r--r-- | front/src/Models/RangedWeapon.ts | 31 | ||||
| -rw-r--r-- | front/src/Pages/Weapons.tsx | 91 | ||||
| -rw-r--r-- | front/src/index.css | 1 |
15 files changed, 214 insertions, 13 deletions
diff --git a/backend/src/Database/RangedWeaponsAccessLayer.zig b/backend/src/Database/RangedWeaponsAccessLayer.zig index 8d86840..34c71ea 100644 --- a/backend/src/Database/RangedWeaponsAccessLayer.zig +++ b/backend/src/Database/RangedWeaponsAccessLayer.zig @@ -7,7 +7,7 @@ const pg = @import("pg"); const model = @import("../Models/RangedWeapon.zig"); pub fn GetAll(alloc: std.mem.Allocator) !std.ArrayList(model.RangedWeaponType) { - const query = "SELECT * FROM RangedWeapons"; + const query = "SELECT * FROM RangedWeapons ORDER BY weapon_type"; var result = try conn.pool.query(query, .{}); defer _ = result.deinit(); diff --git a/backend/src/Handler.zig b/backend/src/Handler.zig index dfd884d..3e40cf8 100644 --- a/backend/src/Handler.zig +++ b/backend/src/Handler.zig @@ -8,7 +8,7 @@ pub const RequestData = struct { pub fn Init(req: *httpz.Request) !RequestData { return .{ - .User = getUser(req), + .User = try getUser(req), }; } @@ -27,6 +27,11 @@ pub const RequestData = struct { return parsed.value; } + + pub fn CanUserAccess(self: RequestData, minimalRole: userModel.Role) bool { + if (self.User == null) return false; + return self.User.?.Role >= minimalRole; + } }; pub const Handler = struct { diff --git a/backend/src/Models/RangedWeapon.zig b/backend/src/Models/RangedWeapon.zig index b2c15cd..36e750e 100644 --- a/backend/src/Models/RangedWeapon.zig +++ b/backend/src/Models/RangedWeapon.zig @@ -11,7 +11,7 @@ pub const RangedWeaponType = struct { Avaliability: []const u8, Damage: []const u8, Ammunition: []const u8, - NumberOfShots: u31, + NumberOfShots: u32, RateOfFire: u32, Reliability: []const u8, diff --git a/backend/src/Models/User.zig b/backend/src/Models/User.zig index a52186d..e7550b7 100644 --- a/backend/src/Models/User.zig +++ b/backend/src/Models/User.zig @@ -11,9 +11,9 @@ const RolesMap = std.static_string_map.StaticStringMap(Role).initComptime(.{ .{ "editor", .editor }, }); -pub const Role = enum { - user, - editor, +pub const Role = enum (u8) { + user = 0, + editor = 1, pub fn ToString(self: Role) []const u8 { return switch (self) { diff --git a/backend/src/main.zig b/backend/src/main.zig index 6def1a1..86e84cb 100644 --- a/backend/src/main.zig +++ b/backend/src/main.zig @@ -26,12 +26,22 @@ pub fn main() !void { .address = .all(6969), }, &httpHandler); + const cors = try server.middleware(httpz.middleware.Cors, .{ + .origin = "*", + .methods = "GET,POST,PUT,DELETE,OPTIONS", + .headers = "authorization,content-type", + }); + defer { server.stop(); server.deinit(); } - var router = try server.router(.{}); + var router = try server.router(.{ + .middlewares = &.{ + cors + } + }); router.get("/", index, .{}); @import("API/WeaponsAPI.zig").RegisterEndpoints(router); diff --git a/front/package-lock.json b/front/package-lock.json index fe858e6..6943f13 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -16,6 +16,7 @@ "@types/node": "^16.18.126", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", + "axios": "^1.15.2", "react": "^19.2.5", "react-dom": "^19.2.5", "react-router": "^7.14.0", @@ -4834,6 +4835,33 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz", + "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -13508,6 +13536,15 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", diff --git a/front/package.json b/front/package.json index b705d58..234d168 100644 --- a/front/package.json +++ b/front/package.json @@ -11,6 +11,7 @@ "@types/node": "^16.18.126", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", + "axios": "^1.15.2", "react": "^19.2.5", "react-dom": "^19.2.5", "react-router": "^7.14.0", diff --git a/front/src/App.css b/front/src/App.css index 5cbbeb8..dcfa013 100644 --- a/front/src/App.css +++ b/front/src/App.css @@ -13,7 +13,7 @@ padding-left: 10px; } -.AppContents > div > h1 { +.AppContents > div > h1, .AppContents > div > span > h1 { background-color: var(--colorscheme-black); color: var(--colorscheme-white); padding-left: 10px; diff --git a/front/src/Config.ts b/front/src/Config.ts new file mode 100644 index 0000000..78f4e0d --- /dev/null +++ b/front/src/Config.ts @@ -0,0 +1 @@ +export const BackendURL = "http://localhost:6969"; diff --git a/front/src/Emelents/Elements.css b/front/src/Emelents/Elements.css index 1f4d60f..31f0fd2 100644 --- a/front/src/Emelents/Elements.css +++ b/front/src/Emelents/Elements.css @@ -65,6 +65,10 @@ display: flex; } +.ContentsList > li > span { + display: flex; +} + .ContentsList > li > span > span { flex-grow: 1; font-family: "Rubik Dirt", system-ui; @@ -88,3 +92,30 @@ font-size: 15px; font-family: "Rubik Dirt", system-ui; } + +.WeaponsDiv { + flex-grow: 1; +} + +.WeaponsDiv > span { + display: flex; +} + +.WeaponTable { + width: 100%; +} + +.WeaponTable > tbody > tr > td { + font-family: "Wix Madefor Text", sans-serif; +} + +.WeaponTable, .WeaponTable > tbody, .WeaponTable > tbody > tr > td, .WeaponTable > tbody > tr { + border: 0px solid red; + border-collapse: collapse; +} + +.CategoryName { + padding-top: 10px; + font-family: "Rubik Dirt", system-ui !important; + font-size: 20px; +} diff --git a/front/src/Fonts.css b/front/src/Fonts.css index c3b986b..c7f3bb6 100644 --- a/front/src/Fonts.css +++ b/front/src/Fonts.css @@ -1,4 +1,4 @@ -@import url('https://fonts.googleapis.com/css2?family=Rubik+Dirt&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Rubik+Dirt&family=Wix+Madefor+Text:ital,wght@0,400..800;1,400..800&display=swap'); h1, h2 { font-family: "Rubik Dirt", system-ui; diff --git a/front/src/Locales/ru_RU.ts b/front/src/Locales/ru_RU.ts index 6c804b9..39c45d0 100644 --- a/front/src/Locales/ru_RU.ts +++ b/front/src/Locales/ru_RU.ts @@ -9,6 +9,7 @@ lang.LocalizedStrings = new Map<string, string>([ ["classes list", "Список классов"], ["weapons", "Оружие"], ["weapons index", "Список вооружения"], + ["ranged weapons", "Дальнобойное оружие"], ]); export default lang; diff --git a/front/src/Models/RangedWeapon.ts b/front/src/Models/RangedWeapon.ts new file mode 100644 index 0000000..2bc9995 --- /dev/null +++ b/front/src/Models/RangedWeapon.ts @@ -0,0 +1,31 @@ +export type RangedWeapon = { + Id: string, + Name: string, + + WeaponType: string, + Accuracy: number, + Concealability: string, + Avaliability: string, + Damage: string, + Ammunition: string, + NumberOfShots: number, + RateOfFire: number, + Reliability: string, + + CreatedAt: number, + UpdatedAt: number, +}; + +export type RangedWeaponRequest = { + Id: string, + Name: string, + WeaponType: string, + Accuracy: number, + Concealability: string, + Avaliability: string, + Damage: string, + Ammunition: string, + NumberOfShots: number, + RateOfFire: number, + Reliability: string, +}; diff --git a/front/src/Pages/Weapons.tsx b/front/src/Pages/Weapons.tsx index d16762c..f918232 100644 --- a/front/src/Pages/Weapons.tsx +++ b/front/src/Pages/Weapons.tsx @@ -1,18 +1,101 @@ -import { useContext } from "react"; +import axios from "axios"; +import { useContext, useState } from "react"; +import { BackendURL } from "../Config"; import { LanguageContext } from "../Locales/Context"; import { GetLocalizedString } from "../Locales/Locales"; +import { RangedWeapon } from "../Models/RangedWeapon"; + +const RangedWeaponsURL = `${BackendURL}/weapons/ranged`; function WeaponsIndex() { - var lang = useContext(LanguageContext); + const lang = useContext(LanguageContext); + const [rangedWeapons, setRangedWeapons] = useState<RangedWeapon[] | null>(null); + + useState(() => { + getRangedWeapons().then(data => setRangedWeapons(data)); + }); return ( - <div> + <div className="WeaponsDiv"> + <span> <h1> {GetLocalizedString("Weapons index", lang)} </h1> + </span> + <h2> {GetLocalizedString("Ranged weapons", lang)} </h2> + {generatedRangedWeaponsList(rangedWeapons)} </div> ); } -function WeaponsType() { +function generatedRangedWeaponsList(rangedWeapons: RangedWeapon[] | null): any { + if (!rangedWeapons) { + return ( + <div> </div> + ); + } + + const categorized = new Map<string, RangedWeapon[]>(); + + rangedWeapons.forEach(el => { + if (!categorized.has(el.WeaponType)) { + categorized.set(el.WeaponType, new Array<RangedWeapon>()); + } + categorized.get(el.WeaponType)?.push(el); + }); + + const keys = Array.from(categorized.keys()); + const list = keys.map(x => { + const elements = categorized.get(x)?.map(w => { + return ( + <tr key={w.Id}> + <td> {w.Name} </td> + <td> {w.WeaponType} </td> + <td> {w.Accuracy} </td> + <td> {w.Concealability} </td> + <td> {w.Avaliability} </td> + <td> {w.Damage}({w.Ammunition}) </td> + <td> {w.NumberOfShots} </td> + <td> {w.RateOfFire} </td> + <td> {w.Reliability} </td> + </tr> + ) + }); + + return ( + <tbody key={x}> + <tr> + <td className="CategoryName"> {`${x}s`} </td> + </tr> + {elements} + </tbody> + ); + }); + + return ( + <table className="WeaponTable"> + {list} + </table> + ); +} + +async function getRangedWeapons(): Promise<RangedWeapon[]> { + try { + const {data, status} = await axios.get<RangedWeapon[]>( + RangedWeaponsURL, + { + headers: { Accept: "application/json" } + } + ); + + if (status != 200) { + return new Array<RangedWeapon>(); + } + + return data; + + } catch (err) { + console.log(`Failed to get ranged weapons: ${err}`); + return new Array<RangedWeapon>(); + } } export default WeaponsIndex; diff --git a/front/src/index.css b/front/src/index.css index 4e7932e..0193f3b 100644 --- a/front/src/index.css +++ b/front/src/index.css @@ -3,6 +3,7 @@ --colorscheme-blue: #00F0FF; --colorscheme-white: #FAFAFA; --colorscheme-gray: #3C3C3C; + --colorscheme-light-gray: #909090F0; } * { |
